< prev index next >

modules/javafx.graphics/src/main/java/com/sun/prism/impl/shape/DMarlinPrismUtils.java

Print this page




   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.impl.shape;
  27 
  28 
  29 import com.sun.javafx.geom.PathConsumer2D;
  30 import com.sun.javafx.geom.PathIterator;
  31 import com.sun.javafx.geom.Path2D;
  32 import com.sun.javafx.geom.Rectangle;
  33 import com.sun.javafx.geom.Shape;
  34 import com.sun.javafx.geom.transform.BaseTransform;
  35 import com.sun.marlin.MarlinConst;
  36 import com.sun.marlin.MarlinRenderer;
  37 import com.sun.marlin.RendererContext;
  38 import com.sun.marlin.TransformingPathConsumer2D;

  39 import com.sun.prism.BasicStroke;
  40 
  41 public final class MarlinPrismUtils {
  42 
  43     private static final boolean FORCE_NO_AA = false;
  44 
  45     static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
  46     static final float LOWER_BND = -UPPER_BND;
  47 
  48     /**
  49      * Private constructor to prevent instantiation.
  50      */
  51     private MarlinPrismUtils() {
  52     }
  53 
  54     private static PathConsumer2D initRenderer(
  55             final RendererContext rdrCtx,
  56             final BasicStroke stroke,
  57             final BaseTransform tx,
  58             final Rectangle clip,
  59             final int pirule,
  60             final MarlinRenderer renderer)
  61     {
  62         final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ?
  63             MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO;
  64 
  65         // We use strokerat so that in Stroker and Dasher we can work only
  66         // with the pre-transformation coordinates. This will repeat a lot of
  67         // computations done in the path iterator, but the alternative is to
  68         // work with transformed paths and compute untransformed coordinates
  69         // as needed. This would be faster but I do not think the complexity
  70         // of working with both untransformed and transformed coordinates in
  71         // the same code is worth it.
  72         // However, if a path's width is constant after a transformation,
  73         // we can skip all this untransforming.
  74 
  75         // As pathTo() will check transformed coordinates for invalid values
  76         // (NaN / Infinity) to ignore such points, it is necessary to apply the
  77         // transformation before the path processing.
  78         BaseTransform strokerTx = null;
  79 
  80         int dashLen = -1;
  81         boolean recycleDashes = false;
  82 
  83         float width = 0f, dashphase = 0f;
  84         float[] dashes = null;
  85 
  86         if (stroke != null) {
  87             width = stroke.getLineWidth();
  88             dashes = stroke.getDashArray();
  89             dashphase = stroke.getDashPhase();







  90 
  91             if (tx != null && !tx.isIdentity()) {
  92                 final double a = tx.getMxx();
  93                 final double b = tx.getMxy();
  94                 final double c = tx.getMyx();
  95                 final double d = tx.getMyy();
  96 
  97                 // If the transform is a constant multiple of an orthogonal transformation
  98                 // then every length is just multiplied by a constant, so we just
  99                 // need to transform input paths to stroker and tell stroker
 100                 // the scaled width. This condition is satisfied if
 101                 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
 102                 // leave a bit of room for error.
 103                 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
 104                     final float scale = (float) Math.sqrt(a*a + c*c);
 105 
 106                     if (dashes != null) {
 107                         recycleDashes = true;
 108                         dashLen = dashes.length;
 109                         dashes = rdrCtx.dasher.copyDashArray(dashes);
 110                         for (int i = 0; i < dashLen; i++) {
 111                             dashes[i] *= scale;
 112                         }
 113                         dashphase *= scale;
 114                     }
 115                     width *= scale;
 116 
 117                     // by now strokerat == null. Input paths to
 118                     // stroker (and maybe dasher) will have the full transform tx
 119                     // applied to them and nothing will happen to the output paths.
 120                 } else {
 121                     strokerTx = tx;
 122 
 123                     // by now strokerat == tx. Input paths to
 124                     // stroker (and maybe dasher) will have the full transform tx
 125                     // applied to them, then they will be normalized, and then
 126                     // the inverse of *only the non translation part of tx* will
 127                     // be applied to the normalized paths. This won't cause problems
 128                     // in stroker, because, suppose tx = T*A, where T is just the
 129                     // translation part of tx, and A is the rest. T*A has already
 130                     // been applied to Stroker/Dasher's input. Then Ainv will be
 131                     // applied. Ainv*T*A is not equal to T, but it is a translation,
 132                     // which means that none of stroker's assumptions about its
 133                     // input will be violated. After all this, A will be applied
 134                     // to stroker's output.
 135                 }
 136             }
 137         }
 138 
 139         PathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
 140 
 141         if (MarlinConst.USE_SIMPLIFIER) {
 142             // Use simplifier after stroker before Renderer
 143             // to remove collinear segments (notably due to cap square)
 144             pc = rdrCtx.simplifier.init(pc);
 145         }
 146 
 147         final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 148         pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 149 
 150         if (stroke != null) {
 151             pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 152                     stroke.getLineJoin(), stroke.getMiterLimit());
 153 
 154             if (dashes != null) {
 155                 if (!recycleDashes) {
 156                     dashLen = dashes.length;
 157                 }
 158                 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes);
 159             }
 160         }
 161 
 162         pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
 163 
 164         /*
 165          * Pipeline seems to be:
 166          * shape.getPathIterator(tx)
 167          * -> (inverseDeltaTransformConsumer)
 168          * -> (Dasher)
 169          * -> Stroker
 170          * -> (deltaTransformConsumer)
 171          *
 172          * -> (CollinearSimplifier) to remove redundant segments
 173          *
 174          * -> pc2d = Renderer (bounding box)
 175          */
 176         return pc;
 177     }
 178 
 179     private static boolean nearZero(final double num) {
 180         return Math.abs(num) < 2.0 * Math.ulp(num);
 181     }
 182 
 183     public static MarlinRenderer setupRenderer(
 184             final RendererContext rdrCtx,
 185             final Shape shape,
 186             final BasicStroke stroke,
 187             final BaseTransform xform,
 188             final Rectangle rclip,
 189             final boolean antialiasedShape)
 190     {
 191         // Test if transform is identity:
 192         final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
 193 
 194         final PathIterator pi = shape.getPathIterator(tf);
 195 
 196         final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
 197                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 198 
 199         final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
 200 
 201         feedConsumer(rdrCtx, pi, pc2d);
 202 
 203         return r;
 204     }
 205 
 206     public static MarlinRenderer setupRenderer(
 207             final RendererContext rdrCtx,
 208             final Path2D p2d,
 209             final BasicStroke stroke,
 210             final BaseTransform xform,
 211             final Rectangle rclip,
 212             final boolean antialiasedShape)
 213     {
 214         // Test if transform is identity:
 215         final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
 216 
 217         final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
 218                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 219 
 220         final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
 221 
 222         feedConsumer(rdrCtx, p2d, tf, pc2d);
 223 
 224         return r;
 225     }
 226 
 227     private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi,
 228                                      final PathConsumer2D pc2d)
 229     {
 230         // mark context as DIRTY:
 231         rdrCtx.dirty = true;
 232 
 233         final float[] coords = rdrCtx.float6;
 234 
 235         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 236         // - removed skip flag = !subpathStarted
 237         // - removed pathClosed (ie subpathStarted not set to false)
 238         boolean subpathStarted = false;
 239 
 240         for (; !pi.isDone(); pi.next()) {
 241             switch (pi.currentSegment(coords)) {
 242             case PathIterator.SEG_MOVETO:
 243                 /* Checking SEG_MOVETO coordinates if they are out of the
 244                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 245                  * and Infinity values. Skipping next path segment in case of
 246                  * invalid data.
 247                  */
 248                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&


 326                         subpathStarted = true;
 327                     }
 328                 }
 329                 break;
 330             case PathIterator.SEG_CLOSE:
 331                 if (subpathStarted) {
 332                     pc2d.closePath();
 333                     // do not set subpathStarted to false
 334                     // in case of missing moveTo() after close()
 335                 }
 336                 break;
 337             default:
 338             }
 339         }
 340         pc2d.pathDone();
 341 
 342         // mark context as CLEAN:
 343         rdrCtx.dirty = false;
 344     }
 345 
 346     private static void feedConsumer(final RendererContext rdrCtx,
 347                                      final Path2D p2d,
 348                                      final BaseTransform xform,
 349                                      final PathConsumer2D pc2d)
 350     {
 351         // mark context as DIRTY:
 352         rdrCtx.dirty = true;
 353 
 354         final float[] coords = rdrCtx.float6;
 355 
 356         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 357         // - removed skip flag = !subpathStarted
 358         // - removed pathClosed (ie subpathStarted not set to false)
 359         boolean subpathStarted = false;
 360 
 361         final float pCoords[] = p2d.getFloatCoordsNoClone();
 362         final byte pTypes[] = p2d.getCommandsNoClone();
 363         final int nsegs = p2d.getNumCommands();
 364 
 365         for (int i = 0, coff = 0; i < nsegs; i++) {
 366             switch (pTypes[i]) {
 367             case PathIterator.SEG_MOVETO:
 368                 if (xform == null) {
 369                     coords[0] = pCoords[coff];




   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.impl.shape;
  27 
  28 

  29 import com.sun.javafx.geom.PathIterator;
  30 import com.sun.javafx.geom.Path2D;
  31 import com.sun.javafx.geom.Rectangle;
  32 import com.sun.javafx.geom.Shape;
  33 import com.sun.javafx.geom.transform.BaseTransform;
  34 import com.sun.marlin.MarlinConst;
  35 import com.sun.marlin.DMarlinRenderer;
  36 import com.sun.marlin.DPathConsumer2D;
  37 import com.sun.marlin.DRendererContext;
  38 import com.sun.marlin.DTransformingPathConsumer2D;
  39 import com.sun.prism.BasicStroke;
  40 
  41 public final class DMarlinPrismUtils {
  42 
  43     private static final boolean FORCE_NO_AA = false;
  44 
  45     static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
  46     static final float LOWER_BND = -UPPER_BND;
  47 
  48     /**
  49      * Private constructor to prevent instantiation.
  50      */
  51     private DMarlinPrismUtils() {
  52     }
  53 
  54     private static DPathConsumer2D initRenderer(
  55             final DRendererContext rdrCtx,
  56             final BasicStroke stroke,
  57             final BaseTransform tx,
  58             final Rectangle clip,
  59             final int pirule,
  60             final DMarlinRenderer renderer)
  61     {
  62         final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ?
  63             DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.WIND_NON_ZERO;
  64 
  65         // We use strokerat so that in Stroker and Dasher we can work only
  66         // with the pre-transformation coordinates. This will repeat a lot of
  67         // computations done in the path iterator, but the alternative is to
  68         // work with transformed paths and compute untransformed coordinates
  69         // as needed. This would be faster but I do not think the complexity
  70         // of working with both untransformed and transformed coordinates in
  71         // the same code is worth it.
  72         // However, if a path's width is constant after a transformation,
  73         // we can skip all this untransforming.
  74 
  75         // As pathTo() will check transformed coordinates for invalid values
  76         // (NaN / Infinity) to ignore such points, it is necessary to apply the
  77         // transformation before the path processing.
  78         BaseTransform strokerTx = null;
  79 
  80         int dashLen = -1;
  81         boolean recycleDashes = false;
  82 
  83         double width = 0f, dashphase = 0f;
  84         double[] dashesD = null;
  85 
  86         if (stroke != null) {
  87             width = stroke.getLineWidth();
  88             final float[] dashes = stroke.getDashArray();
  89             dashphase = stroke.getDashPhase();
  90             
  91             // Ensure converting dashes to double precision:
  92             if (dashes != null) {
  93                 recycleDashes = true;
  94                 dashLen = dashes.length;
  95                 dashesD = rdrCtx.dasher.copyDashArray(dashes);
  96             }
  97 
  98             if (tx != null && !tx.isIdentity()) {
  99                 final double a = tx.getMxx();
 100                 final double b = tx.getMxy();
 101                 final double c = tx.getMyx();
 102                 final double d = tx.getMyy();
 103 
 104                 // If the transform is a constant multiple of an orthogonal transformation
 105                 // then every length is just multiplied by a constant, so we just
 106                 // need to transform input paths to stroker and tell stroker
 107                 // the scaled width. This condition is satisfied if
 108                 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
 109                 // leave a bit of room for error.
 110                 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
 111                     final double scale = Math.sqrt(a*a + c*c);
 112 
 113                     if (dashesD != null) {



 114                         for (int i = 0; i < dashLen; i++) {
 115                             dashesD[i] *= scale;
 116                         }
 117                         dashphase *= scale;
 118                     }
 119                     width *= scale;
 120 
 121                     // by now strokerat == null. Input paths to
 122                     // stroker (and maybe dasher) will have the full transform tx
 123                     // applied to them and nothing will happen to the output paths.
 124                 } else {
 125                     strokerTx = tx;
 126 
 127                     // by now strokerat == tx. Input paths to
 128                     // stroker (and maybe dasher) will have the full transform tx
 129                     // applied to them, then they will be normalized, and then
 130                     // the inverse of *only the non translation part of tx* will
 131                     // be applied to the normalized paths. This won't cause problems
 132                     // in stroker, because, suppose tx = T*A, where T is just the
 133                     // translation part of tx, and A is the rest. T*A has already
 134                     // been applied to Stroker/Dasher's input. Then Ainv will be
 135                     // applied. Ainv*T*A is not equal to T, but it is a translation,
 136                     // which means that none of stroker's assumptions about its
 137                     // input will be violated. After all this, A will be applied
 138                     // to stroker's output.
 139                 }
 140             }
 141         }
 142 
 143         DPathConsumer2D pc = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
 144 
 145         if (MarlinConst.USE_SIMPLIFIER) {
 146             // Use simplifier after stroker before Renderer
 147             // to remove collinear segments (notably due to cap square)
 148             pc = rdrCtx.simplifier.init(pc);
 149         }
 150 
 151         final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 152         pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 153 
 154         if (stroke != null) {
 155             pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 156                     stroke.getLineJoin(), stroke.getMiterLimit());
 157 
 158             if (dashesD != null) {
 159                 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes);



 160             }
 161         }
 162 
 163         pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
 164 
 165         /*
 166          * Pipeline seems to be:
 167          * shape.getPathIterator(tx)
 168          * -> (inverseDeltaTransformConsumer)
 169          * -> (Dasher)
 170          * -> Stroker
 171          * -> (deltaTransformConsumer)
 172          *
 173          * -> (CollinearSimplifier) to remove redundant segments
 174          *
 175          * -> pc2d = Renderer (bounding box)
 176          */
 177         return pc;
 178     }
 179 
 180     private static boolean nearZero(final double num) {
 181         return Math.abs(num) < 2.0 * Math.ulp(num);
 182     }
 183 
 184     public static DMarlinRenderer setupRenderer(
 185             final DRendererContext rdrCtx,
 186             final Shape shape,
 187             final BasicStroke stroke,
 188             final BaseTransform xform,
 189             final Rectangle rclip,
 190             final boolean antialiasedShape)
 191     {
 192         // Test if transform is identity:
 193         final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
 194 
 195         final PathIterator pi = shape.getPathIterator(tf);
 196 
 197         final DMarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
 198                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 199 
 200         final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
 201 
 202         feedConsumer(rdrCtx, pi, pc2d);
 203 
 204         return r;
 205     }
 206 
 207     public static DMarlinRenderer setupRenderer(
 208             final DRendererContext rdrCtx,
 209             final Path2D p2d,
 210             final BasicStroke stroke,
 211             final BaseTransform xform,
 212             final Rectangle rclip,
 213             final boolean antialiasedShape)
 214     {
 215         // Test if transform is identity:
 216         final BaseTransform tf = (xform != null && !xform.isIdentity()) ? xform : null;
 217 
 218         final DMarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
 219                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 220 
 221         final DPathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
 222 
 223         feedConsumer(rdrCtx, p2d, tf, pc2d);
 224 
 225         return r;
 226     }
 227 
 228     private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi,
 229                                      final DPathConsumer2D pc2d)
 230     {
 231         // mark context as DIRTY:
 232         rdrCtx.dirty = true;
 233 
 234         final float[] coords = rdrCtx.float6;
 235 
 236         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 237         // - removed skip flag = !subpathStarted
 238         // - removed pathClosed (ie subpathStarted not set to false)
 239         boolean subpathStarted = false;
 240 
 241         for (; !pi.isDone(); pi.next()) {
 242             switch (pi.currentSegment(coords)) {
 243             case PathIterator.SEG_MOVETO:
 244                 /* Checking SEG_MOVETO coordinates if they are out of the
 245                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 246                  * and Infinity values. Skipping next path segment in case of
 247                  * invalid data.
 248                  */
 249                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&


 327                         subpathStarted = true;
 328                     }
 329                 }
 330                 break;
 331             case PathIterator.SEG_CLOSE:
 332                 if (subpathStarted) {
 333                     pc2d.closePath();
 334                     // do not set subpathStarted to false
 335                     // in case of missing moveTo() after close()
 336                 }
 337                 break;
 338             default:
 339             }
 340         }
 341         pc2d.pathDone();
 342 
 343         // mark context as CLEAN:
 344         rdrCtx.dirty = false;
 345     }
 346 
 347     private static void feedConsumer(final DRendererContext rdrCtx,
 348                                      final Path2D p2d,
 349                                      final BaseTransform xform,
 350                                      final DPathConsumer2D pc2d)
 351     {
 352         // mark context as DIRTY:
 353         rdrCtx.dirty = true;
 354 
 355         final float[] coords = rdrCtx.float6;
 356 
 357         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 358         // - removed skip flag = !subpathStarted
 359         // - removed pathClosed (ie subpathStarted not set to false)
 360         boolean subpathStarted = false;
 361 
 362         final float pCoords[] = p2d.getFloatCoordsNoClone();
 363         final byte pTypes[] = p2d.getCommandsNoClone();
 364         final int nsegs = p2d.getNumCommands();
 365 
 366         for (int i = 0, coff = 0; i < nsegs; i++) {
 367             switch (pTypes[i]) {
 368             case PathIterator.SEG_MOVETO:
 369                 if (xform == null) {
 370                     coords[0] = pCoords[coff];


< prev index next >