C language: Apply a filter to an image


In the 4th week of the CS50 (Harvard) course, we studied the principles of working with memory and data arrays. We were faced with the task of writing a program that will apply filters to the original image (filter-less). You need to make 4 filters: black and white, sepia, specular and blur. The original picture is a 24-bit .BMP file, which means that each pixel of this picture contains 24-bit information that encodes an RGB color (8 bits are allocated for each individual color – red, green, blue).

The color encoding can be considered in hexadecimal as, for example, 0xff – which in decimal corresponds to 255 (if red is encoded this way, then 255 means “the most red”). Each image contains in its code not only the colors themselves, but also metadata in the header (14 bytes BITMAPFILEHEADER and 40 bytes BITMAPINFOHEADER). Immediately after the headers, color encoding in the bitmap begins. Given that a hexadecimal number encodes 4 bits, it would take two hexadecimal numbers to encode one color (eg red).

The result is an array of numbers with data on the colors of the pixels, by changing which we can achieve various effects on the photo.

To make a b/w filter, we take three colors of one pixel (RGB) and calculate the average, so that each source color can then be assigned a new value. As a result, in each pixel, three colors will have the same value from 0 to 255, thereby the image will go from a color image to a black and white image with shades of gray.

The sepia filter is applied using a special formula:

sepiaRed = .393 * originalRed + .769 * originalGreen + .189 * originalBlue
sepiaGreen = .349 * originalRed + .686 * originalGreen + .168 * originalBlue
sepiaBlue = .272 * originalRed + .534 * originalGreen + .131 * originalBlue

That is, for each original RGB value, a new value is calculated, which, when replacing all the pixels, creates an image with the effect of an “old photo”.

A mirror filter can be created using different approaches. But the general idea is this: you need to overwrite the RBG values ​​from the last pixel in place of the first pixel, and thus swap the values ​​in the array so that the photo is reflected as in a mirror. That is, in this filter, the values ​​themselves remain original, but their order changes.

The “blur” filter is achieved by taking a square of 8 pixels that “surround” the source pixel, and from these 9 pixels (including the source itself) we calculate the average, which needs to be overwritten in place of the original RBG value of the taken pixel. And so it continues throughout the array of values. I drew a diagram for myself that helped me figure out this task:

True, in the end it turned out that I mixed up x and y a little, that is, height and width. Because of this error, I didn’t get this program for a long time, and I also had the problem that I initiated int where it should have been float, and as a result, my values \u200b\u200bare rounded a little wrong.

We only needed to write one helpers.c file since the rest of the function was already implemented.

Program code for filters (Shtukensia):

#include "helpers.h"
#include <math.h>

// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    for (int i = 0; i <= (height - 1); i++)
    {
        for (int j = 0; j <= (width - 1); j++)
        {
            int sum = image[i][j].rgbtBlue + image[i][j].rgbtGreen + image[i][j].rgbtRed;
            float average = sum / 3.0;
            float aver_fl = roundf(average);
            int aver = (aver_fl);

            // reassign new RGB values
            image[i][j].rgbtBlue = aver;
            image[i][j].rgbtGreen = aver;
            image[i][j].rgbtRed = aver;
        }
    }
    return;
}

// Convert image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    for (int i = 0; i <= (height - 1); i++)
    {
        for (int j = 0; j <= (width - 1); j++)
        {
            float sepia_red = 0.393 * image[i][j].rgbtRed + 0.769 * image[i][j].rgbtGreen + 0.189 * image[i][j].rgbtBlue;
            float sepia_Green = 0.349 * image[i][j].rgbtRed + 0.686 * image[i][j].rgbtGreen + 0.168 * image[i][j].rgbtBlue;
            float sepia_Blue = 0.272 * image[i][j].rgbtRed + 0.534 * image[i][j].rgbtGreen + 0.131 * image[i][j].rgbtBlue;
            sepia_red = roundf(sepia_red);
            sepia_Green = roundf(sepia_Green);
            sepia_Blue = roundf(sepia_Blue);

            // Assign 255 to each value that is greater than 255
            if (sepia_red > 255)
            {
                sepia_red = 255;
            }

            if (sepia_Green > 255)
            {
                sepia_Green = 255;
            }

            if (sepia_Blue > 255)
            {
                sepia_Blue = 255;
            }

            // reassign new RGB values
            image[i][j].rgbtRed = sepia_red;
            image[i][j].rgbtGreen = sepia_Green;
            image[i][j].rgbtBlue = sepia_Blue;
        }
    }

    return;
}

// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{
    for (int i = 0; i <= (height - 1); i++)
    {
        for (int j = 0; j < (roundf(width / 2)); j++)
        {
            // Make variable to store reflected value

            int refl_Blue = image[i][width - 1 - j].rgbtBlue;
            int refl_Green = image[i][width - 1 - j].rgbtGreen;
            int refl_Red = image[i][width - 1 - j].rgbtRed;

            // Make variable to store copy of initial value

            int temp_blue = image[i][j].rgbtBlue;
            int temp_Green = image[i][j].rgbtGreen;
            int temp_Red = image[i][j].rgbtRed;

            image[i][j].rgbtBlue = refl_Blue;
            image[i][width - 1 - j].rgbtBlue = temp_blue;

            image[i][j].rgbtGreen = refl_Green;
            image[i][width - 1 - j].rgbtGreen = temp_Green;

            image[i][j].rgbtRed = refl_Red;
            image[i][width - 1 - j].rgbtRed = temp_Red;
        }
    }

    return;
}

// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
    RGBTRIPLE copy[height][width];

    for (int i = 0; i <= (height - 1); i++)
    {
        for (int j = 0; j <= (width - 1); j++)
        {
            copy[i][j] = image[i][j]; // make copy of initail array of colors
        }
    }

    for (int k = 0; k <= (height - 1); k++)
    {
        for (int m = 0; m <= (width - 1); m++)
        {
            float amount = 1.0; // to divide at 1.0 and not just 1 to get float

            // Initial sum of RGB is equal to values of point
            int sum_Blue = copy[k][m].rgbtBlue;
            int sum_Green = copy[k][m].rgbtGreen;
            int sum_Red = copy[k][m].rgbtRed;

            // Check if the point has suitable neighbors and calculate sum RGB

            if (0 <= (m - 1) && 0 <= (k - 1))
            {
                sum_Blue += copy[k - 1][m - 1].rgbtBlue;
                sum_Green += copy[k - 1][m - 1].rgbtGreen;
                sum_Red += copy[k - 1][m - 1].rgbtRed;
                amount += 1;
            }
            if (0 <= (m - 1))
            {
                sum_Blue += copy[k][m - 1].rgbtBlue;
                sum_Green += copy[k][m - 1].rgbtGreen;
                sum_Red += copy[k][m - 1].rgbtRed;
                amount += 1;
            }
            if (0 <= (m - 1) && (k + 1) <= (height - 1))
            {
                sum_Blue += copy[k + 1][m - 1].rgbtBlue;
                sum_Green += copy[k + 1][m - 1].rgbtGreen;
                sum_Red += copy[k + 1][m - 1].rgbtRed;
                amount += 1;
            }
            if (0 <= (k - 1))
            {
                sum_Blue += copy[k - 1][m].rgbtBlue;
                sum_Green += copy[k - 1][m].rgbtGreen;
                sum_Red += copy[k - 1][m].rgbtRed;
                amount += 1;
            }
            if ((k + 1) <= (height - 1))
            {
                sum_Blue += copy[k + 1][m].rgbtBlue;
                sum_Green += copy[k + 1][m].rgbtGreen;
                sum_Red += copy[k + 1][m].rgbtRed;
                amount += 1;
            }
            if ((m + 1) <= (width - 1) && 0 <= (k - 1))
            {
                sum_Blue += copy[k - 1][m + 1].rgbtBlue;
                sum_Green += copy[k - 1][m + 1].rgbtGreen;
                sum_Red += copy[k - 1][m + 1].rgbtRed;
                amount += 1;
            }
            if ((m + 1) <= (width - 1))
            {
                sum_Blue += copy[k][m + 1].rgbtBlue;
                sum_Green += copy[k][m + 1].rgbtGreen;
                sum_Red += copy[k][m + 1].rgbtRed;
                amount += 1;
            }
            if ((m + 1) <= (width - 1) && (k + 1) <= (height - 1))
            {
                sum_Blue += copy[k + 1][m + 1].rgbtBlue;
                sum_Green += copy[k + 1][m + 1].rgbtGreen;
                sum_Red += copy[k + 1][m + 1].rgbtRed;
                amount += 1;
            }

            // Calculate the average for RGB

            float averageBlue = sum_Blue / amount; // We divide here on float!
            int averBlue = (roundf(averageBlue));

            float averageGreen = sum_Green / amount; // We divide here on float!
            int averGreen = (roundf(averageGreen));

            float averageRed = sum_Red / amount; // We divide here on float!
            int averRed = (roundf(averageRed));

            // reassign new RGB values

            image[k][m].rgbtBlue = averBlue;
            image[k][m].rgbtGreen = averGreen;
            image[k][m].rgbtRed = averRed;
        }
    }

    return;
}

*Igroglaz at the microphone* Great, Shtukens! Interestingly, we solve all problems in different ways. Igroglaz’s solution:

#include "helpers.h"
#include <math.h>

// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    // 1) loop through image, row by row
    // 2) round up RGB and make them equal to one number

    for (int h = 0; h < height; h++)
    {
        for (int w = 0; w < width; w++)
        {
            image[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w].rgbtGreen + image[h][w].rgbtBlue) / 3.0);
            image[h][w].rgbtGreen = image[h][w].rgbtRed;
            image[h][w].rgbtBlue = image[h][w].rgbtRed;
        }
    }

    return;
}

// Convert image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    for (int h = 0; h < height; h++)
    {
        for (int w = 0; w < width; w++)
        {
            int r, g, b;
            // formulas
            r = round(.393 * image[h][w].rgbtRed + .769 * image[h][w].rgbtGreen + .189 * image[h][w].rgbtBlue);
            g = round(.349 * image[h][w].rgbtRed + .686 * image[h][w].rgbtGreen + .168 * image[h][w].rgbtBlue);
            b = round(.272 * image[h][w].rgbtRed + .534 * image[h][w].rgbtGreen + .131 * image[h][w].rgbtBlue);
            image[h][w].rgbtRed = r;
            // in case of type overflow
            if (r > 255)
                image[h][w].rgbtRed = 255;
            image[h][w].rgbtGreen = g;
            if (g > 255)
                image[h][w].rgbtGreen = 255;
            image[h][w].rgbtBlue = b;
            if (b > 255)
                image[h][w].rgbtBlue = 255;
        }
    }

    return;
}

// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{

    // to reflect we should use buffer to exchange opposite pixels
    // we will need also to find center of picture.. width / 2.. so:
    // 1) find center of picture and split array into two parts
    // 2) create buffer array
    // 3) take array element from 1st part ([1]) and store it in a [buffer]
    // 4) take array element from 2nd part ([2]) and store it to ([1])
    // 5) put buffer to [2]

    // but wait.. there is better way. we can just swap stuff using buffer array
    // without dividing it in halfs

    // create buffer array
    RGBTRIPLE buffer[height][width];

    for (int h = 0; h < height; h++)
    {
        for (int w = 0; w < width; w++)
        {
            // fill buffer array
            buffer[h][w].rgbtRed = image[h][w].rgbtRed;
            buffer[h][w].rgbtGreen = image[h][w].rgbtGreen;
            buffer[h][w].rgbtBlue = image[h][w].rgbtBlue;
        }
    }

    // hack.. -1 cause width/height starts from 1; while we count from 0
    height -= 1;
    width -= 1;

    // put pixels from buffer array back to the picture
    for (int h = 0; h <= height; h++)
    {
        for (int w = 0; w <= width; w++)
        {
            // copy from buffer
            image[h][w].rgbtRed = buffer[h][width - w].rgbtRed;
            image[h][w].rgbtGreen = buffer[h][width - w].rgbtGreen;
            image[h][w].rgbtBlue = buffer[h][width - w].rgbtBlue;
        }
    }

    return;
}

// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
    // to blur we need to take pixel in the center and equalize to it neighboring pixels

    // create buffer array
    RGBTRIPLE buffer[height][width];

    // hack.. -1 cause width/height starts from 1; while we count from 0
    height -= 1;
    width -= 1;

    for (int h = 0; h <= height; h++)
    {
        for (int w = 0; w <= width; w++)
        {
            // upper left corner
            if (h == 0 && w == 0)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w + 1].rgbtRed + image[h + 1][w].rgbtRed + image[h + 1][w + 1].rgbtRed) / 4.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h][w + 1].rgbtGreen + image[h + 1][w].rgbtGreen + image[h + 1][w + 1].rgbtGreen) / 4.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h][w + 1].rgbtBlue + image[h + 1][w].rgbtBlue + image[h + 1][w + 1].rgbtBlue) / 4.0);
            }
            // upper right corner
            else if (h == 0 && w == width)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w - 1].rgbtRed + image[h + 1][w].rgbtRed + image[h + 1][w - 1].rgbtRed) / 4.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h][w - 1].rgbtGreen + image[h + 1][w].rgbtGreen + image[h + 1][w - 1].rgbtGreen) / 4.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h][w - 1].rgbtBlue + image[h + 1][w].rgbtBlue + image[h + 1][w - 1].rgbtBlue) / 4.0);
            }
            // bottom left corner
            else if (h == height && w == 0)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w + 1].rgbtRed + image[h - 1][w].rgbtRed + image[h - 1][w + 1].rgbtRed) / 4.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h][w + 1].rgbtGreen + image[h - 1][w].rgbtGreen + image[h - 1][w + 1].rgbtGreen) / 4.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h][w + 1].rgbtBlue + image[h - 1][w].rgbtBlue + image[h - 1][w + 1].rgbtBlue) / 4.0);
            }
            // bottom right corner
            else if (h == height && w == width)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w - 1].rgbtRed + image[h - 1][w].rgbtRed + image[h - 1][w - 1].rgbtRed) / 4.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h][w - 1].rgbtGreen + image[h - 1][w].rgbtGreen + image[h - 1][w - 1].rgbtGreen) / 4.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h][w - 1].rgbtBlue + image[h - 1][w].rgbtBlue + image[h - 1][w - 1].rgbtBlue) / 4.0);
            }
            // upper border
            else if (h == 0)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w - 1].rgbtRed + image[h][w + 1].rgbtRed + image[h + 1][w - 1].rgbtRed + image[h + 1][w].rgbtRed + image[h + 1][w + 1].rgbtRed) / 6.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h][w - 1].rgbtGreen + image[h][w + 1].rgbtGreen + image[h + 1][w - 1].rgbtGreen + image[h + 1][w].rgbtGreen + image[h + 1][w + 1].rgbtGreen) / 6.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h][w - 1].rgbtBlue + image[h][w + 1].rgbtBlue + image[h + 1][w - 1].rgbtBlue + image[h + 1][w].rgbtBlue + image[h + 1][w + 1].rgbtBlue) / 6.0);
            }
            // left side
            else if (w == 0)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h - 1][w].rgbtRed + image[h - 1][w + 1].rgbtRed + image[h][w + 1].rgbtRed + image[h + 1][w].rgbtRed + image[h + 1][w + 1].rgbtRed) / 6.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h - 1][w].rgbtGreen + image[h - 1][w + 1].rgbtGreen + image[h][w + 1].rgbtGreen + image[h + 1][w].rgbtGreen + image[h + 1][w + 1].rgbtGreen) / 6.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h - 1][w].rgbtBlue + image[h - 1][w + 1].rgbtBlue + image[h][w + 1].rgbtBlue + image[h + 1][w].rgbtBlue + image[h + 1][w + 1].rgbtBlue) / 6.0);
            }
            //right side
            else if (w == width)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h - 1][w].rgbtRed + image[h - 1][w - 1].rgbtRed + image[h][w - 1].rgbtRed + image[h + 1][w].rgbtRed + image[h + 1][w - 1].rgbtRed) / 6.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h - 1][w].rgbtGreen + image[h - 1][w - 1].rgbtGreen + image[h][w - 1].rgbtGreen + image[h + 1][w].rgbtGreen + image[h + 1][w - 1].rgbtGreen) / 6.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h - 1][w].rgbtBlue + image[h - 1][w - 1].rgbtBlue + image[h][w - 1].rgbtBlue + image[h + 1][w].rgbtBlue + image[h + 1][w - 1].rgbtBlue) / 6.0);
            }
            // bottom border
            else if (h == height)
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h][w - 1].rgbtRed + image[h - 1][w - 1].rgbtRed + image[h - 1][w].rgbtRed + image[h][w + 1].rgbtRed + image[h - 1][w + 1].rgbtRed) / 6.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h][w - 1].rgbtGreen + image[h - 1][w - 1].rgbtGreen + image[h - 1][w].rgbtGreen + image[h][w + 1].rgbtGreen + image[h - 1][w + 1].rgbtGreen) / 6.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h][w - 1].rgbtBlue + image[h - 1][w - 1].rgbtBlue + image[h - 1][w].rgbtBlue + image[h][w + 1].rgbtBlue + image[h - 1][w + 1].rgbtBlue) / 6.0);
            }
            // general case
            else
            {
                buffer[h][w].rgbtRed = round((image[h][w].rgbtRed + image[h - 1][w - 1].rgbtRed + image[h - 1][w].rgbtRed + image[h - 1][w + 1].rgbtRed + image[h][w - 1].rgbtRed + image[h][w + 1].rgbtRed  + image[h + 1][w - 1].rgbtRed + image[h + 1][w].rgbtRed + image[h + 1][w + 1].rgbtRed) / 9.0);
                buffer[h][w].rgbtGreen = round((image[h][w].rgbtGreen + image[h - 1][w - 1].rgbtGreen + image[h - 1][w].rgbtGreen + image[h - 1][w + 1].rgbtGreen + image[h][w - 1].rgbtGreen + image[h][w + 1].rgbtGreen  + image[h + 1][w - 1].rgbtGreen + image[h + 1][w].rgbtGreen + image[h + 1][w + 1].rgbtGreen) / 9.0);
                buffer[h][w].rgbtBlue = round((image[h][w].rgbtBlue + image[h - 1][w - 1].rgbtBlue + image[h - 1][w].rgbtBlue + image[h - 1][w + 1].rgbtBlue + image[h][w - 1].rgbtBlue + image[h][w + 1].rgbtBlue  + image[h + 1][w - 1].rgbtBlue + image[h + 1][w].rgbtBlue + image[h + 1][w + 1].rgbtBlue) / 9.0);
            }
        }
    }

    // put pixels from buffer array back to the picture
    for (int h = 0; h <= height; h++)
    {
        for (int w = 0; w <= width; w++)
        {
            image[h][w].rgbtRed = buffer[h][w].rgbtRed;
            image[h][w].rgbtGreen = buffer[h][w].rgbtGreen;
            image[h][w].rgbtBlue = buffer[h][w].rgbtBlue;
        }
    }

    return;

}

I mostly suffered when debugging with bugs that arose when copy-pasting one line to another 🙂 Although in general I stepped on all the traps and rakes. If it were not for debugging in cs50, I would not have noticed many jambs, because Outwardly, with the naked eye, all the pictures were ok, and only when checking through check50 it became clear that there were bugs. Great task.


This entry was posted in C language (en). Bookmark the permalink.

Leave a Reply

🇬🇧 Attention! Comments with URLs/email are not allowed.
🇷🇺 Комментарии со ссылками/email удаляются автоматически.