diff --git a/src/java.desktop/share/classes/java/awt/image/BufferedImage.java b/src/java.desktop/share/classes/java/awt/image/BufferedImage.java index 09c96a6560f6c..dfa728a5e15da 100644 --- a/src/java.desktop/share/classes/java/awt/image/BufferedImage.java +++ b/src/java.desktop/share/classes/java/awt/image/BufferedImage.java @@ -289,9 +289,31 @@ public class BufferedImage extends java.awt.Image * Constructs a {@code BufferedImage} of one of the predefined * image types. The {@code ColorSpace} for the image is the * default sRGB space. + * {@code BufferedImage} is a type that supports only one tile. + * The pixels are stored in a {@code DataBuffer}. + * A {@code DataBuffer} is a container for one or more banks of + * Java primitive arrays so the number of samples that can be + * stored are limited by the maximum size of a Java array. + * This is at most {@code Integer.MAX_VALUE}. + * The number of samples per-pixel for an {@code imageType} affect + * the maximum. For example if an image format uses bytes to store + * separately each of the four samples in an ARGB pixel format image, + * it will only be able to hold one fourth as many pixels as an image + * that uses an int to store all four samples. + * For example {@code TYPE_4BYTE_ABGR} may use 4 bytes to store a pixel + * whereas {@code TYPE_INT_ARGB} may use a single int. + * So the maximum number of pixels in a {@code BufferedImage} is + * format dependent. * @param width width of the created image * @param height height of the created image * @param imageType type of the created image + * @throws IllegalArgumentException if {@code width} or {@code height} is + * not greater than zero. + * @throws IllegalArgumentException if the multiplication product of + * {@code width}, {@code height}, and the number of samples per pixel + * for the specified format exceeds the maximum length of a Java array. + * @throws IllegalArgumentException if the {@code imageType} is not one of + * the pre-defined recognized image types. * @see ColorSpace * @see #TYPE_INT_RGB * @see #TYPE_INT_ARGB @@ -310,6 +332,37 @@ public class BufferedImage extends java.awt.Image public BufferedImage(int width, int height, int imageType) { + + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException( + "width " + width + " height " + height + " must both be > 0"); + } + long lsz = (long)width * height; + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "width " + width + " height " + height + " overflow int"); + } + /* most BufferedImage formats use one data buffer element per pixel. + * But for the NBYTE formats the BufferedImage implementation + * uses an interleaved raster which has a ByteDataBuffer of a single bank, + * so either 3 or 4 bytes is used for each pixel. + */ + int spp = 1; + switch (imageType) { + case TYPE_3BYTE_BGR: + spp = 3; + break; + case TYPE_4BYTE_ABGR: + case TYPE_4BYTE_ABGR_PRE: + spp = 4; + break; + } + if ((spp != 1) && (lsz * spp > Integer.MAX_VALUE)) { + throw new IllegalArgumentException( + "width " + width + " height " + height + " * " + + spp + " samples per pixel overflow int"); + } + switch (imageType) { case TYPE_INT_RGB: { @@ -520,6 +573,11 @@ public BufferedImage(int width, * @param height height of the created image * @param imageType type of the created image * @param cm {@code IndexColorModel} of the created image + * @throws IllegalArgumentException if {@code width} or {@code height} is + * not greater than zero. + * @throws IllegalArgumentException if the multiplication product of + * {@code width} and {@code height} + * exceeds the maximum length of a Java array. * @throws IllegalArgumentException if the imageType is not * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED or if the imageType is * TYPE_BYTE_BINARY and the color map has more than 16 entries. @@ -530,6 +588,17 @@ public BufferedImage (int width, int height, int imageType, IndexColorModel cm) { + + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException( + "width " + width + " height " + height + " must both be > 0"); + } + long lsz = (long)width * height; + if (lsz > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "width " + width + " height " + height + " overflow int"); + } + if (cm.hasAlpha() && cm.isAlphaPremultiplied()) { throw new IllegalArgumentException("This image types do not have "+ "premultiplied alpha."); @@ -599,18 +668,13 @@ public BufferedImage (int width, * components. * @throws IllegalArgumentException if * {@code raster} is incompatible with {@code cm} + * @throws IllegalArgumentException if + * {@code raster} {@code minX} or {@code minY} is not zero * @see ColorModel * @see Raster * @see WritableRaster */ - -/* - * - * FOR NOW THE CODE WHICH DEFINES THE RASTER TYPE IS DUPLICATED BY DVF - * SEE THE METHOD DEFINERASTERTYPE @ RASTEROUTPUTMANAGER - * - */ public BufferedImage (ColorModel cm, WritableRaster raster, boolean isRasterPremultiplied, diff --git a/test/jdk/java/awt/image/BufferedImage/CreateBufferedImageTest.java b/test/jdk/java/awt/image/BufferedImage/CreateBufferedImageTest.java new file mode 100644 index 0000000000000..ff6ae9ce1b390 --- /dev/null +++ b/test/jdk/java/awt/image/BufferedImage/CreateBufferedImageTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, 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.image.BufferedImage; +import java.awt.image.IndexColorModel; +import java.awt.image.WritableRaster; +import static java.awt.image.BufferedImage.*; + +/** + * @test + * @bug 4617681 + * @summary Verify BufferedImage Constructor behaviour with + * invalid image size and type parameters. + */ + +public class CreateBufferedImageTest { + + static byte[] s = new byte[16]; + static IndexColorModel icm = new IndexColorModel(8, 16, s, s, s); + + public static void main(String args[]) { + + test(TYPE_CUSTOM, 10); // TYPE_CUSTOM is not a valid parameter. + test(-1, 10); + test(10001, 10); + + for (int t = TYPE_INT_RGB; t <= TYPE_BYTE_INDEXED; t++) { + test(t, 50_000); // 50_000 ^ 2 will overflow int. + } + test(TYPE_3BYTE_BGR, 30_000); // 3 * (30_000 ^ 2) will overflow int + test(TYPE_4BYTE_ABGR, 25_000); // 4 * (25_000 ^ 2) will overflow int + test(TYPE_4BYTE_ABGR_PRE, 25_000); + + testIndexed(TYPE_INT_RGB, 10); + testIndexed(TYPE_CUSTOM, 10); + testIndexed(-1, 10); + testIndexed(10001, 10); + testIndexed(TYPE_BYTE_BINARY, 50_000); + testIndexed(TYPE_BYTE_INDEXED, 50_000); + + // Verify that IAE is thrown if constructing using a raster with x/y != 0 + BufferedImage bi = new BufferedImage(TYPE_INT_RGB, 10, 10); + WritableRaster raster = bi.getRaster().createCompatibleWritableRaster(20, 20, 1, 1); + try { + bi = new BufferedImage(bi.getColorModel(), raster, true, null); + throw new RuntimeException("No expected exception for invalid min x/y"); + } catch (IllegalArgumentException e) { + System.out.println("Expected exception thrown for invalid raster min x/y"); + System.out.println(e); + } + } + + static void test(int t, int sz) { + try { + new BufferedImage(sz, sz, t); + throw new RuntimeException("No expected exception for type = " + t); + } catch (IllegalArgumentException e) { + System.out.println("Expected exception thrown"); + System.out.println(e); + } catch (NegativeArraySizeException n) { + checkIsOldVersion(26, n); + } + } + + static void testIndexed(int t, int sz) { + try { + new BufferedImage(sz, sz, t, icm); + throw new RuntimeException("No expected exception for type = " + t); + } catch (IllegalArgumentException e) { + System.out.println("Expected exception thrown"); + System.out.println(e); + } + } + + /** + * If running on a JDK of the targetVersion or later, throw + * a RuntimeException becuase the exception argument + * should not have occured. However it is expected on + * prior versions because that was the previous behaviour. + * @param targetVersion to check + * @param t the thrown exception to print + */ + static void checkIsOldVersion(int targetVersion, Throwable t) { + String version = System.getProperty("java.version"); + version = version.split("\\D")[0]; + int v = Integer.parseInt(version); + if (v >= targetVersion) { + t.printStackTrace(); + throw new RuntimeException( + "Unexpected exception for version " + v); + } + } + +}