cimageppmpixelate

How to pixelate an image


I am attempting to pixelate a P6 PPM format image via the following steps:

  1. Read a PPM image in grids of 4x4
  2. Find the average RGB colour value of each 4x4 grid
  3. Write to a new file by setting each 4x4 grid of pixels in the new image to have the average RGB colour value.

The PPM file begins in the following format:

P6 
# ignores comments in header 
width 
height 
max colour value

My problem: The output PPM image file (which I am opening using GIMP image editor, and can also be opened in any text editor like Notepad to view the raw data) consists of one flat colour block, when instead it should resemble a sort of mosaic.

Note: The 4x4 grid can be varied i.e. the higher the value of the grid dimensions, the more pixelated the output image becomes. My code has mostly been influenced from another Stack Overflow question where the user attempted a similar implementation in C#. The link to this question: https://codereview.stackexchange.com/questions/140162/pixelate-image-with-average-cell-color

UPDATE: The output now seems to pixelate the first 1/5 of the image, but the rest of the output image remains one colour block. I think my issue lies in that I am treating the cells as the pixels in linear order.

My attempt:

#include <stdio.h>
#include <assert.h>

//Struct to store RGB values 
typedef struct {
    unsigned char r, g, b;
} pixel;

int main()
{
    int y, x;               //Loop iteration variables
    int yy = 0;             //Loop iteration variables
    int xx = 0;             //Loop iteration variables
    char magic_number[10];  //Variable which reads P6 in the header 
    int w, h, m;            //Image dimension variables
    pixel currentPix;       //Current pixel variable 
    int avR;                //Red declaration
    int avG;                //Green declaration 
    int avB;                //Blue declarataion 
    int total;              //Loop iteration counter declaration 

    //Input file 
    FILE* f;
    f = fopen("Dog2048x2048.ppm", "r"); //Read PPM file 
    if (f == NULL)                      //Error notifiaction if file cannot be found 
    {
        fprintf(stderr, "ERROR: cannot open input file");
        getchar();
        exit(1);
    }
    //Scan the header of the PPM file to get the magic number (P6), width
    //height and max colour value 
    fscanf(f, "%s %d %d %d", &magic_number, &w, &h, &m);

    //initialize file for writing (open and header)
    FILE* f_output;
    f_output = fopen("some_file.ppm", "w");
    //fprintf(f_output, "%s %d %d %d", magic_number, w, h, m);
    fprintf(f_output, "P6\n%d %d\n255\n", w, h);
    if (f_output == NULL)                       //Error notifiaction if file cannot be found 
    {
        fprintf(stderr, "ERROR: cannot open output file");
        getchar();
        exit(1);
    }

    // Loop through the image in 4x4 cells.
    for (int yy = 0; yy < h && yy < h; yy += 4)
    {
        for (int xx = 0; xx < w && xx < w; xx += 4)
        {
            avR = 0;
            avG = 0;
            avB = 0;
            total = 0;

            // Store each color from the 4x4 cell into cellColors.
            for (int y = yy; y < yy + 4 && y < h; y++)
            {
                for (int x = xx; x < xx + 4 && x < w; x++)
                {
                    //Reads input file stream 
                    fread(&currentPix, 3, 1, f);

                    //Current pixels 
                    avR += currentPix.r;
                    avG += currentPix.g;
                    avB += currentPix.b;

                    //Counts loop iterations for later use in colour averaging
                    total++;

                }
            }
            //Average RGB values
            avR /= total; 
            avG /= total;
            avB /= total;

            // Go BACK over the 4x4 cell and set each pixel to the average color.
            for (int y = yy; y < yy + 4 && y < h; y++)
            {
                for (int x = xx; x < xx + 4 && x < w; x++)
                {
                    //Print out to new file 
                    fprintf(f_output, "%i %i %i\t", avR, avG, avB);
                }
            }
        }
        fprintf(f_output, "\n");
    }
    return 0;
}

Solution

  • Your main mistake is that you assume to be reading and writing 4×4 blocks of pixels while actually accessing pixel data linearly; your suspicion is correct.

    Consider the following example. Let there be a 12×4 1-channel image:

    01 02 03 04 05 06 07 08 09 10 11 12
    13 14 15 16 17 18 19 20 21 22 23 24
    25 26 27 28 29 30 31 32 33 34 35 36
    37 38 39 40 41 42 43 44 45 46 47 48
    

    Each of the pixels has the color that equals its position in a PPM file.

    Now these are the pixels which you are expecting to read when pixelating the first 4×4 block:

    01 02 03 04
    13 14 15 16
    25 26 27 28
    37 38 39 40
    

    And these are the pixels which are actually being read by sequentially executing fread():

    01 02 03 04 05 06 07 08 09 10 11 12
    13 14 15 16
    

    So eventually you are treating the input image as if it looked like that:

    01 02 03 04 05 06 07 08 09 10 11 12
    13 14 15 16                                 01 02 03 04   17 18 19 20   33 34 35 36
                17 18 19 20 21 22 23 24   -->   05 06 07 08   21 22 23 24   37 38 39 40
    25 26 27 28 29 30 31 32                     09 10 11 12   25 26 27 28   41 42 43 44
                            33 34 35 36         13 14 15 16   29 30 31 32   45 46 47 48
    37 38 39 40 41 42 43 44 45 46 47 48
    

    instead of:

    01 02 03 04   05 06 07 08   09 10 11 12
    13 14 15 16   17 18 19 20   21 22 23 24
    25 26 27 28   29 30 31 32   33 34 35 36
    37 38 39 40   41 42 43 44   45 46 47 48
    

    One of the simpler methods of resolving that issue is to allocate an array into which the data is to be read. Once you have that array filled with your data, you will be able to access its elements in any order, instead of the strictly linear order fread() implies.