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++:
- Command-line arguments
- File I/O
- Organizing your code into separate files
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.)
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:
P3is the magic number that tells us that this is a text-based PPM image file.4 4tells us that this image is 4 pixels wide by 4 pixels tall.255tells us that 255 is the largest number that will appear in any pixel; this number should always be between 1 and 255.0 0 0tells us that a pixel has0red,0green, and0blue (so it is black).255 255 255tells us that a pixel has255red,255green, and255blue (so it is white).
Here is another example PPM file, slash.ppm:
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.
no-red: All red values are set to0.no-green: All green values are set to0.no-blue: All blue values are set to0.invert: All channels are subtracted from the maximum pixel value. For instance,255 128 0would become0 127 255.grayscale: The channels of each pixel are averaged. For instance, the pixel255 128 0would become127 127 127(since(255+128+0)/3is approximately127).flip-horizontally: All pixels are moved across the X axis. The first pixel in each row becomes the last pixel in that row, the second pixel in each row becomes the second-to-last pixel in that row, and so on.flip-vertically: All pixels are moved across the Y axis, causing the image to appear upside-down.
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:
image.cpp: This file will contain a definition of thePpmImageclass. APpmImageobject represents an in-memory image at runtime.picfilter.cpp: This file contains a function calledpicfilter_main. It looks exactly like amainfunction except for its name and you should treat it as themainof your program. We separate it for testing purposes.program.cpp: This file contains themainfunction for yourpicfilterprogram. It simply callspicfilter_main. You will not need to modify this file, but you should understand what it is doing.tests.cpp: This file contains unit tests of your code.test_utils.o: This file contains tools your tests can use.
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.
- Start by writing the constructor for
PpmImage. That constructor takes anifstream&of the file that should be read and initializes the object to the contents of that image. You’ll need to usenewin this constructor to allocate an array to store your pixel data; the provided code assumes that you will be using a single-dimensional array ofints. Also write the destructor ofPpmImage(which should be a lot easier, since it just has todeletethe pixel array). - 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
PpmImageconstructor 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.) - Implement the
position_ofmethod 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. - Next, write the
savemethod 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 howCHECK_PPM_EQUALis used. - Now, write each of the methods of the
PpmImageclass one at a time. Each time you finish one, write an appropriate test for it usingCHECK_PPM_EQUAL. - After you’ve finished writing your
PpmImageclass and are confident that it is working, you can writepicfilter_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. - Next, write tests for
picfilter_main! Remember to useRUN_MAINas described above. The template separatesmainfrompicfilter_mainso that you can write tests that callpicfilter_mainand 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. - 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:
-
You should pick meaningful variable names.
// Good int* pixels = new int[size]; // Bad int* p = new int[size]; -
You should use correct and consistent indentation. Lines of code within a block (that is, surrounded by
{and}) should be indented four spaces further than the lines surrounding them.// Good if (condition) { cout << "Test" << endl; } // Bad if (condition) { cout << "Test" << endl; } -
You should use a block whenever possible, even if it’s not necessary. This helps you avoid subtle or messy bugs in the future.
// Good if (condition) { cout << "Something" << endl; } // Bad if (condition) cout << "Something" << endl;
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:
- Your program must perform all of the image transformations listed above.
- Your program must take its input from command-line arguments.
- You must gracefully handle situations in which the user provides invalid command-line arguments (incorrect number of arguments, bad transformation names, or non-existing files).
- Your code must respond gracefully when given files that are not PPM files (e.g. a text file containing “
P3 stuff”). - You must write tests for each function/method you write.
- This includes
picfilter_main.
- This includes