Lab 8
Get started now
## Objectives * Employ both binary and text file input and output to read and write image files. * Research and make use of a number of classes in the [javafx.scene.image](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/image/package-summary.html) package. * Construct a non-trivial interactive graphical user interface with at least two windows. * Research and implement at least one event handler that handles events other than `ActionEvent`s. * Handle exceptions in a robust and meaningful fashion. ## Overview In this assignment you will be developing an image manipulation program. The program itself will be capable of reading and writing multiple image formats including `.gif` (non-animated), `.jpg`, `.png`, and two custom formats: `.msoe` and `.bmsoe` files. The program will also be able to apply simple transformations to the image like: * Converting the image to grayscale * Producing the negative of the original image * Blurring or sharpening an image ## Assignment This is the last laboratory assignment for the quarter and is partitioned into multiple parts. The assignment has been designed to provide numerous ways in which the program may be expanded. Students are encouraged to use their creativity to implement additional functionality beyond the requirements of the assignment; however, it is important to complete the required functionality before getting too carried away. <figure>[![User Interface](lab8a.png)](lab8a.png)<figcaption>Figure 1: User Interface</figcaption></figure> ### Week 8 Summary In the first week you must implement the following functionality: * Load and reload images stored in the following image file formats: `.gif`, `.jpg`, `.png`, and `.msoe`. * Note that "large" `.msoe` images will be slow to load and save. * Display the image that has been loaded * Use a [`FileChooser`](https://docs.oracle.com/javase/8/javafx/api/javafx/stage/FileChooser.html) to select the image file to load and save. * Apply the following transforms to the current image: * Make grayscale version of image * Make negative version of the image * Save images in the following image file formats: `.gif`, `.jpg`, `.png`, and `.msoe`. The format written must match the file extension. For example, if the current picture was loaded from a `.jpg` file and the user enters **`image.png`**, the image must be saved in the `.png` file format. * The appropriate graphical user interface to support the operations listed above. * Handle the appropriate exceptions for the operations above. Your program should be able to load [this `.msoe` image](specs.msoe) [.png version](specs.png). ### Week 9 Summary In the second week you must implement the following functionality: * Load and save images in the `.bmsoe` file format. * Apply the following transforms to the current image: * Make red-channel version of the image (removes green and blue channels) * Make red-gray version of the image * Create a second window (an additional stage) for entering filter parameters (see Figure 1). * Provide handlers for the **Blur**, **Sharpen**, and **Edge** buttons. * 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. Your program should be able to load [this .bmsoe image](specs.bmsoe) __[Updated 2/2/18]__. ### Week 10 Summary In the third week you must implement the following functionality: * Provide a handler for the **Apply** button on the second window. * Implement a feature that responds to an event other than an `ActionEvent`. * Correct any issues identified in your previous submissions, if your instructor has provided feedback to you in a timely manner. ## Details ### Handler Methods The text on the buttons in your UI must match Figure 1, and each button must have a corresponding handler method whose name matches the text on the button<sup><a href="#fn__1" name="fnt__1" id="fnt__1" class="fn_top">1)</a></sup>. If you make use of a lambda expression, it must call the appropriately named method. For example, the **Open** button must have the following handler method: ``` public void open(ActionEvent event) { // ... } ``` You may use FXML but are not required to do so. ### `.msoe` File Format The custom `.msoe` file format is an text based file format for storing images. It is designed to be easy to load and save, but results in comparatively large file sizes. The contents of a very small image of a white-on-black `+` symbol is shown below: <pre> MSOE 3 3 #000000FF #FFFFFFFF #000000FF #FFFFFFFF #FFFFFFFF #FFFFFFFF #000000FF #FFFFFFFF #000000FF </pre> * The first line specifies that the file contains an `.msoe` image. * The second line specifies, in order, the width and height of the image. * Each remain line specifies the colors of the pixels for one row of the image. * Line 3 specifies pixel colors for the first row in the image. * Line 4 specifies pixel colors for the second row in the image. * Line `k` specifies pixel colors for the `k-2` row in the image. * The pixel color is a hexidecimal number where each pair of digits represents (from left to right) the red, green, blue, and alpha/opacity * Each pair of hexidecimal digits represents an integer value in the range [0, 255] * The alpha/opacity value specifies how transparent the pixel is (`00` completely transparent, `FF` not at all transparent) ### `.bmsoe` File Format The custom `.bmsoe` file format is a binary based file format for storing images. File sizes will be smaller than `.msoe` for the same image, but the format does not do any image compression so the files will be larger than more common formats. The file consists of a number of bytes. * The file begins with the bytes having the ASCII values of the following characters: `B`, `M`, `S`, `O`, and `E`. * Next in the file are two integers containing the **width** and **height** of the image. * The remainder of the file consists of the rows, starting with the top-most row. * Each row consists of a number of bytes. The number of bytes is evenly divisible by 16. * The bytes in the row specify all of the pixels in the row and, potentially, some padding bytes (to ensure the divisible-by-16 rule). * Each pixel is specified with four bytes, one byte for each of the four pixel components: red, green, blue, alpha/opacity. ### Loading and Saving Images The completed program must support loading and saving `.bmsoe`, `.gif`, `.jpg`, `.png`, and `.msoe` image file formats. The load button will open a `FileChooser` dialog that will allow the user to select the file to be loaded (`showOpenDialog()`). The program will use the extension to determine the format of the file and load the image (provided the extension type is supported). Pressing the reload button will reload the last loaded image. The save button will open a `FileChooser` dialog box that will allow the user to select the destination folder and type in the filename and extension (`showSaveDialog()`). The program will then write the image to that file using the file format that matches the file extension. If an unsupported file extension is entered, the program will display an error message without attempting to save the image. You must create an `ImageIO` class<sup><a href="#fn__2" name="fnt__2" id="fnt__2" class="fn_top">2)</a></sup> that is responsible for loading and saving images. The class must have the following public class methods: * **`read(File file)`** &mdash; Reads in the specified image file and returns a `javafx.scene.image.Image` object containing the image. * **`write(Image image, File file)`** &mdash; Writes the specified image to the specified file. If the extension on the file passed to either of these methods is `.msoe` or `.bmsoe`, then the appropriate private class method below is called to do the actual work. * **`readMSOE(File file)`** &mdash; Reads an image file in `.msoe` format. * **`writeMSOE(Image image, File file)`** &mdash; Writes an image file in `.msoe` format. * **`readBMSOE(File file)`** &mdash; Reads an image file in `.bmsoe` format. * **`writeBMSOE(Image image, File file)`** &mdash; Writes an image file in `.bmsoe` format. You are encouraged to use the [`javax.imageio.ImageIO`](http://docs.oracle.com/javase/8/docs/api/javax/imageio/ImageIO.html) class to read and write files in the `.gif`, `.jpg`, and `.png` formats. The [`SwingFXUtils`](https://docs.oracle.com/javase/8/javafx/api/javafx/embed/swing/SwingFXUtils.html) class can be used to convert from `Image` to `BufferedImage`. You must implement your own code to read and write `.msoe` and `.bmsoe` files. ### Image Transformations Your program will support a number of image transformations. #### Grayscale This transformation converts the image into a grayscale image. If the image is already grayscale, the transformation has no affect. In this transformation, the RGB components of each pixel are replaced with a single value. The replacement value can be calculated as: *gray = 0.2126R + 0.7152G + 0.0722B* [more info](http://en.wikipedia.org/wiki/Grayscale). <figure>[![Grayscale](lab8b.png)](lab8b.png)<figcaption>Grayscale Transform</figcaption></figure> #### Negative This transformation converts the image into a photo negative of the original image. In this transform, each RGB component is replaced with *1.0 - original value*. Thus, an RGB value of (1.0, 1.0, 0.0) would become (0.0, 0.0, 1.0). [more info](http://en.wikipedia.org/wiki/Negative_%28photography%29). <figure>[![Negative](lab8c.png)](lab8c.png)<figcaption>Negative Transform</figcaption></figure> #### Red Only This transformation acts as a red filter. The green and blue components of each pixel are set to zero. <figure>[![Red Only](lab8d.png)](lab8d.png)<figcaption>Red Only Transform</figcaption></figure> #### Red-Gray This transformation is based on an experiment demonstrated by Edwin Land, founder of Polaroid, in 1959 [more info](http://t-a-y-l-o-r.com/land/). The experiment was based on the Retinex Theory of Color Vision. [This page](http://neuronresearch.net/vision/files/retinex.htm) 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 rows<sup><a href="#fn__3" name="fnt__3" id="fnt__3" class="fn_top">3)</a></sup>. <figure>[![Red-Gray](lab8e.png)](lab8e.png)<figcaption>Red-Gray Transform</figcaption></figure> ### Filter Kernel Window When clicked, the **Show Filter** button reveals a second window (shown below the main window in Figure 1), 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**. <figure>[![User Interface](lab8a.png)](lab8a.png)<figcaption>Figure 1: User Interface (repeated for your convenience)</figcaption></figure> The Filter Kernel window contains a 3 x 3 grid<sup><a href="#fn__4" name="fnt__4" id="fnt__4" class="fn_top">4)</a></sup> of filter weights. The text field in the lower left can hold a value to be used as a divisor on all of the filter weights<sup><a href="#fn__5" name="fnt__5" id="fnt__5" class="fn_top">5)</a></sup>. When clicked, the three buttons on the right clear the divisor field and change the filter weights as follows: <pre> Blur Sharpen Edge 0 1 0 0 -1 0 0 -1 0 1 5 1 -1 5 -1 -1 4 -1 0 1 0 0 -1 0 0 -1 0 </pre> 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, making the **Apply** button functional for the filter kernel weights provided by the **Edge** button is optional since the sum of the weights is zero (-1-1+4-1-1). The following code applies the **Blur** filter kernel to a [`BufferedImage`](https://docs.oracle.com/javase/8/docs/api/java/awt/image/BufferedImage.html) called `original` and stores the new `BufferedImage` in `result`: ``` float[] kernel = { 0F, 1/9F, 0F, 1/9F, 5/9F, 1/9F, 0F, 1/9F, 0F}; BufferedImageOp op = new ConvolveOp(new Kernel(width, height, kernel)); BufferedImage result = op.filter(original, null); ``` You may find the [`SwingFXUtils`](https://docs.oracle.com/javafx/2/api/javafx/embed/swing/SwingFXUtils.html) class helpful for integrating the above code in your application. If the **Apply** button is pressed when an unsupported set of weights are entered in the window, an exception should occur (and be handled in a way consistent with the exception handling requirements listed below). ### Class Structure In addition to the `ImageIO` class described above, you must implement the `Transformable` interface. 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 `tranform`, must accept three arguments: the x and y location of the pixel and its color. The method must return the color for the pixel after the applying the transformation. You must provide implementations of the `Transformable` interface for the **grayscale**, **red**, **redGray**, and **negative** image transformations. You must implement the following method in the class that handles the button events that performs the specified transformation: ``` private static Image transformImage(Image image, Transformable transform) { // ... } ``` All of your classes should be placed in a package whose name matches your MSOE username. The rest of the class design is up to you. ### 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 log the error. Your program should write to the log file `Lab8.txt`. ### Responding to an Event other than an `ActionEvent` You must implement a feature that responds to an event other than an `ActionEvent`. For example, you could: * allow the user to draw on the image with the mouse * display the x, y coordinates of the location of the mouse on the image * display the color of the pixel at the location of the mouse on the image * play a sound when clicking on a pixel with a particular color * flip back and forth between the image and its negative whenever the window is minimized * select a region of the image ## Creating an Executable `.jar` File <figure>[![Creating an executable `.jar`](1021jar.gif)](1021jar.gif)<figcaption>Figure 2: Creating an Executable `.jar`</figcaption></figure> ## Just For Fun There are many additional enhancements that could be built on the required functionality. You are encouraged to enhance this application using your creativity. A number of suggested 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 * 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](http://lodev.org/cgtutor/filtering.html) ## Lab Deliverables > See your professor's instructions for details on submission guidelines and due dates. > * Dr. Taylor's students: See below > * All other students should refer to Blackboard > >If you have any questions, consult your instructor. ## Acknowledgment This laboratory assignment was developed by [Dr. Chris Taylor](/taylor/) and [Dr. Walter Schilling](http://www.waltschilling.us). <div class="footnote"> <sup><a href="#fnt__1" id="fn__1" name="fn__1" class="fn_bot">1)</a></sup> There are two exceptions to this naming rule: 1. The handler for the **Red-Gray** button should be called `redGray()`. 1. The handler for the **Show Filter** button should be called `filter()`. When clicked, the Filter Kernel window is shown, and the text on the button is changed to **Hide Filter**. When clicked again, the Filter Kernel window is hidden, and the the text on the button is changed back to **Show Filter**. <br /> <sup><a href="#fnt__2" id="fn__2" name="fn__2" class="fn_bot">2)</a></sup> This [`ImageIO`](ImageIO.java) class is different than the [`javax.imageio.ImageIO`](http://docs.oracle.com/javase/8/docs/api/javax/imageio/ImageIO.html) class. <br /> <sup><a href="#fnt__3" id="fn__3" name="fn__3" class="fn_bot">3)</a></sup> 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. <br /> <sup><a href="#fnt__4" id="fn__4" name="fn__4" class="fn_bot">4)</a></sup> You may choose to make a 5 x 5, 7 x 7, or 9 x 9 grid instead. <br /> <sup><a href="#fnt__5" id="fn__5" name="fn__5" class="fn_bot">5)</a></sup> For example, if **5** were entered into that field, then the filter weights should be **0.20** instead of **1** and **1** instead of **5**. If no value is entered, then the sum of all the filter weights should be used for the divisor. In the case of the example shown, the divisor should be **9** (0+1+0+1+5+1+0+1+0). <br /> </div>

Sunday, 11-Feb-2018 19:05:40 EST