Assignment 2
Due: before class on Wednesday, September 22, 2021
Prologue
This assignment is designed to give you elementary practice in the C programming language. In particular, we will apply our newfound knowledge of C to implement image transformations on a simple file format. Most images are encoded as PNGs or JPGs which are rather complicated file formats. In this assignment, we will use a simpler file format: PPM.
Overall, your task for this assignment is to write functions that read PPM files, write PPM files, and perform simple color transformations.
Put all of your code for this assignment in a file images.c
.
PPM File Format
The file format we are using is the plain PPM file format which stores the image as a sequence of characters. Below is an example plain PPM file which encodes an image that consists of four pixels in a 2x2 grid.
P3
2 2
255
0 0 0
255 255 255
255 0 255
128 128 128
Here is a brief explanation of the file format:
- The first line of the file always consists of the “magic number”
P3
. - The second line consists of two positive integers that are the width and the height of the image in pixels.
- The third line will always be 255 in this assignment. (It is the maximum RGB value in the following pixel data.)
- The rest of the file consists of a sequence of pixels. In this assignment, each line will contain exactly one pixel in the form
r g b
wherer
,g
, andb
are integers between 0 and 255. The total number of lines iswidth * height
wherewidth
andheight
are the numbers on line 2.
Note: The plain PPM file format is a little more complex than this, but you may assume that PPM files are formatted exactly as above in this assignment.
The COLOR
and IMAGE
Structs
For this assignment, you must make use of the following two structs that will help us encode PPM images in a way that is easy to manipulate.
typedef struct {
int red;
int green;
int blue;
} COLOR;
typedef struct {
int width;
int height;
COLOR *raster;
} IMAGE;
Note that we can create a color in the following way:
COLOR c = {.red = 255, .green = 0, .blue = 0};
which creates a COLOR
value that represents the color red.
Note that the IMAGE
struct contains the width and height of an image, and the raster
field is an array of COLOR
s that represents the pixels of the image. Note that the raster
array is one dimensional—this is intentional so that all the pixels appear in a single slot of memory.
We provide the following helper functions for your convenience:
// Creates and returns a pointer to a new blank IMAGE
// with the given dimensions (allocated on the heap)
IMAGE *img_new(int width, int height) {
IMAGE *img = malloc(sizeof(IMAGE));
img->width = width;
img->height = height;
img->raster = malloc(sizeof(COLOR) * width * height);
return img;
}
// Frees the memory of the given IMAGE
void img_free(IMAGE *img) {
free(img->raster);
free(img);
}
Part 1: Reading a PPM File
For this part, your task is to write a function that reads a PPM file and constructs an IMAGE
from it. The prototype of the function must be:
IMAGE *img_read(const char *filename)
This function takes a string as a parameter that is the name of the PPM file to read and returns a pointer to an IMAGE
that is constructed on the heap from that PPM file. The const
keyword helps avoid accidentally modifying the string filename
in the function.
Hints!
- Use the
img_new
function to create a blank image and then modify itsraster
of pixels directly with the file data. - You’ll find the
stdio.h
file functions likefopen
,fclose
,fgets
, andsscanf
helpful. - I recommend that you read the file one line at a time with
fgets
and then parse the line into appropriate integers usingsscanf
. - Populate the
raster
of theIMAGE
so that the pixels are in the same order as they appear in the PPM file.
Part 2: Writing a PPM File
For this part, your task is to write a function that writes an IMAGE
to a file in the plain PPM format. The prototype of the function must be:
void img_write(IMAGE *img, const char *filename)
When you call img_write
, it should write the contents of the img
parameter to a file with the name filename
. Remember that you’ll need to output text so that it looks something like the following:
P3
2 2
255
0 0 0
255 255 255
255 0 255
128 128 128
Hints!
- Use
fprintf
to print string-formatted data to a file. - Remember that the first three lines are special!
- Loop over the
raster
data and print off the pixels in order.
Part 3: Grayscale Effect
For this part, you need to write a function that transforms an IMAGE
by applying an effect to each pixel in the image. In particular, we will be doing a grayscale effect.
Given a pixel with RGB values $(r,g,b)$, we can turn it gray by taking the average of its components. For example, the new color with have all three components being the same: $(n,n,n)$ where
\[n=\frac{r+g+b}{3}\]Ultimately, your goal is to write a function with the following prototype:
void img_gray(IMAGE *img)
The function ought to manipulate the pixels of img
“in place” so that every pixel is turned into its grayscale equivalent.
Testing
For testing purposes, you can use the main function below.
int main(int argc, char const *argv[])
{
if (argc == 3) {
IMAGE *img = img_read(argv[1]);
img_gray(img);
img_write(img, argv[2]);
img_free(img);
return 0;
} else {
printf("Error! Needs exactly two arguments.\n");
return 1;
}
}
The main
function above takes two command line arguments: one for the input filename and one for the output filename. It uses img_read
to read the input file, then img_gray
to turn it grayscale, and then img_write
to write the modified image to a new file.
Here is a cat image PPM file that you can use as a testing input file. It should look like the following:
You can run your program by doing the following:
$ gcc images.c
$ ./a.out cat.ppm gray_cat.ppm
The gray cat image created should look something like: