Для последующего использования я хотел сгенерировать несколько картинок с “белым шумом” и сохранить их в каком-то узнаваемом формате. Чтобы не перегружать проект лишними библиотеками, решил написать собственную маленькую функцию. Ради простоты – без компрессии и только с одним форматом пикселя – 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 при каждом билде.