< prev index next >

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

Print this page




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


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




  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 boolean nearZero(final double num) {
  55         return Math.abs(num) < 2.0d * Math.ulp(num);
  56     }
  57 
  58     private static DPathConsumer2D initPipeline(
  59             final DRendererContext rdrCtx,
  60             final BasicStroke stroke,
  61             final float lineWidth,
  62             final BaseTransform tx,
  63             final DPathConsumer2D 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         double scale = 1.0d;
  83         double width = 0.0f, dashphase = 0.0f;
  84         double[] dashesD = null;
  85 
  86         if (stroke != null) {
  87             width = lineWidth;
  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                     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         // Prepare the pipeline:
 144         DPathConsumer2D pc = out;
 145 
 146         if (MarlinConst.USE_SIMPLIFIER) {
 147             // Use simplifier after stroker before Renderer
 148             // to remove collinear segments (notably due to cap square)
 149             pc = rdrCtx.simplifier.init(pc);
 150         }
 151 
 152         final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;

 153 
 154         if (stroke != null) {
 155             pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 156 
 157             pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 158                     stroke.getLineJoin(), stroke.getMiterLimit());
 159 
 160             if (dashesD != null) {
 161                 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes);
 162             }
 163             pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
 164         }
 165 


 166         /*
 167          * Pipeline seems to be:
 168          * shape.getPathIterator(tx)
 169          * -> (inverseDeltaTransformConsumer)
 170          * -> (Dasher)
 171          * -> Stroker
 172          * -> (deltaTransformConsumer)
 173          *
 174          * -> (CollinearSimplifier) to remove redundant segments
 175          *
 176          * -> pc2d = Renderer (bounding box)
 177          */
 178         return pc;
 179     }
 180 
 181     private static DPathConsumer2D initRenderer(




 182             final DRendererContext rdrCtx,

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



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


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


< prev index next >