thumbnailator icon indicating copy to clipboard operation
thumbnailator copied to clipboard

Bicubic scaling looks like bilinear

Open NathanSweet opened this issue 2 years ago • 2 comments

Expected behavior

Using ScalingMode.BICUBIC and downscaling to 50% I expect the output to be scaled using bicubic filtering.

Actual behavior

The scaled image looks poor, like bilinear was used. The expected image is Photoshop's bicubic. The actual image looks like Photoshop's bilinear.

Steps to reproduce the behavior

Code below and this image file: http://n4te.com/x/2546-test.png

static public void main (String[] args) throws Exception {
	BufferedImage image = ImageIO.read(new File("test.png"));
	Builder builder = Thumbnails.of(image) //
		.imageType(BufferedImage.TYPE_4BYTE_ABGR) //
		.scale(0.5f) //
		.alphaInterpolation(AlphaInterpolation.QUALITY) //
		.antialiasing(Antialiasing.ON) //
		.rendering(Rendering.QUALITY) //
		.scalingMode(ScalingMode.BICUBIC);
	BufferedImage image2 = builder.asBufferedImage();
	ImageIO.write(image2, "png", new File("test2.png"));
}

Environment

  • OS vendor and version: Windows 10
  • JDK vendor and version: Tried with JDK 16, 13, 10, and 8, all gave same results.
  • Thumbnailator version: 0.4.14

Notes

I traced it through BicubicResizer to AbstractResizer to Graphics2D#drawImage, eventually calling the native sun.java2d.loops.TransformHelper#Transform:

    public native void Transform(MaskBlit output,
                                 SurfaceData src, SurfaceData dst,
                                 Composite comp, Region clip,
                                 AffineTransform itx, int txtype,
                                 int sx1, int sy1, int sx2, int sy2,
                                 int dx1, int dy1, int dx2, int dy2,
                                 int[] edges, int dxoff, int dyoff);

The int txtype parameter is 3, which is the "interpolation type" and I assume is AffineTransformOp.TYPE_BICUBIC (which is 3). If that's right, why isn't bicubic used? I hate this AWT black box where only "hints" are provided.

NathanSweet avatar Sep 30 '21 03:09 NathanSweet

Seems like getScaledInstance with SCALE_AREA_AVERAGING is the only sure way to get better results than bilinear.

NathanSweet avatar Oct 05 '21 02:10 NathanSweet

@NathanSweet, have you tried using BufferedImage.TYPE_INT_ARGB? I suspect that Java's Graphics2D rendering pipeline may act differently based on the image type that's being used. And yes, as you mentioned the black box nature of this pipeline makes it hard to chase these issues.

coobird avatar Nov 23 '21 14:11 coobird