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.
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.
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.
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.
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:
Pixel p = new Pixel(-100, 300, 50);The resulting red green and blue color components should be 0, 255 and 50 respectively.
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.
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.
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:
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
.
Pixel
class.
getPixel
– This method must
return null
for pixel positions outside of
the image boundaries.
setPixel
– This method must have
no effect for pixel positions outside of the image
boundaries or if a null
Pixel
argument is provided.
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.
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 Pixel
s 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.
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:
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)
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.
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)
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)
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)
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)
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)
The following code has been provided for you. You should not need to modify any of these files:
MimpLauncher
class below.
main
for
starting the MIMP application.
Submission for this assignment is divided into two parts that should be completed in order.
For this part you must submit your completed Pixel
class through
Autolab. You must also provide JUnit tests for all Pixel
class
methods.
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.
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:
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.
This assignment will include a penalty for excessive Autolab submissions.