RU beehive logo ITEC dept promo banner
ITEC 120
2019fall
asbrennem
ibarland

Special Effects
2-D arrays

Due 2019-Dec-06 (Fri) 23:59 on D2L; Bring hardcopy to following meeting (the final exam). You only need submit/print FX.java — not the provided files.

This homework builds on our color-images lab. It teaches 2-D array handling, including:

It also introduces the notion of timing functions to compare implementations.

Setup


Tasks

  1. Write reflectLR, which reflects a 2d, rectangular array of Colors left-to-right (making a mirror-image).
    Use a helper-function which reflects just one row.
    This function should return a new array, not modify the array it's passed.
    Thus you'll need to allocate the resulting 2-D array. (Do not call Arrays.copyOf).
  2. Write reflectTB_v1, which reflects a 2d, rectangular array of Colors top-to-bottom.
    Use a helper-function which reflects just a single column.
    This function should return a new array, not modify the array it's passed. (The helper will need to be passed both the entire input-array, as well as the result-array which it write its answer into.)
  3. Write reflectTB_v2, which also reflects a 2d, rectangular array of Colors top-to-bottom.
    This function should modify the array it's passed. In particular, you don't even need to process a row at all; simply swap elements of the top-level array-of-rows.

    Your unit-tests for this function should ensure that you are mutating the array: in addition to copying the same assertEquals unit-tests as you had for your previous function, you'll also want to compare pixels from before and after the call using == (not Color.equals).
    (For full credit, also compare an entire row/array before and after the call using == (not Arrays.deepEquals.)

  4. Write a function which compares the running-time of the previous two reflect-top-bottom functions: create a very large array (perhaps via Pict.fileToPixelsColor(String)), and time it takes to relect it, for each of the two functions. Print the time required for each (in ms), as well as the size of the array being processed (in #elements).

    How to time something:

    1. java.lang.System#currentTimeMillis; it returns a long (a “long integer”1).
    2. Be sure to get the current time right before what you want to measure; don't accidentally include time for allocating an array if you only want to measure the time to process it.
    3. Compute the difference of a before- and after-time; the result is in milliseconds.
    There are further considerations to timing things well2, which aren't at all required for this homework.

  5. We'll attempt to detect where edges are, in an image.

    Write function int[][] edges( Color[][] img ) will compute, for each pixel in img, the difference between the brightness of that pixel, and the average brightness of three of its neighbors: the pixel to its right, beneath it, and the pixel to its lower-right. (You can consider the result as a (black-and-white) image, where the result's bright pixels are strong edges, and dark pixels are definite-non-edges.)

    For example, if the brightness of various pixels were:
    30309090
    30309090
    90909090
    90909090
    then the result would be
    0400??
    40600??
    000??
    ????????
    .
    If a pixel doesn't have one of these neighbors — e.g. it's on the last row or column, you can either:
    1. consider an "off-image" pixel to be black, or
    2. consider that a pixel without such neighbors is never an edge (or at least less-likely to be counted as an edge)
    3. ignore the last rwo/column entirely3: if you're passed in a 100x200 image, you'd then return a 99x199 result.
    pro-tip: I recomend writing a method safeLookup( int[][] arr, int r, int c, int default ) which simply returns arr[r][c], except that if those aren't valid indices for arr, then return default instead.

    This method factors out the many if-statements you'd otherwise have sprinkled through your loops, and helps reduce lots of code dealing with processing elements at/near the edges of your array.

  6. In special effects, “green screening” means combining two images into a new one, where the result is just like the foreground-image, except where any of its pixels were green-ish, instead use the pixel from the background. Of course, you don't need to use green; in general the technique is called Chroma Keying; in practice people use either Green or Blue as their key.

    We will implement this special-effect. It is, of course, a function: given two images (and a reference-color key), return a new composited image: Color[][] chromakey( Color[][] foreground, Color[][] background, Color key ).

    1. You must write a helper-function which just tells whether two single pixels are boolean similar(Color,Color). (Use this function to tell if a pixel of the foreground is similar to green, or to whatever reference-color you're using.)

      One way to decide whether two Colors are similar is whether each of their components (red/green/blue) are within COLOR_TOLERANCE of each other4 (where you define that named-constant; a good initial value would be 10, but you can tweak it later.). The function Math.abs might be helpful.

      2-3 unit tests should suffice, for this helper function. (And, the function tint provided below might help.)

    2. One unit-test for chromaKey will suffice; I recommend using small images (maybe 3x4 or even smaller).
    3. Writing unit-tests makes it clear, that we want our background and foreground images to be exactly the same height & width. Or, you might have ways of (partly) relaxing that constraint. Be sure your function's purpose-statement clearly mentions whatever pre-conditions your code requires/assumes. (If stated as a pre-condition, your code does not need to verify them; that's the caller's duty.)

    For fun: If you want to make good use of this function yourself, you can take a photo of yourself in front of a uniform-color background, and crop it to the size as some other background photo. To get the rgb value to use as a suitable key, you might simply grab a picture from your photo's background (e.g. selfie[30][40]). (Or, you could write a loop to find the average value of the top-left 50x50 of your photo.) You will probably want to play with different values of COLOR_TOLERANCE to try to get a better result.


Unit tests

For each of the functions you write, be sure to write test-cases. You can use Object120.assertEquals, or you can use your own comparisons (in which case Arrays.deepEquals) will be helpful to you).

You may assume all arrays are non-empty (are at least 1x1), and are rectangular; for extra-credit allow for arrays with 0 rows or 0 columns.

Here are some arrays you can use, to help with your tests:

          /* Be sure you've `import`ed java.awt.Color java.util.Arrays near the top of your file (*before* `class FX { … }`.) */

          // first, some colors to help us build (test-)2D-arrays
          Color red11    = new Color( 11,  0,  0);
          Color red99    = new Color( 99,  0,  0);
          Color red199   = new Color(199,  0,  0);

          Color green11  = new Color(  0, 11,  0);
          Color green99  = new Color(  0, 99,  0);
          Color green199 = new Color(  0,199,  0);

          Color blue11   = new Color(  0,  0, 11);
          Color blue99   = new Color(  0,  0, 99);
          Color blue199  = new Color(  0,  0,199);

          Color gray11   = new Color( 11, 11, 11);
          Color gray99   = new Color( 99, 99, 99);
          Color gray199  = new Color(199,199,199);

          // BETTER, in retrospect: I should make an array 'pallette', and use *loops* to generate the colors above.

          // next, some arrays to help us build (test-)2D-arrays
          Color[] row0 = new Color[]{  red99,  blue11,  green99, gray199 };
          Color[] row1 = new Color[]{  red199, blue11,  blue99,  Color.MAGENTA };
          Color[] row2 = new Color[]{ blue199, gray99,  blue99,  tint(gray99) };

          // NOW finally make some 2-D arrays.

          // A 3x4 array (three rows, each with four columns):
          Color[][] grid34 = new Color[][]{ row0
                                          , row1 
                                          , row2
                                          };


          // A small 1x1 array
          Color[][] grid11 = new Color[][]{ new Color[](50,40,130)  };

          // Some 1xn arrays:
          Color[][] grid41 = new Color[][]{ Arrays.copyOf(row1,row1.length) };  // make a copy of row1, so that any functions that *change* an array tested on `grid34` won't affect `grid41` (!)
          Color[][] grid14 = new Color[][]{ new Color[]{ green99 }
                                          , new Color[]{ Color.RED }
                                          , new Color[]{ gray199 }
                                          , new Color[]{ blue199 }
                                          };
    
    /** Return a Color very-close-to `src`, but slightly different (or perhaps even the same, occasionally). */
    Color tint(Color src) { return new Color( Math.min(src.getRed()+2,255), Math.max(src.getGreen()-3,0), src.getBlue() ); }
    


1 A long is just like int, except that where int overflows after merely 32-1 (~2billion), a long doesn't overflow until 64-1 (~8 billion billion, a.k.a. 8 quintillion).      
2 Some of the most obvious are:
  1. You may want to run the same code (say) 10 or 1000 times, and take the average of the results.
  2. If you're really serious about measuring, it's traditional to call System.gc() right before each measurement, to help remove the cost of garbage collection from your timings.
  3. Finally, note that measuring “wall time” (the time passed as per the clock on the wall) can be confounded with the time your CPU is spent on other tasks (e.g. if your computer has some other intensive task it's working on, or even just many other small tasks, which is what is happening in real life).
    You could instead focus on “cpu time”; to do that you'd need to call other methods that report the time-used by just one particular Operating-System “process”.
  4. In ITEC220, you'll start considering how long algorithms when you vary int he problem-size: e.g. if you process an array that's nine times as big (threefold in each dimension), does it take your code 9 times longer? 81 times longer? 3 times longer? This is called “algorithmic analysis”, where we capture the running time of the underlying algorithm, rather than the running time on a particular hardware platorm (stats which can get out of date in a couple years).
     
3 Ignoring the last row and column is clearly the better answer, for this definition of “is an edge-pixel”. However, for other notions of an edge, it might not be, and then we'd be in the unfortunate situation that changing our idea of "edge" changes the size of the result! Thus the best solution is probably (ii).      
4 Some slight extra-credit for using a better notion of color difference, or figure out a function that works better for our purpose of Chroma Keying.      

logo for creative commons by-attribution license
This page licensed CC-BY 4.0 Ian Barland
Page last generated
Please mail any suggestions
(incl. typos, broken links)
to ibarlandradford.edu
Rendered by Racket.