Lab 9: Final Project, Part II




In this assignment you will build on the solution created for Lab 8. In addition to updating your solution based on feedback from your instructor, you will add support for a new custom binary image format, additional image transformations, and additional user interface elements to support the new functionality.


This week you must implement the following functionality:

All of your classes should be placed in a package whose name matches your MSOE username.


Handler Methods

Refactoring for Functional Programming

You must refactor your implementations of the grayscale() and negative() so that they make use of a functional programming approach. This same approach must be used in the implementations of red() and redGray().

You must implement the following method in the class that handle the action events for above transformations:

private static Image transformImage(Image image, Transformable transform) {
    // ...

This method applies the specified transformation to each pixel in the image to produce a transformed image. The second argument specifies the behavior of the desired transformation.

The Transformable interface is a functional interface. A functional interface has a single method, and can therefore be implemented with a lambda expression. The method, named apply(), must accept two arguments: the y location of the pixel and its color. The method must return the color for the pixel after the applying the transformation. You must specify implementations of the Transformable interface for the grayscale, red, redGray, and negative image transformations as lambda expressions.

Red Only

This transformation acts as a red filter. The green and blue components of each pixel are set to zero.

Red Only

Red Only Example


This transformation is based on an experiment demonstrated by Edwin Land, founder of Polaroid, in 1959 more info. The experiment was based on the Retinex Theory of Color Vision. This page has a nice explanation of the experiment and the Retinex theory. The transform is quite simple: perform a Grayscale transformation on the pixels in alternating rows and perform a Red Only transformation on the pixels in the other rows2).


Red-Gray Example

Filter Kernel Window

When clicked, the Show Filter button reveals a second window and changes the text on the button to Hide Filter. When Hide Filter is clicked, the second window is hidden, and the text on the button changes back to Show Filter. Note: you may chose an alternate user experience for this but must be able to justify why an alternate approach is superior to this.

User Interface

Filter Kernel Window

The Filter Kernel window contains a 3 x 3 grid3) of filter weights. The user must be able to modify these weights manually or by clicking the Blur or Sharpen buttons. When clicked, the Blur or Sharpen button on the right change the filter weights as follows:

Blur          Sharpen
0  1  0        0  -1   0
1  5  1       -1   5  -1
0  1  0        0  -1   0

The Apply button applies the filter kernel to the image. At a minimum, the button need only apply the filter kernel if the sum of the filter kernel weights is a positive value. For example, if the user enters 0 for all the filter weights, applying the filter can produce unpredictable results. The weights in the array specifying the filter kernel should sum to 1. Since the blur filter kernel has values that sum to 9 (0+1+0+1+5+1+0+1+0), each value must be divided by 9. The following code applies the Blur filter kernel:

double[] kernel = { 0.0,  1.0/9,  0.0,
                   1.0/9, 5.0/9, 1.0/9,
                    0.0,  1.0/9,  0.0};
Image blurredImage = ImageUtil.convolve(originalImage, kernel);

The values for filter kernel for Sharpen sum to 1 (5-1-1-1-1) so the weights in the array here would be:

double[] kernel = { 0.0, -1.0,  0.0,
                   -1.0,  5.0, -1.0,
                    0.0, -1.0,  0.0};


Blurred (applied 10 times) Example


Sharpened (applied 1 time) Example

The Apply button must be disabled whenever an unsupported set of weights are entered in the window.

Loading and Saving Images

Your program must now support loading and saving .bmsoe files. Add the following two methods to your ImageIO class and modify your read() and write() methods to make use of these new methods when appropriate:

.bmsoe Binary Image Format

The custom .bmsoe file format is a binary based file format for storing images. It is designed to be easy to load and save as well as produce smaller file sizes than the .msoe format.

The file consists of a stream of binary data with the following header information:

The remainder of the file contains the pixel data. Each pixel is stored as a single integer value with bits 24-31 representing the alpha channel, bits 16-23 representing the red channel, bits 8-15 representing the green channel, and 0-7 representing the blue channel.

You may wish to add these two methods to your ImageIO class to facilitate converstion between Color and int:

private static Color intToColor(int color) {
    double red = ((color >> 16) & 0x000000FF)/255.0;
    double green = ((color >> 8) & 0x000000FF)/255.0;
    double blue = (color & 0x000000FF)/255.0;
    double alpha = ((color >> 24) & 0x000000FF)/255.0;
    return new Color(red, green, blue, alpha);

private static int colorToInt(Color color) {
    int red = ((int)(color.getRed()*255)) & 0x000000FF;
    int green = ((int)(color.getGreen()*255)) & 0x000000FF;
    int blue = ((int)(color.getBlue()*255)) & 0x000000FF;
    int alpha = ((int)(color.getOpacity()*255)) & 0x000000FF;
    return (alpha << 24) + (red << 16) + (green << 8) + blue;

Your program should be able to load the specs.bmsoe image file in the images folder of the repository.

Alpha Channel Complexities

You may find that some images appear all black when saved in the .bmsoe format. This occurs when the original image does not have alpha channel information. In that case, the saved images will have values of 0 for all of the alpha channel values. You do not need to support saving such images.

Exception Handling

There are a number of situations that could cause your program to throw an exception. For example, if the file is not found, cannot be opened, or contains incorrectly formatted data, it is likely that an exception will be thrown. In these cases, the program should display an useful message and recover gracefully.

Just For Fun

There are many additional enhancements that could build on the required functionality. You are encouraged to enhance this application using your creativity. A number of enhancements are included below; however, you should not feel limited to these suggestions.


This laboratory assignment was developed by Dr. Chris Taylor.

1) These methods will likely be part of a separate controller class associated with the second stage.
2) This is a slight simplification from the actual transformation. You may chose to implement the actual transformation, if you choose to (you'll need to look it up). Be sure to indicate with a comment if you choose to do this.
3) You may choose to make a 5 x 5, 7 x 7, or 9 x 9 grid instead.
See your professor's instructions for details on submission guidelines and due dates.