I am struggling to pixelate an image which is made up of RGB values stored in a binary (P6) PPM file. The steps to pixelate the image are as follows:
int c = 4;
meaning it iterates through pixel data in block dimensions of 4 x 4
Each binary PPM file begins with a header in the following format, consisting of a 'magic number', followed by the width, height, and finally maximum colour value of 255. Header comments are ignored. The following header example shows a PPM image of format P6 (therefore a binary file), a width of 16, a height of 16, and a max colour value of 255:
P6
16
16
255
Where I am struggling:
My attempt:
#define _CRT_SECURE_NO_WARNINGS //preprocessor requirement
#include <stdio.h> //library for I/O functions
#include <stdlib.h> //library for general functions
#define magic_size 10 //macro for PPM character found within header
typedef struct {
int t_r, t_g, t_b; //Struct to hold RGB pixel data
} Pixel;
int main()
{
char magic_number[magic_size]; //variable for PPM format
int width = 0, height = 0, max_col = 0; //imagine dimensions
int c = 4; //mosaic parameter
/* INPUT FILE HANDLING */
FILE *inputFile;
inputFile = fopen("Sheffield512x512.ppm", "r");
//input file error handling
if (inputFile == NULL)
{
printf(stderr, "ERROR: file cannot be opened");
getchar(); //prevent cmd premature closure
exit(1); //exit program cleanly
}
/* OUTPUT FILE HANDLING */
FILE *outputFile;
outputFile = fopen("mosaic.ppm", "w");
//output file error handling
if (outputFile == NULL)
{
printf(stderr, "ERROR: cannot write to file");
getchar(); //prevent cmd premature closure
exit(1); //exit program cleanly
}
// Scan the header (these variables are used later on)
fscanf(inputFile, "%s\n%d\n%d\n%d", &magic_number, &width, &height, &max_col);
// Error handling. Program only supports binary files (i.e. of P6 format)
if (magic_number[1] != '6')
{
printf("Only Binary images supported!\n");
getchar(); //prevent cmd premature closure
return;
}
// Raw 1 dimensional store of pixel data
Pixel *data = malloc(width*height * sizeof(Pixel));
//2D index to access pixel data
Pixel **pixels = malloc(height * sizeof(Pixel*));
// Read the binary file data
size_t r = fread(data, width*height, sizeof(unsigned char), inputFile);
// Build a 1-dimensional index for the binary data
for (unsigned int i = 0; i < height; ++i)
{
pixels[i] = data + (i * width);
}
// Close the input file
fclose(inputFile);
/* BEGIN PIXELATION PROCESS */
// Print the OUTPUT file header
fprintf(outputFile, "%s\n%d\n%d\n%d", magic_number, width, height, max_col);
//loop condition variables
int cw_x = ceil((double)(width / (float)c));
int cw_y = ceil((double)(height / (float)c));
//iterate through 2d array in cells of size c
for (int c_x = 0; c_x < cw_x; c_x += 1)
{
for (int c_y = 0; c_y < cw_y; c_y += 1)
{
//iterate within the cells
for (int _x = 0; _x < c; _x++)
{
int x = c_x * c + _x;
//bounds checking
if (x < width)
{
for (int _y = 0; _y < c; _y++)
{
int y = c_y * c + _y;
//bounds checking
if (y < height)
{
//write data to the output FILE stream
fwrite(data, width*height, sizeof(unsigned char), outputFile);
}
}
}
}
}
}
//close the output file
fclose(outputFile);
return 0;
}
In the comments I gave you some feedback about errors in your code. You can fix those yourself. Take a debugger to test/check all those preliminary steps. For example read the file and write it immediately (and display the image) so you know the reading is OK.
Your main problem and question is about the loop.
In essence you have a one-dimensional array that consists of scanline-after-scanline and each scanline contains pixels. Contrary to the BMP format, your format seems not to be using padding bytes to align scanlines on word boundaries. That makes it a bit easier.
A pixel consists of three color values, R, G and B, and I assume each color value is one byte (unsigned char). The memory allocation and reading then becomes:
unsigned char *data = malloc(width*height*3);
r = fread(data, width*height*3, 1, inputFile);
The loop now goes through all the lines in increments of four and processes each pixel in increments of four. So it processes one square at a time, calculates the average and writes it out:
c= 4;
for (y=0; y<height; y += c)
{
for (x=0; x<width; x += c)
{
unsigned int avgR=0, avgG=0, avgB= 0;
for (dy=0; dy<c && y+dy<height; dy++)
{
for (dx=0; dx<c && x+dx<width; dx++)
{
avgR += data[ y*width*3 // line in image
+ x*3 // pixel on line
+ dy*width*3 // line of square
+ dx*3 // R pixel in line of square
];
avgG += data[ y*width*3 // line in image
+ x*3 // pixel on line
+ dy*width*3 // line of square
+ dx*3 + 1 // G pixel in line of square
];
avgB += data[ y*width*3 // line in image
+ x*3 // pixel on line
+ dy*width*3 // line of square
+ dx*3 + 2 // B pixel in line of square
];
}
}
unsigned char avgRb= avgR/(dx*dy);
unsigned char avgGb= avgG/(dx*dy);
unsigned char avgBb= avgB/(dx*dy);
fwrite(&avgR,1,1,outputFile);
fwrite(&avgG,1,1,outputFile);
fwrite(&avgB,1,1,outputFile);
}
}
This could be optimized using pointer arithmetic, but this shows the basics of the loop you require.
Notes:
..&& y+dy<height
tests the border case when the last square does not fit the height. Same for the width.
as a consequence the average is calculated by dividing by (dx*dy)
.
Disclaimer
I couldn't test it so the algorithm is a mental construction.