Lab 4: Image Manipulator 3000, Part II
Objectives
- Employ binary file input and output to read and write image files.
- Construct a non-trivial interactive graphical user interface with at least two windows.
- Handle exceptions in a robust and meaningful fashion.
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:
- Controls are defined in the provided FXML files.
- Controls that must be accessed by Java code should be declared in the
appropriate controller class and associated with the corresponding
fx:id. - Event handlers referenced by the FXML files must be implemented in the appropriate controller class.
- The
start(Stage stage)method will need to useFXMLLoaderto parse the FXML files and load both user interfaces.
You will then implement the following functionality:
- Load and save images in the custom binary
.bmsoefile format. - Convert an image to a red-channel version.
- Convert an image to a red/grayscale version.
- Refactor the code for grayscale and photo negative transformations using a functional programming approach.
- Create a second window (an additional stage) for entering filter parameters.
- Apply 3 x 3 image filters using a separate
ImageFilterclass. - The appropriate graphical user interface to support the operations listed above.
- Correct any issues identified in your previous submission, if your instructor has provided feedback to you in a timely manner.
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:
load— Handler for loading an image.save— Handler for saving an image.reload— Handler for re-loading the most recently loaded image.grayscale— Handler for converting an image to grayscale.negative— Handler for converting an image to its photo negative.red— Handler for converting an image to red-channel only.redGray— Handler for converting an image to alternating red and grayscale rows.toggleFilter— Handler for showing and hiding the filter window.
The FilterController must provide handlers for:
updateFilterValues— Handler used by the Blur and Sharpen buttons to update the filter kernel values.apply— Handler for applying the current filter kernel to the image.
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-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)
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.
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};
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:
readBMSOE(Path path)— Reads an image file in.bmsoeformat.writeBMSOE(Path path, Image image)— Writes an image file in.bmsoeformat.
.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 characters
B,M,S,O,Eeach written as abyte. - The image width written as an
int. - The image height written as an
int.
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.
- Add a menu to the main window to replace (or in addition to) the buttons.
- Apply a transform to only a selected region of the image.
- Support saving
.bmsoeimages when the input image does not have alpha channel values. - Display the original and transformed images side-by-side.
- Toggle between original and transformed images when mouse button is pressed on the image.
- Create a meme generator by adding styled text on the image.
- Tonal adjustment (e.g., adding a red hue to the image underneath the mouse).
- Provide undo functionality.
- Implement additional transformations, e.g.
- Brighten
- Darken
- Decrease color saturation
- Increase color saturation
- Apply additional filters like the Edge filter see here.
Acknowledgment
This laboratory assignment was developed by Dr. Chris Taylor.




