15

I made this code to resize images with two factors. It works, but the quality of image is very bad after it is resized! Can you help me?

This is the code

public class ImageTest {

private static final int factor1 = 3;
private static final int factor2 = 4;
public static void main(String [] args){

    JFileChooser cs = new JFileChooser();
    cs.setFileSelectionMode(cs.DIRECTORIES_ONLY);
    int i = cs.showOpenDialog(null);
    if(i==cs.APPROVE_OPTION){
        File f = cs.getSelectedFile();
        File[] ff = f.listFiles();
        for(int j=0;j<ff.length;j++){
            String end = ff[j].getName().substring(ff[j].getName().indexOf(".")+1);
            System.out.println(end);
            try{
                BufferedImage originalImage = ImageIO.read(ff[j]);
                int type = originalImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : originalImage.getType();
                BufferedImage resizeImageJpg = resizeImageWithHint(originalImage, type);
                ImageIO.write(resizeImageJpg, end, new File("pr/"+ff[j].getName()));
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }


}
private static BufferedImage resizeImageWithHint(BufferedImage originalImage, int type){
    int IMG_WIDTH = (originalImage.getWidth()*factor1)/factor2;
    int IMG_HEIGHT = (originalImage.getHeight()*factor1)/factor2;
    BufferedImage resizedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, type);
    Graphics2D g = resizedImage.createGraphics();
    g.drawImage(originalImage, 0, 0, IMG_WIDTH, IMG_HEIGHT, null);
    g.dispose();    
    g.setComposite(AlphaComposite.Src);

    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g.setRenderingHint(RenderingHints.KEY_RENDERING,
            RenderingHints.VALUE_RENDER_QUALITY);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

    return resizedImage;
}   
   }

I saw on web that resizeImageWithHint is done within the scope so as not to lose quality.. but it does! why? can you help me with this?

Zopesconk
  • 50
  • 1
  • 4
  • 12
Jayyrus
  • 12,961
  • 41
  • 132
  • 214

9 Answers9

31

The best article I have ever read on this topic is The Perils of Image.getScaledInstance() (web archive).

In short: You need to use several resizing steps in order to get a good image. Helper method from the article:

public BufferedImage getScaledInstance(BufferedImage img,
                                       int targetWidth,
                                       int targetHeight,
                                       Object hint,
                                       boolean higherQuality)
{
    int type = (img.getTransparency() == Transparency.OPAQUE) ?
        BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
    BufferedImage ret = (BufferedImage)img;
    int w, h;
    if (higherQuality) {
        // Use multi-step technique: start with original size, then
        // scale down in multiple passes with drawImage()
        // until the target size is reached
        w = img.getWidth();
        h = img.getHeight();
    } else {
        // Use one-step technique: scale directly from original
        // size to target size with a single drawImage() call
        w = targetWidth;
        h = targetHeight;
    }

    do {
        if (higherQuality && w > targetWidth) {
            w /= 2;
            if (w < targetWidth) {
                w = targetWidth;
            }
        }

        if (higherQuality && h > targetHeight) {
            h /= 2;
            if (h < targetHeight) {
                h = targetHeight;
            }
        }

        BufferedImage tmp = new BufferedImage(w, h, type);
        Graphics2D g2 = tmp.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
        g2.drawImage(ret, 0, 0, w, h, null);
        g2.dispose();

        ret = tmp;
    } while (w != targetWidth || h != targetHeight);

    return ret;
}
Marcel Stör
  • 22,695
  • 19
  • 92
  • 198
nfechner
  • 17,295
  • 7
  • 45
  • 64
  • yes that article is good, both imgscalr and java-image-scaling cite it, but again the result are different, I think the filters that java-image-scaling can apply make a difference. – stivlo Oct 31 '11 at 08:43
  • 1
    Good article, really helped to improve the quality of the scaled images. Changing 'w /= 2' to 'w /= 1.2' give me a better quality (more loops will be executed, so quality is better) – Jacob van Lingen Dec 19 '14 at 09:46
  • I tried the w /= 1.2 suggestion, but I found it made the scaled down images blurrier, especially at much smaller sizes. I tried 1.5 and it didn't help, so I'm sticking with w /= 2. – MiguelMunoz Jul 19 '16 at 08:16
11

The following code produced me highest quality resize with aspect ratio preserved. Tried few things and read several entries presented here in other answers. Lost two days and in the end I got the best result with plain Java method (tried also ImageMagick and java-image-scaling libraries):

public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
  BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
  double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
  if (width < 1) {
    width = (int) (height * ratio + 0.4);
  } else if (height < 1) {
    height = (int) (width /ratio + 0.4);
  }

  Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
  BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
  Graphics2D g2d = bufferedScaled.createGraphics();
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  g2d.drawImage(scaled, 0, 0, width, height, null);
  dest.createNewFile();
  writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
  return true;
}


/**
* Write a JPEG file setting the compression quality.
*
* @param image a BufferedImage to be saved
* @param destFile destination file (absolute or relative path)
* @param quality a float between 0 and 1, where 1 means uncompressed.
* @throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
      throws IOException {
  ImageWriter writer = null;
  FileImageOutputStream output = null;
  try {
    writer = ImageIO.getImageWritersByFormatName("jpeg").next();
    ImageWriteParam param = writer.getDefaultWriteParam();
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(quality);
    output = new FileImageOutputStream(new File(destFile));
    writer.setOutput(output);
    IIOImage iioImage = new IIOImage(image, null, null);
    writer.write(null, iioImage, param);
  } catch (IOException ex) {
    throw ex;
  } finally {
    if (writer != null) {
      writer.dispose();
    }
    if (output != null) {
      output.close();
    }
  }
}
Mladen Adamovic
  • 3,071
  • 2
  • 29
  • 44
  • Code posted by @nfechner is an improvement to yours. If a picture is big, the quality will not be as good as you would expect. As written by given article: 'The reason for this disparity in quality is due to the different filtering algorithms in use. If downscaling by more than two times, the BILINEAR and BICUBIC algorithms tend to lose information due to the way pixels are sampled from the source image'. By using a multi-step render way, the resized-image will be better. – Jacob van Lingen Dec 19 '14 at 09:53
  • I used my eye and downscaled images to around 320px and this produced better quality in my opinion. I understand what you are saying, however, I was not satisfied with the result I got from @nfechner algorithm, I tried that as well... – Mladen Adamovic Feb 10 '15 at 09:59
7

Know question is old... I've tried different solutions surfing then web, I got the best result using getScaledInstance(), supplying Image.SCALE_SMOOTH as argument. In fact the resulting image quality was really better. My code below:

final int THUMB_SIDE = 140;
try {
    BufferedImage masterImage = ImageIO.read(startingImage);
    BufferedImage thumbImage = new BufferedImage(THUMB_SIDE, THUMB_SIDE, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = thumbImage.createGraphics();
    g2d.drawImage(masterImage.getScaledInstance(THUMB_SIDE, THUMB_SIDE, Image.SCALE_SMOOTH), 0, 0, THUMB_SIDE, THUMB_SIDE, null);
    g2d.dispose();
    String thumb_path = path.substring(0, path.indexOf(".png")) + "_thumb.png";
    ImageIO.write(thumbImage, "png", new File(thumb_path));
} catch (IOException e) {
    e.printStackTrace();
}
Jens
  • 67,715
  • 15
  • 98
  • 113
user3564042
  • 71
  • 1
  • 3
3

If your image source is a png then use like this:

Image imgSmall = imgBig.getScaledInstance(
        targetWidth, targetHeight, Image.SCALE_SMOOTH);

If you want to resize jpeg or gif without loose too much quality, I made a library in 2010 for this: beautylib on github that uses internally this other library: java-image-scaling. You can see directly the source code to find something useful: https://github.com/felipelalli/beautylib/blob/master/src/br/eti/fml/beautylib/ResizeImage.java

Felipe
  • 16,649
  • 11
  • 68
  • 92
2

None of the answers will help you to get real quality you desire. Include thumbailator.jar in your project (download it form here):

https://code.google.com/p/thumbnailator/

https://code.google.com/p/thumbnailator/wiki/Downloads?tm=2

Then upload the image first (as file, without Thumbnailator - it's use is to create thumbs, but you can create large images with it of course), and resize it to every dimensions you want (with Thumbnailator 800x600 for example). Quality will be very good. I was searching for this long time, this .jar helped me to achieve what i want.

DarioBB
  • 663
  • 2
  • 8
  • 29
  • Hi, thank you for sharing! This is an old post, but i will try it and remember for the future. Thanks! – Jayyrus Apr 10 '15 at 18:14
1

Yes, I had the same problems and solved them, please read my question (answer is embedded in the question). I tried imgscalr and java-image-scaling libraries and found the second much better quality. Get close to the monitor to appreciate the difference between the thumbnail examples.

Despite my initial thoughts, resizing an image seems a very complicate thing, you don't want to do it yourself. For example I tell java-image-scaling to use ResampleFilters.getLanczos3Filter() to have better result.

It also addresses how to save a JPG with a quality higher than the standard 75, which produces a bad result especially for a thumbnail.

I also wrote a small class, called MyImage to help with common tasks, such as reading an image from a byte array, from a file, scaling by specifying only width or only height, scaling by specifying a bounding box, scaling by specifying width and height and adding a white band to make the image not distorted and writing to JPG file.

Community
  • 1
  • 1
stivlo
  • 83,644
  • 31
  • 142
  • 199
0

Answer: Remove the hints VALUE_INTERPOLATION_BILINEAR, and VALUE_RENDERING_QUALITY.

Example:

public static BufferedImage resizeImage(BufferedImage image, int width, int height) {

    // Temporary image

    BufferedImage tmp = image;

    // Result image

    BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

    // Graphics object

    Graphics2D graphics = (Graphics2D)result.createGraphics();

    // Add rendering hints

    graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    graphics.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);

    // Draw tmp

    graphics.drawImage(tmp, 0, 0, width, height, null);

    // Dispose of graphics object

    graphics.dispose();

    // Return image result

    return result;
}

Note: For some reason, the hints VALUE_INTERPOLATION_BILINEAR and VALUE_RENDERING_QUALITY blur the image being resized.

Zopesconk
  • 50
  • 1
  • 4
  • 12
0

All the methods posted does'nt work for me i have to reduce QrCode size, but with above methods the quality is poor and scanner doesn't work , if i take original picture and resize it in paint the scanner is working.

cyril
  • 872
  • 6
  • 29
0

The do while loop used in The Perils of Image.getScaledInstance() will run into an infinite loop, given those values, w = 606; h = 505, targetWidth = 677, targetHeight = 505

Here is a simplied testing code, you can try it.

public class LoopTest {

    public static void main(String[] args) {
        new LoopTest(true, 606, 505, 677, 505);
    }

    public LoopTest(boolean higherQuality, int w, int h, int targetWidth, int targetHeight) {
        do {
            if (higherQuality && w > targetWidth) {
                w /= 2;
                if (w < targetWidth) {
                    w = targetWidth;
                }
            }

            if (higherQuality && h > targetHeight) {
                h /= 2;
                if (h < targetHeight) {
                    h = targetHeight;
                }
            }
        } while (w != targetWidth || h != targetHeight);        // TODO Auto-generated constructor stub
    }
}

A quick work around: define an index for loop count. If the index is >=10, break out of loop.

Icarus
  • 1,627
  • 7
  • 18
  • 32
mikej1688
  • 193
  • 1
  • 2
  • 10