From 890456f0f78cc37c72b438a50ddf2605e1dfd91b Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Mon, 19 May 2025 23:43:19 +0000 Subject: [PATCH] 8355078: java.awt.Color.createContext() uses unnecessary synchronization Reviewed-by: prr --- .../share/classes/java/awt/Color.java | 11 +- .../classes/java/awt/ColorPaintContext.java | 41 ++-- .../ColorPaintContextBasicTest.java | 210 ++++++++++++++++++ .../ColorPaintContextStateTrackerTest.java | 55 +++++ 4 files changed, 283 insertions(+), 34 deletions(-) create mode 100644 test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextBasicTest.java create mode 100644 test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextStateTrackerTest.java diff --git a/src/java.desktop/share/classes/java/awt/Color.java b/src/java.desktop/share/classes/java/awt/Color.java index 3b67cd6ee3b..923afb91866 100644 --- a/src/java.desktop/share/classes/java/awt/Color.java +++ b/src/java.desktop/share/classes/java/awt/Color.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -1195,11 +1195,10 @@ public class Color implements Paint, java.io.Serializable { * @see AffineTransform * @see RenderingHints */ - public synchronized PaintContext createContext(ColorModel cm, Rectangle r, - Rectangle2D r2d, - AffineTransform xform, - RenderingHints hints) { - return new ColorPaintContext(getRGB(), cm); + public PaintContext createContext(ColorModel cm, Rectangle r, + Rectangle2D r2d, AffineTransform xform, + RenderingHints hints) { + return new ColorPaintContext(getRGB()); } /** diff --git a/src/java.desktop/share/classes/java/awt/ColorPaintContext.java b/src/java.desktop/share/classes/java/awt/ColorPaintContext.java index 15e296cf525..426811bf19e 100644 --- a/src/java.desktop/share/classes/java/awt/ColorPaintContext.java +++ b/src/java.desktop/share/classes/java/awt/ColorPaintContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -23,61 +23,46 @@ * questions. */ - - package java.awt; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; -import sun.awt.image.IntegerComponentRaster; import java.util.Arrays; -class ColorPaintContext implements PaintContext { - int color; - WritableRaster savedTile; +import sun.awt.image.IntegerComponentRaster; - protected ColorPaintContext(int color, ColorModel cm) { +final class ColorPaintContext implements PaintContext { + + private final int color; + private volatile WritableRaster savedTile; + + ColorPaintContext(int color) { this.color = color; } + @Override public void dispose() { } - /* - * Returns the RGB value representing the color in the default sRGB - * {@link ColorModel}. - * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are - * blue). - * @return the RGB value of the color in the default sRGB - * {@code ColorModel}. - * @see java.awt.image.ColorModel#getRGBdefault - * @see #getRed - * @see #getGreen - * @see #getBlue - */ - int getRGB() { - return color; - } - + @Override public ColorModel getColorModel() { return ColorModel.getRGBdefault(); } - public synchronized Raster getRaster(int x, int y, int w, int h) { + @Override + public Raster getRaster(int x, int y, int w, int h) { WritableRaster t = savedTile; if (t == null || w > t.getWidth() || h > t.getHeight()) { t = getColorModel().createCompatibleWritableRaster(w, h); IntegerComponentRaster icr = (IntegerComponentRaster) t; Arrays.fill(icr.getDataStorage(), color); - // Note - markDirty is probably unnecessary since icr is brand new - icr.markDirty(); + // Note - icr.markDirty() is unnecessary since icr is brand new if (w <= 64 && h <= 64) { savedTile = t; } } - return t; } } diff --git a/test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextBasicTest.java b/test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextBasicTest.java new file mode 100644 index 00000000000..3877642897d --- /dev/null +++ b/test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextBasicTest.java @@ -0,0 +1,210 @@ +/* + * Copyright Amazon.com Inc. 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.Color; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.HeadlessException; +import java.awt.Image; +import java.awt.Paint; +import java.awt.PaintContext; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.VolatileImage; +import java.util.function.Consumer; + +/** + * @test + * @bug 8355078 + * @summary Checks if different image types (BufferedImage and VolatileImage) + * produce the same results when using different ways to fill the image + * (setColor, setPaint, and custom Paint) + */ +public final class ColorPaintContextBasicTest { + + private static final int SIZE = 100; + + private static final int[] TYPES = new int[]{ + BufferedImage.TYPE_INT_RGB, + BufferedImage.TYPE_INT_ARGB, + BufferedImage.TYPE_INT_ARGB_PRE, + BufferedImage.TYPE_INT_BGR, + BufferedImage.TYPE_3BYTE_BGR, + BufferedImage.TYPE_4BYTE_ABGR, + BufferedImage.TYPE_4BYTE_ABGR_PRE, + }; + + private static final Color[] COLORS = { + Color.RED, + Color.GREEN, + Color.BLUE, + Color.YELLOW, + Color.MAGENTA, + Color.CYAN, + Color.BLACK, + Color.WHITE, + new Color(255, 165, 0), + new Color(128, 0, 128), + new Color(255, 0, 0, 128) + }; + + /** + * Custom implementation of Paint that wraps a Color but is intentionally + * not a Color. This is used to bypass the "paint instanceof Color" + * optimization in Graphics2D#setPaint(). + */ + private static final class CustomPaint implements Paint { + + private final Color color; + + private CustomPaint(Color color) { + this.color = color; + } + + @Override + public PaintContext createContext(ColorModel cm, + Rectangle deviceBounds, + Rectangle2D userBounds, + AffineTransform xform, + RenderingHints hints) + { + return color.createContext(cm, deviceBounds, userBounds, xform, + hints); + } + + @Override + public int getTransparency() { + return color.getTransparency(); + } + } + + public static void main(String[] args) { + GraphicsConfiguration gc = null; + try { + gc = GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration(); + } catch (HeadlessException ignore) { + // skip VolatileImage validation + } + + for (Color color : COLORS) { + int rgb = color.getRGB(); + System.err.println("Test color: " + Integer.toHexString(rgb)); + for (int type : TYPES) { + var goldBI = new BufferedImage(SIZE, SIZE, type); + var paintBI = new BufferedImage(SIZE, SIZE, type); + var customBI = new BufferedImage(SIZE, SIZE, type); + + fill(goldBI, g -> g.setColor(color)); + fill(paintBI, g -> g.setPaint(color)); + fill(customBI, g -> g.setPaint(new CustomPaint(color))); + + if (!verify(paintBI, goldBI)) { + throw new RuntimeException("paintBI != goldBI"); + } + + if (!verify(customBI, goldBI)) { + throw new RuntimeException("customBI != goldBI"); + } + + if (gc == null) { + continue; + } + + int transparency = goldBI.getTransparency(); + var goldVI = fillVI(gc, transparency, g -> g.setColor(color)); + var paintVI = fillVI(gc, transparency, g -> g.setPaint(color)); + var customVI = fillVI(gc, transparency, + g -> g.setPaint(new CustomPaint(color))); + + if (gc.getColorModel().getPixelSize() >= 24) { + if (color.getAlpha() == 255 && !verify(goldBI, goldVI)) { + throw new RuntimeException("goldBI != goldVI"); + } + } + + if (!verify(paintVI, goldVI)) { + throw new RuntimeException("paintVI != goldVI"); + } + + if (!verify(customVI, goldVI)) { + throw new RuntimeException("customVI != goldVI"); + } + } + } + } + + private static void fill(Image img, Consumer action) { + Graphics2D g2d = (Graphics2D) img.getGraphics(); + action.accept(g2d); + g2d.fillRect(0, 0, SIZE, SIZE); + g2d.dispose(); + } + + private static BufferedImage fillVI(GraphicsConfiguration gc, + int transparency, + Consumer action) + { + var vi = gc.createCompatibleVolatileImage(SIZE, SIZE, transparency); + int attempt = 0; + while (true) { + if (++attempt > 10) { + throw new RuntimeException("Too many attempts: " + attempt); + } + + int status = vi.validate(gc); + if (status == VolatileImage.IMAGE_INCOMPATIBLE) { + vi = gc.createCompatibleVolatileImage(SIZE, SIZE, transparency); + } + + fill(vi, action); + + BufferedImage snapshot = vi.getSnapshot(); + if (vi.contentsLost()) { + continue; + } + return snapshot; + } + } + + private static boolean verify(BufferedImage img1, BufferedImage img2) { + for (int y = 0; y < SIZE; y++) { + for (int x = 0; x < SIZE; x++) { + int rgb1 = img1.getRGB(x, y); + int rgb2 = img2.getRGB(x, y); + if (rgb1 != rgb2) { + System.err.println("rgb1: " + Integer.toHexString(rgb1)); + System.err.println("rgb2: " + Integer.toHexString(rgb2)); + return false; + } + } + } + return true; + } +} diff --git a/test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextStateTrackerTest.java b/test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextStateTrackerTest.java new file mode 100644 index 00000000000..629b8a74957 --- /dev/null +++ b/test/jdk/java/awt/ColorClass/PaintContext/ColorPaintContextStateTrackerTest.java @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com Inc. 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.Color; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; + +import sun.awt.image.SurfaceManager; +import sun.java2d.StateTrackable; +import sun.java2d.SurfaceData; + +/** + * @test + * @bug 8355078 + * @summary Checks that ColorPaintContext surface is STABLE and cacheable + * @modules java.desktop/sun.awt.image + * java.desktop/sun.java2d + */ +public final class ColorPaintContextStateTrackerTest { + + public static void main(String[] args) { + var context = Color.RED.createContext(null, null, null, null, null); + var cm = context.getColorModel(); + var raster = (WritableRaster) context.getRaster(0, 0, 1, 1); + var bi = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + + SurfaceData sd = SurfaceManager.getManager(bi).getPrimarySurfaceData(); + StateTrackable.State state = sd.getState(); + if (state != StateTrackable.State.STABLE) { + System.err.println("Actual: " + state); + System.err.println("Expected: " + StateTrackable.State.STABLE); + throw new RuntimeException("Wrong state"); + } + } +}