Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the antispam-bee domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/ruwritingagame/public_html/wp-includes/functions.php on line 6121
Глава 25. Запись BMP и TGA файлов – Игра в Написание Игры

Глава 25. Запись BMP и TGA файлов

Для последующего использования я хотел сгенерировать несколько картинок с “белым шумом” и сохранить их в каком-то узнаваемом формате. Чтобы не перегружать проект лишними библиотеками, решил написать собственную маленькую функцию. Ради простоты – без компрессии и только с одним форматом пикселя – RGBA. Подойдут 2 варианта: BMP и TGA. BMP – пораспространенней, TGA – попроще. Правда, Photoshop не понимает 4 bytes BMPs, Windows Paint не понимает TGAs, так что на всякий случай, напишем оба (в классе Texture).

Windows

1.Запускаем VS, открываем C:\CPP\a997modeler\p_windows\p_windows.sln.


2. Заменим Texture.h код на:

 #pragma once
#include <string>
#include <vector>

class Texture
{
public:
    //texture's individual descriptor:
    unsigned int GLid = -1; // GL texture id
    int size[2] = { 0,0 };  // image size
    std::string source; //file name
    //end of descriptor

    //static array (vector) of all loaded textures
    static std::vector<Texture*> textures;

public:
    static int loadTexture(std::string filePath);
    static int findTexture(std::string filePath);
    static int cleanUp();
    static unsigned int getGLid(int texN) { return textures.at(texN)->GLid; };

    static int saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);
    static int saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel=4);
};


3. Заменим Texture.cpp код на:

#include "Texture.h"
#define STB_IMAGE_IMPLEMENTATION  //required by stb_image.h
#include "stb_image.h"
#include "platform.h"
#include "utils.h"

//static array (vector) of all loaded textures
std::vector<Texture*> Texture::textures;

int Texture::loadTexture(std::string filePath) {
    int texN = findTexture(filePath);
    if (texN >= 0)
        return texN;
    //if here - texture wasn't loaded
    //create Texture object
    Texture* pTex = new Texture();
    textures.push_back(pTex);
    pTex->source.assign(filePath);
    // load an image
    int nrChannels;
    unsigned char* imgData = stbi_load(filePath.c_str(),
        &pTex->size[0], &pTex->size[1], &nrChannels, 4); //"4"-convert to 4 channels -RGBA
    if (imgData == NULL) {
        mylog("ERROR in Texture::loadTexture loading image %s\n", filePath.c_str());
    }
    // generate texture
    glGenTextures(1, &pTex->GLid);
    glBindTexture(GL_TEXTURE_2D, pTex->GLid);
    // set the texture wrapping/filtering options (on the currently bound texture object)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // attach/load image data
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pTex->size[0], pTex->size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData);
    glGenerateMipmap(GL_TEXTURE_2D);
    // release image data
    stbi_image_free(imgData);

    return (textures.size() - 1);
}
int Texture::findTexture(std::string filePath) {
    int texturesN = textures.size();
    if (texturesN < 1)
        return -1;
    for (int i = 0; i < texturesN; i++) {
        Texture* pTex = textures.at(i);
        if (pTex->source.compare(filePath) == 0)
            return i;
    }
    return -1;
}
int Texture::cleanUp() {
    int texturesN = textures.size();
    if (texturesN < 1)
        return -1;
    //detach all textures
    glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, 0);
    //release all textures
    for (int i = 0; i < texturesN; i++) {
        Texture* pTex = textures.at(i);
        glDeleteTextures(1, &pTex->GLid);
        delete pTex;
    }
    textures.clear();
    return 1;
}
int Texture::saveBMP(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {
    std::string fullPath = getFullPath(filePath);
    std::string inAppPath = getInAppPath(fullPath);
    makeDirs(inAppPath);
    FILE* outFile;
    myFopen_s(&outFile, fullPath.c_str(), "wb");
    if (outFile == NULL) {
        mylog("ERROR in Texture::saveBMP: Can't create file %s\n", filePath.c_str());
        return -1;
    }
    struct {
        char chars2skip[2]; //
            //BMP Header
        char bm[2] = { 0x42, 0x4D }; //	"BM"
        myUint32 fileSize = 0; // Size of the BMP file, little-endian
        myUint32 unused = 0;
        myUint32 dataOffset = 0; // Offset where the pixel array (bitmap data) can be found, little-endian
        //DIB Header
        myUint32 dibHeaderSize = 0; // Number of bytes in the DIB header, little-endian
        myUint32 imgW = 0; // Width of the bitmap in pixels, little-endian
        myUint32 imgH = 0; // Height of the bitmap in pixels, little-endian
        char colorPlainsN[2] = { 1,0 };
        char bitsPerPixel[2] = { 32,0 };
        myUint32 compression = 0; //0-BI_RGB
        myUint32 dataSize = 0; // Size of the raw bitmap data (including padding), little-endian
        myUint32 printResution[2] = { 2835 ,2835 }; // Print resolution of the image,
                //72 DPI × 39.3701 inches per metre yields 2834.6472, little-endian
        myUint32 paletteColors = 0; // Number of colors in the palette
        myUint32 importantColors = 0; //0 means all colors are important
    } bmpHeader;
    int rowSize = w * bytesPerPixel;
    int rowPadding = (4 - rowSize % 4) % 4;
    int rowSizeWithPadding = rowSize + rowPadding;
    int dataSize = rowSizeWithPadding * h;
    int headerSize = sizeof(bmpHeader) - 2; //-chars2skip
    bmpHeader.fileSize = dataSize + headerSize;
    bmpHeader.dataOffset = headerSize;
    bmpHeader.dibHeaderSize = headerSize - 14; //-BMP Header size
    bmpHeader.imgW = w;
    bmpHeader.imgH = h;
    if (bytesPerPixel != 4)
        bmpHeader.bitsPerPixel[0] = bytesPerPixel * 8;
    bmpHeader.dataSize = dataSize;
    fwrite(&bmpHeader.bm, 1, headerSize, outFile);
    //data, from bottom to top
    unsigned char zero[4] = { 0,0,0,0 };
    unsigned char bgra[4];
    for (int y = h - 1; y >= 0; y--) {
        for (int x = 0; x < w; x++) {
            int pixelOffset = y * rowSize + x * 4;
            bgra[0] = buff[pixelOffset + 2];
            bgra[1] = buff[pixelOffset + 1];
            bgra[2] = buff[pixelOffset + 0];
            bgra[3] = buff[pixelOffset + 3];
            fwrite(bgra, 1, bytesPerPixel, outFile);
        }
        if (rowPadding != 0)
            fwrite(zero, 1, rowPadding, outFile);
    }
    fflush(outFile);
    fclose(outFile);

    return 1;
}

int Texture::saveTGA(std::string filePath, unsigned char* buff, int w, int h, int bytesPerPixel) {
    std::string fullPath = getFullPath(filePath);
    std::string inAppPath = getInAppPath(fullPath);
    makeDirs(inAppPath);
    FILE* outFile;
    myFopen_s(&outFile, fullPath.c_str(), "wb");
    if (outFile == NULL) {
        mylog("ERROR in Texture::saveBMP: Can't create file %s\n", filePath.c_str());
        return -1;
    }
    unsigned char tgaHeader[18] = { 0,0,2,0,0,0,0,0,0,0,0,0, (unsigned char)(w % 256), (unsigned char)(w / 256),
        (unsigned char)(h % 256), (unsigned char)(h / 256), (unsigned char)(bytesPerPixel * 8), 0x20 };
    fwrite(tgaHeader, 1, 18, outFile);
    //data
    unsigned char bgra[4];
    for (int i = 0; i < w * h; i++) {
        int pixelOffset = i * 4;
        bgra[0] = buff[pixelOffset + 2];
        bgra[1] = buff[pixelOffset + 1];
        bgra[2] = buff[pixelOffset + 0];
        bgra[3] = buff[pixelOffset + 3];
        fwrite(bgra, 1, bytesPerPixel, outFile);
    }
    fflush(outFile);
    fclose(outFile);

    return 1;
}

Смотрим исходник выше, вроде все понятно. Просто header и сами данные. Только в обоих случаях используется формат пикселя BGRA, а не RGBA.


Теперь, белый шум. Мы создадим 64×64 RGBA изображение (не текстуру, а просто массив 4-хбайтных пикселей). Потом заполним все 4 канала случайными числами в диапазоне от 0 до 255, так что в каждом из 4-х каналов будет собственная черно-белая картинка. Сделаем это в функции TheGame::run(). Следующий код генерирует 2 изображения:

Первое – с бинарными черно-белыми значениями (0 или 255):

Второе – с серым градиентом (от 0 до 255):

И так для всех 4-х каналов. Таким образом, результирующие изображения будут выглядеть как

и

Обе картинки сохраним в формате BMP.

  • Если захотите посмотреть их в Фотошопе, то сохраните еще и в формате TGA.

Код:

int TheGame::run() {
    /*
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    */
    int wh[2] = { 64,64 };
    int bytesPerPixel = 4;
    unsigned char* buff = new unsigned char[wh[1] * wh[0] * 4];
    std::string fileName = "wn64_2";
    for (int y = 0; y < wh[1]; y++)
        for (int x = 0; x < wh[0]; x++) {
            int idx = (y * wh[1] + x) * bytesPerPixel;
            for (int i = 0; i < 4; i++)
                buff[idx + i] = getRandom(0, 1) * 255;
        }
    Texture::saveBMP("/dt/out/" + fileName + ".bmp", (unsigned char*)buff, wh[0], wh[1]);

    fileName = "wn64_256";
    for (int y = 0; y < wh[1]; y++)
        for (int x = 0; x < wh[0]; x++) {
            int idx = (y * wh[1] + x) * bytesPerPixel;
            for (int i = 0; i < 4; i++)
                buff[idx + i] = getRandom(0, 255);
        }
    Texture::saveBMP("/dt/out/" + fileName + ".bmp", (unsigned char*)buff, wh[0], wh[1]);

    delete[] buff;
    mylog("Ready\n");
    return 1;
}

4. Откроем TheGame.cpp и заменим на этот раз НЕ весь код как раньше, а только функцию run() вышеприведенным кодом.


5. Компиляция и запуск.

Картинки сгенерированы и сохранены в каталоге

C:\CPP\a997modeler\p_windows\Debug\dt\out


6. В Windows File Explorer-е под C:\CPP\engine\dt создадим новый каталог

C:\CPP\engine\dt\common\img\whitenoise


7. Скопируем оба BMP файла из C:\CPP\a997modeler\p_windows\Debug\dt\out

в C:\CPP\engine\dt\common\img\whitenoise


9. Каталог C:\CPP\a997modeler\p_windows\Debug\dt\out можно удалить.

Собственно, можно удалить и весь родительский каталог dt ( C:\CPP\a997modeler\p_windows\Debug\dt) тоже, поскольку он автоматически пере-создается инструкциями xcopy при каждом билде.


Leave a Reply

Your email address will not be published. Required fields are marked *