Lab 4: Image Manipulator 3000, Part II

Objectives

Overview

In this assignment you will build on the solution created for Lab 3. 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.

Assignment

This week, you must use the provided FXML files to define the graphical user interface. The main window is defined in imagemanipulator3000.fxml and the filter window is defined in filter.fxml. The fx:id values, controller associations, and event handler names in these files should not be changed.

This will require the following changes:

You will then implement the following functionality:

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

Details

Handler Methods

The provided FXML files refer to handler methods by name. These names must match the names in the FXML files. The methods do not need to accept an ActionEvent parameter unless your implementation needs one.

The main Controller must provide handlers for:

The FilterController must provide handlers for:

Refactoring for Functional Programming

You must refactor your implementations of the grayscale() and negative() handlers 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 your Controller class:

private 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 and must be annotated with @FunctionalInterface. A functional interface has a single abstract 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 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

Red-Gray

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 Red Only transformation on rows with an even zero-based y value (0, 2, 4, ...) and perform a Grayscale transformation on rows with an odd zero-based y value (1, 3, 5, ...).2)

Red-Gray

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 choose 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 grid 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 changes 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

All nine filter weights must contain valid integer values. The total of the nine values must be positive. The Apply button must be disabled whenever any value is missing or is not an integer, or whenever the total of the filter weights is zero or negative.

The filter logic must be implemented in a separate ImageFilter class rather than in FilterController. The ImageFilter class must provide the following public class method:

public static Image applyKernel(Image image, int[][] kernel)

This method validates the image and kernel, normalizes the filter weights, and applies the filter to the image. The kernel must be exactly 3 x 3 and its values must have a positive sum.

The weights passed to ImageUtil.convolve() must sum to 1. The integer values entered by the user should therefore be normalized before applying the filter. 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 shows the normalized values for 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 the Sharpen filter sum to 1 (5-1-1-1-1), so the normalized weights are:

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

Blur

Blurred (applied 10 times) Example

Sharpen

Sharpened (applied 1 time) Example

Loading and Saving Images

Your program must now support loading and saving .bmsoe files. Add the following two private class 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.

These two methods have been added to your ImageIO class to facilitate conversion 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)Math.round(color.getRed()*255)) & 0x000000FF;
    int green = ((int)Math.round(color.getGreen()*255)) & 0x000000FF;
    int blue = ((int)Math.round(color.getBlue()*255)) & 0x000000FF;
    int alpha = ((int)Math.round(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 a 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.

Acknowledgment

This laboratory assignment was developed by Dr. Chris Taylor.

2) This is a slight simplification from the actual transformation. You may choose 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.
See your professor's instructions for details on submission guidelines and due dates.