Image resize quality (Java) [closed]
Asked Answered
B

7

15

I have an open-source app which uploads photos to Facebook. To save bandwidth, the photos are automatically resized before uploading (Facebook imposes a maximum size limit). A few people have complained about the photo quality, and in fact you can see the difference (see this issue for some demo images).

So my question is, what is the "best" way of scaling down images (i.e. photos) in Java without losing quality, or at least, with minimal quality loss / artifacts?

You can see the current code I have here (resize code via this page).

Blairblaire answered 26/10, 2009 at 14:28 Comment(0)
E
12

I've tried it all - including the tricks here, and all I can say is that you're better of using ImageMagick with whatever interface. Javas imaging libraries are just not up to snuff when it comes to this. You need to support so many formats and algorithms to get it right.

Easton answered 26/10, 2009 at 14:54 Comment(4)
I was hoping to avoid an external call but I think you may be right. Java just doesn't seem to have quite got image processing right yet!Blairblaire
This is my conclusion as well! Java image apis just suck! I can work around the sluggishness, but the poor image quality is just not acceptable! I do hope that the relevant apis get some much needed tlc together with JavaFX 2.0.Haerle
You obviously haven't come across java-image-scaling.Nowlin
Tried the java-image-scaling lib and the image is much sharper than my custom approach. thanks for the linkFleecy
H
17

Phil, I don't know which solution you eventually went with, but scaling images in Java can look pretty good if you:

  • Avoid BufferedImage types that aren't well supported by the JDK.
  • Use incremental scaling
  • Stick to bicubic when using incremental scaling

I've done a fair share of testing with these methods and the incremental scaling along with sticking to well supported image types is the key -- I see Alexander mentioned he still didn't get good luck with it which is a bummer.

I released the imgscalr library (Apache 2) about 6 months ago to address the issue of "I want good-looking scaled copies of this image, DO IT NOW!" after reading something like 10 questions like this on SO.

Standard usage looks like:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 640);

The 2nd argument is the bounding width and height imgscalr will use to scale the image -- keeping its proportions correct even if you passed in invalid dimensions -- there are many more detailed methods, but that is the simplest usage.

The use-case you would want, for example if Facebook limited images to 800x600 pixels, would look like this:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY, 800, 600);

That will ensure the image stays in the best supported image type and scaled with the highest quality method that Java can muster.

In my own high-resolution testing I have not noticed any gaping discrepancies with scaled images using this library/these methods EXCEPT when your image gets put into a poorly supported image type by the ImageIO loader -- for example, this happens a lot with GIFs. If you leave them like that and don't get them out of those poorly supported types, it ends up looking really dithered and terrible.

The reason for this is that the Java2D team actually has different hardware accelerated pipelines for all the different types of BufferedImages that the JDK can process - a subset of those image types that are less common all fall back to using the same software rendering pipeline under the covers in Java2D, resulting in poor and sometimes totally incorrect looking images. This was such a PIA to explain and try and figure out that I just wrote that logic directly into the library.

The two best supported types are BufferedImage.TYPE_INT_RGB and _ARGB if you are curious.

Hoofer answered 5/7, 2011 at 16:3 Comment(6)
Hi Riyad, Thank you for putting your effort in to building this. It's great and I just started using it. I have a problem with quality when I try only to RESIZE an image to a specific width and height. I have an image which 240X320 and I have to resize it to 50X75 and 120X180 . I tried both separately using the following code(I am using imgscalr 4.2).Baese
image=resize(image,Method.ULTRA_QUALITY, 50,75, OP_ANTIALIAS, OP_BRIGHTER); saveImage(image, ImageFormatTypes.JPEG, DESTINATION + RESIZED_IMAGE + "."+ImageFormatTypes.JPEG); I have two problems. #1 is very low quality compared to other internal tools I used to resize the image manually. #2. The width increases from 50 to 56 and from 120 to 135 which is weird. Do you have any idea why these are happening? I appreciate your help.Baese
@Baese Ah! Yes I think I know what is happening... I hate that imgscalr doesn't abstract this away from the dev yet, but it operates on raw pixel values -- when you write the image back out with ImageIO, it is using the DEFAULT JPG encoder settings, which are set to 75% quality I believe -- you have to google "java ImageIO JPEG quality" for example code on how to set that to say 95%. The "cheap and easy" way to test if this is the case, write out the image as a PNG (losssles format) -- if it looks 'way better' then that's it.Hoofer
@Baese also one more suggestion, try removing the AA and Brightness OP incase they are doing something weird to image and write just the scaled image out and see how it looks.Hoofer
Thank you. I'll give them a try.Baese
@riyad-kalla Thank you for advice, I googled, here it is https://mcmap.net/q/276932/-setting-jpg-compression-level-with-imageio-in-javaHelprin
M
13

The two most popular open source libs specializing in image resizing in java currently are:

Additonal there is the JDK way with Java's Graphics2D (see this question on how to do it) which is notorious to create bad results especially with downscaling. There is also a Java interface to ImageMagick which will be omitted here because it requires an external tool.

Visual Quality

Here is a comparison of the results of resizing/downscaling a 580x852 png to 145x213. As reference Photoshop CS5 "save for web" resizing is used. Note: the results are 1:1 what the libs created just copied together. The zoom does not use any filtering, just a simple nearest neighbor algorithm. Here you can find the original image.

comparison

  1. Thumbnailator 0.4.8 with default settings, no dimension adjustments
  2. Photoshop CS5 with bicubic algorithm
  3. imgscalr 4.2 with ULTRA_QUALITY setting, no dimension adjustments
  4. Graphics2D (Java 8) with render hints VALUE_INTERPOLATION_BICUBIC, VALUE_RENDER_QUALITY, VALUE_ANTIALIAS_ON

I leave it to the reader to select the best result as this is subjective. Generally, all have good output except Graphics2D. Thumbnailator generates sharper images very similar to Photoshop output, whereas imgscalr's output is considerably softer. For icons/text etc. you want a sharper output, for pictures you may want softer output.

Computational Time

Here is non-scientific benchmark using this tool and 114 images with dimension from about 96x96 up to 2560x1440 treating it as 425% images creating: 100%, 150%, 200%, 300% and 400% scaled versions of it (so 114 * 5 scaling operations). All libs use the same settings as in the quality comparison (so highest quality possible). Times are only scaling not the whole process. Done on a i5-2520M with 8GB Ram and 5 runs.

  • Thumbnailator: 7003.0ms | 6581.3ms | 6019.1ms | 6375.3ms | 8700.3ms
  • imgscalr: 25218.5ms | 25786.6ms | 25095.7ms | 25790.4ms | 29296.3ms
  • Graphics2D: 7387.6ms | 7177.0ms | 7048.2ms | 7132.3ms | 7510.3ms

Here is the code used in this benchmark.

Interestingly Thumbnailator is also the fastest with an average time of 6.9 sec followed by Java2D with 7.2 sec leaving imgscalr behind with a poor 26.2 sec. This is probably not fair since imgscalr is set to ULTRA_QUALITY which seems to be extremely expensive; with the QUALITY setting it averages at a more competitive 11.1 sec.

Monicamonie answered 29/3, 2016 at 21:5 Comment(3)
The performance measurements are difficult (I wrote some words about this in this answer ). Nevertheless, 1 up for this and the other answerChelyuskin
@Chelyuskin true, but I think a had a big and diverese enough sample size to catch at least trends.Monicamonie
I completed the tests myself and observed similar patterns as you did (Thumbnailator provided the fastest performance). I recommend that you would write the "imgscalr" line twice (once for ULTRA_QUALITY and once for QUALITY), so that the benchmarks are more easily comparable on a quick glance.Homogenesis
E
12

I've tried it all - including the tricks here, and all I can say is that you're better of using ImageMagick with whatever interface. Javas imaging libraries are just not up to snuff when it comes to this. You need to support so many formats and algorithms to get it right.

Easton answered 26/10, 2009 at 14:54 Comment(4)
I was hoping to avoid an external call but I think you may be right. Java just doesn't seem to have quite got image processing right yet!Blairblaire
This is my conclusion as well! Java image apis just suck! I can work around the sluggishness, but the poor image quality is just not acceptable! I do hope that the relevant apis get some much needed tlc together with JavaFX 2.0.Haerle
You obviously haven't come across java-image-scaling.Nowlin
Tried the java-image-scaling lib and the image is much sharper than my custom approach. thanks for the linkFleecy
E
5

To resize image with custom quality use thumbnailator.jar.

Example Code http://code.google.com/p/thumbnailator/wiki/Examples

Environ answered 27/1, 2012 at 6:11 Comment(1)
Tested both org.imgscalr.Scalr and com.mortennobel.imagescaling in combination with changing the compression level (blog.carsoncheng.ca/2011/02/…) and none worked. thumbnailator was awesome, at least quality wise. Thanks!Starvation
B
2

What rendering hint are you using? Usually bicubic resampling will be the best. In the photos you are linking to they are very jaggy, which makes me think you are using nearest neighbor as your hint.

In the PictureScaler class that you link to, in the paintComponent method, it uses six different means of resizing the image. Have you tried all six to see which gives the best result?

Bicentenary answered 26/10, 2009 at 14:39 Comment(2)
I would add that it looks like, in the code, that the questioner has a "high-quality" mode that involves resizing the image in several passes. This seems like a bad idea for much the same reason that you would not want to repeatedly compress and uncompress a sound sample; the image quality will actually be worse and not better.Ursala
Hi, actually I haven't tried all six which I probably should. Although I am using bicubic resampling which, as you say, "should" be the best. I'm also not using the "high quality" mode as it doesn't work in that situation, the Java imaging API seems to be prone to deadlocks! Probably something wrong with my code, not sure what though.Blairblaire
L
0

After a few frustrating experiments I found the following resize evaluation, and employed the multi-pass approach in my project.

To do that I copied the getScaledInstance() method into my thumbnail generator class, changed my image read approach to use ImageIO (that one returns a BufferedImage) and am now very happy!

I compared the result with a resize done in Photoshop CS3 and the result is very much the same.

Larrabee answered 1/12, 2009 at 11:25 Comment(0)
P
0

I wanted highest quality resize with aspect ratio preserved. Tried few things and read several entries. 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();
    }
  }
}
Privative answered 31/5, 2013 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.