1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Objects;
import java.util.Vector;
/**
* @test
* @bug 8275345
* @summary RasterFormatException when drawing a tiled image made of non-writable rasters.
*
* Test drawing a tiled image made of non-writable {@link Raster} tiles.
* Drawing works when tiles are instances of {@link WritableRaster}.
* But if tiles are instances of {@link Raster} only, then the following
* exception is thrown:
*
* Exception in thread "main" java.awt.image.RasterFormatException: (parentX + width) is outside raster
* at java.desktop/java.awt.image.WritableRaster.createWritableChild(WritableRaster.java:228)
* at java.desktop/sun.java2d.SunGraphics2D.drawTranslatedRenderedImage(SunGraphics2D.java:2852)
* at java.desktop/sun.java2d.SunGraphics2D.drawRenderedImage(SunGraphics2D.java:2711)
*
* The bug is demonstrated by drawing the same image twice:
* once with {@link WritableRaster} tiles (which succeed),
* then the same operation but with {@link Raster} tiles.
*
* The bug is caused by the following code in {@code SunGraphics2D}:
*
* // Create a WritableRaster containing the tile
* WritableRaster wRaster = null;
* if (raster instanceof WritableRaster) {
* wRaster = (WritableRaster)raster;
* } else {
* // Create a WritableRaster in the same coordinate system
* // as the original raster.
* wRaster =
* Raster.createWritableRaster(raster.getSampleModel(),
* raster.getDataBuffer(),
* null);
* }
* // Translate wRaster to start at (0, 0) and to contain
* // only the relevant portion of the tile
* wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y,
* tileRect.width,
* tileRect.height,
* 0, 0,
* null);
*
* If {@code raster} is not an instance of {@link WritableRaster},
* then a new {@link WritableRaster} is created wrapping the same
* buffer <strong>but with a location of (0,0)</strong>, because
* the {@code location} argument of {@code createWritableRaster}
* is null. Consequently the call to {@code createWritableChild}
* shall not be done in that case, because the raster is already
* translated. The current code applies translation twice.
*
* This bug is largely unnoticed because most {@code Raster.create}
* methods actually create {@link WritableRaster} instances, even
* when the user did not asked for writable raster. To make this
* bug apparent, we need to invoke {@code Raster.createRaster(…)}
* with a sample model for which no optimization is provided.
*/
public class TiledImage implements RenderedImage {
/**
* Run the test using writable tiles first, then read-only tiles.
*/
public static void main(String[] args) {
draw(true); // Pass.
draw(false); // Fail if 8275345 is not fixed.
}
private static final int NUM_X_TILES = 2, NUM_Y_TILES = 3;
private static final int TILE_WIDTH = 16, TILE_HEIGHT = 12;
/**
* Tests rendering a tiled image.
*
* @param writable whether the image shall use writable raster.
*/
private static void draw(final boolean writable) {
final BufferedImage target = new BufferedImage(
TILE_WIDTH * NUM_X_TILES,
TILE_HEIGHT * NUM_Y_TILES,
BufferedImage.TYPE_BYTE_GRAY);
final RenderedImage source = new TiledImage(writable,
target.getColorModel());
Graphics2D g = target.createGraphics();
g.drawRenderedImage(source, new AffineTransform());
g.dispose();
}
private final ColorModel colorModel;
private final Raster[] tiles;
/**
* Creates a tiled image. The image is empty,
* but pixel values are not the purpose of this test.
*
* @param writable whether the image shall use writable raster.
*/
private TiledImage(boolean writable, ColorModel cm) {
/*
* We need a sample model class for which Raster.createRaster
* do not provide a special case. That method has optimizations
* for most SampleModel sub-types, except BandedSampleModel.
*/
SampleModel sm = new BandedSampleModel(DataBuffer.TYPE_BYTE, TILE_WIDTH, TILE_HEIGHT, 1);
tiles = new Raster[NUM_X_TILES * NUM_Y_TILES];
final Point location = new Point();
for (int tileY = 0; tileY < NUM_Y_TILES; tileY++) {
for (int tileX = 0; tileX < NUM_X_TILES; tileX++) {
location.x = tileX * TILE_WIDTH;
location.y = tileY * TILE_HEIGHT;
DataBufferByte db = new DataBufferByte(TILE_WIDTH * TILE_HEIGHT);
Raster r;
if (writable) {
r = Raster.createWritableRaster(sm, db, location);
} else {
// Case causing RasterFormatException later.
r = Raster.createRaster(sm, db, location);
}
tiles[tileX + tileY * NUM_X_TILES] = r;
}
}
colorModel = cm;
}
@Override
public ColorModel getColorModel() {
return colorModel;
}
@Override
public SampleModel getSampleModel() {
return tiles[0].getSampleModel();
}
@Override
public Vector<RenderedImage> getSources() {
return new Vector<>();
}
@Override
public Object getProperty(String key) {
return Image.UndefinedProperty;
}
@Override
public String[] getPropertyNames() {
return null;
}
@Override public int getMinX() {return 0;}
@Override public int getMinY() {return 0;}
@Override public int getMinTileX() {return 0;}
@Override public int getMinTileY() {return 0;}
@Override public int getTileGridXOffset() {return 0;}
@Override public int getTileGridYOffset() {return 0;}
@Override public int getNumXTiles() {return NUM_X_TILES;}
@Override public int getNumYTiles() {return NUM_Y_TILES;}
@Override public int getTileWidth() {return TILE_WIDTH;}
@Override public int getTileHeight() {return TILE_HEIGHT;}
@Override public int getWidth() {return TILE_WIDTH * NUM_X_TILES;}
@Override public int getHeight() {return TILE_HEIGHT * NUM_Y_TILES;}
@Override
public Raster getTile(final int tileX, final int tileY) {
Objects.checkIndex(tileX, NUM_X_TILES);
Objects.checkIndex(tileY, NUM_Y_TILES);
return tiles[tileX + tileY * NUM_X_TILES];
}
@Override
public Raster getData() {
throw new UnsupportedOperationException("Not needed for this test.");
}
@Override
public Raster getData(Rectangle rect) {
throw new UnsupportedOperationException("Not needed for this test.");
}
@Override
public WritableRaster copyData(WritableRaster raster) {
throw new UnsupportedOperationException("Not needed for this test.");
}
}
|