0

I am currently working on a project which is filtering a bmp file. This smoothing filter uses a 3x3 blurring kernel which is a fixed size small matrix. The convolution operation moves this kernel over the image, shifting it on pixel at a time and takes the dot product of matrix elements with the pixel values underneath. Basically this filter traverses the entire image to producing a single pixel out of 9 adjacent pixels.

In order to preserve the image size both horizontally and vertically, padding is added to the input image by adding 0 valued pixels around the edges of the image.

Related images can be seen on the links:

convolution operation image

padding operation

Here is my code:

#include <stdio.h>
#include <stdlib.h>
#include "header.h"

typedef struct{
    uint8_t red;
    uint8_t green;
    uint8_t blue;
}rgb ;

void smoothing_filter(int height, int width, rgb **image, rgb **dest);

int main() {
    FILE *inputPtr;
    if ((inputPtr = fopen("starry_night.bmp", "rb+")) == NULL) {
        printf("The input file could not be opened\n");
    } else {
        BITMAPFILEHEADER fileheader_input;
        fread(&fileheader_input, sizeof(BITMAPFILEHEADER), 1, inputPtr);
        BITMAPINFOHEADER infoheader_input;
        fread(&infoheader_input, sizeof(BITMAPINFOHEADER), 1, inputPtr);

        if (fileheader_input.bfType != 0x4d42 || fileheader_input.bfOffBits != 54 ||
            infoheader_input.biSize != 40 || infoheader_input.biBitCount != 24 || infoheader_input.biCompression != 0) {
            fclose(inputPtr);
            printf("Unsupported input file.\n");
        } else {
            int height = abs(infoheader_input.biHeight);
            int width = infoheader_input.biWidth;

            rgb **rgb_array;
            rgb_array = calloc(height, sizeof(rgb *));

            for (int row = 0; row < height; ++row) {
                rgb_array[row] = calloc(width, sizeof(rgb));
            }

            for (int row = 0; row < height; row++) {
                for (int col = 0; col < width; ++col) {
                    fread(&rgb_array[row][col], sizeof(rgb), 1, inputPtr);
                }
            }

            infoheader_input.biWidth = width + 2;
            infoheader_input.biHeight = height + 2;
            fileheader_input.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) +
                                      3 * abs(infoheader_input.biWidth) * abs(infoheader_input.biHeight);
            rewind(inputPtr);

            fwrite(&(fileheader_input), sizeof(BITMAPFILEHEADER), 1, inputPtr);
            fwrite(&(infoheader_input), sizeof(BITMAPINFOHEADER), 1, inputPtr);

            rgb blanckRgb = {0, 0, 0};

            for (int i = 0; i < (width + 2); ++i) {
                fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr);
            }
            for (int row = 0; row < height; ++row) {
                fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr);
                for (int col = 0; col < width; ++col) {
                    fwrite(&rgb_array[row][col].red, 1, 1, inputPtr);
                    fwrite(&rgb_array[row][col].green, 1, 1, inputPtr);
                    fwrite(&rgb_array[row][col].blue, 1, 1, inputPtr);
                }
                fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr);
            }
            for (int i = 0; i < (width + 2); ++i) {
                fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr);
            }

            FILE *outputPtr;
            if ((outputPtr = fopen("starry_night_filtered.bmp", "wb")) == NULL) {
                printf("The \"starry_night_filtered.bmp\" file could not be created\n");
            }else{
                BITMAPFILEHEADER fileheader_output = fileheader_input;
                BITMAPINFOHEADER infoheader_output = infoheader_input;

                infoheader_output.biWidth = width;
                infoheader_output.biHeight = height;
                fileheader_output.bfSize = sizeof(BITMAPINFOHEADER)+sizeof(BITMAPFILEHEADER)+3*abs(width*height);

                fwrite(&(fileheader_output), sizeof(BITMAPFILEHEADER), 1, outputPtr);
                fwrite(&(infoheader_output), sizeof(BITMAPINFOHEADER), 1, outputPtr);

                rgb **image;
                image = calloc(infoheader_input.biHeight, sizeof(rgb *));

                for (int row = 0; row < infoheader_input.biHeight; ++row) {
                    image[row] = calloc(infoheader_input.biWidth, sizeof(rgb));
                }

                rewind(inputPtr);

                for (int row = 0; row < infoheader_input.biHeight; row++) {
                    for (int col = 0; col < infoheader_input.biWidth; col++) {
                        fread(&image[row][col], sizeof(rgb), 1, inputPtr);
                    }
                }

                rgb **dest;
                dest = calloc(height, sizeof(rgb *));

                for (int row = 0; row < height; ++row) {
                    dest[row] = calloc(width, sizeof(rgb));
                }

                smoothing_filter(infoheader_input.biHeight, infoheader_input.biWidth, image, dest);

                for (int row = 0; row < height; row++) {
                    for (int col = 0; col < width; col++) {
                        fwrite(&dest[row][col], sizeof(rgb), 1, outputPtr);
                    }
                }
                free(image);
                free(rgb_array);
                fclose(outputPtr);
                fclose(inputPtr);
            }
        }
    }
}

void smoothing_filter(int height, int width, rgb **image, rgb **dest)
{
    double redVal, blueVal, greenVal;

    // Iterate through rows
    for (int row = 0; row < (height-2); row++)
    {
        // Iterate through columns
        for (int col = 0; col < (width-2); col++)
        {
            // Obtain RGB values of current image pixel
            redVal = (0.0625*image[row][col].red) + (0.125*image[row][col+1].red) + (0.0625*image[row][col+2].red)
                     +(0.125*image[row+1][col].red) + (0.25*image[row+1][col+1].red) + (0.125*image[row+1][col+2].red)
                     +(0.0625*image[row+2][col].red) + (0.125*image[row+2][col+1].red) + (0.0625*image[row+2][col+2].red);
            greenVal = (0.0625*image[row][col].green) + (0.125*image[row][col+1].green) + (0.0625*image[row][col+2].green)
                       +(0.125*image[row+1][col].green) + (0.25*image[row+1][col+1].green) + (0.125*image[row+1][col+2].green)
                       +(0.0625*image[row+2][col].green) + (0.125*image[row+2][col+1].green) + (0.0625*image[row+2][col+2].green);
            blueVal = (0.0625*image[row][col].blue) + (0.125*image[row][col+1].blue) + (0.0625*image[row][col+2].blue)
                      +(0.125*image[row+1][col].blue) + (0.25*image[row+1][col+1].blue) + (0.125*image[row+1][col+2].blue)
                      +(0.0625*image[row+2][col].blue) + (0.125*image[row+2][col+1].blue) + (0.0625*image[row+2][col+2].blue);
            dest[row][col].red = (int) redVal;
            dest[row][col].green = (int) greenVal;
            dest[row][col].blue = (int) blueVal;
        }
    }
}

THE PROBLEM:

My code works okay except somehow it shifts an image a little bit to the right. I could not find the reason of it. Can you help me about that. Thanks in advance Output of my codes:

Input image

output image

  • I'll post an answer instead. It's not necessarily the source of the problem, but there's lots of things that could be improved, for performance if nothing else. – Lundin Jul 06 '22 at 08:17

1 Answers1

0

This might not be the cause of the problem, but this code is problematic for several reasons:

rgb **rgb_array;
rgb_array = calloc(height, sizeof(rgb *));

for (int row = 0; row < height; ++row) {
    rgb_array[row] = calloc(width, sizeof(rgb));
}

for (int row = 0; row < height; row++) {
    for (int col = 0; col < width; ++col) {
        fread(&rgb_array[row][col], sizeof(rgb), 1, inputPtr);
    }
}
  • You don't allocate a 2D array but a pointer table, so the code turns needlessly slow for no good reason. Each call to calloc will create fragmented memory. Details here: Correctly allocating multi-dimensional arrays
  • Using calloc instead of malloc makes the code needlessly slow for no good reason, since you intend to immediately overwrite the memory anyway and you have no benefit of calloc's zero initialization.
  • fread(&rgb_array[row][col], sizeof(rgb), 1, inputPtr); is very slow for no good reason, since you read one single item at a time. File I/O is usually a bottleneck so it should be minimized. If you used 2D arrays you could read the file in one go.
  • Always check the result of calloc and fread.

Consider something like this instead:

rgb (*rgb_array)[width] ; // pointer to the first item in a rgb[height][width] 2D array

rgb_array = malloc( sizeof(rgb[height][width]) ); // one single call
if(rgb_array == NULL) { /* error handling */ }

size_t result = fread(rgb_array, sizeof(rgb), height*width, inputPtr); // one single call
if(result != height*width) { /* error handling */ }

...
free(rgb_array); // one single call

You'll need to change your functions accordingly, to have them accept 2D arrays instead:

void smoothing_filter(int height, 
                      int width, 
                      rgb image[height][width], 
                      rgb dest[height][width]);
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • thanks for sharing. that helps but still the output image is shifted to right – hakanxdurak Jul 06 '22 at 09:23
  • @hakanxdurak What's the reason you call `abs` everywhere? Everything should be positive or otherwise you have some type-related bug. – Lundin Jul 06 '22 at 09:39
  • the reason is that height may be negative in bitmap file – hakanxdurak Jul 06 '22 at 09:47
  • @hakanxdurak I don't remember the details of the bitmap file format, why would it be negative? – Lundin Jul 06 '22 at 09:52
  • When height is negative the first pixel in file is drawn at top left of the image. However the standard for bitmap files is to use positive height and the first pixel in file is drawn at the bottom left of the image followed by other pixels. – hakanxdurak Jul 06 '22 at 09:58