File: TiledImage.java

package info (click to toggle)
openjdk-21 21.0.8%2B9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 823,976 kB
  • sloc: java: 5,613,338; xml: 1,643,607; cpp: 1,296,296; ansic: 420,291; asm: 404,850; objc: 20,994; sh: 15,271; javascript: 11,245; python: 6,895; makefile: 2,362; perl: 357; awk: 351; sed: 172; jsp: 24; csh: 3
file content (225 lines) | stat: -rw-r--r-- 8,666 bytes parent folder | download | duplicates (4)
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.");
    }
}