15110 Fall 2011 [Cortina/von Ronne]

Programming Assignment 7 - due Tuesday, October 25

Overview

For this assignment, you will create a Ruby source file containing a ruby function(s) implementing each of the problems described below. If you find it useful for any of the problems in this assignment, you may define additional helper functions that your primary function for that problem calls to solve a sub-problem. Definitions for these helper functions should be included in the same Ruby source file as the primary function they help. You should store a source file for each problem in a folder named pa7.

Note: You are responsible for protecting your solutions to these problems from being seen by other students either physically (e.g., by looking over your shoulder) or electronically. (More information can be found in the instructions for Programming Assignment 2.)

Working with Images

In all of the problems given below, you will be writing functions to process images. An image is simply a two-dimensional array of pixels, where each pixel is an array with three integers representing the red, green and blue values for that pixel. (So if you think about it, an image is really a three-dimensional structure.) For example, here is a 3 X 2 image consisting of a red and green pixel in the top row, a blue and white pixel in the middle row, and two black pixels in the bottom row:

[ [ [255,0,0] , [0,255,0] ] , [ [0,0,255] , [255,255,255] ] , [ [0,0,0] , [0,0,0] ] ] 

For each problem, your job is to write a function that takes an image represented in this format, and returns a new image of the same size that has something about the image changed.

As an example, suppose we want to remove the red component from every pixel of an image. We can do this by resetting the red part of each pixel array to 0. We could write the following function to do this:

def remove_red(image)
  num_rows = image.length
  num_columns = image[0].length
  for row in 0..num_rows-1 do
    for column in 0..num_columns-1 do
      green = image[row][column][1]
      blue = image[row][column][2]
      image[row][column] = [0, green, blue]
    end
  end
  return nil
end

Testing with PPM Images

To test your functions, you will need to download ppm.rb and some images in the ppm format, such as nautical.ppm, scottiedog.ppm, penn.ppm, vonronne.ppm, and tcortina.ppm, into your pa7 directory. Once you do that, you should do the following in irb (replacing remove_red(m) with a call to your function):

>> load("ppm.rb")
=> true
>> m = load_ppm("nautical.ppm")
=> [[[255, 255, 255], [255, 255, 255] ...
>> remove_red(m)
=> nil
>> plot(m)
=> nil
becomes

Additional Instructions for Slow Connections

Unfortunately, if you are accessing unix.andrew.cmu.edu, the plot function may take several minutes to send the data to your local window. An alternative that will go faster is to save your modified image, and then view it using the command line program display.

To use display to view your modified image, first save your modified image to a ppm file using the function save_ppm:

save_ppm(m, "redless_nautical.ppm")
=> nil

Then in a separate window, establish an ssh session (with X11 forwarding), and in your pa7 directory, run the command:

[andrewid@unix37 pa7]$ cd ~/private/15110/pa7
[andrewid@unix37 pa7]$ display redless_nautical.ppm

Note: you may also be able to improve performance slightly by telling ssh to use compression. If you are using the command-line ssh (e.g., on a Mac), you can use the "-C" switch:

ssh -CX andrewid@unix.andrew.cmu.edu

Creating Additional PPM Files

The support files require images in the ppm format. If you want new images to test, unix.andrew.cmu.edu has programs that can be used to convert more common image formats (including JPEG, PNG, and GIF to PPM). These can be used as follows:

jpegtopnm file.jpg > file.ppm
giftopnm file.gif > file.ppm
pngtopnm file.png > file.ppm
Each of these example commands will create a PPM file called file.ppm from file.jpg, file.gif, or file.png, respectively.

Problems

  1. [2 points] Write a Ruby function inverse(image) (in the file inverse.rb) that requires an image array (as defined above) as its parameter and modifies it so that it is the inverse of the original image. The inverse of an image is found by taking each pixel [red, green, blue] and replacing it with [255-red, 255-green, 255-blue].

    Here is an algorithm you can follow. (It is very similar to the one for remove_red.) Make sure you understand how this algorithm works. Don't just translate it to Ruby without thinking about it.

    1. Set num_rows equal to the number of rows in the image.

    2. Set num_columns equal to the number of columns in the image.

    3. For each row in the image do the following:

      1. For each column in the image do the following:

        1. Set red to the red component of the pixel array at the current row and column in the image.

        2. Set green to the green component of the pixel array at the current row and column in the image.

        3. Set blue to the blue component of the pixel array at the current row and column in the image.

        4. Store the pixel array [255-red, 255-green, 255-blue] at the current row and column in the image.

    4. Return nil.

    Sample usage:

    >> m = load_ppm("nautical.ppm")
    => [[[255, 255, 255], [255, 255, 255] ...
    >> inverse(m)
    => nil
    >> plot(m)
    => nil
    
    becomes
  2. [2 points] Write a Ruby function white_to_black(image) (in the file white_to_black.rb) that requires an image array (as defined above) as its parameter and alters that image by changing each white pixel into a black pixel. Your function will look very much like the one above, except that the computation done inside the column loop will be different.

    Here is an algorithm you can follow. Make sure you understand how this algorithm works. Don't just translate it to Ruby without thinking about it.

    1. Set num_rows equal to the number of rows in the image.

    2. Set num_columns equal to the number of columns in the image.

    3. For each row in the image do the following:

      1. For each column in the image do the following:

        1. If the pixel array at the current row and column of the image represents white, then store the pixel array for black at the current row and column of the image instead.

    4. Return nil.

    Sample usage:

    >> m = load_ppm("nautical.ppm")
    => [[[255, 255, 255], [255, 255, 255] ...
    >> white_to_black(m)
    => nil
    >> plot(m)
    => nil
    
    becomes
  3. [2 points] Write a Ruby function gray_scale(image) (in the file gray_scale.rb) that requires an image array (as defined above) as its parameter and converts that image to a gray scale version of the original image. To convert each pixel to gray scale, get the pixel's red, green and blue values and then create a new pixel that contains the average of these 3 values as its red, green and blue components. For example, if the pixel is [50, 100, 91], then its gray scale version is [80, 80, 80].

    Algorithm: For each row and column, store the red, green and blue values of the pixel array for the current row and column and then replace the pixel array at the current row and column with its gray scale version as defined above.

    Sample usage (typo fixed):

    >> m = load_ppm("nautical.ppm")
    => [[[255, 255, 255], [255, 255, 255] ...
    >> gray_scale(m)
    => nil
    >> plot(m)
    => nil
    
    becomes
  4. [2 points] Write a Ruby function reduce_colors(image, span) (in the file reduce_colors.rb) that requires an image array (as defined above) and a positive integer for the "span" and returns a new image containing the original image except that each color component of each pixel is divided by the span and then multiplied by the span. This will reduce the number of colors in the image (for spans greater than 1). For example, if a pixel is [25, 50, 75] and the span is 7, then the pixel would become [25/7*7, 50/7*7, 75/7*7] = [21, 49, 70].

    Algorithm: For each row and column, store the red, green and blue values of the pixel array for the current row and column and then replace the pixel array at the current row and column with a new pixel array with new red, green and blue values using the span as defined above. You may assume that the span value is greater than or equal to 1.

    Sample usage:

    >> m = load_ppm("scottiedog.ppm")
    => [[[247, 246, 238], ...
    >> reduce_colors(m,64)
    => nil
    >> plot(m)
    => nil
    
    becomes
  5. [2 points] (HARDER) Write a Ruby function flip(image) (in the file flip.rb) that requires an image array (as defined above) as its parameter and returns a new image containing the same image flipped horizontally (left to right). See sample image below for an example. You will have to figure out the algorithm for this problem!

    Hint: Ruby arrays provides a reverse! operation that changes an array by reversing the order of its elements.

    Sample usage:

    >> m = load_ppm("nautical.ppm")
    => [[[255, 255, 255], [255, 255, 255] ...
    >> flip(m)
    => nil
    >> plot(m)
    => nil
    
    becomes

    Submission

    You should now have a pa7 directory that contains the required files, inverse.rb, white_to_black.rb, gray_scale.rb, reduce_colors.rb, and flip.rb, each—in turn—containing the corresponding function(s). Zip up your directory and upload it using the handin system. (The handin system will accept submissions beginning on Friday until the deadline Tuesday night.)