Concepts / Storing Drawings as Lists of Shape Objects

Storing Drawings as Lists of Shape Objects

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 in which a user draws freehand curves and rectangles on a panel. The user's work appears on the screen, but the screen is not a permanent place to keep anything. If the window is resized, or the operating system asks the panel to repaint itself, the panel has to rebuild everything it shows. Anything that was painted directly onto the panel and not recorded somewhere else is gone.

So the program needs enough stored information to draw the picture again whenever it is asked to. This article looks at one way to hold that information: an image kept in memory, away from the screen, that the program can paint onto and later copy to the screen. As you read, keep one question in mind: where is the picture actually kept between one paint call and the next?

The Off-Screen Canvas as an Idea

An off-screen canvas is an image that lives in memory and is not displayed directly. The program draws into it the way it would draw onto a panel, but nothing appears on the screen until the finished image is copied there. The canvas is a working surface that the program owns, separate from the visible area.

What do you think happens?

If the panel is repainted and the picture is only in the canvas, what must happen before the user sees the picture again?

  • The canvas must be copied onto the panel

  • The user must redraw every shape by hand

  • The canvas disappears and the screen keeps the old pixels

Reveal answer
Answer:

The canvas must be copied onto the panel

The canvas holds the picture in memory. The paint method copies it to the panel with drawImage every time the panel paints, so the picture is rebuilt from the stored image rather than from the screen.

What a BufferedImage Is

Definition

A BufferedImage is an object of the class BufferedImage, which belongs to the package java.awt.image. It is a subclass of Image, and it is how Java represents an off-screen canvas.

Because BufferedImage is a subclass of Image, a BufferedImage can be passed to the drawImage method of a Graphics object exactly 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 copying.

Creating the Canvas and Its Graphics Handle

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 constant BufferedImage.TYPE_INT_RGB selects the format used to store each pixel. Read the line as a sentence: create a new BufferedImage that is this wide, this tall, and stores its pixels in this format, and keep a reference to it in the variable OSC.

java
Notes on the code
  • The constructor receives width, height, and image type in that order
  • createGraphics() returns a Graphics handle whose drawing lands in the image
  • setColor and fillRect work on this handle exactly as they do on a panel
  • Filling the whole canvas makes it start out in the chosen color

Creating the canvas gives you storage but no drawing tools. The BufferedImage itself only holds pixels. To draw on it, you call createGraphics() on the image. That call returns a Graphics object whose operations, such as setColor and fillRect, land in the image instead of on the screen. The handle is the way into the canvas; the canvas is where the pixels live.

Tracing the Paint Method Step by Step

The painting program has a paint method that does three things in order. First it makes sure an off-screen canvas exists. Next it copies that canvas onto the panel. Finally it adds any shape the user is currently dragging out. The code below is a sketch of that method. The fillColor variable and the comment about the dragged shape are illustrative names added for this explanation; the source describes the steps but not these exact identifiers.

java
Notes on the code
  • The null check means the canvas is created only once
  • The canvas is copied to the panel before any in-progress shape is added
  • The dragged shape is drawn on the screen Graphics object g

Trace the three objects involved: the off-screen canvas, the Graphics handle used to draw on it, and the panel's own Graphics object g that paints to the screen. On the first paint call, the canvas and its handle are created and filled. On later paint calls, the canvas already exists, so the method goes straight to copying it. The canvas is the only one of the three that persists between calls; the handle and the screen output are used and then the screen is repainted from the canvas.

Off-screen canvasBufferedImage OSCCanvas Graphicshandlefrom createGraphicsVisible panelwhat the user seesPanel Graphicsthe paint parameter gdraws intodrawImage copiespaints to
How does the BufferedImage relate to the component's visible screen area, and what is the difference between the two?

Copying the Canvas 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 first argument is the image, and the fourth argument is an image observer. The observer is used to track images that are still loading. The source program passes null because the image is already fully available in memory.

Off-screen canvasBufferedImage OSCdrawImage callg.drawImage(OSC, 0, 0,null)Panel screen areatop-left at 0, 0image passed infinished picture copied
When drawImage is called, where does the picture data travel from and to, and what gets copied at that moment?

What do you think happens?

After drawImage runs, which object holds the picture the user drew?

  • Only the panel's screen area

  • The off-screen BufferedImage

  • The Graphics object g passed to paint

Reveal answer
Answer:

The off-screen BufferedImage

drawImage copies the finished image to the screen, but the canvas keeps its pixels in memory. The copy on the screen is refreshed from the canvas each time paint runs, so the canvas remains the stored record of the picture.

Where the Picture Information Lives

The off-screen canvas stores the color of every individual pixel. It does not remember how the picture was made. A stroke, a rectangle, and a dot that produce the same colors are indistinguishable once they are in the image. This is a real design choice with consequences.

A program that keeps only pixels can redraw the picture by copying it, but it cannot easily move or delete one shape afterward. A program that keeps a list of shape objects can do those things, because each shape is a separate piece of information. The source contrasts the canvas approach with a sample program that records each drawn shape as an object.

Color of every pixelEach shape as anobjectRedraw by copyingShapes kept separateMove one shape: hardMove or delete oneshape: possible
If the drawing is stored as pixels, where is its information kept compared with storing it as a list of shape objects?
In the real world

Consider the user's freehand curve on the painting panel. In the canvas approach, once the curve is painted, the program no longer holds a separate record of it: only the pixels that form it remain. In the shape-list approach, the curve would be one object in a list, so the program could select it, move it, or remove it without touching the rest of the drawing.

Mistakes Beginners Make with the Canvas

  • Common mistake

    Expecting createGraphics to return a picture rather than a drawing handle

    Why it fails

    The BufferedImage holds the pixels. createGraphics returns a Graphics object that draws into that image.

    Do this instead

    Keep the image in a variable such as OSC, and use the Graphics object returned by createGraphics only to draw on it.

  • Common mistake

    Drawing on the panel and assuming the picture is saved

    Why it fails

    Anything painted directly onto the panel and not recorded elsewhere is lost when the panel must repaint.

    Do this instead

    Draw the lasting picture onto the off-screen canvas, then copy the canvas to the panel with drawImage in paint.

  • Common mistake

    Forgetting that the canvas must match the panel's size

    Why it fails

    The source program takes width and height from the panel so that the canvas matches the area it is copied into.

    Do this instead

    Pass getWidth() and getHeight() to the BufferedImage constructor, not arbitrary numbers.

  • Common mistake

    Assuming the canvas remembers which shape was drawn last

    Why it fails

    The canvas stores only pixel colors, so individual shapes cannot be found or changed afterward.

    Do this instead

    If you need to move or delete shapes, store each shape as an object in a list and redraw from that list.

Practice Tracing the Canvas

EASY

A painting panel is 300 pixels wide and 200 pixels tall. Write the BufferedImage constructor call that creates its canvas using the panel's size and the TYPE_INT_RGB type, then write the call that gets a Graphics object for drawing on the canvas. Finally, state the coordinates you would pass to drawImage to place the canvas at the top-left corner of the panel.

Hints
  • The width and height come from the panel, so you can use getWidth() and getHeight() rather than the numbers.
  • createGraphics() is called on the BufferedImage, not on the panel.
  • The top-left corner of both the image and the panel is at coordinates 0 and 0.
MEDIUM

A user draws three rectangles and then drags a fourth. Explain which of the three objects, the off-screen canvas, the canvas Graphics handle, or the panel Graphics g, holds the three finished rectangles between paint calls, and then explain how the program would need to change if it had to delete the second rectangle alone.

Hints
  • Think about which object persists between paint calls.
  • Consider what a pixel-only store can and cannot change after drawing.

Key Takeaways from the Canvas Approach

At a glance

  • An off-screen canvas is a BufferedImage, a subclass of Image, that stores a picture in memory instead of showing it directly.
  • A BufferedImage is created with a width, a height, and an image type such as BufferedImage.TYPE_INT_RGB, and createGraphics() returns a Graphics handle that draws into it.
  • drawImage copies the finished canvas to the component's screen area, so the canvas is the stored record that lets the picture be redrawn on demand.
  • A canvas keeps the color of every pixel rather than a record of each shape, which makes redrawing easy but makes moving or deleting one shape harder than with a list of shape objects.