Assignment by Cynthia Lee; inspired by Mark Guzdial and Barbara Ericson; name by Eric Yurko and Arun Debray.
January 11, 2017
Fauxtoshop is due Friday, January 20th at 12:00pm.
In this assignment, you’ll write your own photo editor with advanced features like green screen, "scatter" blur effect, edge detection, rotation, and a very interesting type of blur called a Gaussian Blur. You'll be making extensive use of our week-1 topics: console interaction and the Grid ADT (look for our other ADTs 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.) You might even have some extra time, in which case there are plenty of opportunities for artistic expression and creative extensions (extra credit). We look forward to seeing what you do!
The assignment has several purposes:const
.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: Console 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). Very 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 edit the main code or make other GWindow objects.
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 provided, 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 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) 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 false. 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):
gw.setCanvasSize(img.getWidth(), img.getHeight()); gw.add(&img,0,0); // You'll notice there is an & before img. This is not the same kind of & as // reference parameters. We’ll talk about it later in the quarter. For now, just // copy this line of code when adding images to a GWindow.
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 GWindow. 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 GBufferedImage in 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. Another way to avoid trouble is that if you call gw.clear(), it’s ok for any GBufferedImage objects that were added to the window to cease to exist, because the GWindow has then “forgotten” about them.
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, reprompt 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. By the way: 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 offering to save the image, clear the GWindow object so it is blank again (call its clear method). 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.
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:
const int WHITE = 0xFFFFFF; const int BLACK = 0x000000; const int GREEN = 0x00FF00;
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 const variable of type int. 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
You should not use the GBufferedImage methods such as setColor or setRGB that set individual pixels in the image. Instead, make all the changes you want in a Grid
int pixel = original[row][col];
int red, green, blue;
GBufferedImage::getRedGreenBlue(pixel, red, green, blue);
The 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
GBufferedImage::getRedGreenBlue.
To earn points for the basic assignment you’ll need to precisely implement our algorithm (and not some other algorithm, however reasonable).
First, ask the user to provide a “degree of scatter” for how far we should scatter pixels. The value should be an integer between 1 and 100, inclusive (otherwise reprompt).
Then create a new image Grid that has the same dimensions as the original image Grid. 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):
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 Grid. 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. The Grid class has an inBounds method 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):
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 (reprompt 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 (0x00FF00). The tolerance should be between 1 and 100. 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).
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 reprompt). 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 return), 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):
One way to rotate an image clockwise around its center point is as follows: For each pixel in the rotated image at coordinate (x, y), copy the color of the pixel from the original image closest to (xOld, yOld) such that:
xOld = +x * cos(𝜃) + y * sin(𝜃) yOld = -x * sin(𝜃) + y * cos(𝜃)
Where:
Additionally, part of the new image will not receive any rotated pixels. In this case, we will make those pixels white (suggestion: make the new grid completely white to start).
You will also need to make use of the "gmath.h"
library to use the sinDegrees()
and cosDegrees()
functions. Alternatively, you can use the standard C++ <cmath>
library, which defines the trigonometric functions using radians (but you will need to convert from degrees to radians if you use them).
Here is a screenshot of the rotation effect 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 Gaussian Blur effect waiting for the user to respond to the prompt about saving the file (notice we haven’t cleared the window yet):
Edge detection extension suggestions:
Green screen extension suggestions:
Scatter extension suggestions:
General extension suggestions:
pause(miliseconds);
.Turn in the following files:
fauxtoshop.cpp
, the C++ code for all of your filters.art1.jpg, art2.jpg, art3.jpg
. Three images 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 some of the images provided in the starter code to generate these three pieces of artwork, they must each include some of your own images that were not part of the starter code (e.g., your own photos, things you sourced from the internet). You may use some of your extensions (if any) in making these three artwork files.