25

The task: I have some images, I scale them down, and join them to one image. But I have a little problem with the implementation:

The concrete problem: I want to resize/scale a BufferedImage. The getScaledInstance method returns an Image object, but I can't cast it to BufferedImage:

Exception in thread "main" java.lang.ClassCastException: sun.awt.image.ToolkitImage cannot be cast to java.awt.image.BufferedImage

(I don't know why is it a ToolkitImage instead of an Image...)

I found a solution:

Image tmp = bi.getScaledInstance(SMALL_SIZE, SMALL_SIZE, BufferedImage.SCALE_FAST);
BufferedImage buffered = new BufferedImage(SMALL_SIZE,SMALL_SIZE,BufferedImage.TYPE_INT_RGB);
buffered.getGraphics().drawImage(tmp, 0, 0, null);

But it's slow, and I think there should be a better way to do it.

I need the BufferedImage, because I have to get the pixels to join the small images.

Is there a better (nicer/faster) way to do it?

EDIT: If I cast the Image first to ToolkitImage, it has a getBufferedImage() method. But it always returns null. Do you know why?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
tamas.pflanzner
  • 325
  • 1
  • 6
  • 11
  • 1
    Please have a look at this article: [The Perils of Image.getScaledInstance()](https://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html) as well as the answers to [this question](http://stackoverflow.com/questions/1625137/image-resize-quality-java). – Hovercraft Full Of Eels May 11 '13 at 14:10
  • Also, [this question](http://stackoverflow.com/questions/4216123/how-to-scale-a-bufferedimage) contains answers that explain how to resize a BufferedImage. – Anderson Green Jun 11 '13 at 01:42

6 Answers6

35

The Graphics object has a method to draw an Image while also performing a resize operation:

Graphics.drawImage(Image, int, int, int, int, ImageObserver) method can be used to specify the location along with the size of the image when drawing.

So, we could use a piece of code like this:

BufferedImage otherImage = // .. created somehow
BufferedImage newImage = new BufferedImage(SMALL_SIZE, SMALL_SIZE, BufferedImage.TYPE_INT_RGB);

Graphics g = newImage.createGraphics();
g.drawImage(otherImage, 0, 0, SMALL_SIZE, SMALL_SIZE, null);
g.dispose();

This will take otherImage and draw it on the newImage with the width and height of SMALL_SIZE.


Or, if you don't mind using a library, Thumbnailator could accomplish the same with this:

BufferedImage newImage = Thumbnails.of(otherImage)
                             .size(SMALL_SIZE, SMALL_SIZE)
                             .asBufferedImage();

Thumbnailator will also perform the resize operation quicker than using Image.getScaledInstance while also performing higher quality resize operations than using only Graphics.drawImage.

Disclaimer: I am the maintainer of the Thumbnailator library.

coobird
  • 159,216
  • 35
  • 211
  • 226
  • @coobird First of all, thaks for the library, I'll try it, but if it possible I'd like to solve this problem with standard java. – tamas.pflanzner May 12 '13 at 20:29
  • Is it possible to have a better quality image with Thumbnailator ? Graphics ruins the image and won't even show after 300 pixels .. – Cugomastik Aug 15 '14 at 11:14
  • I need to draw some shapes multiple times over the scaled original image and this code is perfect for this! – croraf Mar 19 '16 at 11:37
  • @JFreeman The original question was about resizing a `BufferedImage` to end up with a `BufferedImage` ;) – coobird Nov 05 '20 at 16:23
14

You can also use OpenCV Java library. It's resize operation is faster than Imgscalr's:

Test

Image 5184 x 3456 scaled to 150 x 100 (this is the smaller version because original file is bigger than 2mb): enter image description here

Imgscalr

Dependency:

<dependency>
    <groupId>org.imgscalr</groupId>
    <artifactId>imgscalr-lib</artifactId>
    <version>4.2</version>
</dependency>

Code:

BufferedImage thumbnail = Scalr.resize(img,
            Scalr.Method.SPEED,
            Scalr.Mode.AUTOMATIC,
            150,
            100);

Result image:

enter image description here

Average time: 80 millis

OpenCV

Dependency:

<dependency>
    <groupId>nu.pattern</groupId>
    <artifactId>opencv</artifactId>
    <version>2.4.9-4</version>
</dependency>

Convert BufferedImage to Mat object (have to):

BufferedImage img = ImageIO.read(image); // load image
byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer())
            .getData();
Mat matImg = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
matImg.put(0, 0, pixels);

Code:

Imgproc.resize(matImg, resizeimage, sz);

Additional configuration (for windows):

Add opencv_java249.dll to your JDK's bin directory.

Result image:

enter image description here

Average time: 13 millis

Overall Results

In the test just "resize" functions times are calculated. Imgscalr resized the given image in 80 milis where OpenCV done the same task in 13 millis. You can find the whole project below here to play around of it a little bit.

As you asked also easy way, if the performance of the Imgscalr library is good for you then it is deadly-easy. Because to use OpenCV as you see a library file must be located at your all development environments and servers. Also you have to use Mat objects.

Whole Project

Pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.btasdemir</groupId>
    <artifactId>testapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>testapp</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.imgscalr</groupId>
            <artifactId>imgscalr-lib</artifactId>
            <version>4.2</version>
        </dependency>

        <dependency>
            <groupId>nu.pattern</groupId>
            <artifactId>opencv</artifactId>
            <version>2.4.9-4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin> 
                <groupId>org.bytedeco</groupId> 
                <artifactId>javacpp</artifactId> 
                <version>0.9</version> 
            </plugin>
        </plugins>
    </build>

</project>

App.java:

package com.btasdemir.testapp;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.imgscalr.Scalr;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;


/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args ) throws IOException
    {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

        File image = new File("C:\\your_dir\\test.jpg");
        BufferedImage img = ImageIO.read(image); // load image
        long startTime = System.currentTimeMillis();//imgscalr------------------------------------------------------
        //resize to 150 pixels max
        BufferedImage thumbnail = Scalr.resize(img,
                Scalr.Method.SPEED,
                Scalr.Mode.AUTOMATIC,
                150,
                100);
//      BufferedImage thumbnail = Scalr.resize(img,
//                Scalr.Method.SPEED,
//                Scalr.Mode.AUTOMATIC,
//                150,
//                100,
//                Scalr.OP_ANTIALIAS);
        System.out.println(calculateElapsedTime(startTime));//END-imgscalr------------------------------------------------------
        File outputfile = new File("C:\\your_dir\\imgscalr_result.jpg");
        ImageIO.write(thumbnail, "jpg", outputfile);


        img = ImageIO.read(image); // load image
        byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer())
                .getData();
        Mat matImg = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
        matImg.put(0, 0, pixels);

        Mat resizeimage = new Mat();
        Size sz = new Size(150, 100);
        startTime = System.currentTimeMillis();//opencv------------------------------------------------------

        Imgproc.resize(matImg, resizeimage, sz);
//      Imgproc.resize(matImg, resizeimage, sz, 0.5, 0.5, Imgproc.INTER_CUBIC);

        System.out.println(calculateElapsedTime(startTime));//END-opencv------------------------------------------------------
        Highgui.imwrite("C:\\your_dir\\opencv_result.jpg", resizeimage);


    }

    protected static long calculateElapsedTime(long startTime) {
        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        return elapsedTime;
    }
}
Bahadir Tasdemir
  • 10,325
  • 4
  • 49
  • 61
  • Thank you very much! Great post. I found and am using the Image Scalr library, easy to download the jar off google, but would be nice if you post a link to it :) – JFreeman Mar 16 '19 at 06:48
9

I get it with this method, it resizes the Image and tries to maintain the proportions:

/**
* Resizes an image using a Graphics2D object backed by a BufferedImage.
* @param srcImg - source image to scale
* @param w - desired width
* @param h - desired height
* @return - the new resized image
*/
private BufferedImage getScaledImage(BufferedImage src, int w, int h){
    int finalw = w;
    int finalh = h;
    double factor = 1.0d;
    if(src.getWidth() > src.getHeight()){
        factor = ((double)src.getHeight()/(double)src.getWidth());
        finalh = (int)(finalw * factor);                
    }else{
        factor = ((double)src.getWidth()/(double)src.getHeight());
        finalw = (int)(finalh * factor);
    }   

    BufferedImage resizedImg = new BufferedImage(finalw, finalh, BufferedImage.TRANSLUCENT);
    Graphics2D g2 = resizedImg.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2.drawImage(src, 0, 0, finalw, finalh, null);
    g2.dispose();
    return resizedImg;
}
digolloco
  • 101
  • 1
  • 3
6

None of these answers were fast enough for me. So I finally programmed my own procedure.

static BufferedImage scale(BufferedImage src, int w, int h)
{
  BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
  int x, y;
  int ww = src.getWidth();
  int hh = src.getHeight();
  for (x = 0; x < w; x++) {
    for (y = 0; y < h; y++) {
      int col = src.getRGB(x * ww / w, y * hh / h);
      img.setRGB(x, y, col);
    }
  }
  return img;
}
Victor
  • 893
  • 1
  • 10
  • 25
  • note that if for example the `src` image is black & white stripes and `w` & `h` are `2w=2ww` and `2h=2hh` then result is black image. This happens because the above algo take every 2nd pixel so it does not take any white pixel. In order to avoid it it necessary to filter high freq of the image – oak Nov 22 '17 at 11:10
  • 1
    Yes, there is an aliasing issue with this approach, ok if speed over quality. – Daniel Hári Nov 25 '17 at 18:51
  • 1
    Would it be worth precaclulating ww/w and hh/h before the loops (the values don't change in the loop) if speed is your only goal? – Old Nick Jan 23 '18 at 13:53
3

Maybe this method will help:

public  BufferedImage resizeImage(BufferedImage image, int width, int height) {
         int type=0;
        type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
        BufferedImage resizedImage = new BufferedImage(width, height,type);
        Graphics2D g = resizedImage.createGraphics();
        g.drawImage(image, 0, 0, width, height, null);
        g.dispose();
        return resizedImage;
     }

Don't forget those "import" lines:

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

And about casting:

The abstract class Imageis the superclass of all classes that represent graphical images. We can't cast Image to BufferedImage because every BufferedImage is Image but vice versa is not true.

Image im = new BufferedImage(width, height, imageType);//this is true

BufferedImage img = new Image(){//.....}; //this is wrong
Bahadir Tasdemir
  • 10,325
  • 4
  • 49
  • 61
Azad
  • 5,047
  • 20
  • 38
  • Thank you for your answer. If it possible, I'd like to completly skip the drawing. Is it possible to get pixel color data from an Image? – tamas.pflanzner May 12 '13 at 20:32
  • @tamas.pflanzner can you see [this post](http://stackoverflow.com/questions/6524196/java-get-pixel-array-from-image), I think it's about getting pixel color from an `Image` – Azad May 12 '13 at 20:41
  • Are you sure? I think it's about getting pixel from a BufferedImage :( – tamas.pflanzner May 13 '13 at 13:04
1
public static double[] reduceQuality(int quality, int width, int height) {
    if(quality >= 1 && quality <= 100) {
        double[] dims = new double[2];
        dims[0] = width * (quality/100.0);
        dims[1] = height * (quality/100.0);
        return dims;
    } else if(quality > 100) {
        return new double[] { width, height };
    } else {
        return new double[] { 1, 1 };
    }
}

public static byte[] resizeImage(byte[] data, int width, int height) throws Exception {
    BufferedImage bi = ImageIO.read(new ByteArrayInputStream(data));
    BufferedImage bo = resizeImage(bi, width, height);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ImageIO.write(bo, "jpg", bos);
    bos.close();
    return bos.toByteArray();
}

private static BufferedImage resizeImage(BufferedImage buf, int width, int height) {
    final BufferedImage bufImage = new BufferedImage(width, height, 
            (buf.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB
            : BufferedImage.TYPE_INT_ARGB));
    final Graphics2D g2 = bufImage.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
    g2.drawImage(buf, 0, 0, width, height, null);
    g2.dispose();
    return bufImage;
}

This is taken straight from imgscalr at https://github.com/rkalla/imgscalr/blob/master/src/main/java/org/imgscalr/Scalr.java

My average time reducing the quality of an image of 8mb with dimensions of 5152x3864 was ~800ms.

No dependencies. I hate them. Sometimes.

THIS WILL ONLY WORK WITH jpg IMAGES. As far as I'm concerned.

Example:

byte[] of = Files.readAllBytes(Paths.get("/home/user/Pictures/8mbsample.jpg"));
    double[] wh = ImageUtil.reduceQuality(2, 6600, 4950);

    long start = System.currentTimeMillis();
    byte[] sof = ImageUtil.resizeImage(of, (int)wh[0], (int)wh[1]);
    long end = System.currentTimeMillis();

    if(!Files.exists(Paths.get("/home/user/Pictures/8mbsample_scaled.jpg"))) {
        Files.createFile(Paths.get("/home/user/Pictures/8mbsample_scaled.jpg"), Util.getFullPermissions());
    }

    FileOutputStream fos = new FileOutputStream("/home/user/Pictures/8mbsample_scaled.jpg");
    fos.write(sof); fos.close();

    System.out.println("Process took: " + (end-start) + "ms");

Output:

Process took: 783ms
VocoJax
  • 1,469
  • 1
  • 13
  • 19