How to scale a BufferedImage

JavaImageImage ProcessingBufferedimageImage Scaling

Java Problem Overview


Following the javadocs, I have tried to scale a BufferedImage without success here is my code:

BufferedImage image = MatrixToImageWriter.getBufferedImage(encoded);
Graphics2D grph = image.createGraphics();
grph.scale(2.0, 2.0);
grph.dispose();

I can't understand why it is not working, any help?

Java Solutions


Solution 1 - Java

AffineTransformOp offers the additional flexibility of choosing the interpolation type.

BufferedImage before = getBufferedImage(encoded);
int w = before.getWidth();
int h = before.getHeight();
BufferedImage after = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
AffineTransform at = new AffineTransform();
at.scale(2.0, 2.0);
AffineTransformOp scaleOp = 
   new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
after = scaleOp.filter(before, after);

The fragment shown illustrates resampling, not cropping; this related answer addresses the issue; some related examples are examined here.

Solution 2 - Java

Unfortunately the performance of getScaledInstance() is very poor if not problematic.

The alternative approach is to create a new BufferedImage and and draw a scaled version of the original on the new one.

BufferedImage resized = new BufferedImage(newWidth, newHeight, original.getType());
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
   	RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, newWidth, newHeight, 0, 0, original.getWidth(),
   	original.getHeight(), null);
g.dispose();

newWidth,newHeight indicate the new BufferedImage size and have to be properly calculated. In case of factor scaling:

int newWidth = new Double(original.getWidth() * widthFactor).intValue();
int newHeight = new Double(original.getHeight() * heightFactor).intValue();

EDIT: Found the article illustrating the performance issue: The Perils of Image.getScaledInstance()

Solution 3 - Java

Using imgscalr – Java Image Scaling Library:

BufferedImage image =
     Scalr.resize(originalImage, Scalr.Method.BALANCED, newWidth, newHeight);

This is fast enough for me.

Solution 4 - Java

As @Bozho says, you probably want to use getScaledInstance.

To understand how grph.scale(2.0, 2.0) works however, you could have a look at this code:

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;


class Main {
    public static void main(String[] args) throws IOException {
        
        final int SCALE = 2;
        
        Image img = new ImageIcon("duke.png").getImage();
        
        BufferedImage bi = new BufferedImage(SCALE * img.getWidth(null),
                                             SCALE * img.getHeight(null),
                                             BufferedImage.TYPE_INT_ARGB);
        
        Graphics2D grph = (Graphics2D) bi.getGraphics();
        grph.scale(SCALE, SCALE);
        
        // everything drawn with grph from now on will get scaled.
        
        grph.drawImage(img, 0, 0, null);
        grph.dispose();
        
        ImageIO.write(bi, "png", new File("duke_double_size.png"));
    }
}

Given duke.png:
enter image description here

it produces duke_double_size.png:
enter image description here

Solution 5 - Java

To scale an image, you need to create a new image and draw into it. One way is to use the filter() method of an AffineTransferOp, as suggested here. This allows you to choose the interpolation technique.

private static BufferedImage scale1(BufferedImage before, double scale) {
	int w = before.getWidth();
	int h = before.getHeight();
	// Create a new image of the proper size
	int w2 = (int) (w * scale);
	int h2 = (int) (h * scale);
	BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
	AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
	AffineTransformOp scaleOp 
		= new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);
	
	scaleOp.filter(before, after);
	return after;
}

Another way is to simply draw the original image into the new image, using a scaling operation to do the scaling. This method is very similar, but it also illustrates how you can draw anything you want in the final image. (I put in a blank line where the two methods start to differ.)

private static BufferedImage scale2(BufferedImage before, double scale) {
	int w = before.getWidth();
	int h = before.getHeight();
	// Create a new image of the proper size
	int w2 = (int) (w * scale);
	int h2 = (int) (h * scale);
	BufferedImage after = new BufferedImage(w2, h2, BufferedImage.TYPE_INT_ARGB);
	AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
	AffineTransformOp scaleOp 
		= new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);
	
	Graphics2D g2 = (Graphics2D) after.getGraphics();
	// Here, you may draw anything you want into the new image, but we're
	// drawing a scaled version of the original image.
	g2.drawImage(before, scaleOp, 0, 0);
	g2.dispose();
	return after;
}

Addendum: Results

To illustrate the differences, I compared the results of the five methods below. Here is what the results look like, scaled both up and down, along with performance data. (Performance varies from one run to the next, so take these numbers only as rough guidelines.) The top image is the original. I scale it double-size and half-size.

As you can see, AffineTransformOp.filter(), used in scaleBilinear(), is faster than the standard drawing method of Graphics2D.drawImage() in scale2(). Also BiCubic interpolation is the slowest, but gives the best results when expanding the image. (For performance, it should only be compared with scaleBilinear() and scaleNearest().) Bilinear seems to be better for shrinking the image, although it's a tough call. And NearestNeighbor is the fastest, with the worst results. Bilinear seems to be the best compromise between speed and quality. The Image.getScaledInstance(), called in the questionable() method, performed very poorly, and returned the same low quality as NearestNeighbor. (Performance numbers are only given for expanding the image.)

enter image description here

public static BufferedImage scaleBilinear(BufferedImage before, double scale) {
	final int interpolation = AffineTransformOp.TYPE_BILINEAR;
	return scale(before, scale, interpolation);
}

public static BufferedImage scaleBicubic(BufferedImage before, double scale) {
	final int interpolation = AffineTransformOp.TYPE_BICUBIC;
	return scale(before, scale, interpolation);
}

public static BufferedImage scaleNearest(BufferedImage before, double scale) {
	final int interpolation = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
	return scale(before, scale, interpolation);
}

@NotNull
private static 
BufferedImage scale(final BufferedImage before, final double scale, final int type) {
	int w = before.getWidth();
	int h = before.getHeight();
	int w2 = (int) (w * scale);
	int h2 = (int) (h * scale);
	BufferedImage after = new BufferedImage(w2, h2, before.getType());
	AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
	AffineTransformOp scaleOp = new AffineTransformOp(scaleInstance, type);
	scaleOp.filter(before, after);
	return after;
}

/**
 * This is a more generic solution. It produces the same result, but it shows how you 
 * can draw anything you want into the newly created image. It's slower 
 * than scaleBilinear().
 * @param before The original image
 * @param scale The scale factor
 * @return A scaled version of the original image
 */
private static BufferedImage scale2(BufferedImage before, double scale) {
	int w = before.getWidth();
	int h = before.getHeight();
	// Create a new image of the proper size
	int w2 = (int) (w * scale);
	int h2 = (int) (h * scale);
	BufferedImage after = new BufferedImage(w2, h2, before.getType());
	AffineTransform scaleInstance = AffineTransform.getScaleInstance(scale, scale);
	AffineTransformOp scaleOp
			= new AffineTransformOp(scaleInstance, AffineTransformOp.TYPE_BILINEAR);

	Graphics2D g2 = (Graphics2D) after.getGraphics();
	// Here, you may draw anything you want into the new image, but we're just drawing
	// a scaled version of the original image. This is slower than 
	// calling scaleOp.filter().
	g2.drawImage(before, scaleOp, 0, 0);
	g2.dispose();
	return after;
}

/**
 * I call this one "questionable" because it uses the questionable getScaledImage() 
 * method. This method is no longer favored because it's slow, as my tests confirm.
 * @param before The original image
 * @param scale The scale factor
 * @return The scaled image.
 */
private static Image questionable(final BufferedImage before, double scale) {
	int w2 = (int) (before.getWidth() * scale);
	int h2 = (int) (before.getHeight() * scale);
	return before.getScaledInstance(w2, h2, Image.SCALE_FAST);
}

Solution 6 - Java

If you do not mind using an external library, Thumbnailator can perform scaling of BufferedImages.

Thumbnailator will take care of handling the Java 2D processing (such as using Graphics2D and setting appropriate rendering hints) so that a simple fluent API call can be used to resize images:

BufferedImage image = Thumbnails.of(originalImage).scale(2.0).asBufferedImage();

Although Thumbnailator, as its name implies, is geared toward shrinking images, it will do a decent job enlarging images as well, using bilinear interpolation in its default resizer implementation.


Disclaimer: I am the maintainer of the Thumbnailator library.

Solution 7 - Java

scale(..) works a bit differently. You can use bufferedImage.getScaledInstance(..)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThiago DinizView Question on Stackoverflow
Solution 1 - JavatrashgodView Answer on Stackoverflow
Solution 2 - JavacharisisView Answer on Stackoverflow
Solution 3 - JavaceklockView Answer on Stackoverflow
Solution 4 - JavaaioobeView Answer on Stackoverflow
Solution 5 - JavaMiguelMunozView Answer on Stackoverflow
Solution 6 - JavacoobirdView Answer on Stackoverflow
Solution 7 - JavaBozhoView Answer on Stackoverflow