coobird / thumbnailator

Thumbnailator - a thumbnail generation library for Java
MIT License
5.08k stars 780 forks source link

Bicubic scaling looks like bilinear #179

Open NathanSweet opened 2 years ago

NathanSweet commented 2 years ago

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

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 commented 2 years ago

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

coobird commented 2 years ago

@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.