Progressive rescaling does make a difference

My dad learned calculus from this book: Practical mathematics, theory and practice with applications to industrial business and military problems.

I wanted to include a photo of the title page from his copy of it in a little biography we’re working on for him, but I ended up with jaggies when using my scaleimage tool.

Title page from "Practical mathematics, theory and practice with applications to industrial business and military problems."

favr.dev’s Java – resize image without losing quality led me to the The Perils of Image.getScaledInstance() Blog (2015, originally on java.net). It says:

When upscaling an image… the choice is fairly simple: for speed, use RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; for good quality, use RenderingHints.VALUE_INTERPOLATION_BILINEAR; or for even better quality, use RenderingHints.VALUE_INTERPOLATION_BICUBIC…

When downscaling an image, the choice is slightly more complex. The same advice regarding RenderingHints given for upscaling is generally applicable to downscaling as well. However, be aware that if you try to downscale an image by a factor of more than two (i.e., the scaled instance is less than half the size of the original), and you are using the BILINEAR or BICUBIC hint, the quality of the scaled instance may not be as smooth as you might like. If you are familiar with the quality of the old Image.SCALE_AREA_AVERAGING (orImage.SCALE_SMOOTH) hint, then you may be especially dismayed. 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; the olderAreaAveragingFilter algorithm used byImage.getScaledInstance() is quite different and does not suffer from this problem as much, but it requires much more processing time in general.

To combat this issue, you can use a multi-step approach when downscaling by more than two times; this helps prevent the information loss issue and produces a much higher quality result that is visually quite close to that produced by Image.SCALE_AREA_AVERAGING. Despite the fact that there may be multiple temporary images created, and multiple calls made to Graphics.drawImage() in the process, this approach can be significantly faster than using the older, slower Image.getScaledInstance() method. The basic idea here is to repeatedly scale the image by half (using BILINEAR filtering), and then, once the target size is near, perform one final scaling step to reach the target dimensions.

I was curious if switching to RenderingHints.VALUE_INTERPOLATION_BICUBIC would be enough by itself. Nope:

Title page from "Practical mathematics, theory and practice with applications to industrial business and military problems."

But the multi-stage scaling recommended by the article did the trick:

Title page from "Practical mathematics, theory and practice with applications to industrial business and military problems."

(I actually didn’t notice the code provided in the article when I read this the first time. The loss of formatting/whatever other horrors Oracle inflicted on it before the Internet Archive got to it left the code all on one, really long line. Trying to find a better copy via Internet Archive led me to lots of now useless redirects to broken Oracle stuff. Makes me sad that Oracle left a hole in the Internet like that, taking a lot of knowledge with it.)

Anyway, you should see a small improvement in the images on this blog going forward.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *