Originally, this post is intended to be on cplusplus.com as an article of mine but they never accepted it. So, I thought why not just put it on my own website. If you know what the heck SDL2 and C++ is, you can understand the technique.
What is Box Blur?
Box Blur is a very easy technique of blurring an image. It is based on the fact that there is replacing of the colour of a pixel with the average of all the colours of its adjacent pixels. With that said, the number of adjacent pixels can be increased or decreased, giving us the option to increase or decrease the extent of blurring effect as well.
The Implementation
First thing, you should know the basics of C++ as well as a bit of SDL2. You should know the bit shifting, some commonly used colour formats and image formats.
We are going to use SDL2 along with SDL2_image. SDL2_image allows us to work with many image formats. We are only going to use the PNG image format.
It is going to be a single file project and there will be no exception handling.
We are not going to make UI for managing things like input image name or blur extent. But we do have a settings file acting as input which will provide necessary data to work properly. Therefore, we will include fstream header.
We will also have some safety to know our errors and for this, we need to include iostream. All other headers are as expected.
#include <iostream>
#include <string>
#include <fstream>
#include <SDL.h>
#include <SDL_image.h>
Now that, we talked about input, there are variables to keep them too.
//Input data
int window_width = 800;
int window_height = 600;
std::string input_image_name = "inputImage.png";
std::string output_image_name = "NULL";
int blur_extent = 1;
There will be no SDL_Texture. It is almost useless to have textures because we have to do pixel manipulation with it, all onto the CPU in one way or the other. SDL_Surface is ideal for direct pixel manipulation onto the CPU.
//SDL structures
SDL_Window* window = NULL;
SDL_Event input;
SDL_Surface* windowSurface = NULL;
SDL_Surface* imageSurface = NULL;
bool quit = false;
A simple function to get input from a file in a proper way.
void getInput(std::string inputFileName)
{
std::ifstream inputFile;
inputFile.open(inputFileName);
if (inputFile.fail())
{
std::cerr << "ERROR: Failed to open " << inputFileName << "." << std::endl;
}
else
{
while (inputFile.good())
{
std::string tag; inputFile >> tag;
if (tag == "[window_width]") inputFile >> window_width;
else if (tag == "[window_height]") inputFile >> window_height;
else if (tag == "[input_image_name]") inputFile >> input_image_name;
else if (tag == "[output_image_name]") inputFile >> output_image_name;
else if (tag == "[blur_extent]") inputFile >> blur_extent;
}
}
inputFile.close();
}
After getting input, it is time for initializing the systems.
void init()
{
SDL_Init(SDL_INIT_EVERYTHING);
window = SDL_CreateWindow("BoxBlurrer",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
window_width, window_height, SDL_WINDOW_SHOWN);
windowSurface = SDL_GetWindowSurface(window);
imageSurface = IMG_Load(input_image_name.c_str());
}
After initializing the systems, we have to blur the image that we loaded in.
Box blur is just averaging the colour values of adjacent pixels and then, using it as the colour values of that pixel. It is called box blur because of the fact that it is always done in a square.
void blur() //This manipulates with SDL_Surface and gives it a box blur effect
{
for (int y = 0; y < imageSurface->h; y++)
{
for (int x = 0; x < (imageSurface->pitch / 4); x++)
{
Uint32 color = ((Uint32*)imageSurface->pixels)[(y * (imageSurface->pitch / 4)) + x];
//SDL_GetRGBA() is a method for getting color
//components from a 32 bit color
Uint8 r = 0, g = 0, b = 0, a = 0;
SDL_GetRGBA(color, imageSurface->format, &r, &g, &b, &a);
Uint32 rb = 0, gb = 0, bb = 0, ab = 0;
//Within the two for-loops below, colours of adjacent pixels are added up
for (int yo = -blur_extent; yo <= blur_extent; yo++)
{
for (int xo = -blur_extent; xo <= blur_extent; xo++)
{
if (y + yo >= 0 && x + xo >= 0
&& y + yo < imageSurface->h && x + xo < (imageSurface->pitch / 4))
{
Uint32 colOth = ((Uint32*)imageSurface->pixels)[((y + yo) * (imageSurface->pitch / 4)) + (x + xo)];
Uint8 ro = 0, go = 0, bo = 0, ao = 0;
SDL_GetRGBA(colOth, imageSurface->format, &ro, &go, &bo, &ao);
rb += ro;
gb += go;
bb += bo;
ab += ao;
}
}
}
//The sum is then, divided by the total number of
//pixels present in a block of blur radius
//For blur_extent 1, it will be 9
//For blur_extent 2, it will be 25
//and so on...
//In this way, we are getting the average of
//all the pixels in a block of blur radius
//(((blur_extent * 2) + 1) * ((blur_extent * 2) + 1)) calculates
//the total number of pixels present in a block of blur radius
r = (Uint8)(rb / (((blur_extent * 2) + 1) * ((blur_extent * 2) + 1)));
g = (Uint8)(gb / (((blur_extent * 2) + 1) * ((blur_extent * 2) + 1)));
b = (Uint8)(bb / (((blur_extent * 2) + 1) * ((blur_extent * 2) + 1)));
a = (Uint8)(ab / (((blur_extent * 2) + 1) * ((blur_extent * 2) + 1)));
//Bit shifting color bits to form a 32 bit proper colour
color = (r) | (g << 8) | (b << 16) | (a << 24);
((Uint32*)imageSurface->pixels)[(y * (imageSurface->pitch / 4)) + x] = color;
}
}
}
After blurring, we want to make sure that we can see the blurred image on the screen.
void update()
{
//Putting surface onto the screen
SDL_BlitSurface(imageSurface, NULL, windowSurface, NULL);
SDL_UpdateWindowSurface(window);
}
In the end, when we are quitting the program, we want to make sure that the blurred image is saved as a PNG image file. If we don’t want to have any saved output, we use “NULL” as the output image name.
void setOutput() //For saving image as PNG
{
if (output_image_name != "NULL")
IMG_SavePNG(imageSurface, output_image_name.c_str());
}
Now, just need to clear the resources.
void clear()
{
SDL_FreeSurface(imageSurface);
SDL_DestroyWindow(window);
IMG_Quit();
SDL_Quit();
}
Getting everything in one piece, all in our main function!
int main()
{
getInput("settings.ini");
init();
blur();
while (!quit)
{
while (SDL_PollEvent(&input) > 0)
if (input.type == SDL_QUIT) quit = true;
update();
}
setOutput();
clear();
return 0;
}
It’s pretty much straight forward and I hope that you understand that…
To those who love to criticize, I know it could be way better than that if we could change the blur extent by pressing some key and having its result instantly on the screen, or if we make another input tag for image format. But I think that we should keep things simple…
The Results
Unfortunately, I couldn’t post the images. But you can try and see it for yourself.
If you want to show some better pixel manipulation, I will appreciate it!