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.
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
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.
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.
- 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
JComponentcontaining 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.
g.drawImage(img, x, y, this);Image drawn at its natural size, placed at x and y
g.drawImage(img, x, y, 200, 100, this);Image scaled to 200 by 100 and placed at the same x and y
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
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.
- 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
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?
- 1Identify 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.
- 2Identify 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.
- 3Compare 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.
- 4Check the observer
The final argument, this, is the image observer, passed as the last parameter just as in the basic form.
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 likeAdding 400, 300 to the call and assuming the image shifts right and down.
Why it failsWidth and height set the drawn size. The position is still set by x and y.
Do this insteadCheck 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 likeCalling drawImage(img, x, y, this) and expecting a specific size.
Why it failsThe basic form supplies no size, so the image is placed at its own size.
Do this insteadAdd 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 likePassing the source corners first, so the destination values are read as the region of the image.
Why it failsThe eight-coordinate form lists destination coordinates first, then source coordinates.
Do this insteadWrite 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 likeWriting the call without this as the last argument.
Why it failsThe source describes the image observer as required in the forms it shows, and it is passed as the final parameter.
Do this insteadKeep 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
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.
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
- 1The Graphics object does the copying. Your code, usually inside
paintComponent()of aJComponent, controls the call. - 2The 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. - 3The 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.
- 4The 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.
- 5When 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 aJComponent. - 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.