Concepts / Loading and Displaying Images From Files

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.

  • Programming

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?

  • Only on the panel's screen area

  • In an off-screen image held in memory

  • In a list of shape objects that the paint method reads

Reveal answer
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

Definition

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.

Create BufferedImagein memorycreateGraphics()drawing handleFill and draw intocanvasstill in memorydrawImage onto panelcopies pixelsScreen shows picturevisiblekeep referencedrawing callspaint callpixels appear
In what order do the drawing and display steps happen, and where does the picture exist at each step, in memory or on 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.

java
Notes on the code
  • OSC is a variable that holds a reference to the canvas
  • getWidth() and getHeight() give the panel's size, so the canvas matches the panel
  • BufferedImage.TYPE_INT_RGB selects the pixel storage format
(0, 0)top-left pixel(0, 1)first pixel, second row(1, 0)next pixel right(2, 0)further right(3, 0)last pixel in rowx increasesy increases
How do the width and height determine how many pixels exist, and how does an (x, y) coordinate map to one position in that grid?

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.

Code walkthrough
Graphics og = OSC.createGraphics();
og.setColor(Color.WHITE);
og.fillRect(0, 0, OSC.getWidth(), OSC.getHeight());
  1. 1
    createGraphics() on OSC returns a Graphics handle whose drawing lands in the canvas
  2. 2
    setColor chooses the fill color, using the same call you would use on a screen Graphics object
  3. 3
    fillRect covers 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.

java
Notes on the code
  • 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.

pixel (0, 0)whitepixel (1, 0)redpixel (2, 0)redpixel (3, 0)whitepixel (4, 0)red
When a drawing is stored as pixels, where does its information live, and what does a shape look like once it has been drawn into the canvas?

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 like

    Calling fillRect on g inside paint and expecting the fill to persist

    Why it fails

    The panel's screen area is not a permanent store, so anything painted there directly is lost when the panel must repaint.

    Do this instead

    Draw 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 fails

    The handle from createGraphics() is the one whose drawing lands in the image. Drawing with another handle goes elsewhere.

    Do this instead

    Use 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 fails

    The source derives width and height from the panel so the canvas covers the area it is copied into.

    Do this instead

    Pass 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.

QuestionOff-screen canvas of pixelsList of shape objects
What is stored?The color of every pixelA separate object for each shape
Redrawing the pictureEasy: copy the canvasRequires drawing each stored shape again
Moving or deleting one shapeHard: the shape is not separate informationEasy: change or remove that shape's object
Where the drawing's information livesIn the pixel valuesIn the shape objects

Trade-offs between the two storage designs described in the source

Tracing the Canvas Yourself

MEDIUM

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.
  • drawImage copies 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.