Hello

Following up on JDK-8166038 [1], in case it may help to justify the proposed fix, attached is another test case demonstrating the bug. Attempt to draw a BufferedImage subimage with Graphics2D causes an exception to be thrown if we hide the fact that the image is a BufferedImage instance, for preventing SunGraphics2D to use its special cases for BufferedImage. Stack trace is:

   Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 
BufferedImages only have one tile with index 0,0
        at 
java.desktop/java.awt.image.BufferedImage.getTile(BufferedImage.java:1401)
        at SubimageDrawingTest$Wrapper.getTile(SubimageDrawingTest.java:136)
        at 
java.desktop/sun.java2d.SunGraphics2D.drawTranslatedRenderedImage(SunGraphics2D.java:2821)
        at 
java.desktop/sun.java2d.SunGraphics2D.drawRenderedImage(SunGraphics2D.java:2708)
        at SubimageDrawingTest.main(SubimageDrawingTest.java:31)

Replacing getTileGridXOffset() and getTileGridYOffset() return values by 0 (as expected and as requested by method contract) fix the bug.

This bug happens with any code that try to handle RenderedImage tiles in a generic way, without making special case for BufferedImage (we have encounter this issue today in Apache Spatial Information System).

    Regards,

        Martin

[1] https://bugs.openjdk.java.net/browse/JDK-8166038

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Vector;

public class SubimageDrawingTest {
    public static void main(String[] args) {
        BufferedImage canvas =
                new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
        BufferedImage image =
                new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB);
        BufferedImage subimage = image.getSubimage(1, 1, 3, 3);

        Graphics2D g = canvas.createGraphics();

        // Drawing the original image even if wrapped: okay.
        g.drawRenderedImage(new Wrapper(image), new AffineTransform());

        // Drawing subimage: okay because SubGraphics2D
        // has special cases for BufferedImage.
        g.drawRenderedImage(subimage, new AffineTransform());

        // Same subimage but hiding the fact that it is
        // a BufferedImage: ArrayIndexOutOfBoundsException
        g.drawRenderedImage(new Wrapper(subimage), new AffineTransform());

        g.dispose();
    }

    /**
     * A wrapper which delegate everything to the wrapped
     * {@link BufferedImage}. The intent is to hide the fact
     * that the image is an instance of {@link BufferedImage},
     * for preventing {@link Graphics2D} to apply special case.
     */
    private static class Wrapper implements RenderedImage {
        private final BufferedImage image;

        Wrapper(BufferedImage toTest) {
            image = toTest;
        }

        @Override
        public Vector<RenderedImage> getSources() {
            return image.getSources();
        }

        @Override
        public Object getProperty(String name) {
            return image.getProperty(name);
        }

        @Override
        public String[] getPropertyNames() {
            return image.getPropertyNames();
        }

        @Override
        public ColorModel getColorModel() {
            return image.getColorModel();
        }

        @Override
        public SampleModel getSampleModel() {
            return image.getSampleModel();
        }

        @Override
        public int getWidth() {
            return image.getWidth();
        }

        @Override
        public int getHeight() {
            return image.getHeight();
        }

        @Override
        public int getMinX() {
            return image.getMinX();
        }

        @Override
        public int getMinY() {
            return image.getMinY();
        }

        @Override
        public int getNumXTiles() {
            return image.getNumXTiles();
        }

        @Override
        public int getNumYTiles() {
            return image.getNumYTiles();
        }

        @Override
        public int getMinTileX() {
            return image.getMinTileX();
        }

        @Override
        public int getMinTileY() {
            return image.getMinTileY();
        }

        @Override
        public int getTileWidth() {
            return image.getTileWidth();
        }

        @Override
        public int getTileHeight() {
            return image.getTileHeight();
        }

        @Override
        public int getTileGridXOffset() {
            return image.getTileGridXOffset();
        }

        @Override
        public int getTileGridYOffset() {
            return image.getTileGridYOffset();
        }

        @Override
        public Raster getTile(int tileX, int tileY) {
            return image.getTile(tileX, tileY);
        }

        @Override
        public Raster getData() {
            return image.getData();
        }

        @Override
        public Raster getData(Rectangle rect) {
            return image.getData(rect);
        }

        @Override
        public WritableRaster copyData(WritableRaster outRaster) {
            return image.copyData(outRaster);
        }
    }
}

Reply via email to