Lab 2: PicFilter

Due on Wednesday, September 19th at 11:59 PM. This is an individual lab. You are to complete this lab on your own, although you may discuss the lab concepts with your classmates. Remember the Academic Integrity Policy: do not show your code to anyone outside of the course staff and do not look at anyone else’s code for this lab. If you need help, please post on the Piazza forum or contact the instructor or ninjas. If you have any doubts about what is okay and what is not, it’s much safer to ask than to risk violating the Academic Integrity Policy.

Overview

In this lab, you will write a program to apply filters to a particular image file format. You will become familiar with the following in C++:

As with the previous lab, you should clone your repository; the URL is git@github.swarthmore.edu:cs35-f18/lab02-<your-username>.

Images

Pixelated Tomato
A pixelated tomato,
zoomed in to show
individual pixels.

Image data on a computer is typically stored in a series of units called pixels, each of which represents a single colored dot. The image in the figure, for instance, was originally 8 pixels wide and 7 pixels tall; it has been magnified for demonstration. In reality, the individual pixels on a modern computer monitor are almost too small to see; by packing the colored dots together so tightly in a grid, we can render pictures, text, and so on.

Image data on computers may be stored in a variety of different formats; common formats are JPEG (often abbreviated “.jpg”), PNG, and GIF. Different image formats have different advantages. This lab will be using the PPM image format, the advantage of which is that it is quite simple.

The PicFilter Program

You will write a program called picfilter which allows the user to manipulate PPM files from the command line. Your program will read a PPM file into memory as an object, perform a transformation on it, and then save it back to disk in another PPM file. Your program will take the input file, the transformation, and the output file as command-line arguments. For instance,

./picfilter old.ppm flipHorizontally new.ppm

will read the file old.ppm, flip it horizontally (left to right, as in a mirror), and then save the result as new.ppm.

Reading PPMs

To read a PPM file, you will pass its filename to three functions:

The array of pixels may be larger than you’d expect; it will actually be three times the size of the number of pixels in the image. For instance, a 100x100 PPM image would produce an array of size 30,000. This is because each pixel is represented by three numbers: the amount of red, green, and blue light to show for the pixel. Each number ranges from 0 (no light) to 255 (all the light). Here are some example pixel values:

The read_ppm function takes care of opening a PPM file, reading the image data into it, and giving it back to you in a new array. Once you’ve read the PPM, you can change it and then write it back out into another file using the corresponding write_ppm function. These functions are defined in ppmio.h.

Transforming PPMs

Blue Butterfly
An image of a butterfly
Source: OpenClipArt.org
Blue Butterfly with noBlue filter applied
Same image with the noBlue filter

Once you’ve read in your PPM as an array of integers, it’s up to you to change those integers in any way you like. You could loop over the entire image and set every number to 255, resulting in a giant white rectangle (since every pixel is now white). You could loop over each position in the first row and set each third number to 0, draining all of the blue light from the top line of the image. (An example of this transformation is shown at right in the butterfly images.)

As mentioned above, the user specifies an input file, a transformation, and an output file. Here are the transformations the user is allowed to request:

For each of these transformations, you will write a void function of the same name that takes an int* to a PPM image and makes the appropriate changes. You will then write a main function which, based upon the command-line arguments to the program, loads the image, calls the right function, and then saves the result. If the user gives an invalid transformation (e.g. flipDiagonal), your program should generate an appropriate error message and quit without saving a PPM file. You are allowed to ignore errors caused when the user gives non-existent or invalid filenames.

In the sub-directory called test_data, we have provided a number of example PPM files. Take a look at these examples to get a better understanding of how each filter will transform images. For instance, you can view the image of a rose using the eog program:

 eog test_data/Rose.ppm

And then look at all of the ways the rose can be filtered.

Writing Your Program

Your starter code has been separated into several files:

Compiling Your Program

Your starter code also contains a Makefile. This file contains instructions to compile your code so you don’t have to mess with the details of calling clang++ yourself. You can compile your program by typing make or make picfilter; if it compiles successfully, you can then run ./picfilter. To test your image transformations, type make tests and then run the program ./tests.

Getting Started

Remember: you don’t have to write everything all at onec. It’s often best to get a small amount of your code to work and then move on to the next part. You can follow these steps to complete your lab:

  1. Start by writing your main function. Write the code necessary to take filenames from argv, load the PPM image, and then save it again. In this step, don’t try to transform the images yet; just read the file in and write it back out. Once you have this small part working, you can add to it a little bit at a time.

  2. Complete the pixelToIndex helper function. This function takes the width of the image and the X and Y coordinates of a pixel in the image and translates them to an index in an array. Writing this helper function will make it easier to write your image transformations.

  3. Write the header and body of a single transformation (e.g. noRed) into your image.h and image.cpp files. Then, add an if statement to main to call that function if the transformation matches it. If not, the else part of your if statement should print an error message. Make sure you don’t save an image if you don’t recognize the transformation. For now, e.g. noRed will be the only transformation you can handle.

  4. One by one, implement more transformations, adding them to main as you complete them. Try each of them out before you move on to the next one.

  5. Once you’ve finished writing your transformations, run make tests and then ./tests to see if the automated testing tools agree that your code is correct.

pixelToIndex

We encourage you to write the pixelToIndex function because it allows you to write your other algorithms in terms of pixels rather than array indices. To explore the difference, let’s briefly consider how an image (a two dimensional grid of pixels where each pixel is three numbers) might be stored in an array (a single dimensional sequence of numbers).

According to long-standing computing traditions that are counterintuitive to geometry students everywhere, the first pixel is in the upper left of the image. We store this pixel as the first three numbers in the array in the order described above: red, then green, then blue. The next (fourth) number in the array is the red value for the next pixel, which is immediately to the right of the first pixel. In this way, all of the pixels in the first row appear in order in the array; afterward, the array contains all pixels in the next row, and so on.

For instance, let’s consider the grid below which represents an image which is three pixels wide and two pixels tall:

This image is probably best described by the following array diagram:

r g b r g b r g b r g b r g b r g b
values 255 255 255 0 255 0 0 0 255 0 0 0 255 255 255 255 192 192
indices 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
pixels A (0,0) B (1,0) C (2,0) D (0,1) E (1,1) F (2,1)

So in this particular example, pixelToIndex(2,1) should return 15 and pixelToIndex(1,0) should return 3.

Coding Style Requirements

For this lab, you will be required to observe some good coding practices:

Well-dressed people (style)

Creating Your Own PPM Files

You can use ImageMagick, a package installed on the CS network, together with picfilter to edit your own images! Start by converting your picture to PPM format:

convert my_image.jpg -compress none my_image.ppm

Then, do anything you want with picfilter:

./picfilter my_image.ppm grayscale my_new_image.ppm
./picfilter my_new_image.ppm invert my_new_image_2.ppm

Finally, convert your new image back to some other format.

convert my_new_image_2.ppm my_new_image_2.jpg

There! You’ve just used your homework assignment to edit your own photos. :)

Going Further

If you like, you may implement additional filters in your picfilter program (as long as doing so does not break any of the required filters above). This is not required and we will not assign extra credit for these filters, but you may find it interesting and fun to experiment with the images your program has loaded. Here are some suggestions:

You’re also welcome to make your own filters and try anything you like. Just make sure the filters required for the assignment work correctly!

Summary

To summarize the requirements of this lab:

Acknowledgements

This lab writeup is based on Joshua Guerin and Debby Keen’s NIFTY 2012 submission titled “PPM Image Editor”.