PA #2: MIMP

Honor Code Reminder

Programming assignments in this course are to be completed individually. No collaborative work is allowed. Think of the programming assignments as low-stakes take-home exams. See the course syllabus for more details. Talk to your instructor if you have questions about the collaboration policy.

Objectives

The purpose of this assignment is to practice working with two-dimensional arrays and implementing Java classes according to an existing specification. In addition, you will gain experience writing JUnit tests that cover 100% of your code.

Introduction

Welcome to ImageCorp! You have been hired to work on a ground-breaking new image manipulation application named MIMP: The Madison Image Manipulation Program. MIMP is designed to be a competitor to existing products such as Photoshop or GIMP.

The User Interface Team has already designed a stylish and functional GUI. It will be your job to program the classes that actually store and manipulate image data.

Representing Images

There are many different ways to digitally represent images. One common approach is to maintain a two-dimensional grid of pixels, where each pixel corresponds to the color of a tiny rectangular region of the overall image.

The color of each pixel is represented by its red, green and blue components. Each color component is restricted to be an integer in the range 0 to 255. Under this representation (red=0, green=0, blue=0) corresponds to black, (255, 255, 255) corresponds to white, (255, 0, 0) corresponds to red etc. This approach enables us to represent \(256 \times 256 \times 256 = 16,777,216\) distinct colors. The individual color components are often referred to as “channels”.

The Java Standard libraries include some built-in image classes, including java.awt.Image and java.awt.image.BufferedImage. Unfortunately these classes are not ideal for our purposes. They are designed primarily to represent images in a graphical interface, not to support pixel-level manipulation.

For this project you will implement three classes: one representing individual pixels, one representing complete images, and a utility class that is able to perform basic image manipulations such as brightening, rotating etc.

The Pixel Class

Your implementation of the Pixel class must conform to the following UML diagram. Note that Pixel is an immutable type: once a Pixel object has been created it cannot be modified.

(The {readOnly} annotation indicates that the fields should be final.)

Method details:

  1. Constructor – The constructor must automatically clip color values to the appropriate range. In other words, if a Pixel is declared as follows:
    Pixel p = new Pixel(-100, 300, 50);
    The resulting red green and blue color components should be 0, 255 and 50 respectively.
  2. toString – The toString method must return a string with the following format: "(red, green, blue)", where red, green and blue are the string representations of the appropriate color values left-padded to have length three. For example the pixel above would be "( 0, 255, 50)". For this method, and all other methods that return strings, your formatting must exactly match the specifications.

  3. getChannel – The getChannel method should take an integer value in the range 0-2, indicating which color channel is requested, 0=red, 1=green, 2=blue, and return the corresponding color value. If any other channel is requested an IllegalArgumentException must be thrown. This is a convenience function that makes it possible to loop over channels.

The Image Class

Your implementation of the Image class should conform to the following UML diagram. Note that location (0, 0) is in the upper-left corner of the image.

Method details:

  1. The two-argument constructor must create a completely white image. The five-argument constructor must create an image using the indicated color values.

    If the provided width or height is less than or equal to zero, both constructors must throw an IllegalArgumentException.

    Color values outside of the allowable range must be clipped according to the logic described for the Pixel class.
  2. getPixel – This method must return null for pixel positions outside of the image boundaries.
  3. setPixel – This method must have no effect for pixel positions outside of the image boundaries or if a null Pixel argument is provided.
  4. toString – The format for the toString method is "<Image width=w height=h>" where w and h are the height and width of the image. For example: "<Image width=640 height=480>" would be the correct string for any image that is 640 pixels wide and 480 pixels high.
  5. toStringDebug – The toStringDebug method is designed to facilitate debugging by returning an easy-to-read string representation of an image. The following string illustrates the correct format for a tiny 4x5 pixel image:

    <Image width=4 height=5>
     (  1, 101, 201) (  2, 102, 202) (  3, 103, 203) (  4, 104, 204)
     (  5, 105, 205) (  6, 106, 206) (  7, 107, 207) (  8, 108, 208)
     (  9, 109, 209) ( 10, 110, 210) ( 11, 111, 211) ( 12, 112, 212)
     ( 13, 113, 213) ( 14, 114, 214) ( 15, 115, 215) ( 16, 116, 216)
     ( 17, 117, 217) ( 18, 118, 218) ( 19, 119, 219) ( 20, 120, 220)

    Each row of Pixels is indented with a single space and there is one space between each Pixel in a row. There must be no trailing spaces and the entire string must be terminated by a newline.

The ImageTransforms Class

ImageTransforms is a utility class that contains static methods for performing basic image transformations. Note that none of the methods defined in ImageTransforms modify the existing image. In each case a new image is returned that is a result of applying the requested transform.

Method details:

  1. adjustBrightness – This method simply increases or decreases all three color channels by the indicated amount. If the original image above is adjusted by +100, the correct result would be the following:

    <Image width=4 height=5>
     (101, 201, 255) (102, 202, 255) (103, 203, 255) (104, 204, 255)
     (105, 205, 255) (106, 206, 255) (107, 207, 255) (108, 208, 255)
     (109, 209, 255) (110, 210, 255) (111, 211, 255) (112, 212, 255)
     (113, 213, 255) (114, 214, 255) (115, 215, 255) (116, 216, 255)
     (117, 217, 255) (118, 218, 255) (119, 219, 255) (120, 220, 255)
  2. convertToGrayscale – This method converts an image to grayscale by setting all three color channels to the same value. Most image processing programs accomplish this by computing a weighted average of the three color components. Green is weighed most heavily, reflecting the fact that the human eye is more sensitive to green wavelengths. Your method must use the following weighting scheme: L = 0.299R + 0.587G + 0.114B. Where L is referred to as “luminosity” or “brightness”. You can find more information on the Wikipedia Grayscale page.

    Here is the resulting image if we compute the grayscale version of the original 4x5 image above:

    <Image width=4 height=5>
     ( 82,  82,  82) ( 83,  83,  83) ( 84,  84,  84) ( 85,  85,  85)
     ( 86,  86,  86) ( 87,  87,  87) ( 88,  88,  88) ( 89,  89,  89)
     ( 90,  90,  90) ( 91,  91,  91) ( 92,  92,  92) ( 93,  93,  93)
     ( 94,  94,  94) ( 95,  95,  95) ( 96,  96,  96) ( 97,  97,  97)
     ( 98,  98,  98) ( 99,  99,  99) (100, 100, 100) (101, 101, 101)

    In this example, the upper left pixel has the value 82 because
    0.299 * 1 + 0.587 * 101 + 0.114 * 201 = 82.5.
    Note that the floating point result is truncated rather than rounded. This is the default behavior in Java when a floating point value is assigned to an integer variable.

  3. threshold – This method converts the image to black and white by setting all pixels with a luminosity below the threshold to black, and all other pixels to white. The result of applying a threshold of 90 to our original image would be the following:

    <Image width=4 height=5>
     (  0,   0,   0) (  0,   0,   0) (  0,   0,   0) (  0,   0,   0)
     (  0,   0,   0) (  0,   0,   0) (  0,   0,   0) (  0,   0,   0)
     (255, 255, 255) (255, 255, 255) (255, 255, 255) (255, 255, 255)
     (255, 255, 255) (255, 255, 255) (255, 255, 255) (255, 255, 255)
     (255, 255, 255) (255, 255, 255) (255, 255, 255) (255, 255, 255)
  4. rotateLeft – This method rotates the image 90° to the left (counter-clockwise). The result of applying this method to our original image would be the following:

    <Image width=5 height=4>
     (  4, 104, 204) (  8, 108, 208) ( 12, 112, 212) ( 16, 116, 216) ( 20, 120, 220)
     (  3, 103, 203) (  7, 107, 207) ( 11, 111, 211) ( 15, 115, 215) ( 19, 119, 219)
     (  2, 102, 202) (  6, 106, 206) ( 10, 110, 210) ( 14, 114, 214) ( 18, 118, 218)
     (  1, 101, 201) (  5, 105, 205) (  9, 109, 209) ( 13, 113, 213) ( 17, 117, 217)
  5. rotateRight – This method rotates the image 90° to the right (clockwise). The result of applying this method to our original image would be the following:

    <Image width=5 height=4>
     ( 17, 117, 217) ( 13, 113, 213) (  9, 109, 209) (  5, 105, 205) (  1, 101, 201)
     ( 18, 118, 218) ( 14, 114, 214) ( 10, 110, 210) (  6, 106, 206) (  2, 102, 202)
     ( 19, 119, 219) ( 15, 115, 215) ( 11, 111, 211) (  7, 107, 207) (  3, 103, 203)
     ( 20, 120, 220) ( 16, 116, 216) ( 12, 112, 212) (  8, 108, 208) (  4, 104, 204)
  6. mirror – This method flips the image around the vertical axis. The result of applying this method to our original image would be the following:

    <Image width=4 height=5>
     (  4, 104, 204) (  3, 103, 203) (  2, 102, 202) (  1, 101, 201)
     (  8, 108, 208) (  7, 107, 207) (  6, 106, 206) (  5, 105, 205)
     ( 12, 112, 212) ( 11, 111, 211) ( 10, 110, 210) (  9, 109, 209)
     ( 16, 116, 216) ( 15, 115, 215) ( 14, 114, 214) ( 13, 113, 213)
     ( 20, 120, 220) ( 19, 119, 219) ( 18, 118, 218) ( 17, 117, 217)
  7. flip – This method flips the image around the horizontal axis. The result of applying this method to our original image would be the following:

    <Image width=4 height=5>
     ( 17, 117, 217) ( 18, 118, 218) ( 19, 119, 219) ( 20, 120, 220)
     ( 13, 113, 213) ( 14, 114, 214) ( 15, 115, 215) ( 16, 116, 216)
     (  9, 109, 209) ( 10, 110, 210) ( 11, 111, 211) ( 12, 112, 212)
     (  5, 105, 205) (  6, 106, 206) (  7, 107, 207) (  8, 108, 208)
     (  1, 101, 201) (  2, 102, 202) (  3, 103, 203) (  4, 104, 204)

Provided Code

The following code has been provided for you. You should not need to modify any of these files:

Submission and Grading

Submission for this assignment is divided into two parts that should be completed in order.

Part A: Pixel Class

For this part you must submit your completed Pixel class through Autolab. You must also provide JUnit tests for all Pixel class methods.

Autolab will evaluate your submission on four factors:
  1. Checkstyle.
  2. Coverage from your own unit tests.
  3. Passing your own unit tests.
  4. Passing the instructor-written unit tests.

In order to receive ANY credit for this part, you must reach 100% statement coverage, your Pixel.java class must not fail any JUnit tests and it must pass all automated Checkstyle tests. Assuming that your submission satisfies these conditions, the score will be based on branch coverage. Submissions with 100% branch coverage will receive full credit.

Part B: Image and ImageTransforms

For Part B you must submit all three required classes. You are not required to submit unit tests for Part B. However, you are unlikely to finish the project successfully without somehow testing each of your methods. You could accomplish this by writing JUnit tests, or by writing a test driver to exercise each method as you code it. If you need to get help from an instructor or a TA you should plan on bringing your testing code to that meeting.

Here are some (incomplete) test files that you may use as a starting point:

Grading

Part A Autolab: 30%
Part B Autolab: 50%
Part B Instructor Style Points: 20%

The portion of your grade labeled “Instructor grading based on style and code quality” will be based on stylistic issues that cannot be checked automatically. These include:

Refer to the course style guide for additional details.

Submission penalties

This assignment will include a penalty for excessive Autolab submissions.