< prev index next >

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

Print this page




  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 = 0.0f, dashphase = 0.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.0d * 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


 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];
 370                     coords[1] = pCoords[coff+1];
 371                 } else {
 372                     xform.transform(pCoords, coff, coords, 0, 1);
 373                 }
 374                 coff += 2;
 375                 /* Checking SEG_MOVETO coordinates if they are out of the
 376                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 377                  * and Infinity values. Skipping next path segment in case of
 378                  * invalid data.
 379                  */
 380                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 381                     coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 382                 {




  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 boolean nearZero(final double num) {
  55         return Math.abs(num) < 2.0d * Math.ulp(num);
  56     }
  57 
  58     private static PathConsumer2D initPipeline(
  59             final RendererContext rdrCtx,
  60             final BasicStroke stroke,
  61             final float lineWidth,
  62             final BaseTransform tx,
  63             final PathConsumer2D out)


  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         float scale = 1.0f;
  83         float width = 0.0f, dashphase = 0.0f;
  84         float[] dashes = null;
  85 
  86         if (stroke != null) {
  87             width = lineWidth;
  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                     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         // Prepare the pipeline:
 140         PathConsumer2D pc = out;
 141 
 142         if (MarlinConst.USE_SIMPLIFIER) {
 143             // Use simplifier after stroker before Renderer
 144             // to remove collinear segments (notably due to cap square)
 145             pc = rdrCtx.simplifier.init(pc);
 146         }
 147 
 148         final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;

 149 
 150         if (stroke != null) {
 151             pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 152 
 153             pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 154                     stroke.getLineJoin(), stroke.getMiterLimit());
 155 
 156             if (dashes != null) {
 157                 if (!recycleDashes) {
 158                     dashLen = dashes.length;
 159                 }
 160                 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes);
 161             }
 162             pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
 163         }
 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 PathConsumer2D initRenderer(




 181             final RendererContext rdrCtx,

 182             final BasicStroke stroke,
 183             final BaseTransform tx,
 184             final Rectangle clip,
 185             final int piRule,
 186             final MarlinRenderer renderer)
 187     {
 188         final int oprule = ((stroke == null) && (piRule == PathIterator.WIND_EVEN_ODD)) ?
 189             MarlinRenderer.WIND_EVEN_ODD : MarlinRenderer.WIND_NON_ZERO;
 190 
 191         renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
 192 
 193         float lw = 0.0f;



 194 
 195         if (stroke != null) {
 196             lw = stroke.getLineWidth();
 197         }
 198 
 199         return initPipeline(rdrCtx, stroke, lw, tx, renderer);
 200     }
 201 
 202     public static MarlinRenderer setupRenderer(
 203             final RendererContext rdrCtx,
 204             final Shape shape,
 205             final BasicStroke stroke,
 206             final BaseTransform xform,
 207             final Rectangle rclip,
 208             final boolean antialiasedShape)
 209     {
 210         // Test if transform is identity:
 211         final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null;
 212 
 213         final MarlinRenderer r =  (!FORCE_NO_AA && antialiasedShape) ?
 214                 rdrCtx.renderer : rdrCtx.getRendererNoAA();
 215 
 216         if (shape instanceof Path2D) {
 217             final Path2D p2d = (Path2D)shape;
 218             final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, p2d.getWindingRule(), r);
 219             feedConsumer(rdrCtx, p2d, tf, pc2d);
 220         } else {
 221             final PathIterator pi = shape.getPathIterator(tf);
 222             final PathConsumer2D pc2d = initRenderer(rdrCtx, stroke, tf, rclip, pi.getWindingRule(), r);
 223             feedConsumer(rdrCtx, pi, pc2d);
 224         }
 225         return r;
 226     }
 227 
 228     public static void strokeTo(
 229             final RendererContext rdrCtx,
 230             final Shape shape,
 231             final BasicStroke stroke,
 232             final float lineWidth,
 233             final PathConsumer2D out)
 234     {
 235         final PathConsumer2D pc2d = initPipeline(rdrCtx, stroke, lineWidth, null, out);
 236 
 237         if (shape instanceof Path2D) {
 238             feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
 239         } else {
 240             feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
 241         }
 242     }
 243 
 244     private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi,
 245                                      final PathConsumer2D pc2d)
 246     {
 247         // mark context as DIRTY:
 248         rdrCtx.dirty = true;
 249 
 250         final float[] coords = rdrCtx.float6;
 251 
 252         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 253         // - removed skip flag = !subpathStarted
 254         // - removed pathClosed (ie subpathStarted not set to false)
 255         boolean subpathStarted = false;
 256 
 257         for (; !pi.isDone(); pi.next()) {
 258             switch (pi.currentSegment(coords)) {
 259             case PathIterator.SEG_MOVETO:
 260                 /* Checking SEG_MOVETO coordinates if they are out of the
 261                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN


 358 
 359         // mark context as CLEAN:
 360         rdrCtx.dirty = false;
 361     }
 362 
 363     private static void feedConsumer(final RendererContext rdrCtx,
 364                                      final Path2D p2d,
 365                                      final BaseTransform xform,
 366                                      final PathConsumer2D pc2d)
 367     {
 368         // mark context as DIRTY:
 369         rdrCtx.dirty = true;
 370 
 371         final float[] coords = rdrCtx.float6;
 372 
 373         // ported from DuctusRenderingEngine.feedConsumer() but simplified:
 374         // - removed skip flag = !subpathStarted
 375         // - removed pathClosed (ie subpathStarted not set to false)
 376         boolean subpathStarted = false;
 377 
 378         final float[] pCoords = p2d.getFloatCoordsNoClone();
 379         final byte[] pTypes = p2d.getCommandsNoClone();
 380         final int nsegs = p2d.getNumCommands();
 381 
 382         for (int i = 0, coff = 0; i < nsegs; i++) {
 383             switch (pTypes[i]) {
 384             case PathIterator.SEG_MOVETO:
 385                 if (xform == null) {
 386                     coords[0] = pCoords[coff];
 387                     coords[1] = pCoords[coff+1];
 388                 } else {
 389                     xform.transform(pCoords, coff, coords, 0, 1);
 390                 }
 391                 coff += 2;
 392                 /* Checking SEG_MOVETO coordinates if they are out of the
 393                  * [LOWER_BND, UPPER_BND] range. This check also handles NaN
 394                  * and Infinity values. Skipping next path segment in case of
 395                  * invalid data.
 396                  */
 397                 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
 398                     coords[1] < UPPER_BND && coords[1] > LOWER_BND)
 399                 {


< prev index next >