8355078: java.awt.Color.createContext() uses unnecessary synchronization

Reviewed-by: prr
This commit is contained in:
Sergey Bylokhov 2025-05-19 23:43:19 +00:00
parent 637e9d16dd
commit 890456f0f7
4 changed files with 283 additions and 34 deletions

View File

@ -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());
}
/**

View File

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

View File

@ -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<Graphics2D> 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<Graphics2D> 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;
}
}

View File

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