Path: blob/master/src/java.desktop/share/classes/sun/java2d/pipe/BufferedPaints.java
41159 views
/*1* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.pipe;2627import java.awt.Color;28import java.awt.GradientPaint;29import java.awt.LinearGradientPaint;30import java.awt.MultipleGradientPaint;31import java.awt.MultipleGradientPaint.ColorSpaceType;32import java.awt.MultipleGradientPaint.CycleMethod;33import java.awt.Paint;34import java.awt.RadialGradientPaint;35import java.awt.TexturePaint;36import java.awt.geom.AffineTransform;37import java.awt.geom.Point2D;38import java.awt.geom.Rectangle2D;39import java.awt.image.AffineTransformOp;40import java.awt.image.BufferedImage;41import sun.awt.image.PixelConverter;42import sun.java2d.SunGraphics2D;43import sun.java2d.SurfaceData;44import sun.java2d.loops.CompositeType;45import sun.java2d.loops.SurfaceType;46import static sun.java2d.pipe.BufferedOpCodes.*;4748import java.lang.annotation.Native;4950public class BufferedPaints {5152static void setPaint(RenderQueue rq, SunGraphics2D sg2d,53Paint paint, int ctxflags)54{55if (sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR) {56setColor(rq, sg2d.pixel);57} else {58boolean useMask = (ctxflags & BufferedContext.USE_MASK) != 0;59switch (sg2d.paintState) {60case SunGraphics2D.PAINT_GRADIENT:61setGradientPaint(rq, sg2d,62(GradientPaint)paint, useMask);63break;64case SunGraphics2D.PAINT_LIN_GRADIENT:65setLinearGradientPaint(rq, sg2d,66(LinearGradientPaint)paint, useMask);67break;68case SunGraphics2D.PAINT_RAD_GRADIENT:69setRadialGradientPaint(rq, sg2d,70(RadialGradientPaint)paint, useMask);71break;72case SunGraphics2D.PAINT_TEXTURE:73setTexturePaint(rq, sg2d,74(TexturePaint)paint, useMask);75break;76default:77break;78}79}80}8182static void resetPaint(RenderQueue rq) {83// assert rq.lock.isHeldByCurrentThread();84rq.ensureCapacity(4);85RenderBuffer buf = rq.getBuffer();86buf.putInt(RESET_PAINT);87}8889/****************************** Color support *******************************/9091private static void setColor(RenderQueue rq, int pixel) {92// assert rq.lock.isHeldByCurrentThread();93rq.ensureCapacity(8);94RenderBuffer buf = rq.getBuffer();95buf.putInt(SET_COLOR);96buf.putInt(pixel);97}9899/************************* GradientPaint support ****************************/100101/**102* Note: This code is factored out into a separate static method103* so that it can be shared by both the Gradient and LinearGradient104* implementations. LinearGradient uses this code (for the105* two-color sRGB case only) because it can be much faster than the106* equivalent implementation that uses fragment shaders.107*108* We use OpenGL's texture coordinate generator to automatically109* apply a smooth gradient (either cyclic or acyclic) to the geometry110* being rendered. This technique is almost identical to the one111* described in the comments for BufferedPaints.setTexturePaint(),112* except the calculations take place in one dimension instead of two.113* Instead of an anchor rectangle in the TexturePaint case, we use114* the vector between the two GradientPaint end points in our115* calculations. The generator uses a single plane equation that116* takes the (x,y) location (in device space) of the fragment being117* rendered to calculate a (u) texture coordinate for that fragment:118* u = Ax + By + Cz + Dw119*120* The gradient renderer uses a two-pixel 1D texture where the first121* pixel contains the first GradientPaint color, and the second pixel122* contains the second GradientPaint color. (Note that we use the123* GL_CLAMP_TO_EDGE wrapping mode for acyclic gradients so that we124* clamp the colors properly at the extremes.) The following diagram125* attempts to show the layout of the texture containing the two126* GradientPaint colors (C1 and C2):127*128* +-----------------+129* | C1 | C2 |130* | | |131* +-----------------+132* u=0 .25 .5 .75 1133*134* We calculate our plane equation constants (A,B,D) such that u=0.25135* corresponds to the first GradientPaint end point in user space and136* u=0.75 corresponds to the second end point. This is somewhat137* non-obvious, but since the gradient colors are generated by138* interpolating between C1 and C2, we want the pure color at the139* end points, and we will get the pure color only when u correlates140* to the center of a texel. The following chart shows the expected141* color for some sample values of u (where C' is the color halfway142* between C1 and C2):143*144* u value acyclic (GL_CLAMP) cyclic (GL_REPEAT)145* ------- ------------------ ------------------146* -0.25 C1 C2147* 0.0 C1 C'148* 0.25 C1 C1149* 0.5 C' C'150* 0.75 C2 C2151* 1.0 C2 C'152* 1.25 C2 C1153*154* Original inspiration for this technique came from UMD's Agile2D155* project (GradientManager.java).156*/157private static void setGradientPaint(RenderQueue rq, AffineTransform at,158Color c1, Color c2,159Point2D pt1, Point2D pt2,160boolean isCyclic, boolean useMask)161{162// convert gradient colors to IntArgbPre format163PixelConverter pc = PixelConverter.ArgbPre.instance;164int pixel1 = pc.rgbToPixel(c1.getRGB(), null);165int pixel2 = pc.rgbToPixel(c2.getRGB(), null);166167// calculate plane equation constants168double x = pt1.getX();169double y = pt1.getY();170at.translate(x, y);171// now gradient point 1 is at the origin172x = pt2.getX() - x;173y = pt2.getY() - y;174double len = Math.sqrt(x * x + y * y);175at.rotate(x, y);176// now gradient point 2 is on the positive x-axis177at.scale(2*len, 1);178// now gradient point 2 is at (0.5, 0)179at.translate(-0.25, 0);180// now gradient point 1 is at (0.25, 0), point 2 is at (0.75, 0)181182double p0, p1, p3;183try {184at.invert();185p0 = at.getScaleX();186p1 = at.getShearX();187p3 = at.getTranslateX();188} catch (java.awt.geom.NoninvertibleTransformException e) {189p0 = p1 = p3 = 0.0;190}191192// assert rq.lock.isHeldByCurrentThread();193rq.ensureCapacityAndAlignment(44, 12);194RenderBuffer buf = rq.getBuffer();195buf.putInt(SET_GRADIENT_PAINT);196buf.putInt(useMask ? 1 : 0);197buf.putInt(isCyclic ? 1 : 0);198buf.putDouble(p0).putDouble(p1).putDouble(p3);199buf.putInt(pixel1).putInt(pixel2);200}201202private static void setGradientPaint(RenderQueue rq,203SunGraphics2D sg2d,204GradientPaint paint,205boolean useMask)206{207setGradientPaint(rq, (AffineTransform)sg2d.transform.clone(),208paint.getColor1(), paint.getColor2(),209paint.getPoint1(), paint.getPoint2(),210paint.isCyclic(), useMask);211}212213/************************** TexturePaint support ****************************/214215/**216* We use OpenGL's texture coordinate generator to automatically217* map the TexturePaint image to the geometry being rendered. The218* generator uses two separate plane equations that take the (x,y)219* location (in device space) of the fragment being rendered to220* calculate (u,v) texture coordinates for that fragment:221* u = Ax + By + Cz + Dw222* v = Ex + Fy + Gz + Hw223*224* Since we use a 2D orthographic projection, we can assume that z=0225* and w=1 for any fragment. So we need to calculate appropriate226* values for the plane equation constants (A,B,D) and (E,F,H) such227* that {u,v}=0 for the top-left of the TexturePaint's anchor228* rectangle and {u,v}=1 for the bottom-right of the anchor rectangle.229* We can easily make the texture image repeat for {u,v} values230* outside the range [0,1] by specifying the GL_REPEAT texture wrap231* mode.232*233* Calculating the plane equation constants is surprisingly simple.234* We can think of it as an inverse matrix operation that takes235* device space coordinates and transforms them into user space236* coordinates that correspond to a location relative to the anchor237* rectangle. First, we translate and scale the current user space238* transform by applying the anchor rectangle bounds. We then take239* the inverse of this affine transform. The rows of the resulting240* inverse matrix correlate nicely to the plane equation constants241* we were seeking.242*/243private static void setTexturePaint(RenderQueue rq,244SunGraphics2D sg2d,245TexturePaint paint,246boolean useMask)247{248BufferedImage bi = paint.getImage();249SurfaceData dstData = sg2d.surfaceData;250SurfaceData srcData =251dstData.getSourceSurfaceData(bi, SunGraphics2D.TRANSFORM_ISIDENT,252CompositeType.SrcOver, null);253boolean filter =254(sg2d.interpolationType !=255AffineTransformOp.TYPE_NEAREST_NEIGHBOR);256257// calculate plane equation constants258AffineTransform at = (AffineTransform)sg2d.transform.clone();259Rectangle2D anchor = paint.getAnchorRect();260at.translate(anchor.getX(), anchor.getY());261at.scale(anchor.getWidth(), anchor.getHeight());262263double xp0, xp1, xp3, yp0, yp1, yp3;264try {265at.invert();266xp0 = at.getScaleX();267xp1 = at.getShearX();268xp3 = at.getTranslateX();269yp0 = at.getShearY();270yp1 = at.getScaleY();271yp3 = at.getTranslateY();272} catch (java.awt.geom.NoninvertibleTransformException e) {273xp0 = xp1 = xp3 = yp0 = yp1 = yp3 = 0.0;274}275276// assert rq.lock.isHeldByCurrentThread();277rq.ensureCapacityAndAlignment(68, 12);278RenderBuffer buf = rq.getBuffer();279buf.putInt(SET_TEXTURE_PAINT);280buf.putInt(useMask ? 1 : 0);281buf.putInt(filter ? 1 : 0);282buf.putLong(srcData.getNativeOps());283buf.putDouble(xp0).putDouble(xp1).putDouble(xp3);284buf.putDouble(yp0).putDouble(yp1).putDouble(yp3);285}286287/****************** Shared MultipleGradientPaint support ********************/288289/**290* The maximum number of gradient "stops" supported by our native291* fragment shader implementations.292*293* This value has been empirically determined and capped to allow294* our native shaders to run on all shader-level graphics hardware,295* even on the older, more limited GPUs. Even the oldest Nvidia296* hardware could handle 16, or even 32 fractions without any problem.297* But the first-generation boards from ATI would fall back into298* software mode (which is unusably slow) for values larger than 12;299* it appears that those boards do not have enough native registers300* to support the number of array accesses required by our gradient301* shaders. So for now we will cap this value at 12, but we can302* re-evaluate this in the future as hardware becomes more capable.303*/304@Native public static final int MULTI_MAX_FRACTIONS = 12;305306/**307* Helper function to convert a color component in sRGB space to308* linear RGB space. Copied directly from the309* MultipleGradientPaintContext class.310*/311public static int convertSRGBtoLinearRGB(int color) {312float input, output;313314input = color / 255.0f;315if (input <= 0.04045f) {316output = input / 12.92f;317} else {318output = (float)Math.pow((input + 0.055) / 1.055, 2.4);319}320321return Math.round(output * 255.0f);322}323324/**325* Helper function to convert a (non-premultiplied) Color in sRGB326* space to an IntArgbPre pixel value, optionally in linear RGB space.327* Based on the PixelConverter.ArgbPre.rgbToPixel() method.328*/329private static int colorToIntArgbPrePixel(Color c, boolean linear) {330int rgb = c.getRGB();331if (!linear && ((rgb >> 24) == -1)) {332return rgb;333}334int a = rgb >>> 24;335int r = (rgb >> 16) & 0xff;336int g = (rgb >> 8) & 0xff;337int b = (rgb ) & 0xff;338if (linear) {339r = convertSRGBtoLinearRGB(r);340g = convertSRGBtoLinearRGB(g);341b = convertSRGBtoLinearRGB(b);342}343int a2 = a + (a >> 7);344r = (r * a2) >> 8;345g = (g * a2) >> 8;346b = (b * a2) >> 8;347return ((a << 24) | (r << 16) | (g << 8) | (b));348}349350/**351* Converts the given array of Color objects into an int array352* containing IntArgbPre pixel values. If the linear parameter353* is true, the Color values will be converted into a linear RGB354* color space before being returned.355*/356private static int[] convertToIntArgbPrePixels(Color[] colors,357boolean linear)358{359int[] pixels = new int[colors.length];360for (int i = 0; i < colors.length; i++) {361pixels[i] = colorToIntArgbPrePixel(colors[i], linear);362}363return pixels;364}365366/********************** LinearGradientPaint support *************************/367368/**369* This method uses techniques that are nearly identical to those370* employed in setGradientPaint() above. The primary difference371* is that at the native level we use a fragment shader to manually372* apply the plane equation constants to the current fragment position373* to calculate the gradient position in the range [0,1] (the native374* code for GradientPaint does the same, except that it uses OpenGL's375* automatic texture coordinate generation facilities).376*377* One other minor difference worth mentioning is that378* setGradientPaint() calculates the plane equation constants379* such that the gradient end points are positioned at 0.25 and 0.75380* (for reasons discussed in the comments for that method). In381* contrast, for LinearGradientPaint we setup the equation constants382* such that the gradient end points fall at 0.0 and 1.0. The383* reason for this difference is that in the fragment shader we384* have more control over how the gradient values are interpreted385* (depending on the paint's CycleMethod).386*/387private static void setLinearGradientPaint(RenderQueue rq,388SunGraphics2D sg2d,389LinearGradientPaint paint,390boolean useMask)391{392boolean linear =393(paint.getColorSpace() == ColorSpaceType.LINEAR_RGB);394Color[] colors = paint.getColors();395int numStops = colors.length;396Point2D pt1 = paint.getStartPoint();397Point2D pt2 = paint.getEndPoint();398AffineTransform at = paint.getTransform();399at.preConcatenate(sg2d.transform);400401if (!linear && numStops == 2 &&402paint.getCycleMethod() != CycleMethod.REPEAT)403{404// delegate to the optimized two-color gradient codepath405boolean isCyclic =406(paint.getCycleMethod() != CycleMethod.NO_CYCLE);407setGradientPaint(rq, at,408colors[0], colors[1],409pt1, pt2,410isCyclic, useMask);411return;412}413414int cycleMethod = paint.getCycleMethod().ordinal();415float[] fractions = paint.getFractions();416int[] pixels = convertToIntArgbPrePixels(colors, linear);417418// calculate plane equation constants419double x = pt1.getX();420double y = pt1.getY();421at.translate(x, y);422// now gradient point 1 is at the origin423x = pt2.getX() - x;424y = pt2.getY() - y;425double len = Math.sqrt(x * x + y * y);426at.rotate(x, y);427// now gradient point 2 is on the positive x-axis428at.scale(len, 1);429// now gradient point 1 is at (0.0, 0), point 2 is at (1.0, 0)430431float p0, p1, p3;432try {433at.invert();434p0 = (float)at.getScaleX();435p1 = (float)at.getShearX();436p3 = (float)at.getTranslateX();437} catch (java.awt.geom.NoninvertibleTransformException e) {438p0 = p1 = p3 = 0.0f;439}440441// assert rq.lock.isHeldByCurrentThread();442rq.ensureCapacity(20 + 12 + (numStops*4*2));443RenderBuffer buf = rq.getBuffer();444buf.putInt(SET_LINEAR_GRADIENT_PAINT);445buf.putInt(useMask ? 1 : 0);446buf.putInt(linear ? 1 : 0);447buf.putInt(cycleMethod);448buf.putInt(numStops);449buf.putFloat(p0);450buf.putFloat(p1);451buf.putFloat(p3);452buf.put(fractions);453buf.put(pixels);454}455456/********************** RadialGradientPaint support *************************/457458/**459* This method calculates six m** values and a focusX value that460* are used by the native fragment shader. These techniques are461* based on a whitepaper by Daniel Rice on radial gradient performance462* (attached to the bug report for 6521533). One can refer to that463* document for the complete set of formulas and calculations, but464* the basic goal is to compose a transform that will convert an465* (x,y) position in device space into a "u" value that represents466* the relative distance to the gradient focus point. The resulting467* value can be used to look up the appropriate color by linearly468* interpolating between the two nearest colors in the gradient.469*/470private static void setRadialGradientPaint(RenderQueue rq,471SunGraphics2D sg2d,472RadialGradientPaint paint,473boolean useMask)474{475boolean linear =476(paint.getColorSpace() == ColorSpaceType.LINEAR_RGB);477int cycleMethod = paint.getCycleMethod().ordinal();478float[] fractions = paint.getFractions();479Color[] colors = paint.getColors();480int numStops = colors.length;481int[] pixels = convertToIntArgbPrePixels(colors, linear);482Point2D center = paint.getCenterPoint();483Point2D focus = paint.getFocusPoint();484float radius = paint.getRadius();485486// save original (untransformed) center and focus points487double cx = center.getX();488double cy = center.getY();489double fx = focus.getX();490double fy = focus.getY();491492// transform from gradient coords to device coords493AffineTransform at = paint.getTransform();494at.preConcatenate(sg2d.transform);495focus = at.transform(focus, focus);496497// transform unit circle to gradient coords; we start with the498// unit circle (center=(0,0), focus on positive x-axis, radius=1)499// and then transform into gradient space500at.translate(cx, cy);501at.rotate(fx - cx, fy - cy);502at.scale(radius, radius);503504// invert to get mapping from device coords to unit circle505try {506at.invert();507} catch (Exception e) {508at.setToScale(0.0, 0.0);509}510focus = at.transform(focus, focus);511512// clamp the focus point so that it does not rest on, or outside513// of, the circumference of the gradient circle514fx = Math.min(focus.getX(), 0.99);515516// assert rq.lock.isHeldByCurrentThread();517rq.ensureCapacity(20 + 28 + (numStops*4*2));518RenderBuffer buf = rq.getBuffer();519buf.putInt(SET_RADIAL_GRADIENT_PAINT);520buf.putInt(useMask ? 1 : 0);521buf.putInt(linear ? 1 : 0);522buf.putInt(numStops);523buf.putInt(cycleMethod);524buf.putFloat((float)at.getScaleX());525buf.putFloat((float)at.getShearX());526buf.putFloat((float)at.getTranslateX());527buf.putFloat((float)at.getShearY());528buf.putFloat((float)at.getScaleY());529buf.putFloat((float)at.getTranslateY());530buf.putFloat((float)fx);531buf.put(fractions);532buf.put(pixels);533}534}535536537