Concepts / Image Observer

Image Observer

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 Where an Image Lands

Drawing an image in a Java program is a request, not a self-contained action. The image does not paint itself onto the screen. Instead, the Graphics class provides the methods that copy an image onto a drawing surface, which is usually the screen but can also be an off-screen canvas. Most often this copying happens inside paintComponent() of a JComponent, where a Graphics object arrives as a parameter and you use it to draw.

What do you think happens?

Given the statement g.drawImage(img, x, y, this), what determines where the image lands and how big it appears? Write down your guess before reading on.

Reveal answer
Answer:

The x and y values determine where the image is placed. The call does not return a new image and does not ask for a size, so the image is placed at its own size.

The source describes this call as a request to draw the image named img with its position at x and y, using the Graphics object g. The call does not return a new image and does not ask for a size. Size is controlled by a different form of the method, which the later sections cover.

This article starts from the simplest drawing statement and treats the more specialized variants as changes to that same call. Scaling and drawing only part of an image are both changes to the argument list. If you can read one drawImage() call carefully, the other forms become much easier to read.

Reading the Basic drawImage() Call

The basic form places an image at a position. Inside a paintComponent() method, the call looks like this. The method receives a Graphics object named g, and the call draws the image named img at the position x and y, with this passed as the final argument.

java
gthe Graphics object thatdoes the drawingimgthe image being drawnxhorizontal positionyvertical positionthisthe image observer
Which part of the basic drawImage() call is the drawing target, which is the image, which sets the position, and which is the image observer?

Each argument has one job. The first argument, g, is the Graphics object that does the drawing. The second, img, is the image being copied. The x and y values give the position where the image is placed. The last argument, this, is the image observer. The source states that the image observer is required when an image is drawn and is passed as the final parameter. In the source's examples, this refers to the JComponent that contains the drawing code.

  • g: the Graphics object that performs the copying
  • img: the image being drawn
  • x and y: the position at which the image is placed
  • this: the image observer, which is required as the final argument

Scaling an Image with Width and Height

The drawImage() method has a 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. The position is still set by x and y, but the drawn size is now set by the width and height you pass rather than the size the image already has.

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

The image is drawn at x and y at the size it already has.

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

The image is drawn at x and y and scaled to 200 by 150.

Image at its own sizeplaced at x and yImage at 200 by 150placed at the same x and y
When width and height are passed, how does the drawn result change compared with the basic call?

Selecting Part of an Image with Eight Coordinates

The most detailed form of drawImage() takes eight coordinates instead of a position and a size. The arguments are the destination coordinates first, then the source coordinates, then the image observer. The source pair describes two corners of a rectangle inside the original image, and only the pixels inside that rectangle are copied. The destination pair describes the rectangle on the drawing surface that receives those pixels.

java
Output
The 100 by 80 pixel region starting at the top-left of img is drawn into the rectangle from (10, 20) to (210, 170) on the drawing surface, a 200 by 150 area.
Notes on the code
  • 10, 20 is dx1, dy1: the top-left corner on the drawing surface
  • 210, 170 is dx2, dy2: the bottom-right corner on the drawing surface
  • 0, 0 is sx1, sy1: the top-left corner of the rectangle in the original image
  • 100, 80 is sx2, sy2: the bottom-right corner of the rectangle in the original image
  • this is the image observer
sx1, sy1top-left corner in theoriginal imagedx1, dy1top-left corner on thedrawing surfacesx2, sy2bottom-right corner in theoriginal imagedx2, dy2bottom-right corner on thedrawing surfacecopied tocopied to
How do the source corners select a region of the image, and how is that region placed into the destination corners on the drawing surface?

The two rectangles are specified separately. The destination rectangle does not have to match the source rectangle in size. In the example above, a 100 by 80 pixel region is placed into a 200 by 150 destination area, so the taken portion is stretched as it is placed. The portion you take and the place you put it are independent choices.

Worked example

Reading an eight-coordinate call

Describe what this call copies and where it appears: g.drawImage(img, 0, 0, 50, 50, 100, 100, 150, 150, this);

  1. 1
    Identify the destination pair

    The first four values after img are 0, 0, 50, 50. These are dx1, dy1, dx2, dy2, the rectangle on the drawing surface.

  2. 2
    Identify the source pair

    The next four values are 100, 100, 150, 150. These are sx1, sy1, sx2, sy2, the rectangle inside the original image.

  3. 3
    Compare the sizes

    The source rectangle is 50 by 50 pixels and the destination rectangle is also 50 by 50, so no stretching occurs.

  4. 4
    Locate the observer

    The final argument this is the image observer, as in the basic form.

Result

The 50 by 50 region of img whose top-left corner is at (100, 100) is drawn into the top-left 50 by 50 area of the drawing surface.

Mistakes That Change the Result

When a drawn image is not what you expected, the cause is usually one argument in one call. Read each mistake below as a question to ask about your own code: at which argument did my expectation and the call stop agreeing?

  • Common mistake

    Expecting a width and height to move the image

    What it looks like

    Changing 200, 150 to 300, 225 and expecting the image to shift right

    Why it fails

    Width and height control the drawn size. The position is still controlled only by x and y.

    Do this instead

    Change x and y to move the image, and change width and height to resize it.

  • Common mistake

    Passing the source rectangle where the destination belongs, or the reverse

    What it looks like

    Writing the source corners first in the eight-coordinate form

    Why it fails

    The destination pair comes first, followed by the source pair. Swapping them takes the wrong region and places it in the wrong spot.

    Do this instead

    Check that the first four coordinates are dx1, dy1, dx2, dy2 and the next four are sx1, sy1, sx2, sy2.

  • Common mistake

    Assuming the source and destination rectangles must match

    What it looks like

    Taking a 100 by 80 region and expecting it to appear at 100 by 80 on screen

    Why it fails

    The destination rectangle is not required to match the source rectangle, so the taken region can be placed at a different size.

    Do this instead

    Compare the source size and the destination size separately to predict the final scale.

  • Common mistake

    Dropping the image observer argument

    Why it fails

    The source states that the image observer is required in the drawImage() forms it shows, and it is the final parameter.

    Do this instead

    Keep this as the last argument in every form of the call.

Tracing an Unexpected Result

When the drawn image looks wrong, work through the call argument by argument instead of rewriting it. Start with the argument that controls the outcome you are looking at. For a misplaced image, check x and y. For an image that is the wrong size, check width and height, or the destination corners in the eight-coordinate form. For an image that shows the wrong part of the picture, check the source corners.

MEDIUM

A drawImage() call uses the eight-coordinate form with source corners 0, 0, 400, 300 and destination corners 0, 0, 200, 150. The image appears too small and shows the whole picture. Which argument group do you check first, and what result do you expect after the fix?

Hints
  • Compare the source rectangle size with the destination rectangle size.
  • Remember which four values are the destination pair and which are the source pair.

Key Takeaways

  • A drawImage() call is a request that copies an image through a Graphics object, usually inside paintComponent() of a JComponent.
  • In the basic form, g is the drawing target, img is the image, x and y set the position, and this is the required image observer.
  • Adding width and height scales the image to a specified size while x and y still control where it starts.
  • The eight-coordinate form takes destination corners first, then source corners, so the portion you copy and the place you put it are chosen independently.