Scaling Images in Java and Kotlin

Published at 08:09 on 18 July 2020

It’s sort of confusing. There’s a lot of ways to do it. And by “a lot” I mean a lot.

Despite the range of options, my initial attempts at downsizing images were disappointing, yielding ugly results. As an example, consider this:

To fully appreciate how bad that is, here’s what Gimp produces when asked to do the same thing:

Yes, the same thing. In both cases, I am requesting the image be scaled with the bicubic algorithm. Clearly, there is more processing going on in Gimp than simply bicubic scaling. Despite my attempts, I kept getting this sort of disappointing result as I experimented with the various ways of doing it.

Finally, I ran across a magic combination of API calls that yields acceptable results:

val nWidth = (imageIn.width * ratio).toInt()
val nHeight = (imageIn.height * ratio).toInt()
val imageOut = BufferedImage(nWidth, nHeight, imageIn.type)
imageOut.createGraphics().run {
    drawImage(imageIn.getScaledInstance(nWidth, nHeight, BufferedImage.SCALE_SMOOTH), 0, 0, null)

That code produced the following:

Not quite as good as what Gimp yields, but close. The takeaway is that getScaledInstance does at least some necessary extra processing, above and beyond the standard scaling, which is needed to produce acceptable results. I probably wouldn’t want to use it for high-resolution production work, but for generating screen-resolution images for sharing on the Web it’s perfectly adequate.

Update: Some more research reveals that getScaledInstance  with BufferedImage.SCALE_SMOOTH doesn’t use the bicubic algorithm after all; it uses an area-averaging algorithm which is even slower. Those details are mostly irrelevant, however: the important thing is that for my application, it delivers acceptable quality in an acceptable amount of time, which is more than can be said for all the other API calls.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.