Assignment by Cynthia Lee; inspired by Mark Guzdial and Barbara Ericson; name by Eric Yurko and Arun Debray. Minor edits by Marty Stepp, Chris Gregg, and Chris Piech.
September 29, 2017
This assignment is an individual assignment. Write your own solution and do not work in a pair/group on this program.
Fauxtoshop is due Monday, October 9th at 6:00pm.
This problem is about C++ grids, images and pixels, functions, using libraries, and decomposing a large problem.
We provide a ZIP archive with a starter project that you should download and open with Qt Creator. You will edit and turn in only the following files. The ZIP contains other files/libraries; do not modify these. Your code must work with the other files unmodified.
When you are finished, submit your assignment using our Paperless web system.
In this assignment, you'll write your own photo editor with advanced features like green screen, "scatter" blur effect, and edge detection.
You'll be making extensive use of our week-1 topics: console interaction and the
Grid collection (look for our other collections to show up on assignment 2).
For extra spice, there is one easy mouse click interaction.
This document is long, but don't let that overwhelm you!
(Much of it is just screenshots anyway.)
There are plenty of opportunities for artistic expression and creative extensions.
I look forward to seeing what you do!
The assignment has several purposes:
Your program should use the console to support basic menu selections, file name input, and other interactions with the user. Below is a screenshot of the initial welcome screens:
You'll notice there are two windows:
a Console window and a Fauxtoshop window.
In the screenshots throughout this document, you will see that what you should print in the window labeled "Console" is in black font, and what the user types is in blue font.
The "Fauxtoshop" window is created using the Stanford library graphics class
GWindow (refer to Stanford library documentation).
Important note: your program should only ever declare and use one
GWindow, and its lifespan should span the entire duration of the program, or else your program may crash.
We have declared one in
main for you in the starter code, thereby making its lifespan the duration of the program.
You should not make other
If you need to use this window in other functions of your code, pass it by reference to avoid making a copy of the window.
This section outlines the sequence of interactions that take place in the main menu in the console window. Later sections will explain other important components of the program, and detail the behavior of each of the four menu options.
First, greet the user with the welcome message shown, then ask the user to specify an image file name. The image files should be located in the res directory (sibling directory of your src and lib directories in the project directory). When images are located there, Qt Creator will handle making them available to your code without additional path (directory location) information.
To open the image file, declare a
GBufferedImage object, and then call the
openImageFromFilename function (provided in the starter code in the file fauxtoshop-provided.cpp) to open the file with the image file name the user specified.
This function returns a Boolean value, where
true indicates success.
You should re-prompt the user for a filename if this function returns
(Tip: If you type "?" as the file name,
openImageFromFilename will pop up a graphical file chooser window, which will let you avoid typing out long file names.)
After the image is opened, you'll want to resize the
GWindow to be the exact same size as the image, and then add the image to the
GWindow by calling
add (the code shown below assumes your
GBufferedImage object is called
img and your
GWindow object is called
gw, but you could use different names):
// adding a BufferedImage to GWindow gw.setCanvasSize(img.getWidth(), img.getHeight()); gw.add(&img, 0, 0);
It is important that the
GBufferedImage object you add to the
GWindow not cease to exist (go out of scope) after you add it to the
For example, if you make a helper function that declares a
GBufferedImage object and adds it to the
GWindow, then returns (causing the local variable of the
GBufferedImage to go out of scope and cease to exist), this could cause your program to crash!
The starter code declares one
main, and if you use that object throughout the program (by changing the contents, not declaring a new one to add to the
GWindow--though you may declare temporary ones that you don't add to the
GWindow), you'll avoid any trouble.
If you call
gw.clear(); , the
GWindow will remove and forget about any images it was containing, so it is okay for them to fall out of scope after that.
Below is a screenshot showing the next steps of the interaction after the image is opened and added to the window. It shows a menu of filter effect options and asks the user to select one (if the user enters something other than a valid menu choice, re-prompt with the entire menu text starting with "Which image filter..."). Each menu option has additional input(s) required of the user, which are explained in each menu option section below.
Aside: Some of the examples shown in this document may take a very long time to render, depending on the speed of your computer. When opening larger image files than your debugger can handle well, it may appear that the program has frozen. If this happens to you, try working with smaller images.
After the filter effect is complete and displayed in the window, you should ask the user if they want to save the image, as shown in the screenshot below.
To save the image file, call the
saveImageToFilename function (provided in the starter code in the file fauxtoshop-provided.cpp) with the image file name the user specified.
This function returns a Boolean value, where
true indicates success.
You should re-prompt the user for a file name if this function returns
(Tip: If you type "?" as the file name,
saveImageToFilename will pop up a graphical file chooser window, which will let you avoid typing out long file names.)
Aside: The saved image file will not save in the input images' res directory, where you might expect! Instead, it will save in the build directory created by the compiler to hold its intermediate work and the final compiled executable. The build directory will be found as a sibling directory of the "Fauxtoshop" project directory, and it will have a name something like build-Fauxtoshop-Desktop_Qt_5_2_0_MinGW_32bit-Debug. Usually you should ignore the build directory and not mess with it, but it's fine to look in there for your saved image files.
After offering to save the image, clear the
GWindow object so it is blank again (call its
clear member function).
Print one blank line.
Then repeat the entire main menu sequence again (as shown in the screenshot below), until the user enters an empty filename to quit the program.
For each of the filter effects in the menu options, you'll want to iterate over the pixels of the image and inspect or change them.
Before doing any such inspection or change of pixels, convert your
GBufferedImage object into a
The conversion step is as follows (where
img is the name of the
GBufferedImage object; yours could have a different name):
// converting a BufferedImage to a Grid of ints Grid<int> original = img.toGrid();
The individual pixels (row/col entries) of the
Grid are integers representing RGB values.
You can read about the RGB color scheme in detail on Wikipedia or RGBWorld, but the basic idea is that there are three separate values packed into one
int: a value between 0 and 255 representing how much Red, a value between 0 and 255 representing how much Green, and a value between 0 and 255 representing how much Blue.
The red, green, and blue combine together to make one particular pixel's color.
By varying the red, green, and blue values, we can make all the colors of the rainbow.
It's a slightly different version of the Primary Colors concept you learned as a kid.
In the starter code, three color values are defined for you as constants:
// pre-defined color constants const int WHITE = 0xFFFFFF; const int BLACK = 0x000000; const int GREEN = 0x00FF00;
Aside: These numbers are written in base 16 (called "hexadecimal"), which you don't need to worry too much about.
They still behave as any other literal value of type
For the curious: The first two digits represent red, the second two digits represent green, and the third two digits represent blue.
So in the case of white, the red, green, and blue components all have the maximum value of 255 (base 10) or "FF" (base 16).
On the other hand, green has the maximum value of green (the middle two digits are "FF") and 0 of red and blue.
We have a nice helper function provided for you in the Stanford libraries that separates the pixel
ints into their individual RGB components for you (it's a nice example of "returning" three values using pass-by-reference).
Here is an example for one pixel (where original is the name of a
Grid<int> object, and
col are integers; as usual, you may choose different names):
// getting the red/green/blue components of a pixel int pixel = original[row][col]; int red, green, blue; GBufferedImage::getRedGreenBlue(pixel, red, green, blue);
GBufferedImage::getRedGreenBlue syntax might look new or strange to you. The
:: is called the scope resolution operator, and it indicates that the
getRedGreenBlue function can be found in the
GBufferedImage class, but that you do not need an instance of the class to use the function (that is, it is a static function in the same sense of the word
static that you may have seen in Java).
You don't really need to worry about exactly what this means until we talk about it later in the course.
Just use the function as if its "full name" is
You should not use the
GBufferedImage member functions such as
setRGB that set individual pixels in the image.
Instead, make all the changes you want in a
Grid<int> object, and then change the image all at once by calling the
img is the name of your
GBufferedImage instance), which takes a
Grid<int> as its argument.
There are two reasons for this requirement: one is efficiency, and the other is to get you to practice using the
Grid collection by doing looping and operations there rather than in the image object directly.
For both the edge detection and green screen effects, your program will need to have a way of calculating the "difference" between two colors. There are a number of ways to do this, and in fact exploring alternate calculations could be one of the extensions you do for this assignment.
To earn points for the basic assignment you'll need to precisely implement our algorithm (and not some other algorithm, however reasonable).
GBufferedImage::getRedGreenBluemember function described above to separate the pixels into their RGB components.
<math>standard library has an integer
maxfunction that takes two values at a time).
That is going to be our definition of difference between pixels.
Note, all calculations should be done using type
For this filter, your program will take the original image and "scatter" its pixels, making something that looks like a sand drawing that was shaken.
First, ask the user to provide a "radius" (it's not quite a radius, mathematically) of how far we should scatter pixels. The value should be an integer between 1 and 100, inclusive (otherwise re-prompt).
Then create a new image
Grid that has the same dimensions as the original image
Next, for each pixel in the new image, randomly select a pixel from nearby that row and column in the original image that will provide the color for this pixel in the new image.
You will randomly select the pixel by randomly selecting a row that is within radius of the current row, and randomly selecting a column that is within radius of the current column.
If the randomly selected row or column is out of bounds of the
Grid of the original image, you should randomly select again until you get an in-bounds pixel.
Here is a screenshot of the Scatter filter effect complete, waiting for the user to respond to the prompt about saving the file (notice we haven't cleared the window yet):
Note: Since the Scatter operation uses randomness, it produces a different output each time, even if you run it on the same image with the same radius.
If you want to try to match our expected outputs exactly, you can call the provided function
fakeRandomNumberGenerator(); at the start of your program's
main function, which will cause the random number generator to always return the same sequence of integers every time you call it.
To match our output, you would also have to use these fake random numbers in row-major order to generate "dr" and "dc" values, offsets from the image's original row and column for each pixel.
You are not required to do this to get full credit; it's optional and is only useful to verify that your implementation exactly matches ours.
For this filter, your program will create a new black and white image of the same size as the original, where a given pixel is black if it was an edge in the original image, and white if it was not an edge in the original image.
First, ask the user for a threshold that controls how different two adjacent pixels must be from each other to be considered an "edge." This should be a positive (nonzero) integer value (otherwise re-prompt). Then, loop over each pixel and determine if it is an edge or not.
A pixel is defined as an edge if at least one of its neighbors has a difference of greater than threshold from it.
The neighbors are the 9 pixels (including self) immediately adjacent or diagonal from the current "self" row/column of the
So if my distances to my neighbors are: 9, 8, 5, 3, 3, 0 (self), 4, 7, 8, 7, then I would only be considered an edge if the threshold were less than 9 (the greatest difference between me and one of my neighbors).
Remember that pixels near edges and corners may not have all 9 neighbors, so take care not to go out of bounds.
Grid class has an
inBounds member function that will be helpful.
Here is a screenshot of the Edge Detection filter effect complete, waiting for the user to respond to the prompt about saving the file (notice we haven't cleared the window yet):
For this filter, your program will paste a "sticker" image on top of a "background" image, but ignore any part of the sticker that is close to pure green in color. This technique is used widely in filmmaking--actors are filmed acting on a stage that is painted bright green and then their forms are later digitally placed on top of some other scenery.
The image that the user specifies in the main menu will be the background image, so the first thing you should do for this filter effect is ask the user to specify a new image file name to be the sticker image.
The prompt will work in a similar fashion (re-prompt if open fails), but you will not offer the option to enter a blank filename to indicate ending the program.
Open the image file and convert it to a
Grid in the same way you did with the original background image.
Next, we need to know how much tolerance we should have for green that isn't pure green (
That way if there are slight shadows or other variations on the green part of the image, the green screen effect will still work as we want it to.
Prompt the user to enter a value, and then you'll use that as a threshold in conjunction with the same pixel color difference calculation that we used in edge detection (described above).
Your code should re-prompt until the tolerance is between 0 and 100 inclusive.
Next, we need to know where to place the sticker image. Ask the user to specify a location as (row,col) in exactly that format, with row and col being non-negative integers (otherwise re-prompt). The row and column specified will be the background location where you will place the upper left corner of the sticker. The only variation from that exact (row,col) format that you should allow is for the user to enter nothing (just hits Enter), in which case you should allow the user to specify the location use a mouse click. The starter code includes a function that receives mouse clicks and reports the location to you as row and column. You should report back to the user the detected click location.
Now we are ready to place the image in the location specified. Any pixel on the sticker image that is difference greater than threshold from pure green will be copied onto the background, otherwise that pixel will be ignored and the background pixel there will remain untouched. You should allow the sticker image to be cut off on the bottom or right edge(s) if it cannot completely fit on the background.
Here is a screenshot of the Green Screen filter effect complete, waiting for the user to respond to the prompt about saving the file (notice we haven't cleared the window yet):
Here is a screenshot of the Green Screen filter effect complete in the case where the user clicked to give the sticker location:
This is the least exciting of the menu options, and it does not produce any visual effect.
However, it will help you debug your code by comparing your output to the sample outputs provided on the course website.
For this menu option, ask the user to name another image file (open it as a
GBufferedImage in the same way described in the Green Screen section), and you will count how many pixels differ between the two.
This would be easy to do by iterating over the pixels yourself, but it's actually even easier than that; there is a
countDiffPixels member function in the
GBufferedImage class that does all the work for you.
Use it, then report the result to the user.
Print a nonzero count as "These images differ in _ pixel locations!" or print "These images are the same!" as applicable.
You will continue to display the original image.
Your main menu should not ask the user if they want to save the resulting image in this case, since no new image is generated.
Here is a screenshot showing this menu option in action:
In addition to printing the number of differing pixels, if the images are not the same, you should also pop up a graphical window to display the differences.
To do this, call the instructor-provided function
showDiffWindow, passing it your
GWindow and the second image's file name as parameters.
You can ask a
BufferedImage for its filename by calling
getFilename() on it as shown below.
(The code shown below assumes your
GWindow object is called
gw and your
GBufferedImage object is called
otherImg, but you could use different names):
// pop up a window to display differences between the two images showDiffWindow(gw, otherImg);
The following screenshot shows an example output from
showDiffWindow when the
GWindow is displaying rainbow.png and the other image to compare against is rainbow-love-you.png:
Here is an example log of interaction from your program (with user input bolded). Your output must match this format exactly to earn full credit. (We are very picky.)
Welcome to Fauxtoshop! Enter name of image file to open (or blank to quit): kitten.jpg Opening image file, may take a minute... Which image filter would you like to apply? 1 - Scatter 2 - Edge detection 3 - "Green screen" with another image 4 - Compare image with another image Your choice: 1 Enter degree of scatter [1-100]: 999 Enter degree of scatter [1-100]: -5 Enter degree of scatter [1-100]: 10 Enter filename to save image (or blank to skip saving): kitten-scatter-10.png Enter name of image file to open (or blank to quit): oops-not-found.png Opening image file, may take a minute... Enter name of image file to open (or blank to quit): kitten-scatter-10.png Opening image file, may take a minute... Which image filter would you like to apply? 1 - Scatter 2 - Edge detection 3 - "Green screen" with another image 4 - Compare image with another image Your choice: what? Illegal integer format. Try again. Your choice: 0 Which image filter would you like to apply? 1 - Scatter 2 - Edge detection 3 - "Green screen" with another image 4 - Compare image with another image Your choice: 4 Now choose another image file to compare to. Enter name of image file to open: bad-image-file-wont-be-found.png Opening image file, may take a minute... Enter name of image file to open: expected-output-kitten-scatter-10.png Opening image file, may take a minute... These images are the same! Enter name of image file to open (or blank to quit): Exiting.
Here are some additional expected output files to compare:
Tired of typing? Your program's Console window has a File → Compare Output feature for checking your output, as well as a Load Input Script feature that can auto-type the user console input for you. Use them!
Along with your code, submit an image named art.png that you created using your code (use the Save resulting image option to save the file). You should use several of the filter effects in combination (e.g., multiple green screen stickers, one of them edges only or scattered), by saving interim files and reading them in as input to the next step. Although you may use one of the images provided in the starter code to generate your piece of artwork, it should include an image of your own that was not part of the starter code (e.g., your own photo, or something you sourced from the internet). You may use some of your extensions (if any) in making this artwork file if you like. This is worth a small part of your grade.
If you enjoy this part of the assignment and want to submit multiple art files, feel free to do so. Name them art.png, art2.png, art3.png and so on.
To achieve a high style score, submit a program with high code quality that conforms to the guidelines presented in our course. There are many general C++ coding styles that you should follow, such as naming, indentation, commenting, avoiding redundancy, etc. While there are many valid programming styles in various contexts, the most important overall stylistic trait a programmer can have is the ability to be given a set of style guidelines and follow them rigorously and consistently. In the context of this course, our style guides and constraints are the laws of the land and are not open for debate.
Before getting started, you should read our Style Guide for information about expected coding style. You are expected to follow the Style Guide on all homework code. The following are some points of emphasis and style contraints specific to this problem:
For full credit, you must use C++ facilities (
string) instead of C equivalents (
main function should represent a concise summary of the overall program.
It is okay for
main to contain some code, such as calls to other functions or brief console output statements to
main should not perform too large a share of the overall work itself directly, such as reading the lines of the input file or prompting the user to replace placeholders.
Instead, it should make calls to other functions to help it achieve the overall goal.
You should declare function prototypes (each function's header followed by a semicolon) near the top of your file for all functions besides
main, regardless of whether this is necessary for the program to compile.
Each function should perform a single clear and coherent task.
No one function should do too large a share of the overall work.
As a rough estimate, a function whose body (excluding the header and closing brace) has more than 30 lines is likely too large.
You should avoid "chaining" long sequences of function calls together without coming back to
main, as described in the Procedural Design Heuristics handout on the course web site.
Your functions should also be used to help you avoid redundant code.
If you are performing identical or very similar commands repeatedly, factor out the common code and logic into a helper function, or otherwise remove the redundancy.
Parameters, Returns, Values, References:
Since your program will have several functions and those functions will want to share information, you will need to appropriately pass parameters and/or return values between the functions.
Each function's parameters and return values should be well chosen.
Do not declare unnecessary parameters that are not needed by your function.
A particular point of emphasis on this assignment is that you should demonstrate that you understand when it is proper to pass by reference, and when it is proper for a parameter to be declared
As much as possible, pass collections and objects by reference, and
const reference when applicable, because passing them by value makes an expensive copy.
You should also demonstrate that you understand when it is better to return a result and when it is better to store a result into an 'output' reference parameter.
Variables and types:
Use descriptive variable and function names.
Use appropriate data types for each variable and parameter; for example, do not use a
double if the variable is intended to hold an integer, and do not use an
int if the variable is storing a
false state that would be better suited to a
When manipulating strings, favor talking to
string objects over individual
char values when possible, and use the
string object's built-in member functions as opposed to rewriting similar behavior yourself.
Do not declare any global variables or
static variables; every variable in your program must be declared inside one of your functions and must exist in only that scope.
No single variable's scope should extend beyond a single invocation of a single function.
Commenting: Your code should have adequate commenting. The top of your file should have a descriptive comment header with your name, a description of the assignment, and a citation of all sources you used to help you write your program. Each function should have a comment header describing that function's behavior, any parameters it accepts and any values it returns, and any assumptions the function makes about how it will be used. For larger functions, you should also place a brief inline comment on any complex sections of code to explain what the code is doing. See the programs written in lecture or the Course Style Guide for examples of proper commenting.
Grid and other Stanford library tools.
Do not use pointers, arrays, or STL containers on this program.
You should not be explicitly allocating or freeing any memory for this assignment (goes along with no pointers).
If you refer to any non-class resource (person, website, your own previous coding projects that are substantially similar), you need to make a clear citation of that fact in the code. Please refer to the Honor Code for CS106A+B+X for more detail.
While high quality solutions can vary widely in length, some students find it helpful to know, as a general point of reference, how many lines of code our solution is.
Our solution is about 300 lines of code, including
#include, comments, etc.
For each assignment problem, we receive various frequent student questions. The answers to some of those questions can be found below.
NOTE: On this first assignment, many students have issues with setting up their Qt Creator software. If your problem or issue is more about that software than it is about HW1 specifically, you should also check out our Qt Creator troubleshooting page for possible solutions.
art.pngfile okay? Will it get full credit?
This assignment offers lots of opportunity to create fun and interesting extra features ("extensions"). For any extensions you write, make their use go through a new top level menu item, so autograders can still test your program using menu items 1-4 and expect them to perform exactly according to specification. So, for example, if you change the behavior of green screen, make a new top level menu item 5 titled something like "Improved green screen."
Edge detection extension suggestions:
Green screen extension suggestions:
Scatter extension suggestions:
General extension suggestions:
If you do any extensions that require any additional image files for the grader to operate your extension options, please make sure to turn in those additional files. Be sure your code includes comments and/or cout statements that make it clear to the grader how to operate your extensions.
You are expected to follow the Stanford Honor Code.
Remember that we run similarity-detection software over all solutions, including this quarter and past quarters, as well as any solutions we find on the web.
If you need help solving an assignment, we are happy to help you. You can go to the LaIR, or the course message forum, or email your section leader, or visit the instructor/Head TA during their office hours. You can do it!