How to scale a BufferedImage

117,950

Solution 1

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

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

Using imgscalr – Java Image Scaling Library:

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

https://github.com/rkalla/imgscalr

Solution 4

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 5

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

Share:
117,950
Thiago Diniz
Author by

Thiago Diniz

Ruby Developer. Former CEO Eventick, Former Product Manager of Codemedia, Former CTO Atela and AgendaRecife. A mac user, but linux enthusiast, specially debian distros. A long a go used to be a Java developer, now I am Ruby enthusiast, mainly using rails framework. Member of FrevoOnRails, a ruby user group.

Updated on July 09, 2022

Comments

  • Thiago Diniz
    Thiago Diniz almost 2 years

    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?

  • Thiago Diniz
    Thiago Diniz over 13 years
    I tried this way, but getScaledInstance returns ToolkitImage, and for my purpose it doesn't fit to me. thanks
  • Bozho
    Bozho over 13 years
    you can convert it to a BufferedImage by copying its raster to a new BufferedImage. Serach for 'convert image to bufferedimage'
  • Martijn Courteaux
    Martijn Courteaux almost 13 years
    Is it really needed to allocate all the memory for after, when you have a statement like: after = ...?
  • trashgod
    trashgod almost 13 years
    @Martijn: It depends on which ColorModel you want in filter(). It's returning a reference, so there's no extra memory.
  • Drazen Bjelovuk
    Drazen Bjelovuk over 10 years
    Is there any way to do this with a desired width and height instead of scale factor?
  • trashgod
    trashgod over 10 years
    Yes, the scale parameters are just the ratios of new to old for x and y, respectively; keep them equal to preserve the aspect ratio.
  • ceklock
    ceklock about 10 years
    I think getScaledInstance() is faster nowadays, at least if you have a decent graphics card, thanks to the optimized Java2D rendering pipeline.
  • zyamys
    zyamys about 10 years
    Agreed, this is the best solution and avoids all the problems with transparency, wrong translations, wrong colors, etc when using affinetransform and the various other methods.
  • rustyx
    rustyx over 9 years
    FYI see here other possible values for RenderingHints.KEY_INTERPOLATION
  • Tomáš Zato
    Tomáš Zato over 9 years
    How fast is this? I need some fast calculations for computer vision.
  • trashgod
    trashgod over 9 years
    @TomášZato: You may need to profile a representative sample.
  • xtempore
    xtempore over 8 years
    A tiny tweak, because 1 line is better than 2... AffineTransform at = AffineTransform.getScaleInstance(2.0, 2.0);
  • Artem
    Artem over 8 years
    This is an Excellent library! Thumbnails are just amazing compare to Graphics2D
  • Shadoninja
    Shadoninja about 8 years
    Awesome! First solution I have seen on this thread that got me what I needed.
  • MiguelMunoz
    MiguelMunoz over 6 years
    I haven't tried this code, but I'm not sure if it will work. You're creating a BufferedImage called "after" that has the same dimensions as the original. You may need to scale the dimensions for the new image. But, again, I'm not sure about this. scaleOp.filter() may take care of that for you.
  • MiguelMunoz
    MiguelMunoz over 6 years
    I just tested it. As I suspected, after is the same size, and it's just the top left quarter of the original. The fix is to multiply w and h by the scale when creating after.
  • MiguelMunoz
    MiguelMunoz over 6 years
    I tried this code, but I didn't get the result shown. The result I got was much more heavily aliased. If you zoom in on the first image in your browser, till you get one about the same size as the second one, you'll get a better idea of what this code produces. (I tried putting the resulting image in this comment, but it didn't work. I guess images aren't allowed in comments.)
  • aioobe
    aioobe over 6 years
    Could you try grph.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolation); where interpolation is RenderingHints.VALUE_INTERPOLATION_... for example VALUE_INTERPOLATION_BICUBIC?
  • Thiago Diniz
    Thiago Diniz about 5 years
    @MiguelMunoz this code is pretty old, I haven't tested your answer, let me get more feedback then I can change the answer.
  • trashgod
    trashgod about 5 years
    @ThiagoDiniz: The fragment is minimal, but correct; this related answer illustrates resampling the result to optimize cropping.
  • user2543253
    user2543253 over 4 years
    Can you also recommend something that gives smooth results when scaling down? getScaledInstance with Image.SCALE_SMOOTH does but is incredibibly slow as everyone knows. Everything else I tried (including AffineTransformOp and drawing with a transformation applied with any combination of RenderingHints) gives me jagged edges.
  • MiguelMunoz
    MiguelMunoz over 4 years
    Okay, I'm going to suggest something, but I have no idea if it will work well, or if it will be any faster. Try doing a two-stage scaling, where the first stage is an integral scale. So, if you need to scale by a factor of 1/3.4, take the reciprocal (3.4) and truncate it to an integer. This gives us 3. So scale down by a factor of 3 in the first stage. Then go the rest of the way in the second stage. (This is just an educated guess, but it's the first thing I would try.) You might also look for some 3rd-party library that has good scaling methods. (Some have been mentioned on this page.)
  • ATutorMe
    ATutorMe over 3 years
    Great library! Fits in nicely with Kotlin as well. Also seems more up-to-date than some of the other options.
  • Maia
    Maia about 3 years
    This helped me a lot without changing the library
  • Ali Farooq
    Ali Farooq almost 2 years
    this solution worked for me. didn't even bother with the other ones.