Pixel Arrays

Beware! This document needs cleaning up. Formatting may be messy or broken. Text may be inaccurate or in need of editing.

You can look at this preview, but be aware that the content will change before the instructor assigns this to the class.

This document is the result of an automated conversion. You can view the original version but again beware: that version too is not necessarily what will eventually be assigned.

In this activity, you will use arrays to modify images based on the underlying pixels. After this activity, you should be able to:

  • Declare and instantiate arrays in Java
  • Iterate through values in an array
  • Modify array values
  • Understand pixel-based image representation

An array is a data type which holds elements arranged by index. In Java, unlike Collections (e.g. Lists, Sets), an array can hold primitive data types without needing a wrapper class. This makes arrays the preferred way to store large quantities of numerical data in Java.

Arrays show up in most programming languages, and if you continue in the Computer Science sequence, you will use them to build Collections in COMP 128: Data Structures and see the details of their implementation in COMP 240: Computer Systems!

We declare an array using bracket notation:

int[] intArray;
String[] strArray;

We also access entries within an array using bracket notation:

int x = intArray[0]; // saves the first entry in intArray
     // into the int variable x

Observe the provided code. There is a command-line interface which asks a user to indicate which type of image filter they would like to apply to an image; after the user enters a value, a CanvasWindow will display the image resulting from applying that filter.

What parts of this code look familiar to you? What looks unfamiliar? Can you figure out the switch() statement? Talk it over with your partner!

Notice that the program should eventually implement three different types of image transformations: color inversion, green shifting, and lightening, each of which you will build in the next three tasks.

This activity provides a starting image, which is the MSCS department shield logo. You can also try out your transformations with images of your own choosing! Add them to the res folder and replace the path in the Image constructor in the main method.

Before we can transform an image, we have to talk a little bit about how color values are handled using pixels.

Each pixel in a raster image (that is, an image which uses a file format that encodes data using a two-dimensional pixel grid) has a color value associated with it. There are three common types of color values that are supported by the Image class: greyscale, RGB, and ARGB.

Grayscale images associate each pixel with one value representing the intensity of light in that pixel: values at the low end of the scale mean less light, and are closer to black; values at the high end of the scale mean more light, and are closer to white.

RGB images use three color channels for each pixel: red, green, and blue. The combination of values across these three channels creates the visible color. Much like intensity, the value in each color channel determines how much of that color is present: high values mean more red, green, or blue, and lower values mean less.

ARGB (or RGBA ) images are similar to RGB images, but they add in one additional channel: alpha, which is used to determine the pixel’s transparency. The higher the value in the alpha channel, the more opaque the pixel is.

Each of these encoding methods can be adapted to work with different value ranges. In some of the activity code in this class, we’ve seen hex codes (e.g. 0xFFFFFF) for setting RGB values with the Color class. In these hex codes, each pair of letters/numbers after the 0x represents an integer value from 0–255. These integer values encode red, green, and blue channels. It’s also common to see float values from 0.0–1.0 used for the same purpose, particularly in image filtering and transformation. We’re going to use both of these in today’s activity.

Lightening an image requires evenly increasing the values across all of its red, green, and blue color channels. That is, the color:

0x111111

Is a darker gray than the color:

0x777777

We will iterate through all of the pixels in our source image and apply a lightening transformation.

Find the static helper method for lightening an image. Currently, it should throw an UnsupportedOperationException when called. You will need to modify this method so that it returns a new image instead.

First, let’s consider the problem of how we get an array of pixels out of an Image. Take a look at the Image class documentation. Are there any methods with an array return type? Hint: look for square brackets []!

We’re going to use an array of floats for lightening these images, because float encodings of RGB values have a nice property: values outside of the range 0.0–1.0 are treated as the closer endpoint (that is, a value of 2.5 is treated as 1.0), which is convenient because it means we don’t have to check the values of each new pixel value that we set.

Create a new variable, pixels, of type float[]. Assign it the array of pixels from our input parameter srcImage, using the RGB encoding.

Hint:

Hint about specifying the pixel format:

Now that we have an array of floats representing our pixels, we have to iterate through it! What does the data in this array look like?

There is a line in the description of the helper method that you used to instantiate your float[] which reads:

one number per color channel per pixel

How many color channels are there for RGB-encoded images? How many entries will there be in our array for each pixel?

Thankfully, we want to evenly lighten this image, so we can apply the same transformation to every array value. (This is a preview of tasks to come, hint hint!) We need to iterate through our array, apply the transformation at each step, and then use the transformed array to create (and return) a new Image object.

There are many ways to lighten an image. The simplest method is to multiply the value at each index by a constant that is greater than one, e.g. value becomes value*1.5.

Once you’ve modified all of the values in your float[], you can turn it back into an Image using one of the Image constructor methods:

Image(int width, int height, float[] pixels, PixelFormat format)

To review, the steps for this task are:

  1. Create a float[] called pixels, and give it the value of the RGB-encoded float array of pixels from the source image
  2. Write a for loop to iterate through the full length of the array. Hint:
  3. At each step in the loop, lighten the color of the value at that index
  4. Package the transformed pixel values into a new Image, and return it

Don’t forget to remove the UnsupportedOperationException line and try out your code to confirm that it works!

Great, now you should have a working sense of how to get a pixel array from an image and modify the values within it! For this task, we’re going to do the same thing, but we’re going to modify only the green color channel.

Locate the greenShift() method, which should also be empty except for an UnsupportedOperationException. For this task, you will need to increase the value in the green color channel by 0.25, which will shift the image colors more towards the green end of the spectrum, as compared to the original values.

The steps for this task will look very similar to those in Task 1, above. However, we only want to change the value in the green channel, and leave the values for red and blue alone. Your challenge is to do this without using any condition statements.

Given the encoding, three array entries are used to represent each pixel:

  1. Red
  2. Green
  3. Blue

That is, only the second channel is of any interest to us. How can we modify our code so that we only look at this value?

Hint:

Don’t forget to run and test your code once you’ve implemented the method!

Now let’s invert the colors in an image! For this task, instead of using an array of floats, we are going to use an array of bytes. The Java byte type is an integer with values between 0 and 255, which conveniently matches the integer range for the RGB encoding.

The formula to invert an image color is to subtract the current value of the color channel from 255. For example the inverse of (255, 255, 255) or 0xFFFFFF is (0, 0, 0), or 0x000000. A less straightforward example:

RGB: (177, 5, 68) or 0xB10544 inverted is:

Red: 255 - 177 = 78

Green: 255 - 5 = 250

Blue: 255 - 68 = 187

0x4EFABB

Ouch, very bright!

We can do this with subtraction. Or… we can do this with the nifty exclusive-or operator ^. Subtracting the byte value for each channel from 255 is the same as exclusive-or’ing that value with 255 (or its hex equivalent, 0xFF)! If you take COMP 240, you will learn exactly what this means and why it works.

Whether you use the - or the ^ operator, Java will insist that you specify that the result is supposed to be a byte and not an int. Hint: Another hint:

The steps for this task look like:

  • Create a byte array for the source image
  • Iterate through each entry in the array, inverting its value
  • Construct and return a new Image based on the inverted byte array

Don’t forget to test your code after you’ve written it!

Congratulations on finishing the activity! For a bonus exercise, create your own image transformations! For example, can you:

  • Convert the image to grayscale?
  • Brighten or darken a grayscale image?
  • Rotate the image 90° to the left?

Or invent your own!