I was planning to generate and save a few "white noise" 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't use compression and will support only 1 pixel format - RGBA. The only 2 options that would fit are BMP and TGA formats. BMP is more common, TGA is simpler. However, Photoshop doesn't understand 4 bytes BMPs, Windows Paint doesn't understand TGAs, so, just in case, we'll implement both in Texture class.
Windows
1. Start VS, open C:\CPP\a997modeler\p_windows\p_windows.sln.
2. Open Texture.h and replace code by:
#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. Open Texture.cpp and replace code by:
#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;
}
See the source code above, it's pretty self explanatory. Simply header and data. Just 1 detail: both formats are using BGRA pixel format, not RGBA.
Now, white noise. We will generate 64x64 RGBA image (not a texture, but simply 4-bytes pixels array). Then we'll fill out all 4 channels by random numbers in 0 to 255 range, so each of 4 channels will contain it's own black-and-white image. We'll do it in TheGame::run(). Following code generates 2 images:
First - with binary black/white values (0 or 255):
Second one - with gray-scale values (from 0 to 255):
And so on for all 4 channels, so resulting images will look like
and
Both images will be recorded in BMP format.
- If you want to view them in Photoshop, you can save in TGA format also.
The code:
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. Open TheGame.cpp and replace this time NOT entire code as we did before, but only run() function by the code above.
5. Build and run.
Now images are generated and saved in
C:\CPP\a997modeler\p_windows\Debug\dt\out
6. In Windows File Explorer under C:\CPP\engine\dt create new folder:
C:\CPP\engine\dt\common\img\whitenoise
7. Copy both BMP files from C:\CPP\a997modeler\p_windows\Debug\dt\out
to C:\CPP\engine\dt\common\img\whitenoise
9. Ok to delete folder C:\CPP\a997modeler\p_windows\Debug\dt\out
Actually, it's ok to delete parent folder dt ( C:\CPP\a997modeler\p_windows\Debug\dt) too, since it's automatically re-generating by xcopy instructions during each build.