4.2: Image Kernel
https://en.wikipedia.org/wiki/Kernel_(image_processing)
press key 0, 1, 2, 3, 4, 5, or 6 to switch between the different kernels
drop another .bmp file onto the window to load it
Compute Shader Source:
#version 450 core
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
uniform readonly layout (rgba8) image2D img1;
uniform writeonly image2D img2;
layout (location = 0) uniform uint UseKernel = 0;
const float KernelIdentity[3][3] = { {0, 0, 0}, {0, 1, 0}, {0, 0, 0} };
const float KernelEdgeDetection1[3][3] = { {1, 0, -1}, {0, 0, 0}, {-1, 0, 1} };
const float KernelEdgeDetection2[3][3] = { {0, 1, 0}, {1, -4, 1}, {0, 1, 0} };
const float KernelEdgeDetection3[3][3] = { {-1, -1, -1}, {-1, 8, -1}, {-1, -1, -1} };
const float KernelSharpen[3][3] = { {0, -1, 0}, {-1, 5, -1}, {0, -1, 0} };
const float KernelBoxBlur[3][3] = { {0.111f, 0.111f, 0.111f}, {0.111f, 0.111f, 0.111f}, {0.111f, 0.111f, 0.111f} };
const float KernelGaussianBlur[3][3] = { {0.0625f, 0.125f, 0.0625f}, {0.125f, 0.25f, 0.125f}, {0.0625f, 0.125f, 0.0625f} };
vec4 ProcessImage(ivec2 texel0)
{
vec4 result = vec4(0, 0, 0, 0);
float Kernel[3][3] = KernelIdentity; int kernelsize = 3;
if (UseKernel == 1) Kernel = KernelEdgeDetection1;
if (UseKernel == 2) Kernel = KernelEdgeDetection2;
if (UseKernel == 3) Kernel = KernelEdgeDetection3;
if (UseKernel == 4) Kernel = KernelSharpen;
if (UseKernel == 5) Kernel = KernelBoxBlur;
if (UseKernel == 6) Kernel = KernelGaussianBlur;
for (int x = 0; x < kernelsize; x++)
{
for (int y = 0; y < kernelsize; y++)
{
ivec2 texel = texel0 + ivec2(x - kernelsize / 2, y - kernelsize / 2);
result = result + imageLoad(img1, texel) * Kernel[x][y];
}
}
return result;
}
void main()
{
ivec2 texel0 = ivec2(gl_GlobalInvocationID.xy);
vec4 color = ProcessImage(texel0);
imageStore(img2, texel0, color);
}
Main.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <iostream>
#include <string>
#include "Shader.h"
#include "BMP.h"
using namespace std;
using namespace glm;
// main functions
void Initialize();
void Render();
void CleanUp();
bool LoadImage(const string& filename);
void ProcessImage();
/* callbacks */
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void drop_callback(GLFWwindow* window, int pathcount, const char** paths);
unsigned int program_compute = 0;
unsigned int computeshader = 0;
unsigned int program_render = 0;
unsigned int vertexshader = 0;
unsigned int fragmentshader = 0;
unsigned int vertexarray = 0;
unsigned int vertexbuffer = 0;
unsigned int texture_read = 0;
unsigned int texture_draw = 0;
unsigned int texturewidth = 2;
unsigned int textureheight = 5;
void* texture_read_data = nullptr;
unsigned int UseKernel = 0;
string computeshader_source = {
"#version 450 core\n"
"layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;"
"uniform readonly layout (rgba8) image2D img1;"
"uniform writeonly image2D img2;"
"layout (location = 0) uniform uint UseKernel = 0;"
"const float KernelIdentity[3][3] = { {0, 0, 0}, {0, 1, 0}, {0, 0, 0} };"
"const float KernelEdgeDetection1[3][3] = { {1, 0, -1}, {0, 0, 0}, {-1, 0, 1} };"
"const float KernelEdgeDetection2[3][3] = { {0, 1, 0}, {1, -4, 1}, {0, 1, 0} };"
"const float KernelEdgeDetection3[3][3] = { {-1, -1, -1}, {-1, 8, -1}, {-1, -1, -1} };"
"const float KernelSharpen[3][3] = { {0, -1, 0}, {-1, 5, -1}, {0, -1, 0} };"
"const float KernelBoxBlur[3][3] = { {0.111f, 0.111f, 0.111f}, {0.111f, 0.111f, 0.111f}, {0.111f, 0.111f, 0.111f} };"
"const float KernelGaussianBlur[3][3] = { {0.0625f, 0.125f, 0.0625f}, {0.125f, 0.25f, 0.125f}, {0.0625f, 0.125f, 0.0625f} };"
"vec4 ProcessImage(ivec2 texel0) {"
"vec4 result = vec4(0, 0, 0, 0);"
"float Kernel[3][3] = KernelIdentity; int kernelsize = 3;"
"if (UseKernel == 1) Kernel = KernelEdgeDetection1;"
"if (UseKernel == 2) Kernel = KernelEdgeDetection2;"
"if (UseKernel == 3) Kernel = KernelEdgeDetection3;"
"if (UseKernel == 4) Kernel = KernelSharpen;"
"if (UseKernel == 5) Kernel = KernelBoxBlur;"
"if (UseKernel == 6) Kernel = KernelGaussianBlur;"
"for (int x = 0; x < kernelsize; x++) {"
"for (int y = 0; y < kernelsize; y++) {"
"ivec2 texel = texel0 + ivec2(x - kernelsize / 2, y - kernelsize / 2);"
"result = result + imageLoad(img1, texel) * Kernel[x][y];"
"}}"
"return result; }"
"void main() {"
"ivec2 texel0 = ivec2(gl_GlobalInvocationID.xy);"
"vec4 color = ProcessImage(texel0);"
"imageStore(img2, texel0, color);"
"}"
};
string vertexshader_source = {
"#version 450 core\n"
"in layout (location = 0) vec3 in_position;"
"in layout (location = 1) vec2 in_texcoord;"
"out vec2 texcoord;"
"void main() {"
"gl_Position = vec4(in_position, 1);"
"texcoord = in_texcoord;"
"}"
};
string fragmentshader_source = {
"#version 450 core\n"
"in vec2 texcoord;"
"uniform sampler2D tex1;"
"out layout (location = 0) vec4 out_color;"
"void main() {"
"out_color = texture(tex1, texcoord);"
"}"
};
struct Vertex {
vec3 Position;
vec2 TexCoord;
};
int main(void)
{
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window and its OpenGL context */
GLFWwindow* window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
/* Set callback functions */
glfwSetKeyCallback(window, key_callback);
glfwSetDropCallback(window, drop_callback);
/* Initialize GLEW */
if (glewInit() != GLEW_OK)
{
glfwTerminate();
return -1;
}
Initialize();
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
Render();
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
CleanUp();
glfwTerminate();
return 0;
}
void Initialize()
{
// create all objects
//------------------------------------------------------------------------------------------------------------------------
program_compute = glCreateProgram();
program_render = glCreateProgram();
vertexshader = glCreateShader(GL_VERTEX_SHADER);
fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
computeshader = glCreateShader(GL_COMPUTE_SHADER);
glGenVertexArrays(1, &vertexarray);
glGenBuffers(1, &vertexbuffer);
glGenTextures(1, &texture_read);
glGenTextures(1, &texture_draw);
//------------------------------------------------------------------------------------------------------------------------
// setup programs
//------------------------------------------------------------------------------------------------------------------------
// compute
CompileShader(computeshader, computeshader_source);
LinkProgram(program_compute, { computeshader });
// render
CompileShader(vertexshader, vertexshader_source);
CompileShader(fragmentshader, fragmentshader_source);
LinkProgram(program_render, { vertexshader, fragmentshader });
//------------------------------------------------------------------------------------------------------------------------
// setup vertex array
//------------------------------------------------------------------------------------------------------------------------
glBindVertexArray(vertexarray);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(0));
glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(vec3)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
//------------------------------------------------------------------------------------------------------------------------
// setup vertex buffer
//------------------------------------------------------------------------------------------------------------------------
// quad
Vertex vertices[] = {
// x y z u v
{ { -0.9f, -0.9f, 0 },{ 0, 0 } },
{ { +0.9f, -0.9f, 0 },{ 1, 0 } },
{ { +0.9f, +0.9f, 0 },{ 1, 1 } },
{ { -0.9f, +0.9f, 0 },{ 0, 1 } },
};
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//------------------------------------------------------------------------------------------------------------------------
// setup textures ("read" and "draw")
//------------------------------------------------------------------------------------------------------------------------
// 2 x 5 texture data
unsigned char texturedata[] = {
0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, // red green
0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // blue cyan
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // magenta yellow
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, // white black
0x50, 0x50, 0x50, 0xFF, 0xA0, 0xA0, 0xA0, 0xFF, // gray gray (brighter)
};
texture_read_data = texturedata;
glBindTexture(GL_TEXTURE_2D, texture_read);
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texturewidth, textureheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_read_data);
glBindTexture(GL_TEXTURE_2D, 0);
glBindTexture(GL_TEXTURE_2D, texture_draw);
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texturewidth, textureheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
LoadImage("image.bmp");
// connect "texture_draw" to sampler in fragment shader
unsigned int texture_unit = 1;
glBindTextureUnit(texture_unit, texture_draw);
int location = glGetUniformLocation(program_render, "tex1");
glProgramUniform1i(program_render, location, texture_unit);
// connect "texture_read" to image variable in compute shader
unsigned int image_unit = 2;
glBindImageTexture(image_unit, texture_read, 0, false, 0, GL_READ_ONLY, GL_RGBA8);
location = glGetUniformLocation(program_compute, "img1");
glProgramUniform1i(program_compute, location, image_unit);
// connect "texture_draw" to image variable in compute shader
image_unit = 3;
glBindImageTexture(image_unit, texture_draw, 0, false, 0, GL_WRITE_ONLY, GL_RGBA8);
location = glGetUniformLocation(program_compute, "img2");
glProgramUniform1i(program_compute, location, image_unit);
//------------------------------------------------------------------------------------------------------------------------
ProcessImage();
}
void Render()
{
glClearColor(0.5f, 0.5f, 0.5f, 0); // background = gray
glClear(GL_COLOR_BUFFER_BIT);
// render quad
glUseProgram(program_render);
glBindVertexArray(vertexarray);
glDrawArrays(GL_QUADS, 0, 4);
glBindVertexArray(0);
glUseProgram(0);
}
void CleanUp()
{
// delete all objects
glDeleteProgram(program_compute);
glDeleteProgram(program_render);
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
glDeleteShader(computeshader);
glDeleteVertexArrays(1, &vertexarray);
glDeleteBuffers(1, &vertexbuffer);
glDeleteTextures(1, &texture_read);
glDeleteTextures(1, &texture_draw);
}
bool LoadImage(const string& filename)
{
// load .bmp file
// credits for this bmp file loader go to "Benjamin Kalytta", http://www.kalytta.com/bitmap.h
// https://en.wikipedia.org/wiki/BMP_file_format#External_links
CBitmap bmp;
if (!bmp.Load(filename.c_str()))
{
cout << "error: cant load .bmp file " << filename << endl;
return false;
}
texturewidth = bmp.GetWidth();
textureheight = bmp.GetHeight();
texture_read_data = bmp.GetBits();
glBindTexture(GL_TEXTURE_2D, texture_read);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texturewidth, textureheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_read_data);
glBindTexture(GL_TEXTURE_2D, texture_draw);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texturewidth, textureheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void ProcessImage()
{
// invoke compute shader
//------------------------------------------------------------------------------------------------------------------------
glProgramUniform1ui(program_compute, 0, UseKernel);
glUseProgram(program_compute);
glDispatchCompute(texturewidth, textureheight, 1);
glUseProgram(0);
//------------------------------------------------------------------------------------------------------------------------
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
/* switch kernel to use */
if (key == GLFW_KEY_0 && action == GLFW_PRESS)
UseKernel = 0;
if (key == GLFW_KEY_1 && action == GLFW_PRESS)
UseKernel = 1;
if (key == GLFW_KEY_2 && action == GLFW_PRESS)
UseKernel = 2;
if (key == GLFW_KEY_3 && action == GLFW_PRESS)
UseKernel = 3;
if (key == GLFW_KEY_4 && action == GLFW_PRESS)
UseKernel = 4;
if (key == GLFW_KEY_5 && action == GLFW_PRESS)
UseKernel = 5;
if (key == GLFW_KEY_6 && action == GLFW_PRESS)
UseKernel = 6;
ProcessImage();
}
void drop_callback(GLFWwindow* window, int pathcount, const char** paths)
{
for (unsigned int i = 0; i < pathcount; i++)
{
/* try to load image dropped onto the window */
if (LoadImage(string(paths[i])))
{
ProcessImage();
break;
}
}
}
Shader.h
#ifndef SHADER_H
#define SHADER_H
#include <GL/glew.h>
#include <string>
#include <list>
std::string LoadTextFile(const std::string& filepath);
std::string ShaderTypeName(GLuint shader);
bool CompileShader(GLuint shader, const std::string& sourcecode);
std::string ShaderInfoLog(GLuint shader);
bool LinkProgram(GLuint program, const std::list<GLuint>& shaderlist);
std::string ProgramInfoLog(GLuint program);
#endif // SHADER_H
Shader.cpp
#include "Shader.h"
#include <iostream>
#include <fstream>
std::string LoadTextFile(const std::string & filepath)
{
std::string result(""), line;
std::fstream f(filepath.c_str(), std::ios::in);
while (f.good())
{
std::getline(f, line);
result += line + '\n';
}
return result;
}
std::string ShaderTypeName(GLuint shader)
{
if (glIsShader(shader))
{
GLint type = 0;
glGetShaderiv(shader, GL_SHADER_TYPE, &type);
if (type == GL_VERTEX_SHADER)
return "Vertex Shader";
if (type == GL_TESS_CONTROL_SHADER)
return "Tessellation Control Shader";
if (type == GL_TESS_EVALUATION_SHADER)
return "Tessellation Evaluation Shader";
if (type == GL_GEOMETRY_SHADER)
return "Geometry Shader";
if (type == GL_FRAGMENT_SHADER)
return "Fragment Shader";
if (type == GL_COMPUTE_SHADER)
return "Compute Shader";
}
return "invalid shader";
}
bool CompileShader(GLuint shader, const std::string& sourcecode)
{
if (!glIsShader(shader))
{
std::cout << "ERROR: shader compilation failed, no valid shader specified" << std::endl;
return false;
}
if (sourcecode.empty())
{
std::cout << "ERROR: shader compilation failed, no source code specified (" << ShaderTypeName(shader) << ")" << std::endl;
return false;
}
const char* sourcearray[] = { sourcecode.c_str() };
glShaderSource(shader, 1, sourcearray, NULL);
glCompileShader(shader);
// check compile status
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
// successfully compiled shader
if (status == GL_TRUE)
return true;
// show compile errors
std::cout << "ERROR: shader compilation failed (" << ShaderTypeName(shader) << ")" << std::endl << ShaderInfoLog(shader) << std::endl;
return false;
}
std::string ShaderInfoLog(GLuint shader)
{
if (glIsShader(shader))
{
GLint logsize = 0;
GLchar infolog[1024] = { 0 };
glGetShaderInfoLog(shader, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid shader";
}
bool LinkProgram(GLuint program, const std::list<GLuint>& shaderlist)
{
if (!glIsProgram(program))
{
std::cout << "ERROR: shader linking failed, no valid program specified" << std::endl;
return false;
}
// attach all shaders to the program
for (std::list<GLuint>::const_iterator it = shaderlist.begin(); it != shaderlist.end(); it++)
{
if (glIsShader(*it))
glAttachShader(program, *it);
}
// link program
glLinkProgram(program);
// detach all shaders again
for (std::list<GLuint>::const_iterator it = shaderlist.begin(); it != shaderlist.end(); it++)
{
if (glIsShader(*it))
glDetachShader(program, *it);
}
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
// successfully linked program
if (status == GL_TRUE)
return true;
// show link errors
std::cout << "ERROR: shader linking failed" << std::endl << ProgramInfoLog(program) << std::endl;
return false;
}
std::string ProgramInfoLog(GLuint program)
{
if (glIsProgram(program))
{
GLint logsize = 0;
GLchar infolog[1024] = { 0 };
glGetProgramInfoLog(program, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid program";
}
BMP.h
/*
* Windows Bitmap File Loader
* Version 1.2.5 (20120929)
*
* Supported Formats: 1, 4, 8, 16, 24, 32 Bit Images
* Alpha Bitmaps are also supported.
* Supported compression types: RLE 8, BITFIELDS
*
* Created by: Benjamin Kalytta, 2006 - 2012
* Thanks for bug fixes goes to: Chris Campbell
*
* Licence: Free to use, URL to my source and my name is required in your source code.
*
* Source can be found at http://www.kalytta.com/bitmap.h
*
* Warning: This code should not be used in unmodified form in a production environment.
* It should only serve as a basis for your own development.
* There is only a minimal error handling in this code. (Notice added 20111211)
*/
#ifndef BITMAP_H
#define BITMAP_H
#include <iostream>
#include <fstream>
#include <string>
#include <string.h>
#ifndef __LITTLE_ENDIAN__
#ifndef __BIG_ENDIAN__
#define __LITTLE_ENDIAN__
#endif
#endif
#ifdef __LITTLE_ENDIAN__
#define BITMAP_SIGNATURE 0x4d42
#else
#define BITMAP_SIGNATURE 0x424d
#endif
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
typedef unsigned __int32 uint32_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int8 uint8_t;
typedef __int32 int32_t;
#elif defined(__GNUC__) || defined(__CYGWIN__) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__PGI) || defined(__LCC__)
#include <stdint.h>
#else
typedef unsigned int uint32_t;
typedef unsigned short int uint16_t;
typedef unsigned char uint8_t;
typedef int int32_t;
#endif
#pragma pack(push, 1)
typedef struct _BITMAP_FILEHEADER {
uint16_t Signature;
uint32_t Size;
uint32_t Reserved;
uint32_t BitsOffset;
} BITMAP_FILEHEADER;
#define BITMAP_FILEHEADER_SIZE 14
typedef struct _BITMAP_HEADER {
uint32_t HeaderSize;
int32_t Width;
int32_t Height;
uint16_t Planes;
uint16_t BitCount;
uint32_t Compression;
uint32_t SizeImage;
int32_t PelsPerMeterX;
int32_t PelsPerMeterY;
uint32_t ClrUsed;
uint32_t ClrImportant;
uint32_t RedMask;
uint32_t GreenMask;
uint32_t BlueMask;
uint32_t AlphaMask;
uint32_t CsType;
uint32_t Endpoints[9]; // see http://msdn2.microsoft.com/en-us/library/ms536569.aspx
uint32_t GammaRed;
uint32_t GammaGreen;
uint32_t GammaBlue;
} BITMAP_HEADER;
typedef struct _RGBA {
uint8_t Red;
uint8_t Green;
uint8_t Blue;
uint8_t Alpha;
} RGBA;
typedef struct _BGRA {
uint8_t Blue;
uint8_t Green;
uint8_t Red;
uint8_t Alpha;
} BGRA;
#pragma pack(pop)
class CBitmap {
private:
BITMAP_FILEHEADER m_BitmapFileHeader;
BITMAP_HEADER m_BitmapHeader;
RGBA *m_BitmapData;
unsigned int m_BitmapSize;
// Masks and bit counts shouldn't exceed 32 Bits
public:
class CColor {
public:
static inline unsigned int BitCountByMask(unsigned int Mask) {
unsigned int BitCount = 0;
while (Mask) {
Mask &= Mask - 1;
BitCount++;
}
return BitCount;
}
static inline unsigned int BitPositionByMask(unsigned int Mask) {
return BitCountByMask((Mask & (~Mask + 1)) - 1);
}
static inline unsigned int ComponentByMask(unsigned int Color, unsigned int Mask) {
unsigned int Component = Color & Mask;
return Component >> BitPositionByMask(Mask);
}
static inline unsigned int BitCountToMask(unsigned int BitCount) {
return (BitCount == 32) ? 0xFFFFFFFF : (1 << BitCount) - 1;
}
static unsigned int Convert(unsigned int Color, unsigned int FromBitCount, unsigned int ToBitCount) {
if (ToBitCount < FromBitCount) {
Color >>= (FromBitCount - ToBitCount);
}
else {
Color <<= (ToBitCount - FromBitCount);
if (Color > 0) {
Color |= BitCountToMask(ToBitCount - FromBitCount);
}
}
return Color;
}
};
public:
CBitmap() : m_BitmapData(0), m_BitmapSize(0) {
Dispose();
}
CBitmap(const char* Filename) : m_BitmapData(0), m_BitmapSize(0) {
Load(Filename);
}
~CBitmap() {
Dispose();
}
void Dispose() {
if (m_BitmapData) {
delete[] m_BitmapData;
m_BitmapData = 0;
}
memset(&m_BitmapFileHeader, 0, sizeof(m_BitmapFileHeader));
memset(&m_BitmapHeader, 0, sizeof(m_BitmapHeader));
}
/* Load specified Bitmap and stores it as RGBA in an internal buffer */
bool Load(const char *Filename) {
std::ifstream file(Filename, std::ios::binary | std::ios::in);
if (file.bad()) {
return false;
}
if (file.is_open() == false) {
return false;
}
Dispose();
file.read((char*)&m_BitmapFileHeader, BITMAP_FILEHEADER_SIZE);
if (m_BitmapFileHeader.Signature != BITMAP_SIGNATURE) {
return false;
}
file.read((char*)&m_BitmapHeader, sizeof(BITMAP_HEADER));
/* Load Color Table */
file.seekg(BITMAP_FILEHEADER_SIZE + m_BitmapHeader.HeaderSize, std::ios::beg);
unsigned int ColorTableSize = 0;
if (m_BitmapHeader.BitCount == 1) {
ColorTableSize = 2;
}
else if (m_BitmapHeader.BitCount == 4) {
ColorTableSize = 16;
}
else if (m_BitmapHeader.BitCount == 8) {
ColorTableSize = 256;
}
// Always allocate full sized color table
BGRA* ColorTable = new BGRA[ColorTableSize]; // std::bad_alloc exception should be thrown if memory is not available
file.read((char*)ColorTable, sizeof(BGRA) * m_BitmapHeader.ClrUsed);
/* ... Color Table for 16 bits images are not supported yet */
m_BitmapSize = GetWidth() * GetHeight();
m_BitmapData = new RGBA[m_BitmapSize];
unsigned int LineWidth = ((GetWidth() * GetBitCount() / 8) + 3) & ~3;
uint8_t *Line = new uint8_t[LineWidth];
file.seekg(m_BitmapFileHeader.BitsOffset, std::ios::beg);
int Index = 0;
bool Result = true;
if (m_BitmapHeader.Compression == 0) {
for (unsigned int i = 0; i < GetHeight(); i++) {
file.read((char*)Line, LineWidth);
uint8_t *LinePtr = Line;
for (unsigned int j = 0; j < GetWidth(); j++) {
if (m_BitmapHeader.BitCount == 1) {
uint32_t Color = *((uint8_t*)LinePtr);
for (int k = 0; k < 8; k++) {
m_BitmapData[Index].Red = ColorTable[Color & 0x80 ? 1 : 0].Red;
m_BitmapData[Index].Green = ColorTable[Color & 0x80 ? 1 : 0].Green;
m_BitmapData[Index].Blue = ColorTable[Color & 0x80 ? 1 : 0].Blue;
m_BitmapData[Index].Alpha = ColorTable[Color & 0x80 ? 1 : 0].Alpha;
Index++;
Color <<= 1;
}
LinePtr++;
j += 7;
}
else if (m_BitmapHeader.BitCount == 4) {
uint32_t Color = *((uint8_t*)LinePtr);
m_BitmapData[Index].Red = ColorTable[(Color >> 4) & 0x0f].Red;
m_BitmapData[Index].Green = ColorTable[(Color >> 4) & 0x0f].Green;
m_BitmapData[Index].Blue = ColorTable[(Color >> 4) & 0x0f].Blue;
m_BitmapData[Index].Alpha = ColorTable[(Color >> 4) & 0x0f].Alpha;
Index++;
m_BitmapData[Index].Red = ColorTable[Color & 0x0f].Red;
m_BitmapData[Index].Green = ColorTable[Color & 0x0f].Green;
m_BitmapData[Index].Blue = ColorTable[Color & 0x0f].Blue;
m_BitmapData[Index].Alpha = ColorTable[Color & 0x0f].Alpha;
Index++;
LinePtr++;
j++;
}
else if (m_BitmapHeader.BitCount == 8) {
uint32_t Color = *((uint8_t*)LinePtr);
m_BitmapData[Index].Red = ColorTable[Color].Red;
m_BitmapData[Index].Green = ColorTable[Color].Green;
m_BitmapData[Index].Blue = ColorTable[Color].Blue;
m_BitmapData[Index].Alpha = ColorTable[Color].Alpha;
Index++;
LinePtr++;
}
else if (m_BitmapHeader.BitCount == 16) {
uint32_t Color = *((uint16_t*)LinePtr);
m_BitmapData[Index].Red = ((Color >> 10) & 0x1f) << 3;
m_BitmapData[Index].Green = ((Color >> 5) & 0x1f) << 3;
m_BitmapData[Index].Blue = (Color & 0x1f) << 3;
m_BitmapData[Index].Alpha = 255;
Index++;
LinePtr += 2;
}
else if (m_BitmapHeader.BitCount == 24) {
uint32_t Color = *((uint32_t*)LinePtr);
m_BitmapData[Index].Blue = Color & 0xff;
m_BitmapData[Index].Green = (Color >> 8) & 0xff;
m_BitmapData[Index].Red = (Color >> 16) & 0xff;
m_BitmapData[Index].Alpha = 255;
Index++;
LinePtr += 3;
}
else if (m_BitmapHeader.BitCount == 32) {
uint32_t Color = *((uint32_t*)LinePtr);
m_BitmapData[Index].Blue = Color & 0xff;
m_BitmapData[Index].Green = (Color >> 8) & 0xff;
m_BitmapData[Index].Red = (Color >> 16) & 0xff;
m_BitmapData[Index].Alpha = Color >> 24;
Index++;
LinePtr += 4;
}
}
}
}
else if (m_BitmapHeader.Compression == 1) { // RLE 8
uint8_t Count = 0;
uint8_t ColorIndex = 0;
int x = 0, y = 0;
while (file.eof() == false) {
file.read((char*)&Count, sizeof(uint8_t));
file.read((char*)&ColorIndex, sizeof(uint8_t));
if (Count > 0) {
Index = x + y * GetWidth();
for (int k = 0; k < Count; k++) {
m_BitmapData[Index + k].Red = ColorTable[ColorIndex].Red;
m_BitmapData[Index + k].Green = ColorTable[ColorIndex].Green;
m_BitmapData[Index + k].Blue = ColorTable[ColorIndex].Blue;
m_BitmapData[Index + k].Alpha = ColorTable[ColorIndex].Alpha;
}
x += Count;
}
else if (Count == 0) {
int Flag = ColorIndex;
if (Flag == 0) {
x = 0;
y++;
}
else if (Flag == 1) {
break;
}
else if (Flag == 2) {
char rx = 0;
char ry = 0;
file.read((char*)&rx, sizeof(char));
file.read((char*)&ry, sizeof(char));
x += rx;
y += ry;
}
else {
Count = Flag;
Index = x + y * GetWidth();
for (int k = 0; k < Count; k++) {
file.read((char*)&ColorIndex, sizeof(uint8_t));
m_BitmapData[Index + k].Red = ColorTable[ColorIndex].Red;
m_BitmapData[Index + k].Green = ColorTable[ColorIndex].Green;
m_BitmapData[Index + k].Blue = ColorTable[ColorIndex].Blue;
m_BitmapData[Index + k].Alpha = ColorTable[ColorIndex].Alpha;
}
x += Count;
// Attention: Current Microsoft STL implementation seems to be buggy, tellg() always returns 0.
if (file.tellg() & 1) {
file.seekg(1, std::ios::cur);
}
}
}
}
}
else if (m_BitmapHeader.Compression == 2) { // RLE 4
/* RLE 4 is not supported */
Result = false;
}
else if (m_BitmapHeader.Compression == 3) { // BITFIELDS
/* We assumes that mask of each color component can be in any order */
uint32_t BitCountRed = CColor::BitCountByMask(m_BitmapHeader.RedMask);
uint32_t BitCountGreen = CColor::BitCountByMask(m_BitmapHeader.GreenMask);
uint32_t BitCountBlue = CColor::BitCountByMask(m_BitmapHeader.BlueMask);
uint32_t BitCountAlpha = CColor::BitCountByMask(m_BitmapHeader.AlphaMask);
for (unsigned int i = 0; i < GetHeight(); i++) {
file.read((char*)Line, LineWidth);
uint8_t *LinePtr = Line;
for (unsigned int j = 0; j < GetWidth(); j++) {
uint32_t Color = 0;
if (m_BitmapHeader.BitCount == 16) {
Color = *((uint16_t*)LinePtr);
LinePtr += 2;
}
else if (m_BitmapHeader.BitCount == 32) {
Color = *((uint32_t*)LinePtr);
LinePtr += 4;
}
else {
// Other formats are not valid
}
m_BitmapData[Index].Red = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.RedMask), BitCountRed, 8);
m_BitmapData[Index].Green = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.GreenMask), BitCountGreen, 8);
m_BitmapData[Index].Blue = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.BlueMask), BitCountBlue, 8);
m_BitmapData[Index].Alpha = CColor::Convert(CColor::ComponentByMask(Color, m_BitmapHeader.AlphaMask), BitCountAlpha, 8);
Index++;
}
}
}
delete[] ColorTable;
delete[] Line;
file.close();
return Result;
}
bool Save(const char* Filename, unsigned int BitCount = 32) {
bool Result = true;
std::ofstream file(Filename, std::ios::out | std::ios::binary);
if (file.is_open() == false) {
return false;
}
BITMAP_FILEHEADER bfh;
BITMAP_HEADER bh;
memset(&bfh, 0, sizeof(bfh));
memset(&bh, 0, sizeof(bh));
bfh.Signature = BITMAP_SIGNATURE;
bfh.BitsOffset = BITMAP_FILEHEADER_SIZE + sizeof(BITMAP_HEADER);
bfh.Size = (GetWidth() * GetHeight() * BitCount) / 8 + bfh.BitsOffset;
bh.HeaderSize = sizeof(BITMAP_HEADER);
bh.BitCount = BitCount;
if (BitCount == 32) {
bh.Compression = 3; // BITFIELD
bh.AlphaMask = 0xff000000;
bh.BlueMask = 0x00ff0000;
bh.GreenMask = 0x0000ff00;
bh.RedMask = 0x000000ff;
}
else if (BitCount == 16) {
bh.Compression = 3; // BITFIELD
bh.AlphaMask = 0x00000000;
bh.BlueMask = 0x0000001f;
bh.GreenMask = 0x000007E0;
bh.RedMask = 0x0000F800;
}
else {
bh.Compression = 0; // RGB
}
unsigned int LineWidth = (GetWidth() + 3) & ~3;
bh.Planes = 1;
bh.Height = GetHeight();
bh.Width = GetWidth();
bh.SizeImage = (LineWidth * BitCount * GetHeight()) / 8;
bh.PelsPerMeterX = 3780;
bh.PelsPerMeterY = 3780;
if (BitCount == 32) {
file.write((char*)&bfh, sizeof(BITMAP_FILEHEADER));
file.write((char*)&bh, sizeof(BITMAP_HEADER));
file.write((char*)m_BitmapData, bh.SizeImage);
}
else if (BitCount < 16) {
uint8_t* Bitmap = new uint8_t[bh.SizeImage];
BGRA *Palette = 0;
unsigned int PaletteSize = 0;
if (GetBitsWithPalette(Bitmap, bh.SizeImage, BitCount, Palette, PaletteSize)) {
bfh.BitsOffset += PaletteSize * sizeof(BGRA);
file.write((char*)&bfh, BITMAP_FILEHEADER_SIZE);
file.write((char*)&bh, sizeof(BITMAP_HEADER));
file.write((char*)Palette, PaletteSize * sizeof(BGRA));
file.write((char*)Bitmap, bh.SizeImage);
}
delete[] Bitmap;
delete[] Palette;
}
else {
uint32_t RedMask = 0;
uint32_t GreenMask = 0;
uint32_t BlueMask = 0;
uint32_t AlphaMask = 0;
if (BitCount == 16) {
RedMask = 0x0000F800;
GreenMask = 0x000007E0;
BlueMask = 0x0000001F;
AlphaMask = 0x00000000;
}
else if (BitCount == 24) {
RedMask = 0x00FF0000;
GreenMask = 0x0000FF00;
BlueMask = 0x000000FF;
}
else {
// Other color formats are not valid
Result = false;
}
if (Result) {
if (GetBits(NULL, bh.SizeImage, RedMask, GreenMask, BlueMask, AlphaMask)) {
uint8_t* Bitmap = new uint8_t[bh.SizeImage];
if (GetBits(Bitmap, bh.SizeImage, RedMask, GreenMask, BlueMask, AlphaMask)) {
file.write((char*)&bfh, sizeof(BITMAP_FILEHEADER));
file.write((char*)&bh, sizeof(BITMAP_HEADER));
file.write((char*)Bitmap, bh.SizeImage);
}
delete[] Bitmap;
}
}
}
file.close();
return Result;
}
unsigned int GetWidth() {
/* Add plausibility test */
// if (abs(m_BitmapHeader.Width) > 8192) {
// m_BitmapHeader.Width = 8192;
// }
return m_BitmapHeader.Width < 0 ? -m_BitmapHeader.Width : m_BitmapHeader.Width;
}
unsigned int GetHeight() {
/* Add plausibility test */
// if (abs(m_BitmapHeader.Height) > 8192) {
// m_BitmapHeader.Height = 8192;
// }
return m_BitmapHeader.Height < 0 ? -m_BitmapHeader.Height : m_BitmapHeader.Height;
}
unsigned int GetBitCount() {
/* Add plausibility test */
// if (m_BitmapHeader.BitCount > 32) {
// m_BitmapHeader.BitCount = 32;
// }
return m_BitmapHeader.BitCount;
}
/* Copies internal RGBA buffer to user specified buffer */
bool GetBits(void* Buffer, unsigned int &Size) {
bool Result = false;
if (Size == 0 || Buffer == 0) {
Size = m_BitmapSize * sizeof(RGBA);
Result = m_BitmapSize != 0;
}
else {
memcpy(Buffer, m_BitmapData, Size);
Result = true;
}
return Result;
}
/* Returns internal RGBA buffer */
void* GetBits() {
return m_BitmapData;
}
/* Copies internal RGBA buffer to user specified buffer and converts it into destination
* bit format specified by component masks.
*
* Typical Bitmap color formats (BGR/BGRA):
*
* Masks for 16 bit (5-5-5): ALPHA = 0x00000000, RED = 0x00007C00, GREEN = 0x000003E0, BLUE = 0x0000001F
* Masks for 16 bit (5-6-5): ALPHA = 0x00000000, RED = 0x0000F800, GREEN = 0x000007E0, BLUE = 0x0000001F
* Masks for 24 bit: ALPHA = 0x00000000, RED = 0x00FF0000, GREEN = 0x0000FF00, BLUE = 0x000000FF
* Masks for 32 bit: ALPHA = 0xFF000000, RED = 0x00FF0000, GREEN = 0x0000FF00, BLUE = 0x000000FF
*
* Other color formats (RGB/RGBA):
*
* Masks for 32 bit (RGBA): ALPHA = 0xFF000000, RED = 0x000000FF, GREEN = 0x0000FF00, BLUE = 0x00FF0000
*
* Bit count will be rounded to next 8 bit boundary. If IncludePadding is true, it will be ensured
* that line width is a multiple of 4. padding bytes are included if necessary.
*
* NOTE: systems with big endian byte order may require masks in inversion order.
*/
bool GetBits(void* Buffer, unsigned int &Size, unsigned int RedMask, unsigned int GreenMask, unsigned int BlueMask, unsigned int AlphaMask, bool IncludePadding = true) {
bool Result = false;
uint32_t BitCountRed = CColor::BitCountByMask(RedMask);
uint32_t BitCountGreen = CColor::BitCountByMask(GreenMask);
uint32_t BitCountBlue = CColor::BitCountByMask(BlueMask);
uint32_t BitCountAlpha = CColor::BitCountByMask(AlphaMask);
unsigned int BitCount = (BitCountRed + BitCountGreen + BitCountBlue + BitCountAlpha + 7) & ~7;
if (BitCount > 32) {
return false;
}
unsigned int w = GetWidth();
//unsigned int LineWidth = (w + 3) & ~3;
unsigned int dataBytesPerLine = (w * BitCount + 7) / 8;
unsigned int LineWidth = (dataBytesPerLine + 3) & ~3;
if (Size == 0 || Buffer == 0) {
//Size = (LineWidth * GetHeight() * BitCount) / 8 + sizeof(unsigned int);
Size = (GetWidth() * GetHeight() * BitCount) / 8 + sizeof(unsigned int);
return true;
}
uint8_t* BufferPtr = (uint8_t*)Buffer;
Result = true;
uint32_t BitPosRed = CColor::BitPositionByMask(RedMask);
uint32_t BitPosGreen = CColor::BitPositionByMask(GreenMask);
uint32_t BitPosBlue = CColor::BitPositionByMask(BlueMask);
uint32_t BitPosAlpha = CColor::BitPositionByMask(AlphaMask);
unsigned int j = 0;
for (unsigned int i = 0; i < m_BitmapSize; i++) {
*(uint32_t*)BufferPtr =
(CColor::Convert(m_BitmapData[i].Blue, 8, BitCountBlue) << BitPosBlue) |
(CColor::Convert(m_BitmapData[i].Green, 8, BitCountGreen) << BitPosGreen) |
(CColor::Convert(m_BitmapData[i].Red, 8, BitCountRed) << BitPosRed) |
(CColor::Convert(m_BitmapData[i].Alpha, 8, BitCountAlpha) << BitPosAlpha);
if (IncludePadding) {
j++;
if (j >= w) {
for (unsigned int k = 0; k < LineWidth - dataBytesPerLine; k++) {
BufferPtr += (BitCount >> 3);
}
j = 0;
}
}
BufferPtr += (BitCount >> 3);
}
Size -= sizeof(unsigned int);
return Result;
}
/* See GetBits().
* It creates a corresponding color table (palette) which have to be destroyed by the user after usage.
*
* Supported Bit depths are: 4, 8
*
* Todo: Optimize, use optimized palette, do ditehring (see my dithering class), support padding for 4 bit bitmaps
*/
bool GetBitsWithPalette(void* Buffer, unsigned int &Size, unsigned int BitCount, BGRA* &Palette, unsigned int &PaletteSize, bool OptimalPalette = false, bool IncludePadding = true) {
bool Result = false;
if (BitCount > 16) {
return false;
}
unsigned int w = GetWidth();
unsigned int dataBytesPerLine = (w * BitCount + 7) / 8;
unsigned int LineWidth = (dataBytesPerLine + 3) & ~3;
if (Size == 0 || Buffer == 0) {
Size = (LineWidth * GetHeight() * BitCount) / 8;
return true;
}
if (OptimalPalette) {
PaletteSize = 0;
// Not implemented
}
else {
if (BitCount == 1) {
PaletteSize = 2;
// Not implemented: Who need that?
}
else if (BitCount == 4) { // 2:2:1
PaletteSize = 16;
Palette = new BGRA[PaletteSize];
for (int r = 0; r < 4; r++) {
for (int g = 0; g < 2; g++) {
for (int b = 0; b < 2; b++) {
Palette[r | g << 2 | b << 3].Red = r ? (r << 6) | 0x3f : 0;
Palette[r | g << 2 | b << 3].Green = g ? (g << 7) | 0x7f : 0;
Palette[r | g << 2 | b << 3].Blue = b ? (b << 7) | 0x7f : 0;
Palette[r | g << 2 | b << 3].Alpha = 0xff;
}
}
}
}
else if (BitCount == 8) { // 3:3:2
PaletteSize = 256;
Palette = new BGRA[PaletteSize];
for (int r = 0; r < 8; r++) {
for (int g = 0; g < 8; g++) {
for (int b = 0; b < 4; b++) {
Palette[r | g << 3 | b << 6].Red = r ? (r << 5) | 0x1f : 0;
Palette[r | g << 3 | b << 6].Green = g ? (g << 5) | 0x1f : 0;
Palette[r | g << 3 | b << 6].Blue = b ? (b << 6) | 0x3f : 0;
Palette[r | g << 3 | b << 6].Alpha = 0xff;
}
}
}
}
else if (BitCount == 16) { // 5:5:5
// Not implemented
}
}
unsigned int j = 0;
uint8_t* BufferPtr = (uint8_t*)Buffer;
for (unsigned int i = 0; i < m_BitmapSize; i++) {
if (BitCount == 1) {
// Not implemented: Who needs that?
}
else if (BitCount == 4) {
*BufferPtr = ((m_BitmapData[i].Red >> 6) | (m_BitmapData[i].Green >> 7) << 2 | (m_BitmapData[i].Blue >> 7) << 3) << 4;
i++;
*BufferPtr |= (m_BitmapData[i].Red >> 6) | (m_BitmapData[i].Green >> 7) << 2 | (m_BitmapData[i].Blue >> 7) << 3;
}
else if (BitCount == 8) {
*BufferPtr = (m_BitmapData[i].Red >> 5) | (m_BitmapData[i].Green >> 5) << 3 | (m_BitmapData[i].Blue >> 5) << 6;
}
else if (BitCount == 16) {
// Not implemented
}
if (IncludePadding) {
j++;
if (j >= w) {
for (unsigned int k = 0; k < (LineWidth - dataBytesPerLine); k++) {
BufferPtr += BitCount / 8;
}
j = 0;
}
}
BufferPtr++;
}
Result = true;
return Result;
}
/* Set Bitmap Bits. Will be converted to RGBA internally */
bool SetBits(void* Buffer, unsigned int Width, unsigned int Height, unsigned int RedMask, unsigned int GreenMask, unsigned int BlueMask, unsigned int AlphaMask = 0) {
if (Buffer == 0) {
return false;
}
uint8_t *BufferPtr = (uint8_t*)Buffer;
Dispose();
m_BitmapHeader.Width = Width;
m_BitmapHeader.Height = Height;
m_BitmapHeader.BitCount = 32;
m_BitmapHeader.Compression = 3;
m_BitmapSize = GetWidth() * GetHeight();
m_BitmapData = new RGBA[m_BitmapSize];
/* Find bit count by masks (rounded to next 8 bit boundary) */
unsigned int BitCount = (CColor::BitCountByMask(RedMask | GreenMask | BlueMask | AlphaMask) + 7) & ~7;
uint32_t BitCountRed = CColor::BitCountByMask(RedMask);
uint32_t BitCountGreen = CColor::BitCountByMask(GreenMask);
uint32_t BitCountBlue = CColor::BitCountByMask(BlueMask);
uint32_t BitCountAlpha = CColor::BitCountByMask(AlphaMask);
for (unsigned int i = 0; i < m_BitmapSize; i++) {
unsigned int Color = 0;
if (BitCount <= 8) {
Color = *((uint8_t*)BufferPtr);
BufferPtr += 1;
}
else if (BitCount <= 16) {
Color = *((uint16_t*)BufferPtr);
BufferPtr += 2;
}
else if (BitCount <= 24) {
Color = *((uint32_t*)BufferPtr);
BufferPtr += 3;
}
else if (BitCount <= 32) {
Color = *((uint32_t*)BufferPtr);
BufferPtr += 4;
}
else {
/* unsupported */
BufferPtr += 1;
}
m_BitmapData[i].Alpha = CColor::Convert(CColor::ComponentByMask(Color, AlphaMask), BitCountAlpha, 8);
m_BitmapData[i].Red = CColor::Convert(CColor::ComponentByMask(Color, RedMask), BitCountRed, 8);
m_BitmapData[i].Green = CColor::Convert(CColor::ComponentByMask(Color, GreenMask), BitCountGreen, 8);
m_BitmapData[i].Blue = CColor::Convert(CColor::ComponentByMask(Color, BlueMask), BitCountBlue, 8);
}
return true;
}
};
#endif // BMP_H_INCLUDED