Data Structures and Algorithms

Lab 2: PicFilter

Due on Sunday, February 7th 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++:

You will also write a series of tests to verify the correctness of your program without running it directly. As with the previous lab, you should clone your repository; the URL is git@github.swarthmore.edu:cs35-s16/lab02-<your-username>.

The PPM Image Format

PPM is an image file format similar to JPG/JPEG, PNG, GIF, and so on. PPM is less frequently used because it is very inefficient: a PPM file can easily be ten times larger than a JPG containing the same picture because a JPG file uses clever compression algorithms. We will be using PPM in this lab because, in comparison to other image formats, it is very simple.

The PPM format we are using is really just a text file. The file starts by giving some simple information: a “magic number” identifying the PPM format (which helps us be sure that we’re actually editing a PPM file and not junk), the height and width of the PPM file, a maximum color value, and then a series of numbers to describe the pixels of the image. For instance, suppose a file square.ppm contained the following. (You could even create this file in a text editor if you wanted.)

The image square.ppm magnified by 2500%.
P3
4 4
255
0 0 0       0 0 0           0 0 0           0 0 0
0 0 0       255 255 255     255 255 255     0 0 0
0 0 0       255 255 255     255 255 255     0 0 0
0 0 0       0 0 0           0 0 0           0 0 0

The above file is broken down as follows:

Here is another example PPM file, slash.ppm:

The image slash.ppm magnified by 2500%.
P3
5 3
255
0 0 255     0 0 255     0 0 255     0 0 0       255 128 0
0 0 255     0 0 255     0 0 0       255 128 0   255 128 0
0 0 255     0 0 0       255 128 0   255 128 0   255 128 0

This time, the image is five pixels wide by 3 pixels tall. The pixel in the upper left has 0 red, 0 green, 0 blue (which displays as a bright blue), while the pixel in the lower right has 255 red, 128 green, and 0 blue (which appears as orange).

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 transformations on it, and then save it back to disk in another PPM file. Your program will take the input file, the output file, and all of the transformations to perform as command-line arguments. For instance,

./picfilter old.ppm new.ppm flip-horizontal

will read the file old.ppm, flip it horizontally (left to right, as in a mirror), and then save the result as new.ppm. The user is permitted to specify multiple transformations, so

./picfilter old.ppm new.ppm flip-horizontal grayscale

will flip the image horizontally and then apply a grayscale filter (to make the image appear “black and white”). The operations you will be required to perform are as follows; the example text for each of them assumes that 255 is the maximum pixel value.

If the user gives an input file which doesn’t exist or gives a transformation which does not exist (e.g. flip-diagonal), your program should generate an appropriate error message and quit.

Writing Your Program

Your starter code has been separated into several files:

There are also header files – image.h, picfilter.h, and test_utils.h – which you will not need to modify for this assignment. They describe the functions that their corresponding cpp files implement.

The PpmImage Class

The image.cpp file contains a skeleton for a PpmImage class. It includes one method for each of the above transformations; it also includes a constructor (which is used to load image files), a few methods (save and position_of), and a destructor. The image.h file documents the expectation for each method. It’s important to note that the constructor for PpmImage will throw an exception if it fails to read the image file correctly.

The PpmImage class also defines fields for your class to use: width, height, max_value, and pixel_values. The pixel_values field is an int* which points to an array of integers; there are three integers for each pixel in the image so that a given pixel’s red, green, and blue are stored next to each other. You will write the position_of method of PpmImage, which will give the position of the red part of a pixel given its X and Y coordinates. This way, you can use the position_of method from within your other methods to keep them simpler.

Compiling Your Program

Like the previous lab, this lab includes a Makefile. You can compile your program by typing make or make picfilter; you can then run it with ./picfilter. You can compile your tests by typing make tests; you can then run them with ./tests.

Testing Your Program

The initial repository for your assignment includes a directory test_data which has several PPM files in it. There are two original images provided: Rose.ppm and Gerbil.ppm. The remaining PPM files are the result of the above-described transformations. For instance, Gerbil__no-red.ppm should be the PPM file produced by running the no-red filter on the Gerbil.ppm file.

The tests.cpp file includes test_utils.h, which provides a function CHECK_PPM_EQUAL. This function is similar to CHECK_EQUAL except that it verifies that two PPM files are equal (that is, they have the same size and pixel values). When you write tests for your program, you can use CHECK_PPM_EQUAL to verify the files that you have written. An example test has already been written for you so that you can see how CHECK_PPM_EQUAL might be used.

test_utils.h also includes a function RUN_MAIN to run picfilter_main for you. picfilter_main is just a function, so you could call it yourself, but it’s a bit of a pain to create the char** pointing to all of the arguments you want to provide. So we’ve provided RUN_MAIN to make this easier for you; inside of your TEST, you can just do something like this:

RUN_MAIN(4, "picfilter", "test_data/Gerbil.ppm", "test_output/Gerbil__no-red.ppm", "no-red");

Note that “picfilter” is the first argument given; recall that the first command-line argument is always the name of the program we are running and, since we’re calling the picfilter_main function, we have to simulate that. Note also that the command-line arguments are just given as a sequence of strings above; the RUN_MAIN function will take care of bundling them into an array for you. The above code does not perform any tests, though, so you’ll need to do that (with CHECK_PPM_EQUAL) yourself.

Getting Started

Remember: you don’t have to write your whole program all at once. In fact, it’s often best to make some small amount of progress and write tests for that code. You don’t even have to start by writing code for picfilter_main. Here are some steps you can follow to complete your program.

  1. Start by writing the constructor for PpmImage. That constructor takes an ifstream& of the file that should be read and initializes the object to the contents of that image. You’ll need to use new in this constructor to allocate an array to store your pixel data; the provided code assumes that you will be using a single-dimensional array of ints. Also write the destructor of PpmImage (which should be a lot easier, since it just has to delete the pixel array).
  2. Compile and run the unit tests. There aren’t very many right now, but you’ve been provided some example tests. The first test makes sure that you can load a PPM file without an exception. The second test makes sure that your PpmImage constructor throws an exception when given a file that is not a PPM. Do not write more code until these two tests pass! You won’t be able to make meaningful progress until you can read in the PPM file and write it back out again. (The third test – copying the PPM – will fail, but that’s okay; you haven’t written the code for that yet.)
  3. Implement the position_of method for your class. The rest of your methods should call this method to deal with finding pixels in your array, since every pixel is composed of three integers.
  4. Next, write the save method for your class. You should be able to load and save a file and wind up with the same picture. Once you’re finished, run the tests again. The last example test that you have been provided will load a picture, save it to another name, and then check that the two files are equal. This helps to show how CHECK_PPM_EQUAL is used.
  5. Now, write each of the methods of the PpmImage class one at a time. Each time you finish one, write an appropriate test for it using CHECK_PPM_EQUAL.
  6. After you’ve finished writing your PpmImage class and are confident that it is working, you can write picfilter_main. This should be pretty easy: just use the command-line arguments to determine the file to load, the operations to perform, and the file to save.
  7. Next, write tests for picfilter_main! Remember to use RUN_MAIN as described above. The template separates main from picfilter_main so that you can write tests that call picfilter_main and make sure it does the right thing. This way, you can use tests to be sure that you are handling command-line arguments correctly as well.
  8. Once all of your tests pass, you should be able to run your program with no bugs or problems. If you discover a bug, try writing more tests to see if you can figure out what went wrong!

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 my_new_image.ppm grayscale invert flip-horizontal

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

convert my_new_image.ppm my_image.jpg

There! You’ve just used your homework assignment to perform photo editing on your own files. :)

Summary

To summarize the requirements of this lab: