Lab 1: Image Displayer 3000
Overview
In this assignment you will read and write several file formats representing black and white images.
Pre-Lab
As part of the pre-lab activity you will see how to use GitHub Classroom to start your lab assignment and submit it when you are finished. When watching the video below, consider the following.
- How to start a new project from your GitHub repository
- How to commit changes to your local repo
- How to push changes from your local repo to GitHub
- Watch [5:24] [link]
The project is cloned from the GitHub repo to your local machine at the three-minute mark in the video. You will likely need to authorize IntelliJ to access your GitHub account. Hopefully the process is simple enough, but you can see screenshots for what to do in Figures 13-16 of this page.
What Must Be Done by the Pre-lab Due Date?
- Ensure you have a GitHub account
- Accept your instructor's invitation to the lab 1 assignment in Canvas
- Associate your GitHub account with your name in GitHub Classroom
- Clone the project, openning it in IntelliJ
- Rename the
username
package to your username - Commit and push the changes
Assignment
Your program must allow users to read and write multiple black and white image formats specified in this assignment. Whenever an image is read from a file, your program must display it to the console using Unicode characters. Your program will have a simple menu with three options:
- Load image — the user is asked for a filename and the program loads and displays the image on the console. The program should provide a user-friendly error message if the file was not found or if the file was invalid.
- Save image — the user is asked to select the desired format for the saved image and the filename. If the file already exists, the user should be asked whether they would like to overwrite the existing file. If the user responds affirmatively, the file should be overwritten. Otherwise, nothing should be written, and the menu should be displayed again. User-friend error messages should be presented when appropriate.
- Exit
File Formats
Your program must support two file formats:
- 0/1 Text Format
- Block Unicode Character Text Format
0/1 Text File Format
This is the first of two text file formats. Here the image is represented with 0s and 1s. A zero represents a white pixel and one represents a black pixel. A \( 4 \times 4 \) image with black dots in all the corners and a diagonal line going from the lower left to the upper right is represented by the following:
1001
0010
0100
1001
- The number of lines in the file represent the height of the image.
- The length of each line in the file represents the width of each image.
- All lines must be the same length.
- All characters must be either
'0'
or'1'
. - There are no constraints on the height or width of the image.
When files in this format are written, the file extension must be .txt
.
Block Unicode Character Text File Format
The Unicode Consortium is a standards body for the internationalization of software and services. The Block Elements document specifies the block characters to be used in this assignment. You will use the block characters that divide the character into four quadrants:
\u2588 | \u0020 | \u2580 | \u2584 | \u258C | \u2590 | \u259A | \u259E |
---|---|---|---|---|---|---|---|
█ | ▀ | ▄ | ▌ | ▐ | ▚ | ▞ |
\u2596 | \u2597 | \u2598 | \u259D | \u259F | \u2599 | \u259C | \u259B |
---|---|---|---|---|---|---|---|
▖ | ▗ | ▘ | ▝ | ▟ | ▙ | ▜ | ▛ |
A \( 4 \times 4 \) image with black dots in all the corners and a diagonal line going from the lower left to the upper right is represented by the following string written to the file:
\u2598\u259E\n\u259E\u2597
- All characters must either
\n
or one of the Block Unicode characters from the tables above. - A
\n
character are place between rows of Unicode characters. - Each row of Unicode characters represents two rows of pixels.
- All rows must be the same length.
- There are no constraints on the height or width of the image.
When files in this format are written, the file extension must be .txt
.
When reading a .txt
file, your program will need to determine which
of the two text formats the file is using.
Software Design
You are responsible for the overall design of your software solution; however,
it must incorporate a PixelCluster
enum and Image
class that conform to
the following requirements.
PixelCluster
Enum
You must create an enum to represent the 16 unique Unicode Block elements. The enum
should have a single char
attribute code that stores the Unicode value for each enumeration.
In addition, it should provide the following public
methods:
String toString()
— The string representation of the Unicode characterstatic PixelCluster getCluster(int[][] cluster)
— Returns the appropriate enumeration for the argument passed to the method.- The argument must be a 2x2 array of ints.
- All values passed to the method must be either 0 or 1.
- An
IllegalArgumentException
, with a meaningful message, must be thrown if the argument passed is invalid.
static int[][] getPixels(char code)
— Returns a 2x2 int array containing the pixels represented by the enumeration.- The argument must be the Unicode
char
representation of aPixelCluster
. - An
IllegalArgumentException
, with a meaningful message, must be thrown if the argument passed is invalid.
- The argument must be the Unicode
Image
Class
You must implement the Image
class with the following public
methods:
Image(Path imagePath)
— A constructor that reads the contents of theimagePath
. The method should throw anIOException
if an error occurs reading the file and anIllegalArgumentException
if the format of the file does not match one of the supported formats.String toString()
— Returns a string containing the Unicode Block Character representation of the image.void save(String filename, String format)
— writes the image to the specified filename using the specified format. Valid formats are: "01text" and "unicode". The filename should not contain a file extension.
You must also have as an instance variable a 2-dimensional int
array called pixels to store the pixels in their appropriate configuration.
2D Arrays
Recall that a 2-dimensional array in Java is actually an array of arrays. This can be thought of as a grid, where the first index is the row (the y-axis) and the second index is the column (the x-axis). When instantiating a 2D array, you may either allocate the memory for an empty array or assign values directly to the new array. To create an empty 2x2 int array, for example, you would declare the variable and assign the lengths of both dimensions.
int[][] array = new int[2][2];
If you had specific values you wanted to populate an array with, you could declare them as an array literal
where you separate each value with a comma:
int[] array = new int[] {1, 0};
For a 2D array, the same applies. Remember, it is an array of arrays, so you would need to declare each array literal, separated by commas. So if you wanted a 2D array that contains the following values:
10
01
you would initialize it like so:
int[][] array = new int[][] {
{1, 0},
{0, 1}
};
Switch Expressions
A switch
expression in Java is the modern form of a switch
statement that has numerous improvements. The basic switch
statement in Java is a slightly cleaner way of writing if/else
statements when there are more than a few cases.
For example, if you had a different result depending on which letter was stored in a String
word, you may have a switch
statement that looks like this:
char c;
switch(word) {
case "A": c = 'a';
break;
case "B": c = 'b';
break;
.
.
.
default: throw new IlegalArgumentException("Not a letter!");
}
return c;
A switch
statement will start at the top and compare its parameter with each case. When it matches, it will execute that code. One of the downsides to a switch
statement is that, unlike an if/else
block, it will continue to compare with every possible case even if it has already matched with one.
This results in having to end each block of code for each case with a break
statement to get out of the switch
block.
The switch
expression, on the other hand, functions like an if/else
block, so once a match is found, it executes that case's block of code and exits the switch
block. Here is what the above code would look like as a switch
expression:
char c;
switch(word) {
case "A" -> c = 'a';
case "B" -> c = 'b';
.
.
.
default -> throw new IlegalArgumentException("Not a letter!");
}
return c;
One other useful feature of a switch
expression is to return the contents of whatever case block matches the parameter, so the above code could be written like this:
return switch(word) {
case "A" -> 'a';
case "B" -> 'b';
.
.
.
default: throw new IlegalArgumentException("Not a letter!");
};
Exception Handling
If any problems are encountered with reading the input files or writing the output file, the program should display a useful error message to the console and terminate gracefully. The program should not crash or display any exceptions.
Just For Fun
- Select File > Settings
- Select Editor > Color Scheme > Console Font
- Check "Use console font instead of the default"
- Change "Line height" to "0.9"
Ambitious students may wish to:
- Perform simple image transformations such as:
- Rotate image 90, 180, or 270 degrees
- Flip image horizontally or vertically
- Generate the negative of the image
- Support displaying large images with fewer characters. For example, you could take a 8x8 portion of the image and represent it with one Unicode character. Here you could make the upper left block of the Unicode character black if at least three of the pixels in the upper left corner of the 8x8 are black. If two of the four pixels are black, you could make the upper left block of the Unicode character black 50% of the time.
Acknowledgement
This laboratory assignment, developed by Dr. Chris Taylor and Prof. Sean Jones.