Loading Images From Files in Java
Java has two fundamentally different kinds of Image: those read from external sources such as files or network connections, and off-screen canvases created by drawing within the program.
A Picture That Exists or Not Yet
Suppose a Java program needs to show a picture. The picture might already exist as a file on the hard disk, or it might not exist at all until the program draws it. In both cases the result is an image in the computer's memory, but the two cases are not the same. Java's image library gives you a single idea, the Image, that sits underneath both situations. Before you write any drawing code, it helps to know which kind of image you are holding, because the kind determines how you can use it.
What do you think happens?
A program creates an image with nothing in it yet, then draws a line onto it. Was this image read from a file?
Reveal answer
No, the program built it. It is an off-screen canvas, the second kind of Image.
The source describes two kinds of Image. The second kind is created by the program by drawing to it with a graphics context. Only this second kind starts blank and is filled in by your code.
In this article you will follow one image through two stages. First it is something loaded from outside the program, and then it is a surface the program builds for itself. Watch for the moment the image changes from a passive thing you receive into something you actively write on.
Two Kinds of Image in Memory
The class java.awt.Image represents an image stored in the computer's memory. The source identifies two fundamentally different kinds of Image. The first kind is an image read from a source outside the program, such as a file on the computer's hard disk or a network connection. For this kind, the program does not create the pixels itself. It receives them from outside.
The second kind is an image created by the program itself, by drawing to it using a graphics context. The source calls this second type an off-screen canvas. Both kinds are images in memory, but only the second kind starts blank and is filled in by your code. The comparison below lines up the two kinds so you can see where each one's pixel data comes from.
Read the two columns as a question about direction. In the left column, pixels arrive at the program from somewhere else. In the right column, pixels are produced by the program's own drawing instructions. Everything else in this article builds on that difference.
What an Off-Screen Canvas Is
An off-screen canvas is a region of memory that can be used as a drawing surface. It is not required to be the visible window.
Notice what this definition does and does not promise. It says the canvas is a region of memory that can serve as a drawing surface. It does not say the canvas is always visible, and it does not say the canvas is the same thing as the window on the screen. Keeping those two ideas apart makes the rest of the article easier to follow.
BufferedImage Is an Image
An off-screen canvas can be represented by an object of the class BufferedImage. The source says this class is defined in the package java.awt.image, so to use it you refer to that package. BufferedImage is the concrete tool Java provides for the program-built kind of image you met in the previous sections.
The most important relationship is inheritance. BufferedImage is a subclass of Image. Because of this, a BufferedImage is still an Image, so it can be used anywhere the program expects an Image. The source states the practical consequence directly: once you have a BufferedImage, you can copy it into a graphics context g using one of the g.drawImage() methods, just as you would with any other image.
A BufferedImage inherits the general nature of an Image and adds the role of an off-screen canvas. Anything you can do with an Image in a drawImage call, you can do with a BufferedImage.
The source also notes that a BufferedImage can be created with a constructor. The source excerpt for this section stops before showing it, so this article does not describe the constructor's parameters. What matters here is the relationship: the object is a canvas, and it is also an Image.
Following Pixels Through a Drawing Step
Creating a canvas is only half of the story. The canvas becomes useful when something is drawn onto it. The source explains that you can draw to an off-screen image using the same Graphics class you use for drawing on the screen. The drawing code therefore looks much like the code you would write for the screen. The difference is the destination. The Graphics object points at the off-screen canvas instead of the visible window.
Once the canvas has content, the source describes a second step. A BufferedImage can be copied into a graphics context g with a g.drawImage() method. This is how a finished off-screen canvas reaches a visible surface. The diagram below traces the path the data takes from the program's drawing calls to the final copy.
Read the diagram from top to bottom as a story. The program issues drawing instructions through a Graphics object. Those instructions change the canvas, which is the highlighted node because its content is the thing being built. Only at the end does the canvas leave the picture, when a g.drawImage() call copies it into the destination graphics context g.
Tracing one canvas from blank to copied
A program makes a BufferedImage, draws a shape onto it, and then copies it to a destination graphics context g. Trace where the image data is at each step.
- 1Step 1: The canvas exists but is empty
A BufferedImage is created as an off-screen canvas. It is a region of memory used as a drawing surface, and it is not the visible window.
- 2Step 2: Drawing changes the canvas
The program draws using the same Graphics class it would use for the screen, but the Graphics object points at the canvas. The pixel content of the canvas changes. The screen is not touched yet.
- 3Step 3: The finished canvas is copied
A
g.drawImage()call takes the BufferedImage and copies it into the graphics context g. Because a BufferedImage is an Image, it is accepted by this call just like any other image. - 4Step 4: The copy reaches the visible surface
The source says a finished canvas reaches a visible surface through this copy step. Only now does the image appear where a user can see it.
The image data moved from drawing calls, into the off-screen canvas, and then into the destination through one drawImage copy. The canvas was the middle stage, and it was never required to be the window itself.
Mistakes When Canvas and Window Are Confused
Common mistake Assuming every Image is a picture loaded from a file
What it looks likeA program creates a blank image and draws on it, then assumes its pixels came from disk.
Why it failsThe source identifies two kinds of Image. Only the first kind is read from a source outside the program. The second kind is created and filled by the program's own drawing.
Do this insteadAsk where the pixels come from before you decide how to use the image. If your code is drawing onto it, it is an off-screen canvas.
Common mistake Treating an off-screen canvas as the visible window
What it looks likeA program draws onto a BufferedImage and expects the user to see the drawing immediately.
Why it failsThe definition says an off-screen canvas is not required to be the visible window. Drawing onto it changes the canvas, not the screen.
Do this insteadAfter drawing, copy the canvas into the destination graphics context with g.drawImage().
Common mistake Thinking BufferedImage is unrelated to Image
What it looks likeA programmer writes code that accepts only Image and then passes a BufferedImage to it, expecting a type error.
Why it failsBufferedImage is a subclass of Image, so a BufferedImage is still an Image.
Do this insteadUse a BufferedImage wherever an Image is expected, and rely on the inherited behavior for the copy step.
Common mistake Forgetting that the Graphics object decides the destination of drawing calls
Tracing an Image Yourself
A program receives an image from a network connection, creates a BufferedImage, draws a label onto the BufferedImage, and then copies the BufferedImage to a destination graphics context g. For each of the two images, state which kind of Image it is, where its pixel data comes from, and at what point it reaches a visible surface.
Hints
- Ask first whether the program creates the pixels or receives them from outside.
- The image from the network connection is the first kind of Image described in the source.
- The BufferedImage that is drawn on is an off-screen canvas, and it reaches the visible surface only through the
g.drawImage()copy.
Checking the Two Kinds Again
- 1Java has two fundamentally different kinds of Image: images read from external sources such as files or network connections, and off-screen canvases created by drawing within the program.
- 2An off-screen canvas is a region of memory that can serve as a drawing surface, and it is not required to be the visible window.
- 3BufferedImage is a subclass of Image, defined in
java.awt.image, that represents an off-screen canvas. - 4Drawing to a canvas uses the same Graphics class as screen drawing, but the destination is the canvas. A
g.drawImage()call copies the finished canvas into a graphics context g, which is how it reaches a visible surface.
Key Takeaways
- Java has two kinds of Image: one read from an external source such as a file or network connection, and one created by the program through drawing, called an off-screen canvas.
- An off-screen canvas is a region of memory used as a drawing surface, and it is not required to be the visible window.
- BufferedImage is a subclass of Image in
java.awt.imagethat represents an off-screen canvas, so it can be used wherever an Image is expected. - Drawing to a canvas uses the same Graphics class as screen drawing, and a
g.drawImage()call copies the finished canvas into a graphics context g to reach a visible surface.