3

I have the following code:

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;


public class JavaApplication
{
    public static void main(String[] args) throws Exception
    {
        File orig = new File ("/home/xxx/Pictures/xxx.jpg");
        BufferedImage bm1 = ImageIO.read(orig);

        Image scaled = bm1.getScaledInstance(100, 200, BufferedImage.SCALE_SMOOTH);
        BufferedImage bm2 = toBufferedImage(scaled);

        File resized = new File ("/home/xxx/Pictures/resized.jpg");
        ImageIO.write(bm2, "jpg", resized);
    }

    public static BufferedImage toBufferedImage(Image img)
    {
        if (img instanceof BufferedImage)
        {
            return (BufferedImage) img;
        }

        BufferedImage bimage = new BufferedImage(img.getWidth(null),       img.getHeight(null), BufferedImage.TYPE_INT_ARGB);

        bimage.getGraphics().drawImage(img, 0, 0 , null);
        return bimage;
      }
}

If I use this code on a .png file, it works fine, and resizes the file as expected. However on jpg files, it results in a black background.

If I remove the getScaledInstance() code and simply try to re-write the original bm1 to the disk using ImageIO.write(bm1, "jpg", resized), that works fine. Only when resizing using getScaledInstance() and then trying to conver the resulting Image back to BufferedImage, do I get a completely black background file.

Any ideas on how to fix this, or what I'm doing wrong?

Ali
  • 261,656
  • 265
  • 575
  • 769
  • It's been too long since I've done image scaling, but I always found this article useful: https://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html. Maybe some of the techniques in there will help? – Ash Oct 29 '13 at 09:22
  • @Ash Can you give me a tl;dr of this? I'm not using `Image.getScaledInstance()` anyway, I'm using `BufferedImage.getScaledInstance()`. – Ali Oct 29 '13 at 09:32
  • Your problem is that you are using `getScaledInstance()`. It returns immediately with an `Image`, *but the image is not yet scaled at this point*. So when you later invoke `drawImage` using this image, it's effectively a no-op. Update: There's no difference if you inkoke `getScaledInstance` on an `Image` or a `BufferedImage`. – Harald K Oct 29 '13 at 09:34
  • @haraldK I see, but this method works for png files. Is it specific to jpgs that it takes longer for the image to scale? Should I just put in a `Thread.sleep(2000)` before doing the `drawImage`? – Ali Oct 29 '13 at 09:35
  • @haraldK I've tried: `Image scaled = bm1.getScaledInstance(100, 200, BufferedImage.SCALE_SMOOTH); Thread.sleep(10000); BufferedImage bm2 = toBufferedImage(scaled);` , but it has had no effect. – Ali Oct 29 '13 at 09:37
  • @ClickUpvote That's expected, something has to request the scaled version as well. I'm more surprised it works for PNG than not working for JPEG.. ;-) You should read up on `Image` vs `BufferedImage` (and the completely different models). In any case, @PeterWalser's solution using `MediaTracker` should work, even if I prefer not using `getScaledInstance()`. – Harald K Oct 29 '13 at 09:48
  • @haraldK Then please post the solution that you prefer to using getScaledInstance ? – Ali Oct 29 '13 at 12:44
  • See Peter's edited answer, or Google for more options. I like my own [ResampleOp](https://github.com/haraldk/TwelveMonkeys/blob/master/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java) :-) – Harald K Oct 29 '13 at 13:33
  • @haraldK Thanks.. could you tell me if your method works with the images in this question? http://stackoverflow.com/questions/19659269/losing-colors-when-resizing-jpegs-in-java-tried-with-multiple-libraries – Ali Oct 29 '13 at 13:39

1 Answers1

11

When I run your code, I don't get a black background, but the colors of the image look all weird (channels seem to be messed up).

When I change the image type in toBufferedImage(..) to BufferedImage.TYPE_INT_RGB (no alpha, as JPEG doesn't support transparency), all works fine.

Still weird that the ImageIO doesn't take this into regard when writing JPEG images...

By the way, asynchronous image scaling (as getScaledInstance(..) does) was not the problem, I made sure the image resizing was done before continuing, this had no effect on the outcome.

To load an image completely, use MediaTracker:

public static void loadCompletely (Image img) {

    MediaTracker tracker = new MediaTracker(new JPanel());
    tracker.addImage(img, 0);
    try {
        tracker.waitForID(0);
    } catch (InterruptedException ex) {
        throw new RuntimeException(ex);
    }
}

EDIT
Here's the code I use to resize images, retaining proportions (different resizing methods depending on whether you're upscaling or downscaling, and faster alternative to area averaging):

public static BufferedImage resizeImage (BufferedImage image, int areaWidth, int areaHeight) {
    float scaleX = (float) areaWidth / image.getWidth();
    float scaleY = (float) areaHeight / image.getHeight();
    float scale = Math.min(scaleX, scaleY);
    int w = Math.round(image.getWidth() * scale);
    int h = Math.round(image.getHeight() * scale);

    int type = image.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

    boolean scaleDown = scale < 1;

    if (scaleDown) {
        // multi-pass bilinear div 2
        int currentW = image.getWidth();
        int currentH = image.getHeight();
        BufferedImage resized = image;
        while (currentW > w || currentH > h) {
            currentW = Math.max(w, currentW / 2);
            currentH = Math.max(h, currentH / 2);

            BufferedImage temp = new BufferedImage(currentW, currentH, type);
            Graphics2D g2 = temp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(resized, 0, 0, currentW, currentH, null);
            g2.dispose();
            resized = temp;
        }
        return resized;
    } else {
        Object hint = scale > 2 ? RenderingHints.VALUE_INTERPOLATION_BICUBIC : RenderingHints.VALUE_INTERPOLATION_BILINEAR;

        BufferedImage resized = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = resized.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
        g2.drawImage(image, 0, 0, w, h, null);
        g2.dispose();
        return resized;
    }
}
Peter Walser
  • 15,208
  • 4
  • 51
  • 78
  • thanks. one more thing.. are you aware of any libraries which will resize the image while also keeping its original dimensions into account, e.g if I give 100x100 as the new size, then it doesn't just resize to 100x100 and distort the image, but keeps the original proportions in mind and resizes to the closest of 100x100 while keeping the original proportions? – Ali Oct 29 '13 at 09:44
  • Added my resizing code to the answer, was too long for the comment :) – Peter Walser Oct 29 '13 at 09:54
  • 1
    Hey, while this is working great for most images, for some images it gives some weird colors. E.g for http://i.imgur.com/8cZl4NJ.jpg it resizes to http://i.imgur.com/DLFwNSM.jpg , and for http://i.imgur.com/bF9gqlv.jpg it resizes to http://i.imgur.com/hiS3Nh7.jpg . Any ideas? – Ali Oct 29 '13 at 12:42
  • 1
    Seems to be a common problem with JPEGs read using ImageIO, when they files don't have JFIF information they get wrongly interpreted. See also: http://stackoverflow.com/questions/9340569/jpeg-image-with-wrong-colors – Peter Walser Oct 29 '13 at 15:05
  • Any ideas how I can fix this? I can either give you a 500 rep bounty here, or a small cash payment, if you're able to change your function so that it will work with these images. – Ali Oct 29 '13 at 16:43
  • 1
    After some research, I found dozens of post from people having the same issue. Some attribute it to embedded ICC profiles in the JPGs, some to misinterpreting the image data as CMYK instead of RGB. What worked for me was to read the images using Toolkit.getDefaultToolkit().getImage() (seems to ignore ICC). This image can be saved correctly as PNG, but when you want to save it as JPG you have to paint it onto a new BufferedImage with TYPE_INT_RGB (no alpha) first. – Peter Walser Oct 29 '13 at 21:02
  • Did the method of `toolkit.getDefault()` work on a bufferedImage? And does it work specifically for the two jpegs where I was having the issue? – Ali Oct 30 '13 at 03:21
  • The image read using the Toolkit can be painted correctly on a BufferedImage, and yes, it worked with your example images (I took those for testing). – Peter Walser Oct 30 '13 at 08:42
  • Thanks for your help, I've been able to make it work. My full code is here: http://stackoverflow.com/a/19687400/49153 , you might find it useful. And I'll be awarding you a bounty :). – Ali Oct 30 '13 at 15:43
  • 1
    your method is cool works just like https://github.com/thebuzzmedia/imgscalr , http://mvnrepository.com/artifact/org.imgscalr/imgscalr-lib without having to include Sclr in maven i may use your method to answer another question if thats fine. – shareef May 14 '16 at 15:30