mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-30 13:08:24 +00:00
536 lines
22 KiB
Java
536 lines
22 KiB
Java
/*
|
|
* Copyright (c) 2007, 2013, 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. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
package sun.java2d.pipe;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.GradientPaint;
|
|
import java.awt.LinearGradientPaint;
|
|
import java.awt.MultipleGradientPaint;
|
|
import java.awt.MultipleGradientPaint.ColorSpaceType;
|
|
import java.awt.MultipleGradientPaint.CycleMethod;
|
|
import java.awt.Paint;
|
|
import java.awt.RadialGradientPaint;
|
|
import java.awt.TexturePaint;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.awt.geom.Point2D;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.awt.image.AffineTransformOp;
|
|
import java.awt.image.BufferedImage;
|
|
import sun.awt.image.PixelConverter;
|
|
import sun.java2d.SunGraphics2D;
|
|
import sun.java2d.SurfaceData;
|
|
import sun.java2d.loops.CompositeType;
|
|
import sun.java2d.loops.SurfaceType;
|
|
import static sun.java2d.pipe.BufferedOpCodes.*;
|
|
|
|
import java.lang.annotation.Native;
|
|
|
|
public class BufferedPaints {
|
|
|
|
static void setPaint(RenderQueue rq, SunGraphics2D sg2d,
|
|
Paint paint, int ctxflags)
|
|
{
|
|
if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {
|
|
setColor(rq, sg2d.pixel);
|
|
} else {
|
|
boolean useMask = (ctxflags & BufferedContext.USE_MASK) != 0;
|
|
switch (sg2d.paintState) {
|
|
case SunGraphics2D.PAINT_GRADIENT:
|
|
setGradientPaint(rq, sg2d,
|
|
(GradientPaint)paint, useMask);
|
|
break;
|
|
case SunGraphics2D.PAINT_LIN_GRADIENT:
|
|
setLinearGradientPaint(rq, sg2d,
|
|
(LinearGradientPaint)paint, useMask);
|
|
break;
|
|
case SunGraphics2D.PAINT_RAD_GRADIENT:
|
|
setRadialGradientPaint(rq, sg2d,
|
|
(RadialGradientPaint)paint, useMask);
|
|
break;
|
|
case SunGraphics2D.PAINT_TEXTURE:
|
|
setTexturePaint(rq, sg2d,
|
|
(TexturePaint)paint, useMask);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void resetPaint(RenderQueue rq) {
|
|
// assert rq.lock.isHeldByCurrentThread();
|
|
rq.ensureCapacity(4);
|
|
RenderBuffer buf = rq.getBuffer();
|
|
buf.putInt(RESET_PAINT);
|
|
}
|
|
|
|
/****************************** Color support *******************************/
|
|
|
|
private static void setColor(RenderQueue rq, int pixel) {
|
|
// assert rq.lock.isHeldByCurrentThread();
|
|
rq.ensureCapacity(8);
|
|
RenderBuffer buf = rq.getBuffer();
|
|
buf.putInt(SET_COLOR);
|
|
buf.putInt(pixel);
|
|
}
|
|
|
|
/************************* GradientPaint support ****************************/
|
|
|
|
/**
|
|
* Note: This code is factored out into a separate static method
|
|
* so that it can be shared by both the Gradient and LinearGradient
|
|
* implementations. LinearGradient uses this code (for the
|
|
* two-color sRGB case only) because it can be much faster than the
|
|
* equivalent implementation that uses fragment shaders.
|
|
*
|
|
* We use OpenGL's texture coordinate generator to automatically
|
|
* apply a smooth gradient (either cyclic or acyclic) to the geometry
|
|
* being rendered. This technique is almost identical to the one
|
|
* described in the comments for BufferedPaints.setTexturePaint(),
|
|
* except the calculations take place in one dimension instead of two.
|
|
* Instead of an anchor rectangle in the TexturePaint case, we use
|
|
* the vector between the two GradientPaint end points in our
|
|
* calculations. The generator uses a single plane equation that
|
|
* takes the (x,y) location (in device space) of the fragment being
|
|
* rendered to calculate a (u) texture coordinate for that fragment:
|
|
* u = Ax + By + Cz + Dw
|
|
*
|
|
* The gradient renderer uses a two-pixel 1D texture where the first
|
|
* pixel contains the first GradientPaint color, and the second pixel
|
|
* contains the second GradientPaint color. (Note that we use the
|
|
* GL_CLAMP_TO_EDGE wrapping mode for acyclic gradients so that we
|
|
* clamp the colors properly at the extremes.) The following diagram
|
|
* attempts to show the layout of the texture containing the two
|
|
* GradientPaint colors (C1 and C2):
|
|
*
|
|
* +-----------------+
|
|
* | C1 | C2 |
|
|
* | | |
|
|
* +-----------------+
|
|
* u=0 .25 .5 .75 1
|
|
*
|
|
* We calculate our plane equation constants (A,B,D) such that u=0.25
|
|
* corresponds to the first GradientPaint end point in user space and
|
|
* u=0.75 corresponds to the second end point. This is somewhat
|
|
* non-obvious, but since the gradient colors are generated by
|
|
* interpolating between C1 and C2, we want the pure color at the
|
|
* end points, and we will get the pure color only when u correlates
|
|
* to the center of a texel. The following chart shows the expected
|
|
* color for some sample values of u (where C' is the color halfway
|
|
* between C1 and C2):
|
|
*
|
|
* u value acyclic (GL_CLAMP) cyclic (GL_REPEAT)
|
|
* ------- ------------------ ------------------
|
|
* -0.25 C1 C2
|
|
* 0.0 C1 C'
|
|
* 0.25 C1 C1
|
|
* 0.5 C' C'
|
|
* 0.75 C2 C2
|
|
* 1.0 C2 C'
|
|
* 1.25 C2 C1
|
|
*
|
|
* Original inspiration for this technique came from UMD's Agile2D
|
|
* project (GradientManager.java).
|
|
*/
|
|
private static void setGradientPaint(RenderQueue rq, AffineTransform at,
|
|
Color c1, Color c2,
|
|
Point2D pt1, Point2D pt2,
|
|
boolean isCyclic, boolean useMask)
|
|
{
|
|
// convert gradient colors to IntArgbPre format
|
|
PixelConverter pc = PixelConverter.ArgbPre.instance;
|
|
int pixel1 = pc.rgbToPixel(c1.getRGB(), null);
|
|
int pixel2 = pc.rgbToPixel(c2.getRGB(), null);
|
|
|
|
// calculate plane equation constants
|
|
double x = pt1.getX();
|
|
double y = pt1.getY();
|
|
at.translate(x, y);
|
|
// now gradient point 1 is at the origin
|
|
x = pt2.getX() - x;
|
|
y = pt2.getY() - y;
|
|
double len = Math.sqrt(x * x + y * y);
|
|
at.rotate(x, y);
|
|
// now gradient point 2 is on the positive x-axis
|
|
at.scale(2*len, 1);
|
|
// now gradient point 2 is at (0.5, 0)
|
|
at.translate(-0.25, 0);
|
|
// now gradient point 1 is at (0.25, 0), point 2 is at (0.75, 0)
|
|
|
|
double p0, p1, p3;
|
|
try {
|
|
at.invert();
|
|
p0 = at.getScaleX();
|
|
p1 = at.getShearX();
|
|
p3 = at.getTranslateX();
|
|
} catch (java.awt.geom.NoninvertibleTransformException e) {
|
|
p0 = p1 = p3 = 0.0;
|
|
}
|
|
|
|
// assert rq.lock.isHeldByCurrentThread();
|
|
rq.ensureCapacityAndAlignment(44, 12);
|
|
RenderBuffer buf = rq.getBuffer();
|
|
buf.putInt(SET_GRADIENT_PAINT);
|
|
buf.putInt(useMask ? 1 : 0);
|
|
buf.putInt(isCyclic ? 1 : 0);
|
|
buf.putDouble(p0).putDouble(p1).putDouble(p3);
|
|
buf.putInt(pixel1).putInt(pixel2);
|
|
}
|
|
|
|
private static void setGradientPaint(RenderQueue rq,
|
|
SunGraphics2D sg2d,
|
|
GradientPaint paint,
|
|
boolean useMask)
|
|
{
|
|
setGradientPaint(rq, (AffineTransform)sg2d.transform.clone(),
|
|
paint.getColor1(), paint.getColor2(),
|
|
paint.getPoint1(), paint.getPoint2(),
|
|
paint.isCyclic(), useMask);
|
|
}
|
|
|
|
/************************** TexturePaint support ****************************/
|
|
|
|
/**
|
|
* We use OpenGL's texture coordinate generator to automatically
|
|
* map the TexturePaint image to the geometry being rendered. The
|
|
* generator uses two separate plane equations that take the (x,y)
|
|
* location (in device space) of the fragment being rendered to
|
|
* calculate (u,v) texture coordinates for that fragment:
|
|
* u = Ax + By + Cz + Dw
|
|
* v = Ex + Fy + Gz + Hw
|
|
*
|
|
* Since we use a 2D orthographic projection, we can assume that z=0
|
|
* and w=1 for any fragment. So we need to calculate appropriate
|
|
* values for the plane equation constants (A,B,D) and (E,F,H) such
|
|
* that {u,v}=0 for the top-left of the TexturePaint's anchor
|
|
* rectangle and {u,v}=1 for the bottom-right of the anchor rectangle.
|
|
* We can easily make the texture image repeat for {u,v} values
|
|
* outside the range [0,1] by specifying the GL_REPEAT texture wrap
|
|
* mode.
|
|
*
|
|
* Calculating the plane equation constants is surprisingly simple.
|
|
* We can think of it as an inverse matrix operation that takes
|
|
* device space coordinates and transforms them into user space
|
|
* coordinates that correspond to a location relative to the anchor
|
|
* rectangle. First, we translate and scale the current user space
|
|
* transform by applying the anchor rectangle bounds. We then take
|
|
* the inverse of this affine transform. The rows of the resulting
|
|
* inverse matrix correlate nicely to the plane equation constants
|
|
* we were seeking.
|
|
*/
|
|
private static void setTexturePaint(RenderQueue rq,
|
|
SunGraphics2D sg2d,
|
|
TexturePaint paint,
|
|
boolean useMask)
|
|
{
|
|
BufferedImage bi = paint.getImage();
|
|
SurfaceData dstData = sg2d.surfaceData;
|
|
SurfaceData srcData =
|
|
dstData.getSourceSurfaceData(bi, SunGraphics2D.TRANSFORM_ISIDENT,
|
|
CompositeType.SrcOver, null);
|
|
boolean filter =
|
|
(sg2d.interpolationType !=
|
|
AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
|
|
|
// calculate plane equation constants
|
|
AffineTransform at = (AffineTransform)sg2d.transform.clone();
|
|
Rectangle2D anchor = paint.getAnchorRect();
|
|
at.translate(anchor.getX(), anchor.getY());
|
|
at.scale(anchor.getWidth(), anchor.getHeight());
|
|
|
|
double xp0, xp1, xp3, yp0, yp1, yp3;
|
|
try {
|
|
at.invert();
|
|
xp0 = at.getScaleX();
|
|
xp1 = at.getShearX();
|
|
xp3 = at.getTranslateX();
|
|
yp0 = at.getShearY();
|
|
yp1 = at.getScaleY();
|
|
yp3 = at.getTranslateY();
|
|
} catch (java.awt.geom.NoninvertibleTransformException e) {
|
|
xp0 = xp1 = xp3 = yp0 = yp1 = yp3 = 0.0;
|
|
}
|
|
|
|
// assert rq.lock.isHeldByCurrentThread();
|
|
rq.ensureCapacityAndAlignment(68, 12);
|
|
RenderBuffer buf = rq.getBuffer();
|
|
buf.putInt(SET_TEXTURE_PAINT);
|
|
buf.putInt(useMask ? 1 : 0);
|
|
buf.putInt(filter ? 1 : 0);
|
|
buf.putLong(srcData.getNativeOps());
|
|
buf.putDouble(xp0).putDouble(xp1).putDouble(xp3);
|
|
buf.putDouble(yp0).putDouble(yp1).putDouble(yp3);
|
|
}
|
|
|
|
/****************** Shared MultipleGradientPaint support ********************/
|
|
|
|
/**
|
|
* The maximum number of gradient "stops" supported by our native
|
|
* fragment shader implementations.
|
|
*
|
|
* This value has been empirically determined and capped to allow
|
|
* our native shaders to run on all shader-level graphics hardware,
|
|
* even on the older, more limited GPUs. Even the oldest Nvidia
|
|
* hardware could handle 16, or even 32 fractions without any problem.
|
|
* But the first-generation boards from ATI would fall back into
|
|
* software mode (which is unusably slow) for values larger than 12;
|
|
* it appears that those boards do not have enough native registers
|
|
* to support the number of array accesses required by our gradient
|
|
* shaders. So for now we will cap this value at 12, but we can
|
|
* re-evaluate this in the future as hardware becomes more capable.
|
|
*/
|
|
@Native public static final int MULTI_MAX_FRACTIONS = 12;
|
|
|
|
/**
|
|
* Helper function to convert a color component in sRGB space to
|
|
* linear RGB space. Copied directly from the
|
|
* MultipleGradientPaintContext class.
|
|
*/
|
|
public static int convertSRGBtoLinearRGB(int color) {
|
|
float input, output;
|
|
|
|
input = color / 255.0f;
|
|
if (input <= 0.04045f) {
|
|
output = input / 12.92f;
|
|
} else {
|
|
output = (float)Math.pow((input + 0.055) / 1.055, 2.4);
|
|
}
|
|
|
|
return Math.round(output * 255.0f);
|
|
}
|
|
|
|
/**
|
|
* Helper function to convert a (non-premultiplied) Color in sRGB
|
|
* space to an IntArgbPre pixel value, optionally in linear RGB space.
|
|
* Based on the PixelConverter.ArgbPre.rgbToPixel() method.
|
|
*/
|
|
private static int colorToIntArgbPrePixel(Color c, boolean linear) {
|
|
int rgb = c.getRGB();
|
|
if (!linear && ((rgb >> 24) == -1)) {
|
|
return rgb;
|
|
}
|
|
int a = rgb >>> 24;
|
|
int r = (rgb >> 16) & 0xff;
|
|
int g = (rgb >> 8) & 0xff;
|
|
int b = (rgb ) & 0xff;
|
|
if (linear) {
|
|
r = convertSRGBtoLinearRGB(r);
|
|
g = convertSRGBtoLinearRGB(g);
|
|
b = convertSRGBtoLinearRGB(b);
|
|
}
|
|
int a2 = a + (a >> 7);
|
|
r = (r * a2) >> 8;
|
|
g = (g * a2) >> 8;
|
|
b = (b * a2) >> 8;
|
|
return ((a << 24) | (r << 16) | (g << 8) | (b));
|
|
}
|
|
|
|
/**
|
|
* Converts the given array of Color objects into an int array
|
|
* containing IntArgbPre pixel values. If the linear parameter
|
|
* is true, the Color values will be converted into a linear RGB
|
|
* color space before being returned.
|
|
*/
|
|
private static int[] convertToIntArgbPrePixels(Color[] colors,
|
|
boolean linear)
|
|
{
|
|
int[] pixels = new int[colors.length];
|
|
for (int i = 0; i < colors.length; i++) {
|
|
pixels[i] = colorToIntArgbPrePixel(colors[i], linear);
|
|
}
|
|
return pixels;
|
|
}
|
|
|
|
/********************** LinearGradientPaint support *************************/
|
|
|
|
/**
|
|
* This method uses techniques that are nearly identical to those
|
|
* employed in setGradientPaint() above. The primary difference
|
|
* is that at the native level we use a fragment shader to manually
|
|
* apply the plane equation constants to the current fragment position
|
|
* to calculate the gradient position in the range [0,1] (the native
|
|
* code for GradientPaint does the same, except that it uses OpenGL's
|
|
* automatic texture coordinate generation facilities).
|
|
*
|
|
* One other minor difference worth mentioning is that
|
|
* setGradientPaint() calculates the plane equation constants
|
|
* such that the gradient end points are positioned at 0.25 and 0.75
|
|
* (for reasons discussed in the comments for that method). In
|
|
* contrast, for LinearGradientPaint we setup the equation constants
|
|
* such that the gradient end points fall at 0.0 and 1.0. The
|
|
* reason for this difference is that in the fragment shader we
|
|
* have more control over how the gradient values are interpreted
|
|
* (depending on the paint's CycleMethod).
|
|
*/
|
|
private static void setLinearGradientPaint(RenderQueue rq,
|
|
SunGraphics2D sg2d,
|
|
LinearGradientPaint paint,
|
|
boolean useMask)
|
|
{
|
|
boolean linear =
|
|
(paint.getColorSpace() == ColorSpaceType.LINEAR_RGB);
|
|
Color[] colors = paint.getColors();
|
|
int numStops = colors.length;
|
|
Point2D pt1 = paint.getStartPoint();
|
|
Point2D pt2 = paint.getEndPoint();
|
|
AffineTransform at = paint.getTransform();
|
|
at.preConcatenate(sg2d.transform);
|
|
|
|
if (!linear && numStops == 2 &&
|
|
paint.getCycleMethod() != CycleMethod.REPEAT)
|
|
{
|
|
// delegate to the optimized two-color gradient codepath
|
|
boolean isCyclic =
|
|
(paint.getCycleMethod() != CycleMethod.NO_CYCLE);
|
|
setGradientPaint(rq, at,
|
|
colors[0], colors[1],
|
|
pt1, pt2,
|
|
isCyclic, useMask);
|
|
return;
|
|
}
|
|
|
|
int cycleMethod = paint.getCycleMethod().ordinal();
|
|
float[] fractions = paint.getFractions();
|
|
int[] pixels = convertToIntArgbPrePixels(colors, linear);
|
|
|
|
// calculate plane equation constants
|
|
double x = pt1.getX();
|
|
double y = pt1.getY();
|
|
at.translate(x, y);
|
|
// now gradient point 1 is at the origin
|
|
x = pt2.getX() - x;
|
|
y = pt2.getY() - y;
|
|
double len = Math.sqrt(x * x + y * y);
|
|
at.rotate(x, y);
|
|
// now gradient point 2 is on the positive x-axis
|
|
at.scale(len, 1);
|
|
// now gradient point 1 is at (0.0, 0), point 2 is at (1.0, 0)
|
|
|
|
float p0, p1, p3;
|
|
try {
|
|
at.invert();
|
|
p0 = (float)at.getScaleX();
|
|
p1 = (float)at.getShearX();
|
|
p3 = (float)at.getTranslateX();
|
|
} catch (java.awt.geom.NoninvertibleTransformException e) {
|
|
p0 = p1 = p3 = 0.0f;
|
|
}
|
|
|
|
// assert rq.lock.isHeldByCurrentThread();
|
|
rq.ensureCapacity(20 + 12 + (numStops*4*2));
|
|
RenderBuffer buf = rq.getBuffer();
|
|
buf.putInt(SET_LINEAR_GRADIENT_PAINT);
|
|
buf.putInt(useMask ? 1 : 0);
|
|
buf.putInt(linear ? 1 : 0);
|
|
buf.putInt(cycleMethod);
|
|
buf.putInt(numStops);
|
|
buf.putFloat(p0);
|
|
buf.putFloat(p1);
|
|
buf.putFloat(p3);
|
|
buf.put(fractions);
|
|
buf.put(pixels);
|
|
}
|
|
|
|
/********************** RadialGradientPaint support *************************/
|
|
|
|
/**
|
|
* This method calculates six m** values and a focusX value that
|
|
* are used by the native fragment shader. These techniques are
|
|
* based on a whitepaper by Daniel Rice on radial gradient performance
|
|
* (attached to the bug report for 6521533). One can refer to that
|
|
* document for the complete set of formulas and calculations, but
|
|
* the basic goal is to compose a transform that will convert an
|
|
* (x,y) position in device space into a "u" value that represents
|
|
* the relative distance to the gradient focus point. The resulting
|
|
* value can be used to look up the appropriate color by linearly
|
|
* interpolating between the two nearest colors in the gradient.
|
|
*/
|
|
private static void setRadialGradientPaint(RenderQueue rq,
|
|
SunGraphics2D sg2d,
|
|
RadialGradientPaint paint,
|
|
boolean useMask)
|
|
{
|
|
boolean linear =
|
|
(paint.getColorSpace() == ColorSpaceType.LINEAR_RGB);
|
|
int cycleMethod = paint.getCycleMethod().ordinal();
|
|
float[] fractions = paint.getFractions();
|
|
Color[] colors = paint.getColors();
|
|
int numStops = colors.length;
|
|
int[] pixels = convertToIntArgbPrePixels(colors, linear);
|
|
Point2D center = paint.getCenterPoint();
|
|
Point2D focus = paint.getFocusPoint();
|
|
float radius = paint.getRadius();
|
|
|
|
// save original (untransformed) center and focus points
|
|
double cx = center.getX();
|
|
double cy = center.getY();
|
|
double fx = focus.getX();
|
|
double fy = focus.getY();
|
|
|
|
// transform from gradient coords to device coords
|
|
AffineTransform at = paint.getTransform();
|
|
at.preConcatenate(sg2d.transform);
|
|
focus = at.transform(focus, focus);
|
|
|
|
// transform unit circle to gradient coords; we start with the
|
|
// unit circle (center=(0,0), focus on positive x-axis, radius=1)
|
|
// and then transform into gradient space
|
|
at.translate(cx, cy);
|
|
at.rotate(fx - cx, fy - cy);
|
|
at.scale(radius, radius);
|
|
|
|
// invert to get mapping from device coords to unit circle
|
|
try {
|
|
at.invert();
|
|
} catch (Exception e) {
|
|
at.setToScale(0.0, 0.0);
|
|
}
|
|
focus = at.transform(focus, focus);
|
|
|
|
// clamp the focus point so that it does not rest on, or outside
|
|
// of, the circumference of the gradient circle
|
|
fx = Math.min(focus.getX(), 0.99);
|
|
|
|
// assert rq.lock.isHeldByCurrentThread();
|
|
rq.ensureCapacity(20 + 28 + (numStops*4*2));
|
|
RenderBuffer buf = rq.getBuffer();
|
|
buf.putInt(SET_RADIAL_GRADIENT_PAINT);
|
|
buf.putInt(useMask ? 1 : 0);
|
|
buf.putInt(linear ? 1 : 0);
|
|
buf.putInt(numStops);
|
|
buf.putInt(cycleMethod);
|
|
buf.putFloat((float)at.getScaleX());
|
|
buf.putFloat((float)at.getShearX());
|
|
buf.putFloat((float)at.getTranslateX());
|
|
buf.putFloat((float)at.getShearY());
|
|
buf.putFloat((float)at.getScaleY());
|
|
buf.putFloat((float)at.getTranslateY());
|
|
buf.putFloat((float)fx);
|
|
buf.put(fractions);
|
|
buf.put(pixels);
|
|
}
|
|
}
|