JDK-6196792 : Update Image.getScaledInstance with acceleration or documentation
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 1.4.0
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2004-11-17
  • Updated: 2012-01-16
Related Reports
Relates :  
Some time ago, when I found that my image scaling was incredibly slow, I was told by the 2D team that using Image.getScaledInstance() was not recommended any more. The preferred approach is to create a new Image and paint to it, scaling on the fly, to take advantage of faster loops.

I am still seeing people using getScaledInstance() and I've been trying to correct them. But we need to fix this in our API. Either a) by deprecating the old method, b) documenting the preferred approach, c) changing the implementation to use the new faster loops.

I suspect there may be similar situations with other methods - but I can't confirm.
###@###.### 2004-11-17 20:58:35 GMT

WORK AROUND Until now, this bug report did not explicitly mention the proper way of resizing an image, which is covered in the Java 2D FAQ: http://java.sun.com/products/java-media/2D/reference/faqs/index.html#Q_How_do_I_create_a_resized_copy I'll paste that full entry here, in case developers trip across this bug report and are wondering about the correct/best way to resize an image: ---- Create a BufferedImage of the desired size and draw the original image into it, scaling on the fly. Note that depending on whether your original image is opaque or non-opaque (that is, if it's translucent or transparent), you may need to create an image with an alpha channel. BufferedImage createResizedCopy(Image originalImage, int scaledWidth, int scaledHeight, boolean preserveAlpha) { int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType); Graphics2D g = scaledBI.createGraphics(); if (preserveAlpha) { g.setComposite(AlphaComposite.Src); } g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null); g.dispose(); return scaledBI; } You can control the quality of the scaled copy with the Graphics2D.setRenderingHint() method. See http://java.sun.com/j2se/1.5.0/docs/api/java/awt/RenderingHints.html#KEY_INTERPOLATION Add the following before the drawImage() call: g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_[NEAREST_NEIGHBOR BILINEAR BICUBIC]); ----

EVALUATION In response to the JDC comment, I've added code to the workaround section demonstrating the appropriate way of resizing an image without using Image.getScaledInstance(). We are still planning to fix this bug at some point either by putting similar workaround code in the javadocs for Image.getScaledInstance(), or by updating the implementation of that method with something that performs better. In the meantime (and regardless of whether this bug is fixed at all), we still recommend that developers use the suggested workaround code instead of Image.getScaledInstance(), not only because of the performance benefits, but also because the suggested code allows for more flexibility and control. Just to demonstrate the performance difference between Image.getScaledInstance() and the preferred approach of using the scaling variant of Graphics.drawImage(), I wrote a small testcase that measures the time taken for each. I think the numbers speak for themselves: bash-2.05$ j160 ScaledInstancePerf Testing upscale performance (200x200 to 500x500)... getScaledInstance(): avg scale=2663ms, avg draw=1972ms drawImage(): avg scale=340ms, avg draw=310ms getScaledInstance(): avg scale=3236ms, avg draw=2449ms drawImage(): avg scale=521ms, avg draw=489ms getScaledInstance(): avg scale=2296ms, avg draw=1657ms drawImage(): avg scale=391ms, avg draw=364ms Testing downscale performance (200x200 to 80x80)... getScaledInstance(): avg scale=130ms, avg draw=88ms drawImage(): avg scale=4ms, avg draw=2ms getScaledInstance(): avg scale=117ms, avg draw=61ms drawImage(): avg scale=4ms, avg draw=2ms getScaledInstance(): avg scale=96ms, avg draw=59ms drawImage(): avg scale=3ms, avg draw=2ms

EVALUATION We've discussed this bug internally for what seems like years, but we haven't yet done anything to resolve it (other than tell developers not to use getScaledInstance()). It's unlikely that we will deprecate the method at this point. At the very least we could provide some documentation about how to use the more optimal alternatives (i.e. Graphics.drawImage()). It would be relatively simple to change the implementation of getScaledInstance() so that if the hint is SCALE_REPLICATE (synonymous with SCALE_FAST), then we could ensure that the image is fully loaded and then use the scaling drawImage() variant to produce a scaled copy of the original image. That part is not very controversial. The real problem comes when we talk about about the SCALE_AREA_AVERAGING hint (synonymous with SCALE_SMOOTH). Over the years developers have come to appreciate the quality of that filtering algorithm, but unfortunately we don't have an equivalent RenderingHint (along the lines of BILINEAR or BICUBIC), so it wouldn't be as simple as the case I've descibed above. We have a couple options on how to deal with SCALE_AREA_AVERAGING. We could potentially offer a new RenderingHint called AREA_AVERAGING that would be "smooth filtering" alternative to BILINEAR/BICUBIC. We could then update the implementation of getScaledInstance() as suggested above and use this new AREA_AVERAGING hint. This would require quite a lot of work (our mediaLib layer is only prepared to handle NEAREST_NEIGHBOR, BILINEAR, and BICUBIC, as are our TransformHelper loops), not to mention that we'd need to add new API, which is tough at this stage of the release. Perhaps as a middle ground we could update the implementation of getScaledInstance() to use drawImage(), except we would mimic the filtering quality of AREA_AVERAGING by instead using a multistep BILINEAR approach, which would produce similar results, but probably with much better performance than what we have currently. In the unlikely chance that a developer is not satisfied with the filtering quality of this approach, they could always use the AreaAveragingScaleFilter, which would still offer the historical AREA_AVERAGING algorithm that they are used to.