Concepts / Partial Image Drawing

Partial Image Drawing

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 a Simple Image Call

Start with the simplest statement for drawing an image in Java: drawImage(img, x, y, this), called with a Graphics object named g. Read it as a request. It asks for the image named img to be placed at the position x and y, using the Graphics object g. The call does not return a new image and does not ask for a size. It only asks for the image to be placed.

What do you think happens?

Before reading on, decide what determines where the image lands and how big it appears. Write down your guess.

Reveal answer
Answer:

The x and y values determine where the image is placed, and the image is drawn at its own size because the basic call does not specify a size.

The basic form of the call carries only a position. The rest of this article tests guesses like this one: each variant of drawImage() is a change to the same call, so reading one call carefully makes the others easier to read.

Where Image Copying Happens

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.

The control flow works like this. The paintComponent() method is the caller. It hands its Graphics object to the drawing statement, and the Graphics object performs the copying onto whatever surface it is attached to. Your code controls the call, while the copying itself is carried out through the Graphics object.

java
Notes on the code
  • paintComponent receives the Graphics object g as a parameter
  • g.drawImage is the call that copies the image onto the component's drawing surface
  • this refers to the JComponent that contains the drawing code

Parameters of the Basic Call

Each piece of the basic call has its own job. The first parameter, 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.

gGraphics object thatperforms the drawingimgthe image being drawnxhorizontal position ofplacementyvertical position ofplacementthisimage observer, required
Which part of the drawImage() call identifies the drawing surface, the image, the destination position, and the observer, and what does each one do?

The image observer deserves attention because beginners often treat it as optional decoration. The source describes the image observer as required when an image is drawn, and it is passed as the final parameter of drawImage(). In the examples in the source, this is used as the observer, which is the JComponent that contains the drawing code. The same final argument appears in every form of the method discussed here.

Scaling With Width and Height

One variant of drawImage() scales the image as it is drawn, so that it fills a width and height you specify. The form inserts two size values after the position values, which gives the pattern drawImage(img, x, y, width, height, this). Compare this with the basic 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.

What do you think happens?

If you call g.drawImage(img, 10, 20, 200, 100, this) instead of g.drawImage(img, 10, 20, this), does the image move?

  • Yes, it moves to a new position

  • No, it keeps the same position and only its drawn size changes

Reveal answer
Answer:

No, it keeps the same position and only its drawn size changes.

Scaling changes only the size at which the image is placed. The position is still defined by x and y, so the upper-left placement point stays where you put it, and the image grows or shrinks from that starting location. Position and size are separate arguments that answer separate questions.

java
Output
The first call places the image at (10, 20) at its original size. The second call places it at the same point, stretched or shrunk to 200 by 100.
Notes on the code
  • The x and y values are identical in both calls
  • Only the two size values differ, so only the drawn size changes

Eight Coordinates: Source and Destination

The most detailed variant of drawImage() takes eight coordinates instead of a position and size. It 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.

source rectanglesx1, sy1 to sx2, sy2 insidethe original imagedestination rectangledx1, dy1 to dx2, dy2 on thedrawing surfacecopied and scaled
How do the four source coordinates pick a rectangle inside the original image, and how do the four destination coordinates place and resize that rectangle on screen?
java
Notes on the code
  • Destination corners (0,0) and (150,150) define the place and size on the surface
  • Source corners (40,40) and (140,140) select a 100 by 100 region of the original image
  • The 100 by 100 region is stretched to fill the 150 by 150 destination
Worked example

Cropping and placing part of an image

An image is 300 pixels wide and 300 pixels tall. You want to draw only its central 100 by 100 region, placed at the top left corner of the component and enlarged to 200 by 200.

  1. 1
    Identify the source rectangle

    The central 100 by 100 region runs from (100, 100) to (200, 200) in the original image, so the source corners are sx1 = 100, sy1 = 100, sx2 = 200, sy2 = 200.

  2. 2
    Identify the destination rectangle

    The top left placement is (0, 0) and the enlarged size is 200 by 200, so the destination corners are dx1 = 0, dy1 = 0, dx2 = 200, dy2 = 200.

  3. 3
    Assemble the call

    Destination coordinates come first, then source coordinates, then the observer: g.drawImage(img, 0, 0, 200, 200, 100, 100, 200, 200, this).

  4. 4
    Check the independence

    The source region is 100 by 100 and the destination is 200 by 200. The two rectangles do not have to match, so the region is enlarged as it is placed.

Result

Only the central 100 by 100 part of the original image appears, enlarged to fill 200 by 200 at the top left of the drawing surface.

Where Unexpected Results Come From

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

drawImage callarguments passed frompaintComponentsource coordinateswhich region of the imageis takendestinationcoordinateswhere and at what rectangleit landsscalingsource region fitted todestination sizerendered outputwhat appears on thecomponent
In the sequence from call to rendered output, which step (source coordinates, destination coordinates, or scaling) is most likely to produce a wrong-looking result?
  • Common mistake

    Expecting the position values to change the image size

    What it looks like

    Changing x and y from 10, 20 to 50, 60 and expecting the picture to get bigger

    Why it fails

    Position and size are separate arguments. x and y only set where the image is placed.

    Do this instead

    Change width and height to change the drawn size, and change x and y to move the image.

  • Common mistake

    Assuming the source rectangle must match the destination rectangle

    What it looks like

    Taking a 100 by 100 source region and expecting it to appear at 100 by 100 on the surface

    Why it fails

    The destination rectangle is not required to match the source rectangle, so the region is stretched or shrunk to fit.

    Do this instead

    Check both rectangles separately, and compute the destination size you actually want.

  • Common mistake

    Reversing the coordinate order in the eight-argument form

    What it looks like

    Writing the source corners before the destination corners

    Why it fails

    The eight-coordinate form takes the destination pair first, then the source pair, then the observer.

    Do this instead

    Write destination coordinates first, source coordinates second, and this last.

  • Common mistake

    Dropping the image observer

    What it looks like

    Writing g.drawImage(img, 10, 20) without the final argument

    Why it fails

    The source describes the image observer as required when an image is drawn.

    Do this instead

    Always pass this, or the JComponent that contains the drawing code, as the final argument.

Practice Reading Drawing Calls

MEDIUM

Given g.drawImage(img, 5, 5, 300, 120, 0, 0, 150, 60, this), state which pixels of the original image are taken, what rectangle they fill on the drawing surface, and whether the image is stretched or shrunk. Then explain what changes if you keep the same source corners but change the destination corners to 5, 5, 150, 60.

Hints
  • The source corners 0, 0, 150, 60 describe a rectangle inside the original image, not on the surface.
  • Compare the width and height of the source rectangle with the destination rectangle.
  • Changing only the destination corners changes where and how large the result is, but not which pixels are taken.

Key Takeaways

  • The basic drawImage(img, x, y, this) call places an image at a position using the Graphics object g, and the final argument is the image observer, which is required.
  • Adding width and height after the position scales the image to that size; the position is still set by x and y, so position and size answer separate questions.
  • The eight-coordinate form takes destination corners, then source corners, then the observer; the source rectangle selects a portion of the original image, and the destination rectangle sets where and how large it is drawn.
  • When a result looks wrong, check the source rectangle first, then the destination rectangle, then the scaling that connects them.