Introduction: Green Screen

Almost certainly, you're familiar with green screening, where a plain green background is replaced with some other image. The most common example is weather maps on the news - you'll never see a weather person wearing green clothes, or they would disappear.

In this project, we're going to do some green screening, combining a background from one picture with a foreground from another.

For example, consider a picture of Bill the Goat (on the left below). He deserves to be someplace nice. So, for every pixel that is green, we'll replace it with the corresponding pixel from the image in the middle below, giving us the image on the right below.

foreground picture background picture combined picture

Honor

The course policy and the instructions it references, most pertinently COMPSCIDEPTINST 1531.1C, spell out what kinds of assistance are permissible for programming projects. The instructor can give explicit permission for things that would otherwise not be allowed.

For this project, you have explicit permission

  1. to get help from your instructor or from current IC210 MGSP leaders (any assistance must be documented though), and
  2. to use general purpose C++ resources online (though such use must be documented). Resources that might specifically address this project are not allowed.
With the instructions referenced in the course policy and the explicit permissions above, what you get is summarized as:
Warning: Do not even vaguely talk about the contents of the project with your colleagues.

If you have questions about any of these rules, email your instructor and ask about it.

Submitting Your Work

Deadlines

Early bonus and late penalty

Grading

70% of your grade will be based on functionality (i.e., whether your program works as specified), mostly based on the test cases that you see when you submit, but perhaps also extra test cases according to the specifications of the project. The code you submit must work if you expect to earn a passing grade.

30% of your grade will be based on coding style, which includes:

Calculation

All of this is factored into your maximum score based on which part you have completed. So for example: Then, your final grade would be grade would be 85*(0.70+0.22)*1.05 = 82.11.

Tips for success

Part 1: Understanding ppm image format (30/100)

Project files

Download proj01files.tgz. Extract the project files by running following the command in the terminal:
tar -xvf proj01files.tgz

Understanding PPM format

In this project, to manipulate the pixels of images easily, we're going to use a horribly space-inefficient image file format called "plain PPM."

The project files contain a bunch of PPM files that you can use. For example, open bg.ppm in a text editor (i.e., vi, emacs, or atom), you'll see something that looks like this:
P3
6 5
255 
0 255 0 255 0 0 0 255 0 255 0 0 0 255 0 255 0 0 
255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 
0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 
0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 
255 255 0 255 255 0 255 255 0 255 255 0 255 255 0 255 255 0 
file_header = P3
width=6 height=5
largest_possible_value=255
6 pixels: (r 0, g 255, b 0) ... 
6 pixels: (r 255, g 0, b 0) ... 
6 pixels: (r 0, g 255, b 0) ... 
6 pixels: (r 0, g 0, b 255) ... 
6 pixels: (r 255, g 255, b 0) ... 


Every PPM file must be in this format.

Viewing a PPM image

To view a PPM image on your Linux machine, use the command:
eog mule.ppm
Of course, you can specify any other ppm file instead of mule.ppm

Your task: readppm.cpp

Write a program in a file called readppm.cpp. The program should first prompt the user for the filename of an existing PPM image. Then, it should print out the contents of the image as shown on the right. In particular, for each pixel, your program should provide its row and column position in the image.

An example run (with user input colored in red):

~$ ./readppm
Input file: bg.ppm
P3
width = 6, height = 5
max value = 255
*** row 0 ***
row 0, col 0: r0 g255 b0
row 0, col 1: r255 g0 b0
row 0, col 2: r0 g255 b0
row 0, col 3: r255 g0 b0
row 0, col 4: r0 g255 b0
row 0, col 5: r255 g0 b0
*** row 1 ***
row 1, col 0: r255 g0 b0
row 1, col 1: r255 g0 b0
row 1, col 2: r255 g0 b0
row 1, col 3: r255 g0 b0
row 1, col 4: r255 g0 b0
row 1, col 5: r255 g0 b0
*** row 2 ***
row 2, col 0: r0 g255 b0
row 2, col 1: r0 g255 b0
row 2, col 2: r0 g255 b0
row 2, col 3: r0 g255 b0
row 2, col 4: r0 g255 b0
row 2, col 5: r0 g255 b0
*** row 3 ***
row 3, col 0: r0 g0 b255
row 3, col 1: r0 g0 b255
row 3, col 2: r0 g0 b255
row 3, col 3: r0 g0 b255
row 3, col 4: r0 g0 b255
row 3, col 5: r0 g0 b255
*** row 4 ***
row 4, col 0: r255 g255 b0
row 4, col 1: r255 g255 b0
row 4, col 2: r255 g255 b0
row 4, col 3: r255 g255 b0
row 4, col 4: r255 g255 b0
row 4, col 5: r255 g255 b0

Part 2: Grayscale an image (50/100)

To get used to the PPM format more, let's make an image grayscale (black and white).

Write a program in a file called gray.cpp:

Tip: To find the gray value of a pixel, take the r, g, and b values, average them (to an integer with fractions truncated), and set all three values to that number. So, the gray version of (5, 100, 16) would be (40, 40, 40).

As an example, running this program on our picture of flowers gives us a picture on the right.

Note: If the input file to be read in does not exist, the program should print an error message and gracefully exit. Example runs of the program are shown here (with user input colored in red):

~$ ./gray
Input file: bg.ppm
Output file: graybg.ppm
Image saved to graybg.ppm

~$ ./gray
Input file: flowers.ppm
Output file: grayflowers.ppm
Image saved to grayflowers.ppm

~$ ./gray
Input file: nonexisting.ppm
Output file: output.ppm
Error: Input file not found
Checking if your output is correct

The correct output ppm files for bg.ppm and folowers.ppm are provided in proj01files.tgz. In particular:

Do as follows in order to check the correctness of your code:
  1. Change the permission of the script to be executable (also found in proj01files.tgz):
    ~/$ chmod 700 diff.sh
    
  2. Make sure that your readppm compiles and works correctly (the script diff.sh uses your readppm).
    ~/$ g++ readppm.cpp -o readppm
  3. Use the script to see if your output file is the same as the corresponding test data.
    ~/$ ./diff.sh graybg.ppm test_graybg.ppm
    ~/$ ./diff.sh grayflowers.ppm test_grayflowers.ppm
    

Part 3: Combine images (70/100)

In this part, we'll do the part described in the introduction. In particular:

Example runs of the program (with user input red):

~$ ./green
Foreground file: fg.ppm
Background file: bg.ppm
Output file: fgbg.ppm
Image saved to fgbg.ppm

~$ ./green
Foreground file: bill.ppm
Background file: flowers.ppm
Output file: billflowers.ppm
Image saved to billflowers.ppm

~$ ./green
Foreground file: fg.ppm
Background file: flowers.ppm
Output file: fgflowers.ppm
Error: Images have different sizes

~$ ./green
Foreground file: bill.ppm
Background file: nonexisting.ppm
Output file: billflowers.ppm
Error: Input file not found

Your program should be in a file called green.cpp.

Your program should prompt the user

Your program should work for any two images that are the same size as each other. If the foreground and background images are not the same size, your program should output an error message.

If one of the two files to be read in don't exist, the program should print an error message and gracefully exit.

Note: A one-pixel wide green border around your foreground subject is fine.

Checking your output with test vectors

Use the script to see if your output file is the same as the test data.
 ./diff.sh fgbg.ppm test_fgbg.ppm 
 ./diff.sh billflowers.ppm test_billflowers.ppm 

Part 4: Move down the foreground image (85/100)

The problem with our program so far is that it only accepts pictures that are the same size. What if we want Bill somewhere other than in the middle of the picture? Some pictures in the tarball you downloaded are cropped (i.e., fgc.ppm, billc.ppm, bbillc.ppm, and mulec.ppm), so we should be able to put them anywhere in a picture that is at least as big as they are.
Write a program in a file called rowshift.cpp that prompts the user for:
  • a foreground image, a background image, a row shift, and the output image

Example pictures illustrating how the row shift works:

row shift=0

row shift=100

Example runs of the program (with user input red):
~$ ./rowshift
Foreground file: fgc.ppm
Background file: bg.ppm
Row shift: 1
Output file: fb_1.ppm
Image saved to fb_1.ppm

~$ ./rowshift
Foreground file: billc.ppm
Background file: flowers.ppm
Row shift: 100
Output file: bf_100.ppm
Image saved to bf_100.ppm

~$ ./rowshift
Foreground file: billc.ppm
Background file: nonexisting.ppm
Row shift: 100
Output file: bf_100.ppm
Error: Input file not found

~$ ./rowshift
Foreground file: billc.ppm
Background file: flowers.ppm
Row shift: 1200
Output file: bf_1200.ppm
Error: The foreground goes past the background 

Read carefully (if necessary many times)

Let rs be a row shift.
  • For the first rs rows, no green screening should be performed (do you see why?). In particular:
    • Only the background figure pixels should be read and stored into the output figure.
    • To reiterate, the foreground figure pixels should not be read here, yet!!
  • For the next fg_height rows, the program should perform the green screening.
    • Be careful about the pixels represented with the red dot (labeled ???) on the right, though. Like the first rs rows above, the program should not perform green screening in those regions.

If the given row shift is so large that the foreground image would extend past the background image, an error message should be given, and no output image created. In addition, if one of the two images input does not exist, the program should print an error message and gracefully exit.

Checking your output

First, check if your program runs correctly for the first example test case. Your program should create fb_1.ppm (recall: rowshift = 1) that should be analyzed by readppm as follows.
~/$ ./readppm
Input file: fgc.ppm
P3
width = 3, height = 2
max value = 255
*** row 0 ***
row 0, col 0: r0 g255 b0
row 0, col 1: r0 g255 b0
row 0, col 2: r127 g127 b127
*** row 1 ***
row 1, col 0: r127 g127 b127
row 1, col 1: r127 g127 b127
row 1, col 2: r0 g255 b0
~/$ ./readppm
Input file: bg.ppm
P3
width = 6, height = 5
max value = 255
*** row 0 ***
row 0, col 0: r0 g255 b0
row 0, col 1: r255 g0 b0
row 0, col 2: r0 g255 b0
row 0, col 3: r255 g0 b0
row 0, col 4: r0 g255 b0
row 0, col 5: r255 g0 b0
*** row 1 ***
row 1, col 0: r255 g0 b0
row 1, col 1: r255 g0 b0
row 1, col 2: r255 g0 b0
row 1, col 3: r255 g0 b0
row 1, col 4: r255 g0 b0
row 1, col 5: r255 g0 b0
*** row 2 ***
row 2, col 0: r0 g255 b0
row 2, col 1: r0 g255 b0
row 2, col 2: r0 g255 b0
row 2, col 3: r0 g255 b0
row 2, col 4: r0 g255 b0
row 2, col 5: r0 g255 b0
*** row 3 ***
row 3, col 0: r0 g0 b255
row 3, col 1: r0 g0 b255
row 3, col 2: r0 g0 b255
row 3, col 3: r0 g0 b255
row 3, col 4: r0 g0 b255
row 3, col 5: r0 g0 b255
*** row 4 ***
row 4, col 0: r255 g255 b0
row 4, col 1: r255 g255 b0
row 4, col 2: r255 g255 b0
row 4, col 3: r255 g255 b0
row 4, col 4: r255 g255 b0
row 4, col 5: r255 g255 b0
~/$ ./readppm
Input file: fb_1.ppm
P3
width = 6, height = 5
max value = 255
*** row 0 ***
row 0, col 0: r0 g255 b0
row 0, col 1: r255 g0 b0
row 0, col 2: r0 g255 b0
row 0, col 3: r255 g0 b0
row 0, col 4: r0 g255 b0
row 0, col 5: r255 g0 b0
*** row 1 ***
row 1, col 0: r255 g0 b0
row 1, col 1: r255 g0 b0
row 1, col 2: r127 g127 b127
row 1, col 3: r255 g0 b0
row 1, col 4: r255 g0 b0
row 1, col 5: r255 g0 b0
*** row 2 ***
row 2, col 0: r127 g127 b127
row 2, col 1: r127 g127 b127
row 2, col 2: r0 g255 b0
row 2, col 3: r0 g255 b0
row 2, col 4: r0 g255 b0
row 2, col 5: r0 g255 b0
*** row 3 ***
row 3, col 0: r0 g0 b255
row 3, col 1: r0 g0 b255
row 3, col 2: r0 g0 b255
row 3, col 3: r0 g0 b255
row 3, col 4: r0 g0 b255
row 3, col 5: r0 g0 b255
*** row 4 ***
row 4, col 0: r255 g255 b0
row 4, col 1: r255 g255 b0
row 4, col 2: r255 g255 b0
row 4, col 3: r255 g255 b0
row 4, col 4: r255 g255 b0
row 4, col 5: r255 g255 b0

Checking your output with the test vectors

Use the script to see if your output file is the same as the test data.
 ./diff.sh fb_1.ppm test_fb_1.ppm 
 ./diff.sh bf_100.ppm test_bf_100.ppm 

Part 5: Fully offset foreground image (100/100)

The problem with our program so far is that it only accepts pictures that are the same size. What if we want Bill somewhere other than in the middle of the picture? Some pictures in the tarball you downloaded are cropped (i.e., fgc.ppm, billc.ppm, bbillc.ppm, and mulec.ppm), so we should be able to put them anywhere in a picture that is at least as big as they are.
Example runs of the program (with user input red):
~$ ./shift
Foreground file: fgc.ppm
Background file: bg.ppm
Row shift: 1
Column shift: 2
Output file: fb_1_2.ppm
Image saved to fb_1_2.ppm

Foreground file: billc.ppm
Background file: flowers.ppm
Row shift: 100
Column shift: 400
Output file: bf_100_400.ppm
Image saved to bf_100_400.ppm

~$ ./shift
Foreground file: billc.ppm
Background file: nonexisting.ppm
Row shift: 100
Column shift: 200
Output file: bf_100_200.ppm
Error: Input file not found

~$ ./shift
Foreground file: billc.ppm
Background file: flowers.ppm
Row shift: 0
Column shift: 1200
Output file: bf_0_1200.ppm
Error: The foreground goes past the background 
Write a program in a file called shift.cpp that prompts the user for:

Example pictures illustrating how the row shift and column shift work:

row shift=0, column shift=0

row shift=0, column shift=450

row shift=100, column shift=0

Tips

As with the previous part, you need to be careful about when not to perform green screening.

If the given row shift or column shift is so large that the foreground image would extend past the background image, an error message should be given, and no output image created. In addition, if one of the two images input does not exist, the program should print an error message and gracefully exit.

Checking your output with test vectors

Use the script to see if your output file is the same as the test data.
 ./diff.sh fb_1_2.ppm test_fb_1_2.ppm 
 ./diff.sh bf_100_400.ppm test_bf_100_400.ppm