Loading and Displaying Images From Files
An off-screen canvas is a BufferedImage, a subclass of Image, that stores a picture in memory instead of showing it directly.
Why a Painted Picture Can Disappear
Imagine a program where a user drags the mouse across a panel to draw curves and rectangles. The strokes appear on the screen as the user works. It is tempting to think the screen is where the drawing is kept, but that is not quite true. The screen is not a permanent store. If the window is resized, or the operating system asks the panel to repaint itself, the panel has to rebuild everything it shows. Anything painted directly onto the panel and not recorded somewhere else is lost.
What do you think happens?
Between one paint call and the next, where is the user's picture actually kept?
Reveal answer
In an off-screen image held in memory
The program in this lesson keeps an off-screen canvas, a BufferedImage stored in memory. Its paint method first makes sure that canvas exists, copies the canvas onto the panel, and only then adds the shape currently being dragged. The canvas is what survives between paint calls.
The Idea Behind an Off-Screen Canvas
The program needs enough stored information to draw the picture again whenever it is asked. One way to hold that information is an image that lives in memory, away from the screen. The program can paint onto that image, and later copy it to the screen. This stored image is called an off-screen canvas. Its job is to be a safe place for the picture, not to be seen directly.
Keeping a canvas is a design decision with trade-offs. Because the canvas holds the finished picture, redrawing is simple: copy it. The canvas does not, however, remember which strokes or shapes produced the picture. The rest of this article follows the lifecycle of that canvas, then compares it with the alternative of keeping a list of shapes.
BufferedImage as the Canvas Class
An off-screen canvas is a BufferedImage, a subclass of Image, that stores a picture in memory instead of showing it directly. In Java, the class BufferedImage belongs to the package java.awt.image.
Because BufferedImage is a subclass of Image, a BufferedImage can be passed to the drawImage method of a Graphics object just as any other image can. The image you copy onto the screen is the same kind of object you drew on, so no conversion step is needed between drawing and displaying.
Tracing the Paint Method Step by Step
The painting panel in this lesson does three things in a fixed order each time its paint method runs. It first ensures that the off-screen canvas exists. It then copies that canvas onto the panel. Finally, it draws any shape the user is currently dragging out. Three objects take part: the off-screen canvas, the Graphics handle used to draw on that canvas, and the panel's own Graphics object that paints to the screen.
Read the diagram from left to right. Every step up to the copy happens in memory, so the canvas exists as data even before anything is visible. Only the final drawImage call touches the panel's screen area. This is why the question of where the picture lives has a clear answer: it lives in the canvas, and the screen only shows a copy of it.
Creating the Canvas With Width, Height, and Type
A BufferedImage is created with a constructor that takes three arguments: a width, a height, and an image type. In the source program the width and height come from the panel's own size, so the canvas always matches the area it will be copied into. The image type constant BufferedImage.TYPE_INT_RGB selects the format used to store each pixel. Read the line as a sentence: create a new BufferedImage this wide, this tall, storing its pixels in this format, and keep a reference to it in the variable OSC.
- OSC is a variable that holds a reference to the canvas
getWidth()andgetHeight()give the panel's size, so the canvas matches the panel- BufferedImage.TYPE_INT_RGB selects the pixel storage format
The width and height together fix how many pixels the canvas has, and each pixel is addressed by an x coordinate (across) and a y coordinate (down). The top-left corner is the origin. The source describes the stored content as the color of every pixel, so the canvas is a grid of color values rather than a set of drawing commands. Because the width and height come from the panel, a canvas created this way always covers the whole area it will later be copied into.
Getting a Graphics Handle From the Canvas
Creating the canvas gives you storage but no drawing tools. To draw on it, call createGraphics() on the BufferedImage. That method returns a Graphics object whose drawing operations land in the image instead of on the screen. Methods you already know from painting a component, such as setColor and fillRect, work the same way on this handle. The only difference is the destination: the pixels of the image rather than the panel's screen area.
Graphics og = OSC.createGraphics();
og.setColor(Color.WHITE);
og.fillRect(0, 0, OSC.getWidth(), OSC.getHeight());- 1
createGraphics()on OSC returns a Graphics handle whose drawing lands in the canvas - 2
setColorchooses the fill color, using the same call you would use on a screen Graphics object - 3
fillRectcovers the whole canvas, so the canvas starts out entirely in that color
Copying the Canvas to the Screen With drawImage
Once the canvas holds the picture, the paint method copies it to the panel with g.drawImage(OSC, 0, 0, null). The coordinates 0 and 0 place the top-left corner of the image at the top-left corner of the panel. The fourth argument is an image observer, used to track images that are still loading. The source program passes null because the image is already fully available in memory.
- The canvas is created only if it does not yet exist
- The panel's Graphics object g is used for the copy
- The canvas is copied in one call, after it is ready
Notice the role of the panel's own Graphics object, g. It paints to the screen, and drawImage copies the canvas's pixels into the region that g controls. The source also notes that because the canvas completely fills the panel, the paint method does not need to call the superclass paint behavior first, since nothing else needs to be cleared.
Where the Picture Lives Between Paint Calls
Now return to the question from the start. Suppose the canvas has been drawn and the user is dragging a new stroke. Each paint call copies the canvas to the panel, and the stroke is then added on top. Look at what is stored in the canvas itself: each pixel has a color, and nothing records which shape produced it.
The red cells are the stroke as the canvas remembers it: a set of pixels with red color values. Nothing in the canvas says these three cells form one line or that they came from a single drag. A stroke, a rectangle, and a dot that produce the same colors are indistinguishable once they are in the image. This is the essential answer to where a drawing's information lives: in the pixel values themselves.
Mistakes Beginners Make With Canvases
Common mistake Drawing on the panel's Graphics object when the picture should be kept
What it looks likeCalling fillRect on g inside paint and expecting the fill to persist
Why it failsThe panel's screen area is not a permanent store, so anything painted there directly is lost when the panel must repaint.
Do this insteadDraw the picture into the canvas through its own Graphics handle, then copy the canvas with drawImage.
Common mistake Calling createGraphics() but drawing with the panel's Graphics object
Why it failsThe handle from
createGraphics()is the one whose drawing lands in the image. Drawing with another handle goes elsewhere.Do this insteadUse the Graphics object returned by OSC.createGraphics() for every drawing call meant for the canvas.
Common mistake Creating the canvas with a size that does not match the panel
Why it failsThe source derives width and height from the panel so the canvas covers the area it is copied into.
Do this insteadPass getWidth() and getHeight() to the constructor, as the source program does.
Common mistake Assuming the canvas remembers individual shapes so one can be moved later
Pixels Versus a List of Shapes
The off-screen canvas is one design. The source contrasts it with a sample program that records each drawn shape as an object. Both designs can produce the same picture, but they make different things easy and difficult.
| Question | Off-screen canvas of pixels | List of shape objects |
|---|---|---|
| What is stored? | The color of every pixel | A separate object for each shape |
| Redrawing the picture | Easy: copy the canvas | Requires drawing each stored shape again |
| Moving or deleting one shape | Hard: the shape is not separate information | Easy: change or remove that shape's object |
| Where the drawing's information lives | In the pixel values | In the shape objects |
Trade-offs between the two storage designs described in the source
Tracing the Canvas Yourself
A paint method runs twice in a row with no drawing between the calls. Trace what happens to the canvas and to the panel on each call. Then say which object holds the picture across the two calls and why the panel alone could not hold it.
Hints
- On the first call, the canvas may need to be created before it is copied
- Think about which object survives between calls, the canvas or the screen area
- Recall that the panel must rebuild what it shows when it repaints
Key Takeaways
- An off-screen canvas is a BufferedImage, a subclass of Image, that stores a picture in memory instead of showing it directly.
- The constructor takes a width, a height, and an image type such as BufferedImage.TYPE_INT_RGB, and
createGraphics()returns a Graphics handle whose drawing lands in the image. drawImagecopies the finished canvas to the panel's screen area, with the top-left corner placed at coordinates 0 and 0.- A canvas stores the color of every pixel rather than a record of each shape, which makes redrawing easy but editing individual shapes harder.