Scaling Images
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 a Simple Image Draw
Java programs often need to show an image on screen. The question this article answers is how a single drawing statement decides where an image appears and how large it looks. The method that does the work is drawImage(), which belongs to the Graphics class. Rather than explain everything up front, we will start from the simplest form of the call, guess what it does, and then test that guess by changing the call one piece at a time.
What do you think happens?
Given the basic call g.drawImage(img, x, y, this), what do you think determines where the image lands and how big it appears? Write down your guess before reading on.
Where the Copying Actually Happens
An image in a Java program is not drawn by the image itself. A method belonging to the Graphics class copies the image onto a drawing surface. 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 a Graphics object arrives as a parameter for you to use.
The flow of control runs like this. The paintComponent() method is the caller. It hands its Graphics object to the drawing statement, and the Graphics object carries out the copying onto whatever surface it is attached to. Your code decides the call and its arguments, while the copying itself is performed through the Graphics object.
paintComponentreceives the Graphics object g as a parameter- g is the object that performs the copying onto the component
- this refers to the
JComponentthat contains this drawing code
Parameters of the Basic drawImage() Call
Each piece of the basic call has a single 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 describes the image observer as something required when an image is drawn, and it is passed as the final parameter. In the source's examples, this is the JComponent that contains the drawing code.
Notice what the call does not include. It does not return a new image, and it does not specify a size. It only asks for the image to be placed at a location on the drawing surface. The position argument is the one that answers the question of where on the component the image appears.
Adding Width and Height
The drawImage() method has useful variations. One lets you scale 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. Compared with the basic call, the position is still set by x and y, but the image is no longer drawn at whatever size it has. Its drawn size is set by width and height.
- 10, 20 is the position, unchanged from the basic call
- 200 is the width the image is scaled to
- 25 is the height the image is scaled to
- this is still the image observer, passed last
Working through the example: the width grows from 100 to 200, so the drawn image is stretched horizontally by a factor of two. The height shrinks from 50 to 25, so the drawn image is compressed vertically by a factor of one half. Each dimension is set independently by its own value, which is why the two changes can point in opposite directions.
What do you think happens?
If you keep the same position but change only the width from 200 to 400 in g.drawImage(img, 10, 20, 400, 25, this), what happens to the drawn size?
Reveal answer
The drawn width becomes 400, and the height stays at 25
The width and height are separate arguments. Changing one changes only that dimension, and the position stays at 10, 20.
Trace the Call: Position, Then Size
Tracing a scaled draw
Trace what happens when paintComponent() runs g.drawImage(img, 30, 40, 150, 75, this), where img is 100 by 50 pixels.
- 1Identify the Graphics object
g is the Graphics object passed into
paintComponent(). It performs the copying onto the component. - 2Read the position
The values 30 and 40 give the position. The image's placement point is at x = 30, y = 40 on the drawing surface.
- 3Read the size
The values 150 and 75 are the width and height the image is scaled to as it is drawn. The original 100 by 50 size no longer determines the drawn size.
- 4Read the observer
this is the final argument and the image observer, which is the
JComponentcontaining the code.
The image appears with its upper-left point at (30, 40) and is drawn 150 units wide and 75 units tall. Its width grew by a factor of 1.5 and its height grew by a factor of 1.5 relative to the original 100 by 50.
Selecting a Region with Eight Coordinates
The most detailed variant of drawImage() takes eight coordinates instead of a position and size. The source describes it 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 source coordinates are what matter most when you want only part of an image. They pick out a rectangle inside the original image, and only the pixels inside that rectangle are copied. The destination coordinates then determine the rectangle on the drawing surface that receives those pixels. Because the two rectangles are specified separately, the portion you take and the place you put it are independent choices.
- 0, 0, 200, 100 are the destination corners on the component
- 10, 10, 60, 40 are the source corners inside the image
- this is the observer, still passed last
In this call the source rectangle is 50 units wide and 30 tall, taken from the region starting at (10, 10) in the original image. The destination rectangle is 200 units wide and 100 tall, starting at (0, 0) on the component. The source and destination rectangles do not need to match in size, so the selected portion is stretched to fill the destination.
Mistakes That Produce Unexpected Results
When a drawn image is not what you expected, the cause is usually one argument in one call. The list below describes common places where the result departs from the plan. Read each entry as a question to ask about your own code: at which argument did my expectation and the call stop agreeing?
Common mistake Expecting the image to keep its original size after adding width and height
What it looks likeg.drawImage(img, 10, 20, 200, 25, this) on a 100 by 50 imageWhy it failsThe width and height values set the drawn size, so the image is scaled as it is drawn rather than kept at its original dimensions.
Do this insteadCheck the width and height arguments first when the image looks stretched or squashed.
Common mistake Passing the position values where the size values belong
What it looks likeWriting the size as if it were a second position in the call
Why it failsThe scaled form inserts two size values after the position values, so each number's role depends on its place in the argument list.
Do this insteadCount the arguments: g, img, x, y, then width and height, then the observer.
Common mistake Forgetting the image observer or placing it in the wrong position
Why it failsThe source describes the image observer as required and passed as the final parameter.
Do this insteadKeep this as the last argument in every form of the call.
Common mistake Confusing the source rectangle with the destination rectangle in the eight-coordinate form
What it looks likeUsing coordinates from the component where coordinates from the original image were needed
Why it failsThe source pair picks a region of the original image, while the destination pair picks a region on the drawing surface, so swapping them copies the wrong part of the image to the wrong place.
Do this insteadLabel the four source values and four destination values before writing the call.
- Blank result: check that the Graphics object g is the one passed into
paintComponent()and that the drawing happens inside that method. - Offset result: check the x and y position values, since they alone decide where the upper-left placement point sits.
- Distorted result: check the width and height values, since they decide the drawn size in each dimension independently.
- Partial region wrong: check the source corners against the original image and the destination corners against the drawing surface.
Practice Tracing Your Own Call
A 100 by 50 image is drawn with g.drawImage(img, 5, 5, 100, 100, this). Describe the position and the drawn size of the image, and say in which dimension the image is stretched and in which it is unchanged. Then state which argument you would check first if the image appeared shifted to the right.
Hints
- The position is the pair immediately after the image argument.
- Compare the width and height values to the original 100 by 50 size.
- The position arguments decide where the upper-left point sits on the component.
Write an eight-coordinate drawImage() call that copies the 40 by 40 region starting at (20, 20) in the original image and draws it into the component's top-left 80 by 80 area. Label each of the four source values and the four destination values in a comment before you write the call.
Hints
- The source corners are (20, 20) and (60, 60), since the region is 40 units on each side.
- The destination corners are (0, 0) and (80, 80).
Key Takeaways
drawImage()copies an image onto a drawing surface through a Graphics object, most often insidepaintComponent()of aJComponent.- In the basic call
drawImage(img, x, y, this), the x and y values set the position, and the final argument is the image observer. - Adding width and height after the position scales the image as it is drawn, and each dimension is set independently of the other.
- The eight-coordinate form takes destination corners and then source corners, so it selects a rectangle from the original image and places it on a separate destination rectangle.
- When a result is unexpected, check the argument that controls that aspect: position for placement, width and height for size, and the source and destination corners for partial regions.