1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.prism.sw;
  27 
  28 import com.sun.javafx.geom.RectBounds;
  29 import com.sun.javafx.geom.Shape;
  30 import com.sun.javafx.geom.transform.Affine2D;
  31 import com.sun.javafx.geom.transform.BaseTransform;
  32 import com.sun.pisces.GradientColorMap;
  33 import com.sun.pisces.PiscesRenderer;
  34 import com.sun.pisces.RendererBase;
  35 import com.sun.pisces.Transform6;
  36 import com.sun.prism.Image;
  37 import com.sun.prism.PixelFormat;
  38 import com.sun.prism.Texture;
  39 import com.sun.prism.impl.PrismSettings;
  40 import com.sun.prism.paint.Color;
  41 import com.sun.prism.paint.Gradient;
  42 import com.sun.prism.paint.ImagePattern;
  43 import com.sun.prism.paint.LinearGradient;
  44 import com.sun.prism.paint.Paint;
  45 import com.sun.prism.paint.RadialGradient;
  46 import com.sun.prism.paint.Stop;
  47 
  48 final class SWPaint {
  49 
  50     private final SWContext context;
  51     private final PiscesRenderer pr;
  52 
  53     private final BaseTransform paintTx = new Affine2D();
  54     private final Transform6 piscesTx = new Transform6();
  55 
  56     private float compositeAlpha = 1.0f;
  57     private float px, py, pw, ph;
  58 
  59     SWPaint(SWContext context, PiscesRenderer pr) {
  60         this.context = context;
  61         this.pr = pr;
  62     }
  63 
  64     float getCompositeAlpha() {
  65         return compositeAlpha;
  66     }
  67 
  68     void setCompositeAlpha(float newValue) {
  69         compositeAlpha = newValue;
  70     }
  71 
  72     void setColor(Color c, float compositeAlpha) {
  73         if (PrismSettings.debug) {
  74             System.out.println("PR.setColor: " + c);
  75         }
  76         this.pr.setColor((int) (c.getRed() * 255),
  77                 (int) (255 * c.getGreen()),
  78                 (int) (255 * c.getBlue()),
  79                 (int) (255 * c.getAlpha() * compositeAlpha));
  80     }
  81 
  82     void setPaintFromShape(Paint p, BaseTransform tx, Shape shape, RectBounds nodeBounds,
  83                            float localX, float localY, float localWidth, float localHeight)
  84     {
  85         this.computePaintBounds(p, shape, nodeBounds, localX, localY, localWidth, localHeight);
  86         this.setPaintBeforeDraw(p, tx, px, py, pw, ph);
  87     }
  88 
  89     private void computePaintBounds(Paint p, Shape shape, RectBounds nodeBounds,
  90                                     float localX, float localY, float localWidth, float localHeight)
  91     {
  92         if (p.isProportional()) {
  93             if (nodeBounds != null) {
  94                 px = nodeBounds.getMinX();
  95                 py = nodeBounds.getMinY();
  96                 pw = nodeBounds.getWidth();
  97                 ph = nodeBounds.getHeight();
  98             } else if (shape != null) {
  99                 final RectBounds bounds = shape.getBounds();
 100                 px = bounds.getMinX();
 101                 py = bounds.getMinY();
 102                 pw = bounds.getWidth();
 103                 ph = bounds.getHeight();
 104             } else {
 105                 px = localX;
 106                 py = localY;
 107                 pw = localWidth;
 108                 ph = localHeight;
 109             }
 110         } else {
 111             px = py = pw = ph = 0;
 112         }
 113     }
 114 
 115     void setPaintBeforeDraw(Paint p, BaseTransform tx, float x, float y, float width, float height) {
 116         switch (p.getType()) {
 117             case COLOR:
 118                 this.setColor((Color)p, this.compositeAlpha);
 119                 break;
 120             case LINEAR_GRADIENT:
 121                 final LinearGradient lg = (LinearGradient)p;
 122                 if (PrismSettings.debug) {
 123                     System.out.println("PR.setLinearGradient: " + lg.getX1() + ", " + lg.getY1() + ", " + lg.getX2() + ", " + lg.getY2());
 124                 }
 125 
 126                 paintTx.setTransform(tx);
 127                 SWUtils.convertToPiscesTransform(paintTx, piscesTx);
 128 
 129                 float x1 = lg.getX1();
 130                 float y1 = lg.getY1();
 131                 float x2 = lg.getX2();
 132                 float y2 = lg.getY2();
 133                 if (lg.isProportional()) {
 134                     x1 = x + width * x1;
 135                     y1 = y + height * y1;
 136                     x2 = x + width * x2;
 137                     y2 = y + height * y2;
 138                 }
 139                 this.pr.setLinearGradient((int)(SWUtils.TO_PISCES * x1), (int)(SWUtils.TO_PISCES * y1),
 140                         (int)(SWUtils.TO_PISCES * x2), (int)(SWUtils.TO_PISCES * y2),
 141                         getFractions(lg), getARGB(lg, this.compositeAlpha), getPiscesGradientCycleMethod(lg.getSpreadMethod()), piscesTx);
 142                 break;
 143             case RADIAL_GRADIENT:
 144                 final RadialGradient rg = (RadialGradient)p;
 145                 if (PrismSettings.debug) {
 146                     System.out.println("PR.setRadialGradient: " + rg.getCenterX() + ", " + rg.getCenterY() + ", " + rg.getFocusAngle() + ", " + rg.getFocusDistance() + ", " + rg.getRadius());
 147                 }
 148 
 149                 paintTx.setTransform(tx);
 150 
 151                 float cx = rg.getCenterX();
 152                 float cy = rg.getCenterY();
 153                 float r = rg.getRadius();
 154                 if (rg.isProportional()) {
 155                     float dim = Math.min(width, height);
 156                     float bcx = x + width * 0.5f;
 157                     float bcy = y + height * 0.5f;
 158                     cx = bcx + (cx - 0.5f) * dim;
 159                     cy = bcy + (cy - 0.5f) * dim;
 160                     r *= dim;
 161                     if (width != height && width != 0.0 && height != 0.0) {
 162                         paintTx.deriveWithTranslation(bcx, bcy);
 163                         paintTx.deriveWithConcatenation(width / dim, 0, 0, height / dim, 0, 0);
 164                         paintTx.deriveWithTranslation(-bcx, -bcy);
 165                     }
 166                 }
 167                 SWUtils.convertToPiscesTransform(paintTx, piscesTx);
 168 
 169                 final float fx = (float)(cx + rg.getFocusDistance() * r * Math.cos(Math.toRadians(rg.getFocusAngle())));
 170                 final float fy = (float)(cy + rg.getFocusDistance() * r * Math.sin(Math.toRadians(rg.getFocusAngle())));
 171 
 172                 this.pr.setRadialGradient((int) (SWUtils.TO_PISCES * cx), (int) (SWUtils.TO_PISCES * cy),
 173                         (int) (SWUtils.TO_PISCES * fx), (int) (SWUtils.TO_PISCES * fy), (int) (SWUtils.TO_PISCES * r),
 174                         getFractions(rg), getARGB(rg, this.compositeAlpha), getPiscesGradientCycleMethod(rg.getSpreadMethod()), piscesTx);
 175                 break;
 176             case IMAGE_PATTERN:
 177                 final ImagePattern ip = (ImagePattern)p;
 178                 if (ip.getImage().getPixelFormat() == PixelFormat.BYTE_ALPHA) {
 179                     throw new UnsupportedOperationException("Alpha image is not supported as an image pattern.");
 180                 } else {
 181                     this.computeImagePatternTransform(ip, tx, x, y, width, height);
 182                     final SWArgbPreTexture tex = context.validateImagePaintTexture(ip.getImage().getWidth(), ip.getImage().getHeight());
 183                     tex.update(ip.getImage());
 184                     if (this.compositeAlpha < 1.0f) {
 185                         tex.applyCompositeAlpha(this.compositeAlpha);
 186                     }
 187 
 188                     this.pr.setTexture(RendererBase.TYPE_INT_ARGB_PRE, tex.getDataNoClone(),
 189                             tex.getContentWidth(), tex.getContentHeight(), tex.getPhysicalWidth(),
 190                             piscesTx,
 191                             tex.getWrapMode() == Texture.WrapMode.REPEAT,
 192                             tex.hasAlpha());
 193                 }
 194                 break;
 195             default:
 196                 throw new IllegalArgumentException("Unknown paint type: " + p.getType());
 197         }
 198     }
 199 
 200     private static int[] getARGB(Gradient grd, float compositeAlpha) {
 201         final int nstops = grd.getNumStops();
 202         final int argb[] = new int[nstops];
 203         for (int i = 0; i < nstops; i++) {
 204             final Stop stop = grd.getStops().get(i);
 205             final Color stopColor = stop.getColor();
 206             float alpha255 = 255 * stopColor.getAlpha() * compositeAlpha;
 207             argb[i] = ((((int)(alpha255)) & 0xFF) << 24) +
 208                     ((((int)(alpha255 * stopColor.getRed())) & 0xFF) << 16) +
 209                     ((((int)(alpha255 * stopColor.getGreen())) & 0xFF) << 8) +
 210                     (((int)(alpha255 * stopColor.getBlue())) & 0xFF);
 211         }
 212         return argb;
 213     }
 214 
 215     private static int[] getFractions(Gradient grd) {
 216         final int nstops = grd.getNumStops();
 217         final int fractions[] = new int[nstops];
 218         for (int i = 0; i < nstops; i++) {
 219             final Stop stop = grd.getStops().get(i);
 220             fractions[i] = (int)(SWUtils.TO_PISCES * stop.getOffset());
 221         }
 222         return fractions;
 223     }
 224 
 225     private static int getPiscesGradientCycleMethod(final int prismCycleMethod) {
 226         switch (prismCycleMethod) {
 227             case Gradient.PAD:
 228                 return GradientColorMap.CYCLE_NONE;
 229             case Gradient.REFLECT:
 230                 return GradientColorMap.CYCLE_REFLECT;
 231             case Gradient.REPEAT:
 232                 return GradientColorMap.CYCLE_REPEAT;
 233         }
 234         return GradientColorMap.CYCLE_NONE;
 235     }
 236 
 237     Transform6 computeDrawTexturePaintTransform(BaseTransform tx, float dx1, float dy1, float dx2, float dy2,
 238                                                 float sx1, float sy1, float sx2, float sy2)
 239     {
 240         paintTx.setTransform(tx);
 241 
 242         final float scaleX = computeScale(dx1, dx2, sx1, sx2);
 243         final float scaleY = computeScale(dy1, dy2, sy1, sy2);
 244 
 245         if (scaleX == 1 && scaleY == 1) {
 246             paintTx.deriveWithTranslation(-Math.min(sx1, sx2) + Math.min(dx1, dx2),
 247                     -Math.min(sy1, sy2) + Math.min(dy1, dy2));
 248         } else {
 249             paintTx.deriveWithTranslation(Math.min(dx1, dx2), Math.min(dy1, dy2));
 250             paintTx.deriveWithTranslation((scaleX >= 0) ? 0 : Math.abs(dx2 - dx1),
 251                     (scaleY >= 0) ? 0 : Math.abs(dy2 - dy1));
 252             paintTx.deriveWithConcatenation(scaleX, 0, 0, scaleY, 0, 0);
 253             paintTx.deriveWithTranslation(-Math.min(sx1, sx2), -Math.min(sy1, sy2));
 254         }
 255 
 256         SWUtils.convertToPiscesTransform(paintTx, piscesTx);
 257         return piscesTx;
 258     }
 259 
 260     private float computeScale(float dv1, float dv2, float sv1, float sv2) {
 261         final float dv_diff = dv2 - dv1;
 262         float scale = dv_diff / (sv2 - sv1);
 263         if (Math.abs(scale) > (Integer.MAX_VALUE >> 16)) {
 264             scale = Math.signum(scale) * (Integer.MAX_VALUE >> 16);
 265         }
 266         return scale;
 267     }
 268 
 269     Transform6 computeSetTexturePaintTransform(Paint p, BaseTransform tx, RectBounds nodeBounds,
 270                                                float localX, float localY, float localWidth, float localHeight)
 271     {
 272         this.computePaintBounds(p, null, nodeBounds, localX, localY, localWidth, localHeight);
 273 
 274         final ImagePattern ip = (ImagePattern)p;
 275         this.computeImagePatternTransform(ip, tx, px, py, pw, ph);
 276         return piscesTx;
 277     }
 278 
 279     private void computeImagePatternTransform(ImagePattern ip, BaseTransform tx, float x, float y, float width, float height) {
 280         final Image image = ip.getImage();
 281         if (PrismSettings.debug) {
 282             System.out.println("PR.setTexturePaint: " + image);
 283             System.out.println("imagePattern: x: " + ip.getX() + ", y: " + ip.getY() +
 284                     ", w: " + ip.getWidth() + ", h: " + ip.getHeight() + ", proportional: " + ip.isProportional());
 285         }
 286 
 287         paintTx.setTransform(tx);
 288         if (ip.isProportional()) {
 289             paintTx.deriveWithConcatenation(width / image.getWidth() * ip.getWidth(), 0,
 290                     0, height / image.getHeight() * ip.getHeight(),
 291                     x + width * ip.getX(), y + height * ip.getY());
 292         } else {
 293             paintTx.deriveWithConcatenation(ip.getWidth() / image.getWidth(), 0,
 294                     0, ip.getHeight() / image.getHeight(),
 295                     x + ip.getX(), y + ip.getY());
 296         }
 297         SWUtils.convertToPiscesTransform(paintTx, piscesTx);
 298     }
 299 }