{"id":762,"date":"2021-12-09T00:07:08","date_gmt":"2021-12-09T00:07:08","guid":{"rendered":"https:\/\/writingagame.com\/?p=762"},"modified":"2021-12-09T03:10:46","modified_gmt":"2021-12-09T03:10:46","slug":"chapter-25-writing-bmp-and-tga-files","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/09\/chapter-25-writing-bmp-and-tga-files\/","title":{"rendered":"Chapter 25. Writing BMP and TGA files"},"content":{"rendered":"\n<p>I was planning to generate and save a few &#8220;white noise&#8221; images for future use in some recognizable graphics format. In order not to overload the project by new heavy libraries, I decided to write own little function. For the sake of simplicity I won&#8217;t use compression and will support only 1 pixel format &#8211; RGBA. The only 2 options that would fit are BMP and TGA formats. BMP is more common, TGA is simpler. However, Photoshop doesn&#8217;t understand 4 bytes BMPs, Windows Paint doesn&#8217;t understand TGAs, so, just in case, we&#8217;ll implement both in <strong>Texture <\/strong>class.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>1. Start VS, open <em>C:\\CPP\\<em>a997modeler<\/em>\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>2. Open <em>Texture.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [23,24]; title: ; notranslate\" title=\"\">\n #pragma once\n#include &lt;string&gt;\n#include &lt;vector&gt;\n\nclass Texture\n{\npublic:\n    \/\/texture&#039;s individual descriptor:\n    unsigned int GLid = -1; \/\/ GL texture id\n    int size&#91;2] = { 0,0 };  \/\/ image size\n    std::string source; \/\/file name\n    \/\/end of descriptor\n\n    \/\/static array (vector) of all loaded textures\n    static std::vector&lt;Texture*&gt; textures;\n\npublic:\n    static int loadTexture(std::string filePath);\n    static int findTexture(std::string filePath);\n    static int cleanUp();\n    static unsigned int getGLid(int texN) { return textures.at(texN)-&gt;GLid; };\n\n    static int saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);\n    static int saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p> 3. Open <em>Texture.cpp<\/em> and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [75,140]; title: ; notranslate\" title=\"\">\n#include &quot;Texture.h&quot;\n#define STB_IMAGE_IMPLEMENTATION  \/\/required by stb_image.h\n#include &quot;stb_image.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n\n\/\/static array (vector) of all loaded textures\nstd::vector&lt;Texture*&gt; Texture::textures;\n\nint Texture::loadTexture(std::string filePath) {\n    int texN = findTexture(filePath);\n    if (texN &gt;= 0)\n        return texN;\n    \/\/if here - texture wasn&#039;t loaded\n    \/\/create Texture object\n    Texture* pTex = new Texture();\n    textures.push_back(pTex);\n    pTex-&gt;source.assign(filePath);\n    \/\/ load an image\n    int nrChannels;\n    unsigned char* imgData = stbi_load(filePath.c_str(),\n        &amp;pTex-&gt;size&#91;0], &amp;pTex-&gt;size&#91;1], &amp;nrChannels, 4); \/\/&quot;4&quot;-convert to 4 channels -RGBA\n    if (imgData == NULL) {\n        mylog(&quot;ERROR in Texture::loadTexture loading image %s\\n&quot;, filePath.c_str());\n    }\n    \/\/ generate texture\n    glGenTextures(1, &amp;pTex-&gt;GLid);\n    glBindTexture(GL_TEXTURE_2D, pTex-&gt;GLid);\n    \/\/ set the texture wrapping\/filtering options (on the currently bound texture object)\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \/\/ attach\/load image data\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pTex-&gt;size&#91;0], pTex-&gt;size&#91;1], 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);\n    glGenerateMipmap(GL_TEXTURE_2D);\n    \/\/ release image data\n    stbi_image_free(imgData);\n\n    return (textures.size() - 1);\n}\nint Texture::findTexture(std::string filePath) {\n    int texturesN = textures.size();\n    if (texturesN &lt; 1)\n        return -1;\n    for (int i = 0; i &lt; texturesN; i++) {\n        Texture* pTex = textures.at(i);\n        if (pTex-&gt;source.compare(filePath) == 0)\n            return i;\n    }\n    return -1;\n}\nint Texture::cleanUp() {\n    int texturesN = textures.size();\n    if (texturesN &lt; 1)\n        return -1;\n    \/\/detach all textures\n    glActiveTexture(GL_TEXTURE0); \/\/ activate the texture unit first before binding texture\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glActiveTexture(GL_TEXTURE1);\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glActiveTexture(GL_TEXTURE2);\n    glBindTexture(GL_TEXTURE_2D, 0);\n    glActiveTexture(GL_TEXTURE3);\n    glBindTexture(GL_TEXTURE_2D, 0);\n    \/\/release all textures\n    for (int i = 0; i &lt; texturesN; i++) {\n        Texture* pTex = textures.at(i);\n        glDeleteTextures(1, &amp;pTex-&gt;GLid);\n        delete pTex;\n    }\n    textures.clear();\n    return 1;\n}\nint Texture::saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {\n    std::string fullPath = getFullPath(filePath);\n    std::string inAppPath = getInAppPath(fullPath);\n    makeDirs(inAppPath);\n    FILE* outFile;\n    myFopen_s(&amp;outFile, fullPath.c_str(), &quot;wb&quot;);\n    if (outFile == NULL) {\n        mylog(&quot;ERROR in Texture::saveBMP: Can&#039;t create file %s\\n&quot;, filePath.c_str());\n        return -1;\n    }\n    struct {\n        char chars2skip&#91;2]; \/\/\n            \/\/BMP Header\n        char bm&#91;2] = { 0x42, 0x4D }; \/\/\t&quot;BM&quot;\n        myUint32 fileSize = 0; \/\/ Size of the BMP file, little-endian\n        myUint32 unused = 0;\n        myUint32 dataOffset = 0; \/\/ Offset where the pixel array (bitmap data) can be found, little-endian\n        \/\/DIB Header\n        myUint32 dibHeaderSize = 0; \/\/ Number of bytes in the DIB header, little-endian\n        myUint32 imgW = 0; \/\/ Width of the bitmap in pixels, little-endian\n        myUint32 imgH = 0; \/\/ Height of the bitmap in pixels, little-endian\n        char colorPlainsN&#91;2] = { 1,0 };\n        char bitsPerPixel&#91;2] = { 32,0 };\n        myUint32 compression = 0; \/\/0-BI_RGB\n        myUint32 dataSize = 0; \/\/ Size of the raw bitmap data (including padding), little-endian\n        myUint32 printResution&#91;2] = { 2835 ,2835 }; \/\/ Print resolution of the image,\n                \/\/72 DPI \u00d7 39.3701 inches per metre yields 2834.6472, little-endian\n        myUint32 paletteColors = 0; \/\/ Number of colors in the palette\n        myUint32 importantColors = 0; \/\/0 means all colors are important\n    } bmpHeader;\n    int rowSize = w * bytesPerPixel;\n    int rowPadding = (4 - rowSize % 4) % 4;\n    int rowSizeWithPadding = rowSize + rowPadding;\n    int dataSize = rowSizeWithPadding * h;\n    int headerSize = sizeof(bmpHeader) - 2; \/\/-chars2skip\n    bmpHeader.fileSize = dataSize + headerSize;\n    bmpHeader.dataOffset = headerSize;\n    bmpHeader.dibHeaderSize = headerSize - 14; \/\/-BMP Header size\n    bmpHeader.imgW = w;\n    bmpHeader.imgH = h;\n    if (bytesPerPixel != 4)\n        bmpHeader.bitsPerPixel&#91;0] = bytesPerPixel * 8;\n    bmpHeader.dataSize = dataSize;\n    fwrite(&amp;bmpHeader.bm, 1, headerSize, outFile);\n    \/\/data, from bottom to top\n    unsigned char zero&#91;4] = { 0,0,0,0 };\n    unsigned char bgra&#91;4];\n    for (int y = h - 1; y &gt;= 0; y--) {\n        for (int x = 0; x &lt; w; x++) {\n            int pixelOffset = y * rowSize + x * 4;\n            bgra&#91;0] = buff&#91;pixelOffset + 2];\n            bgra&#91;1] = buff&#91;pixelOffset + 1];\n            bgra&#91;2] = buff&#91;pixelOffset + 0];\n            bgra&#91;3] = buff&#91;pixelOffset + 3];\n            fwrite(bgra, 1, bytesPerPixel, outFile);\n        }\n        if (rowPadding != 0)\n            fwrite(zero, 1, rowPadding, outFile);\n    }\n    fflush(outFile);\n    fclose(outFile);\n\n    return 1;\n}\n\nint Texture::saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {\n    std::string fullPath = getFullPath(filePath);\n    std::string inAppPath = getInAppPath(fullPath);\n    makeDirs(inAppPath);\n    FILE* outFile;\n    myFopen_s(&amp;outFile, fullPath.c_str(), &quot;wb&quot;);\n    if (outFile == NULL) {\n        mylog(&quot;ERROR in Texture::saveBMP: Can&#039;t create file %s\\n&quot;, filePath.c_str());\n        return -1;\n    }\n    unsigned char tgaHeader&#91;18] = { 0,0,2,0,0,0,0,0,0,0,0,0, (unsigned char)(w % 256), (unsigned char)(w \/ 256),\n        (unsigned char)(h % 256), (unsigned char)(h \/ 256), (unsigned char)(bytesPerPixel * 8), 0x20 };\n    fwrite(tgaHeader, 1, 18, outFile);\n    \/\/data\n    unsigned char bgra&#91;4];\n    for (int i = 0; i &lt; w * h; i++) {\n        int pixelOffset = i * 4;\n        bgra&#91;0] = buff&#91;pixelOffset + 2];\n        bgra&#91;1] = buff&#91;pixelOffset + 1];\n        bgra&#91;2] = buff&#91;pixelOffset + 0];\n        bgra&#91;3] = buff&#91;pixelOffset + 3];\n        fwrite(bgra, 1, bytesPerPixel, outFile);\n    }\n    fflush(outFile);\n    fclose(outFile);\n\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>See the source code above, it&#8217;s pretty self explanatory. Simply header and data. Just 1 detail: both formats are using <strong>BGRA <\/strong>pixel format, not RGBA.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now, <strong>white noise<\/strong>. We will generate 64&#215;64 RGBA image (not a texture, but simply 4-bytes pixels array). Then we&#8217;ll fill out all 4 channels by random numbers in 0 to 255 range, so each of 4 channels will contain it&#8217;s own black-and-white image. We&#8217;ll do it in <em>TheGame::run()<\/em>. Following code generates 2 images:<\/p>\n\n\n\n<p>First &#8211; with binary black\/white values (0 or 255):<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c25\/bw64_2.jpg\"><\/p>\n\n\n\n<p>Second one &#8211; with gray-scale values (from 0 to 255):<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c25\/bw64_256.jpg\"><\/p>\n\n\n\n<p>And so on for all 4 channels, so resulting images will look like<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c25\/wn64_2.jpg\"><\/p>\n\n\n\n<p>and<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c25\/wn64_256.jpg\"><\/p>\n\n\n\n<p>Both images will be recorded in BMP format.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>If you want to view them in Photoshop, you can save in TGA format also.<\/li><\/ul>\n\n\n\n<p>The code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nint TheGame::run() {\n    \/*\n    getReady();\n    while (!bExitGame) {\n        drawFrame();\n    }\n    cleanUp();\n    *\/\n    int wh&#91;2] = { 64,64 };\n    int bytesPerPixel = 4;\n    unsigned char* buff = new unsigned char&#91;wh&#91;1] * wh&#91;0] * 4];\n    std::string fileName = &quot;wn64_2&quot;;\n    for (int y = 0; y &lt; wh&#91;1]; y++)\n        for (int x = 0; x &lt; wh&#91;0]; x++) {\n            int idx = (y * wh&#91;1] + x) * bytesPerPixel;\n            for (int i = 0; i &lt; 4; i++)\n                buff&#91;idx + i] = getRandom(0, 1) * 255;\n        }\n    Texture::saveBMP(&quot;\/dt\/out\/&quot; + fileName + &quot;.bmp&quot;, (unsigned char*)buff, wh&#91;0], wh&#91;1]);\n\n    fileName = &quot;wn64_256&quot;;\n    for (int y = 0; y &lt; wh&#91;1]; y++)\n        for (int x = 0; x &lt; wh&#91;0]; x++) {\n            int idx = (y * wh&#91;1] + x) * bytesPerPixel;\n            for (int i = 0; i &lt; 4; i++)\n                buff&#91;idx + i] = getRandom(0, 255);\n        }\n    Texture::saveBMP(&quot;\/dt\/out\/&quot; + fileName + &quot;.bmp&quot;, (unsigned char*)buff, wh&#91;0], wh&#91;1]);\n\n    delete&#91;] buff;\n    mylog(&quot;Ready\\n&quot;);\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>4. Open <em>TheGame.cpp<\/em> and replace this time <strong>NOT<\/strong> entire code as we did before, but only <em>run()<\/em> function by the code above.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. Build and run. <\/p>\n\n\n\n<p>Now images are generated and saved in<\/p>\n\n\n\n<p><em>C:\\CPP\\a997modeler\\p_windows\\Debug\\dt\\<strong>out<\/strong><\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>6. In <strong>Windows File Explorer<\/strong> under <em>C:\\CPP\\engine\\dt<\/em> create new folder:<\/p>\n\n\n\n<p><em>C:\\CPP\\engine\\dt\\<strong>common\\img\\whitenoise<\/strong><\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>7. Copy both BMP files from  <em>C:\\CPP\\a997modeler\\p_windows\\Debug\\dt\\out<\/em> <\/p>\n\n\n\n<p>to <em>C:\\CPP\\engine\\dt\\common\\img\\whitenoise <\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>9. Ok to delete folder  <em>C:\\CPP\\a997modeler\\p_windows\\Debug\\dt\\out<\/em> <\/p>\n\n\n\n<p>Actually, it&#8217;s ok to delete parent folder <strong>dt <\/strong>( <em>C:\\CPP\\a997modeler\\p_windows\\Debug\\<strong>dt<\/strong><\/em>) too, since it&#8217;s automatically re-generating by <em>xcopy <\/em>instructions during each build.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">I was planning to generate and save a few &#8220;white noise&#8221; images for future use in some recognizable graphics format. In order not to overload the project by new heavy libraries, I decided to write own little function. For the sake of simplicity I won&#8217;t use compression and will support only 1 pixel format &#8211; [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":765,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-762","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/762","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/comments?post=762"}],"version-history":[{"count":9,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/762\/revisions"}],"predecessor-version":[{"id":779,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/762\/revisions\/779"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/765"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=762"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=762"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=762"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}