VisibleImage

VisibleImage is a shape class that displays a rectangular picture. That picture can be loaded from an image file, like a GIF or PNG or JPG. It is also possible to alter an image or create a completely new image by manipulating the colors of the pixels that make up the image.

VisibleImage implements the Drawable2DInterface and the Resizable2DInterface. So, any of the normal things that you can do with Rectangular Shapes you can also do with VisibleImage. In addition, there are methods for dealing with the image that it draws.

Table of Contents

1. Creating a VisibleImage to display an image file

VisibleImage behaves exactly like a FilledRect, except that instead of being filled with a color, it is filled with an image you provide. One convenient feature of the constructors is that you need not provide a width and height, since this information is part of the image. If you do provide your own width and height, the image will be resized if necessary to fit into that size:

public VisibleImage(Image image, double x, double y, double width, double height, DrawingCanvas canvas)
public VisibleImage(Image image, double x, double y, DrawingCanvas canvas)
public VisibleImage(Image image, Location point, double width, double height, DrawingCanvas canvas)
public VisibleImage(Image image, Location point, DrawingCanvas canvas)

VisibleImage is unique in being the only shape that doesn't have a slot for a Color in any of its constructors; this is because its color comes from the image you give it.

The Image used here is not part of the objectdraw library, but part of java.awt. You may want to import it at the top of your file:

import java.awt.Image;

An image can be loaded from a file using the getImage() method in WindowController:

VisibleImage image = new VisibleImage(getImage("ball.png"), 10, 10, canvas);

The getImage() method can load png|, gif, and jpg images on all computers; some computers support other formats as well. The program may pause briefly as the image is loaded. The file you name should be in the same directory as your java source file. If the file is missing or for some other reason cannot be loaded, nothing will appear in your VisibleImage, and it will return 0 as the image width and height. The same thing will happen if you pass along null as the image.

2. Image size versus shape size

Your browser doesn't do Java.
ResizeImage.java

The file newton.gif is
really 256×256 pixels.
There are really two different sizes that you need to think about when you use a VisibleImage: the size of the shape you will see on the screen, and the size of the image file that is being squeezed into that shape. To the left, you can see an applet that uses the file "newton.gif", and below it, the actual image as you would see it if you opened it up in a paint program. The image file is 256 pixels wide and 256 pixels high, but it is distorted to fit into whatever frame is defined by its VisibleObject.

You can find out the size of the image, as opposed to the size of the frame, by using these methods:

public int getImageWidth()
public int getImageHeight()

Because this width and height are characteristics taken from the underlying image file, they will change only if you change the image with setImage().

If you use one of the constructors that doesn't give a width and a height, the image will appear on the screen at the same size as in its file. However, if you later try to resize() it to specific dimensions, this will fix the size of its frame; it will keep trying to fit itself into that frame, even if you later change the image.

Often, when you are resizing an image, you will want to keep from distorting it, by ensuring that it has the same proportions as its image file. If I have a square picture showing a person, and I squeeze it into a frame that is half as tall as it is wide, the person will appear very fat. If, on the other hand, I squeeze it into a frame that is a much smaller square, the person will look the right shape, even though they're smaller, because the frame has the same proportions as the picture.

Suppose that I have an image that is 200 wide and 300 tall. I want to fit it into a frame that is just 100 wide. How would I figure out how tall the frame is? I could do it by setting up a proportion:
I can do the same sort of thing with Java code. Suppose that I want to get my image to fill the full width of the canvas, and stretch down as far as it has to to fit its original proportions. I can think like this:

image.setWidth(canvas.getWidth());
image.setHeight(canvas.getWidth() * image.getImageHeight() / image.getImageWidth());

Here's another example of working with image size versus frame size. This code will resize an image to be its "natural" size, leaving it centered around the same point it is centered around right now. See if you can understand how this works:

image.move((image.getWidth() - image.getImageWidth()) / 2,
              (image.getHeight() - image.getImageHeight()) / 2);
image.setSize(image.getImageWidth(), image.getImageHeight());

3. Changing and animating the image

Your browser doesn't do Java.
AsteroidRunner.java
BouncingAsteroid.java
As you might expect, there are methods to find out what Image a VisibleImage is displaying, or to change it to display another image:

public Image getImage()
public void setImage(Image image)

The setImage() method can be used to animate a visible image by displaying one picture after another in it, using a timer in an ActiveObject, so that it appears to be doing something. The BouncingAsteroid demo to the right shows how this might be done. It works by loading an array of sixteen images, from sixteen different files, and swapping in the next image for each asteroid every few seconds.

4. Creating a BufferedImage to draw an image pixel by pixel

An image on a computer screen is really made up of tiny square pixels, each of which is a spot of a single color. The Color of each is produced by three tiny lights - one red, one green, one blue - that correspond to the three colors that your eyes actually have detectors for.

So, theoretically, you can build up an Image just by setting the Color of every pixel in it. The applet that you see to the right is one that does precisely this. It shows the same picture you saw above, but instead of loading it from a file, it calculates the color of each pixel, one by one, to make the whole image. (This image is a type of fractal produced when you use an algorithm called Newton's Method to try to find complex-number solutions to an equation)
Your browser doesn't do Java.
NewtonFractal.java
ComplexNumber.java

The class BufferedImage is what I am using to construct this image. It is a type of Image that allows you to create an image of a certain size in pixels, and then individually set the color of each pixel. Here's some code that creates a BufferedImage checkerboard of black and red pixels, and stretches it to fill the whole window:

BufferedImage image = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
for(int x = 0; x < 8; x++) {
    for(int y = 0; y < 8; y++) {
        if((x + y) % 2 == 1) {
            image.setRGB(x, y, Color.RED.getRGB());
        } else {
            image.setRGB(x, y, Color.BLACK.getRGB());
        }
    }
}
new VisibleImage(image, 0, 0, canvas.getWidth(), canvas.getHeight(), canvas);

To use BufferedImage, you first need to import it, by typing this at the top of the file where you will be using BufferedImage:

import java.awt.image.BufferedImage;

Then, you can create a BufferedImage using this constructor:

public BufferedImage(int width, int height, int type)

The type can have several possible values, but BufferedImage.TYPE_INT_ARGB is the type we will always use, because it matches the "alpha-red-green-blue" format of the Color object.

You set the color of an individual pixel in the image by using this method:

public void setRGB(int x, int y, int rgb)

The rgb here is a value you can retrieve from a color with the getRGB() method. The x and y start at (0, 0) at the top left corner of the image, and go up to (width - 1, height - 1) at the opposite corner.
Your browser doesn't do Java.
RippleRunner.java
Ripples.java

If you change pixels in this way in an image that is already on the canvas, the canvas will not necessarily show those updates immediately, because it won't realize that a change has been made. You might have to call canvas.repaint() when you are done making changes in order for the changes to be seen. You can see an image being animated in this way in the applet to the right (click to start it).

It is important not to try to change a pixel that is outside the image, since this will result in an error. Remember, the maximum x value is one less than the width, and the maximum y is one less than the height. If you have a BufferedImage and you don't know its dimensions, you can retrieve them with these methods:

public int getWidth()
public int getHeight()

So, for example, this code will fill in all of image with blue:

for(int x = 0; x < image.getWidth(); x++) {
    for(int y = 0; y < image.getHeight(); y++) {
        image.setRGB(x, y, Color.BLUE.getRGB());
    }
}

5. Reading and altering the pixels of an image

Your browser doesn't do Java.
PixelGrabbing.java
Now suppose that I want to be able to do this sort of pixel alteration, but I want to build on an existing image rather than creating it entirely from scratch. The image to the right was loaded into a BufferedImage to allow me to get the colors of individual pixels as the mouse moves over it. When you click and drag the mouse, it takes the color the mouse was last hovering over and uses setRGB() to paint a little square of that color as you drag the mouse across the image.

There is a method called createBufferedCopy() in the VisibleImage class that will take any Image object and make a copy of it in a BufferedImage:

public static BufferedImage createBufferedCopy(Image image)

This only copies an Image object; it doesn't make the buffered copy visible on the screen. Typically, the whole process I would go through looks something like this:

Image image = getImage("picture.jpg");
BufferedImage buffer = VisibleImage.createBufferedCopy(image);

// modify buffer somehow...

new VisibleImage(buffer, 0, 0, canvas);

Often when you have loaded an image like this, you will want to be able to access the pixel colors in order to change them in some way. You get the color of a pixel using the getRGB() method in BufferedImage:

public int getRGB()

Your browser doesn't do Java.
ImageProcessing.java
You will notice that this method return an int value, not a Color object as you might wish. You will probably want to wrap this int in a Color in order to make use of it somehow. For example, here is some code that cuts in half the brightness of an entire image, by dividing the red, green, and blue components of every pixel by two:

for(int x = 0; x < image.getWidth(); x++) {
    for(int y = 0; y < image.getHeight(); y++) {
        Color oldColor = new Color(image.getRGB(x, y));
        Color newColor = new Color(oldColor.getRed() / 2, oldColor.getGreen() / 2, oldColor.getBlue() / 2);
        image.setRGB(x, y, newColor.getRGB());
    }
}

You can see something like this being done in the example above and to the right. If you click in the four different quadrants of the screen, the applet will process the image to extract just one of the tree colors of the image, using a loop almost identical to what you see above.

6. Getting and setting pixel data with arrays

If you are computing all the pixels in an image, it would be more efficient to compute all the int values first, storing them in an array, and then set all the pixels of the image at once. Or, if you are going to be doing something to all the pixels in an image, it is somewhat more efficient to retrieve all their int values into an array, modify that array, and then send it back to the image all at once.

To do this, you need to first create an array of ints big enough to hold the total number of pixels you are going to retrieve. So, how do I figure out how many pixels that is?

In order to hold an image that is 20×30 pixels, for example, I need thirty rows of twenty pixels each - a total of 600 pixels. In general, if I know the width and height I want to hold, I can create my array like this:

int[] pixels = new int[width * height];

You may be wondering, if I have an array that is one-dimensional like this, how do I know where in the array a particular pixel is? The key to understanding this is to realize that the pixels are listed off in the same kind of order that you would read a book in: first the top row is listed off from left to right, then the next row. So, pixels[19<], in a 20×30 pixel image, would be the pixel at the right end of the top row; pixels[20<] is the first pixel in the second row. The start of the third row would be at pixels[40<], the fourth at pixels[60<], In general, pixels[y * width<] is the start of row y, where y = 0 is the top row. So, the general formula for locating a pixel in the array is:

pixels[y * width + height];

Make sure you understand that formula fully - it's an idea you'll find yourself using in all kinds of places.

Just to give you an example of using this formula, I could use code like that below to make an image that is just a red square. When the array is created, all the pixels are set to zero, so the one that I didn't set to something else will stay transparent.

int[] pixels = new int[width * height];
int red = Color.RED.getRGB();
for(int y = 0; y < height; y++) {
    pixels[y * width] = red;
    pixels[y * width + width - 1] = red;
}
for(int x = 0; x < width; x++) {
    pixels[x] = red;
    pixels[(height - 1) * width + x] = red;
}

Once you have created an image in an array like this, how do you make it appear in an image? You do this by creating a BufferedImage of the size you want, and then using another version of the setRGB() method to set all its pixels at once:

public void setRGB(int x, int y, int width, int height, int[] pixels, int startIndex, int rowLength)

Here, the (x, y, width, height) specifies the rectangle in the image that I want to copy pixels into. And of course pixels is the array I have been building. But what are these parameters startIndex and rowLength?
Your browser doesn't do Java.
ImageClipping.java

The idea here is to allow me, if I want, to copy only part of the array. Suppose that I wanted to copy just the 10×10 square at the top right into some image. To do this, I would start reading from pixels at index 10 - halfway through the firs row - read ten pixels, then jump down to index 30, one row below where I started and therefore 20 forward from it. I need to know the actual number of pixels in a row of pixels in order to jump down to the next row, even though the rows I am writing are only 10 pixels wide.

So, startIndex is the index in pixels of the top left corner of the rectangle to copy, and rowLength is the row length of pixels, not the width of the piece being copied.

It is also possible to get a whole array of pixels at once. Actually, the method to do this looks just like setRGB():

public void getRGB(int x, int y, int width, int height, int[] pixels, int startIndex, int rowLength)

Again, the (x, y, width, height) is a rectangle in the BufferedImage that I want to copy, and pixels is an array that I am copying it into, starting at startIndex. So, for example, if pixels is an int[] representing an image 80 wide, and I want to copy a rectangle from (x, y, width, height) to the same location in pixels, I would do this:

image.getRGB(x, y, width, height, pixels, y * 80 + x, 80);

7. Manipulating color data in int format

It is somewhat inconvenient - and time-consuming, even in computer terms - to have to translate the int value from getRGB() into a Color and then back into an int. If you want faster image processing, it is possible sometimes to manipulate the int value directly.

A simple example of this would be if you wanted to flip an image horizontally. All you need to do to accomplish this is to exchange the pixel at the left end of a row with the pixel at the right end, then exchange the pixel one in from the left with the pixel one in from the right, and so on. You could do this with a loop like this:

int w = image.getWidth(), h = image.getHeight();
int[] pixels = new int[w * h];
image.getRGB(0, 0, w, h, pixels, 0, w);
for(int y = 0; y < h; y++) {
    for(int left = y * w, right = y * w + w - 1; left < right; left++, right--) {
        int temp = pixels[right];
        pixels[right] = pixels[left];
        pixels[left] = temp;
    }
}
image.setRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

So, if you only need to move a pixel, not change its color, you can do something like this. You could use a similar process to rotate an image, assuming that it was square and you were rotating in increments of 90°. But what if you want to get inside that int value and manipulate the red, green, and blue parts? If you understand how binary data is stored in computers, and how binary logic operators like & work, it's actually not too difficult to do.

You may recall from the Color page that a Color, when represented as an int, can be written as a hexadecimal number with the largest two digits being the alpha, the next two being the red, then green, then blue. So, this would be a color that is mostly opaque, with a medium amount of red, not much green, and the maximum amount of blue:

// AARRGGBB
new Color(0xBB9933FF);

I can use the various bitwise operations to "mask out" any one part of that int. For example, suppose that I want to find just the blue part. If I do a bitwise & "and" between the color and 0x000000FF, I will get a number that has bits only where they appear in the color and the mask. Since the mask is all 0's outside of the blue part, only whatever is in the blue part will remain:

int blue = rgb & 0x000000FF;

Getting the green part is a little bit more complicated. I can mask the rgb with 0x0000FF00 to mask out everything that isn't green, but I still have a number that is 256 times the actual green value. I need to shift those bits back into place:

int green = (rgb & 0x0000FF00) >> 8;
int red = (rgb & 0x00FF0000) >> 16;
int alpha = (rgb & 0xFF000000) >> 24;

Assembling an RGB int is also possible, provided that all my colors are in the range from 0 to 255. I simply shift each color into the appropriate place, then add them all up:

int rgb = (alpha << 24) + (red << 16) + (green << 8) + (blue);

As a simple application of this, I could implement a "red-only" filter by doing just one thing to each pixel, and-ing it with 0xFFFF0000:

int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
for(int i = 0; i < pixels.length; i++) {
    pixels[i] &= 0xFFFF0000;
}
image.setRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

Here my "mask" includes both the red value and the alpha value. If I accidentally masked out the alpha, I would end up with an image that was completely transparent, and therefore invisible! You should always be careful, when assembling int values to represent a pixel, that you remember to add in 0xFF000000 to give the pixel full alpha value.

You can read more about bitwise operators online; giving a full explanation of them here would be excessive. The image clipping example in the previous section uses int values constructed in this way to initialize the "checkerboard" image it starts with.

8. Saving an Image

At some point, you might want to create a drawing with a program, then save it as a .png or .jpg file so that you can, for example, display it on a website or put it in a report. There is no easy way to save the whole ObjectDraw canvas, unless you want to use your computer's screen capture ability, but if you can draw in an Image, it is very easy to save it:

// Assume you've written a method makeImage() that creates a BufferedImage,
// draws in it, and then returns it.
Image image = makeImage();

try {
    // Of course, I can specify whatever file name I want
    File file = new File("image.png");
    ImageIO.write(image, "png", file);
} catch (IOException e) {
}

At the top of the file, you will have to include some extra classes:

import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

9. Drawing lines and shapes into a BufferedImage

If the image you want to draw is made up of lines and shapes, you want a more powerful drawing method than simply drawing one pixel at a time. There is another way to draw into a BufferedImage: you can create a Graphics2D object that allows you to draw many other shapes.

BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();

// Erase the background
g.setColor(Color.WHITE);
g.fillRect(0, 0, 100, 100);

// Draw a line
g.setColor(Color.RED);
g.drawLine(10, 10, 90, 50);

// Draw a circle
g.setColor(Color.BLUE);
g.fillOval(20, 60, 30, 30);

There are many, many things that you can draw with a Graphics2D. ObjectDraw was created in order to make it easier for beginners to draw things in Java, without worrying about all the complexities that come along with Graphics2D drawing. However, if you really want complete control over what you draw, this is the way to do it. You can read more about Graphics2D on Sun's website.

10. Summary of image methods

Most of the basic methods of VisibleImage are discussed in Rectangular Shapes:

public VisibleImage(Image image, double x, double y, double width, double height, DrawingCanvas canvas)
public VisibleImage(Image image, double x, double y, DrawingCanvas canvas)
public VisibleImage(Image image, Location point, double width, double height, DrawingCanvas canvas)
public VisibleImage(Image image, Location point, DrawingCanvas canvas)

public int getX();
public int getY();
public double getDoubleX();
public double getDoubleY();
public Location getLocation();

public int getWidth();
public int getHeight();
public double getDoubleWidth();
public double getDoubleHeight();
public boolean overlaps(Drawable2DInterface other);
public Rectangle2D getBounds();

public void setSize(double width, double height);
public void setWidth(double w);
public void setHeight(double h);

In addition, VisibleImage has methods for manipulating its image:

public Image getImage()
public void setImage(Image image)
public int getImageWidth()
public int getImageHeight()

Finally, the VisibleImage class has one static method you might want to use:

public static BufferedImage createBufferedCopy(Image image)

Remember, the fact that this method is static means that it is something you ask the class to do, not a method of a particular object. You call it by writing VisibleImage.createBufferedCopy(...).

The BufferedImage class has lots of methods in it, but for now you only really need to worry about the constructor, getters for width and height, and the methods to get and set RGB color data in int form. The type in the constructor should probably be BufferedImage.TYPE_INT_ARGB.

public BufferedImage(int width, int height, int type)

public int getWidth()
public int getHeight()

public int getRGB(int x, int y)
public void setRGB(int x, int y, int rgb)

public void getRGB(int x, int y, int width, int height, int[] pixels, int startIndex, int rowLength)
public void setRGB(int x, int y, int width, int height, int[] pixels, int startIndex, int rowLength)

You can also get a Graphics2D object that will allow you to draw lines, rectangles, ovals, and other shapes into a BufferedImage:

public Graphics2D createGraphics()

There is a vast number of things that you can do with a Graphics2D object, so I won't describe them here; you can read about them on Sun's website.

To work with the int form of an RGB color, the Color class has a constructor to make a Color out of an int, and a method to retrieve the int form of a Color:

public Color(int rgb)
public Color(int rgba, boolean hasAlpha)
public int getRGB()

If the color you are trying to represent has an alpha component, you should use the second constructor, passing a value of true for hasAlpha.