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);
}
}
}