Concepts / Drawing Images using Graphics

Drawing Images using Graphics

The Graphics class provides the methods that copy an image onto the screen or an off-screen canvas, most commonly from inside paintComponent() of a JComponent.

  • Programming

Predicting What a Single Draw Call Does

An image in a Java program is not drawn by the image itself. It is copied onto a drawing surface by a method that belongs to the Graphics class. That surface is usually the screen, though it can also be an off-screen canvas. In practice this copying most often happens inside the paintComponent() method of a JComponent, where the Graphics object arrives as a parameter that you then use to draw.

Start with the simplest drawing statement you will meet for images: a call of the form drawImage(img, x, y, this), made through a Graphics object named g. Read it as a request. It asks Java to draw the image named img with its position at x and y. Notice what the call does not do. It does not return a new image, and it does not ask for a size. It only asks for the image to be placed.

What do you think happens?

Before reading on, write down your guess: what determines where the image lands on the component, and what determines how big it appears?

Reveal answer
Answer:

The x and y values determine where the image is placed. In the basic form, the size is not specified in the call, so the image is drawn at its own size.

The basic call only supplies a position, so position is the only thing the call controls. Size is a separate question that the basic form does not answer. The rest of this article tests guesses like yours by changing one argument at a time.

Tracing the Flow of Control

Follow the control as the program runs. The paintComponent() method is the caller. It hands its Graphics object to the drawing statement. The Graphics object then performs the copying onto whatever surface it is attached to. Your code controls the call, but the copying itself is carried out through the Graphics object. This is why the Graphics object is the first argument: it is the thing doing the work.

paintComponent()the callerdrawImage callyour code controls thisGraphics object gperforms the copyingDrawing surfaceusually the screenpasses gusescopies image onto
Which component hands work to which, when an image is drawn from inside paintComponent()?

Reading Each Argument of the Basic Call

Each piece of the basic call has one job. The first argument, g, is the Graphics object that does the drawing. The second, img, is the image being drawn. The x and y values give the position at which the image is placed. The final argument, this, is the image observer. The source states that the image observer is required when an image is drawn, and it is passed to drawImage() as the final parameter. In the source's examples, this is used as the observer, which is the JComponent that contains the drawing code.

gGraphics object that doesthe drawingimgthe image being drawnx, yposition where the image isplacedthisimage observer, required
In g.drawImage(img, x, y, this), which argument does what, and which of them the basic form requires?
java
Notes on the code
  • g: the Graphics object doing the copying
  • img: the image to draw
  • x, y: the position of the image
  • this: the image observer, here the JComponent containing the drawing code

Setting a Width and Height

The drawImage() method has a useful variation that scales the image as it is drawn, so that it fills a width and height you specify. The form that does this inserts two size values after the position values. Compare it with the earlier call. The position is still set by x and y. What has changed is that the image is no longer drawn at whatever size it has. Its drawn size is set by width and height, so the same image can appear larger or smaller depending on the values you pass.

Before
g.drawImage(img, x, y, this);

Image drawn at its natural size, placed at x and y

After
g.drawImage(img, x, y, 200, 100, this);

Image scaled to 200 by 100 and placed at the same x and y

Natural sizedrawn at the image's ownsizePosition x, yupper-left placement pointWidth and heightsize set by the two valuesPosition x, yupper-left placement pointplaced atplaced at
When width and height are supplied, what changes about the drawn image and what stays the same?

What do you think happens?

If you change width and height from 200 by 100 to 400 by 300 but keep x and y the same, where does the upper-left corner of the image land?

Reveal answer
Answer:

At the same x and y as before. Only the size changes.

The source describes scaling as changing only the size at which the image is placed. The position is still defined by x and y.

Selecting Part of an Image with Eight Coordinates

The most detailed variant of drawImage() takes eight coordinates instead of a position and size. The call is written as a set of destination coordinates, followed by a set of source coordinates, and then the observer. The destination pair of corners describes where on the drawing surface the result goes. The source pair of corners describes which rectangle of the original image is taken. The form separates the two, which lets you draw only part of an image.

Source imagethe original imageSource rectanglesx1, sy1 to sx2, sy2Destination rectangledx1, dy1 to dx2, dy2Drawing surfacethe componentonly this region is takencopied intoplaced on
Which rectangle of the source image is copied, and where does that rectangle land on the component, given the source and destination corner coordinates?
java
Notes on the code
  • dx1, dy1 = 0, 0: upper-left corner of the destination rectangle
  • dx2, dy2 = 200, 150: lower-right corner of the destination rectangle
  • sx1, sy1 = 50, 40: upper-left corner of the source rectangle inside img
  • sx2, sy2 = 250, 190: lower-right corner of the source rectangle inside img
  • this: the image observer
Worked example

Reading one eight-coordinate call

Given g.drawImage(img, 0, 0, 200, 150, 50, 40, 250, 190, this), what part of img is drawn, and where does it land?

  1. 1
    Identify the destination pair

    The first four values, 0, 0, 200, 150, are the destination corners. They describe where on the drawing surface the result goes.

  2. 2
    Identify the source pair

    The next four values, 50, 40, 250, 190, are the source corners. They pick out a rectangle inside the original image, and only that rectangle is copied.

  3. 3
    Compare the two rectangles

    The source rectangle is 200 wide and 150 tall. The destination rectangle is also 200 wide and 150 tall here, so no scaling occurs. The source and destination are specified separately, so a different destination size would scale the copied region.

  4. 4
    Check the observer

    The final argument, this, is the image observer, passed as the last parameter just as in the basic form.

Result

The rectangle from (50, 40) to (250, 190) inside img is copied and placed into the rectangle from (0, 0) to (200, 150) on the component.

Finding Where a Wrong Result Starts

When a drawn image is not what you expected, the cause is usually one argument in one call. Treat each argument as a question to ask about your own code: at which argument did my expectation and the call stop agreeing? The checks below follow the parts of the call in the order a learner should usually examine them.

  • Common mistake

    Expecting a width and height to move the image

    What it looks like

    Adding 400, 300 to the call and assuming the image shifts right and down.

    Why it fails

    Width and height set the drawn size. The position is still set by x and y.

    Do this instead

    Check x and y for placement. Check the two size values only for how big the image appears.

  • Common mistake

    Forgetting that the basic form draws at natural size

    What it looks like

    Calling drawImage(img, x, y, this) and expecting a specific size.

    Why it fails

    The basic form supplies no size, so the image is placed at its own size.

    Do this instead

    Add width and height if the drawn size must differ from the natural size.

  • Common mistake

    Mixing up the source and destination pairs

    What it looks like

    Passing the source corners first, so the destination values are read as the region of the image.

    Why it fails

    The eight-coordinate form lists destination coordinates first, then source coordinates.

    Do this instead

    Write the destination corners first, then the source corners, then the observer, and check the order against the signature.

  • Common mistake

    Omitting the image observer

    What it looks like

    Writing the call without this as the last argument.

    Why it fails

    The source describes the image observer as required in the forms it shows, and it is passed as the final parameter.

    Do this instead

    Keep the observer as the last argument in every form.

  • Common mistake

    Assuming the whole image is drawn when only a source rectangle was given

Practice: Reading Calls Before Running Them

MEDIUM

Read this call without running it: g.drawImage(img, 10, 20, 300, 120, 0, 0, 150, 60, this). Describe in one sentence which part of img is copied, which rectangle on the component receives it, and whether that rectangle is larger or smaller than the copied region.

Hints
  • Locate the four destination values and the four source values separately.
  • Source corners are 0, 0 and 150, 60. Destination corners are 10, 20 and 300, 120.
  • Compare the width of each rectangle: 150 minus 0 for the source, 300 minus 10 for the destination.
EASY

A teammate's image appears too small, though the position is correct. Using the basic form and the width-and-height form, explain which argument you would check first and why.

Hints
  • Think about which argument controls drawn size.
  • Ask whether the call includes width and height at all.

Summary of the Drawing Call

  1. 1
    The Graphics object does the copying. Your code, usually inside paintComponent() of a JComponent, controls the call.
  2. 2
    The basic form drawImage(img, x, y, this) places the image at x and y. It takes no size, so the image appears at its natural size.
  3. 3
    The width-and-height form scales the image to a specified size. Position stays set by x and y, and size is set by width and height.
  4. 4
    The eight-coordinate form takes destination corners, then source corners, then the observer. The source rectangle selects part of the image, and the destination rectangle places it.
  5. 5
    When the output is wrong, check the argument where your expectation and the call diverge, starting with position and size.

At a glance

  • The Graphics object does the copying onto the screen or an off-screen canvas, most often from inside paintComponent() of a JComponent.
  • The basic drawImage(img, x, y, this) call places an image at x and y; the image observer is passed as the final parameter.
  • Adding width and height scales the image to a specified size without changing its position, which is still set by x and y.
  • The eight-coordinate form separates a source rectangle from the original image and a destination rectangle on the drawing surface, so only part of an image can be drawn and placed at a chosen size.
  • When a result is unexpected, check the argument where the expectation and the call diverge, beginning with position and size.