< prev index next >

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

Print this page




  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.MarlinProperties;
  37 import com.sun.marlin.MarlinRenderer;

  38 import com.sun.marlin.RendererContext;
  39 import com.sun.marlin.Stroker;
  40 import com.sun.marlin.TransformingPathConsumer2D;
  41 import com.sun.prism.BasicStroke;

  42 
  43 public final class MarlinPrismUtils {
  44 
  45     private static final boolean FORCE_NO_AA = false;
  46 
  47     // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases
  48     static final boolean DISABLE_2ND_STROKER_CLIPPING = true;
  49 
  50     static final boolean DO_TRACE_PATH = false;
  51 
  52     static final boolean DO_CLIP = MarlinProperties.isDoClip();
  53     static final boolean DO_CLIP_FILL = true;
  54     static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
  55 
  56     static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
  57     static final float LOWER_BND = -UPPER_BND;
  58 
  59     /**
  60      * Private constructor to prevent instantiation.
  61      */


  69             BaseTransform tx,
  70             final PathConsumer2D out)
  71     {
  72         // We use strokerat so that in Stroker and Dasher we can work only
  73         // with the pre-transformation coordinates. This will repeat a lot of
  74         // computations done in the path iterator, but the alternative is to
  75         // work with transformed paths and compute untransformed coordinates
  76         // as needed. This would be faster but I do not think the complexity
  77         // of working with both untransformed and transformed coordinates in
  78         // the same code is worth it.
  79         // However, if a path's width is constant after a transformation,
  80         // we can skip all this untransforming.
  81 
  82         // As pathTo() will check transformed coordinates for invalid values
  83         // (NaN / Infinity) to ignore such points, it is necessary to apply the
  84         // transformation before the path processing.
  85         BaseTransform strokerTx = null;
  86 
  87         int dashLen = -1;
  88         boolean recycleDashes = false;
  89         float scale = 1.0f;
  90         float width = lineWidth;
  91         float[] dashes = stroke.getDashArray();
  92         float dashphase = stroke.getDashPhase();
  93 
  94         if ((tx != null) && !tx.isIdentity()) {
  95             final double a = tx.getMxx();
  96             final double b = tx.getMxy();
  97             final double c = tx.getMyx();
  98             final double d = tx.getMyy();
  99 
 100             // If the transform is a constant multiple of an orthogonal transformation
 101             // then every length is just multiplied by a constant, so we just
 102             // need to transform input paths to stroker and tell stroker
 103             // the scaled width. This condition is satisfied if
 104             // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
 105             // leave a bit of room for error.
 106             if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
 107                 scale = (float) Math.sqrt(a*a + c*c);
 108 
 109                 if (dashes != null) {
 110                     recycleDashes = true;
 111                     dashLen = dashes.length;
 112                     dashes = rdrCtx.dasher.copyDashArray(dashes);
 113                     for (int i = 0; i < dashLen; i++) {
 114                         dashes[i] *= scale;
 115                     }
 116                     dashphase *= scale;
 117                 }
 118                 width *= scale;
 119 
 120                 // by now strokerat == null. Input paths to
 121                 // stroker (and maybe dasher) will have the full transform tx
 122                 // applied to them and nothing will happen to the output paths.
 123             } else {
 124                 strokerTx = tx;
 125 
 126                 // by now strokerat == tx. Input paths to
 127                 // stroker (and maybe dasher) will have the full transform tx
 128                 // applied to them, then they will be normalized, and then
 129                 // the inverse of *only the non translation part of tx* will
 130                 // be applied to the normalized paths. This won't cause problems
 131                 // in stroker, because, suppose tx = T*A, where T is just the
 132                 // translation part of tx, and A is the rest. T*A has already
 133                 // been applied to Stroker/Dasher's input. Then Ainv will be
 134                 // applied. Ainv*T*A is not equal to T, but it is a translation,
 135                 // which means that none of stroker's assumptions about its
 136                 // input will be violated. After all this, A will be applied
 137                 // to stroker's output.
 138             }
 139         } else {
 140             // either tx is null or it's the identity. In either case
 141             // we don't transform the path.
 142             tx = null;
 143         }
 144 
 145         // Get renderer offsets:
 146         float rdrOffX = 0.0f, rdrOffY = 0.0f;
 147 
 148         if (rdrCtx.doClip && (tx != null)) {
 149             final MarlinRenderer renderer = (MarlinRenderer)out;
 150             rdrOffX = renderer.getOffsetX();
 151             rdrOffY = renderer.getOffsetY();
 152         }
 153 
 154         // Prepare the pipeline:
 155         PathConsumer2D pc = out;
 156 
 157         final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 158 
 159         if (DO_TRACE_PATH) {
 160             // trace Stroker:
 161             pc = transformerPC2D.traceStroker(pc);
 162         }
 163 
 164         if (MarlinConst.USE_SIMPLIFIER) {
 165             // Use simplifier after stroker before Renderer
 166             // to remove collinear segments (notably due to cap square)
 167             pc = rdrCtx.simplifier.init(pc);
 168         }
 169 
 170         // deltaTransformConsumer may adjust the clip rectangle:
 171         pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY);
 172 
 173         // stroker will adjust the clip rectangle (width / miter limit):
 174         pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 175                 stroke.getLineJoin(), stroke.getMiterLimit(),
 176                 scale, rdrOffX, rdrOffY, (dashes == null));
 177 
 178         // Curve Monotizer:
 179         rdrCtx.monotonizer.init(width);
 180 
 181         if (dashes != null) {
 182             if (!recycleDashes) {
 183                 dashLen = dashes.length;
 184             }
 185             if (DO_TRACE_PATH) {
 186                 pc = transformerPC2D.traceDasher(pc);
 187             }
 188             pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase,
 189                                     recycleDashes);
 190 
 191             if (DISABLE_2ND_STROKER_CLIPPING) {
 192                 // disable stoker clipping:
 193                 rdrCtx.stroker.disableClipping();
 194             }
 195 
 196         } else if (rdrCtx.doClip && (stroke.getEndCap() != Stroker.CAP_BUTT)) {


 222          */
 223         return pc;
 224     }
 225 
 226     private static boolean nearZero(final double num) {
 227         return Math.abs(num) < 2.0d * Math.ulp(num);
 228     }
 229 
 230     private static PathConsumer2D initRenderer(
 231             final RendererContext rdrCtx,
 232             final BasicStroke stroke,
 233             final BaseTransform tx,
 234             final Rectangle clip,
 235             final int piRule,
 236             final MarlinRenderer renderer)
 237     {
 238         if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
 239             // Define the initial clip bounds:
 240             final float[] clipRect = rdrCtx.clipRect;
 241 
 242             clipRect[0] = clip.y;
 243             clipRect[1] = clip.y + clip.height;
 244             clipRect[2] = clip.x;
 245             clipRect[3] = clip.x + clip.width;
















 246 
 247             // Enable clipping:
 248             rdrCtx.doClip = true;
 249         }
 250 
 251         if (stroke != null) {
 252             renderer.init(clip.x, clip.y, clip.width, clip.height,
 253                           MarlinConst.WIND_NON_ZERO);
 254 
 255             return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer);
 256         } else {
 257             // Filler:
 258             final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ?
 259                 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO;
 260 
 261             renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
 262 
 263             PathConsumer2D pc = renderer;
 264 
 265             final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 266 
 267             if (DO_CLIP_FILL && rdrCtx.doClip) {
 268                 float rdrOffX = renderer.getOffsetX();
 269                 float rdrOffY = renderer.getOffsetY();
 270 
 271                 if (DO_TRACE_PATH) {
 272                     // trace Filler:
 273                     pc = rdrCtx.transformerPC2D.traceFiller(pc);
 274                 }
 275                 pc = rdrCtx.transformerPC2D.pathClipper(pc, rdrOffX, rdrOffY);
 276             }
 277 
 278             if (DO_TRACE_PATH) {
 279                 // trace Input:
 280                 pc = transformerPC2D.traceInput(pc);
 281             }
 282             return pc;
 283         }
 284     }
 285 
 286     public static MarlinRenderer setupRenderer(
 287             final RendererContext rdrCtx,
 288             final Shape shape,
 289             final BasicStroke stroke,
 290             final BaseTransform xform,
 291             final Rectangle rclip,
 292             final boolean antialiasedShape)
 293     {
 294         // Test if transform is identity:
 295         final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null;




  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.MarlinProperties;
  37 import com.sun.marlin.MarlinRenderer;
  38 import com.sun.marlin.MarlinUtils;
  39 import com.sun.marlin.RendererContext;
  40 import com.sun.marlin.Stroker;
  41 import com.sun.marlin.TransformingPathConsumer2D;
  42 import com.sun.prism.BasicStroke;
  43 import java.util.Arrays;
  44 
  45 public final class MarlinPrismUtils {
  46 
  47     private static final boolean FORCE_NO_AA = false;
  48 
  49     // slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases
  50     static final boolean DISABLE_2ND_STROKER_CLIPPING = true;
  51 
  52     static final boolean DO_TRACE_PATH = false;
  53 
  54     static final boolean DO_CLIP = MarlinProperties.isDoClip();
  55     static final boolean DO_CLIP_FILL = true;
  56     static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
  57 
  58     static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
  59     static final float LOWER_BND = -UPPER_BND;
  60 
  61     /**
  62      * Private constructor to prevent instantiation.
  63      */


  71             BaseTransform tx,
  72             final PathConsumer2D out)
  73     {
  74         // We use strokerat so that in Stroker and Dasher we can work only
  75         // with the pre-transformation coordinates. This will repeat a lot of
  76         // computations done in the path iterator, but the alternative is to
  77         // work with transformed paths and compute untransformed coordinates
  78         // as needed. This would be faster but I do not think the complexity
  79         // of working with both untransformed and transformed coordinates in
  80         // the same code is worth it.
  81         // However, if a path's width is constant after a transformation,
  82         // we can skip all this untransforming.
  83 
  84         // As pathTo() will check transformed coordinates for invalid values
  85         // (NaN / Infinity) to ignore such points, it is necessary to apply the
  86         // transformation before the path processing.
  87         BaseTransform strokerTx = null;
  88 
  89         int dashLen = -1;
  90         boolean recycleDashes = false;

  91         float width = lineWidth;
  92         float[] dashes = stroke.getDashArray();
  93         float dashphase = stroke.getDashPhase();
  94 
  95         if ((tx != null) && !tx.isIdentity()) {
  96             final double a = tx.getMxx();
  97             final double b = tx.getMxy();
  98             final double c = tx.getMyx();
  99             final double d = tx.getMyy();
 100 
 101             // If the transform is a constant multiple of an orthogonal transformation
 102             // then every length is just multiplied by a constant, so we just
 103             // need to transform input paths to stroker and tell stroker
 104             // the scaled width. This condition is satisfied if
 105             // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
 106             // leave a bit of room for error.
 107             if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
 108                 final float scale = (float) Math.sqrt(a*a + c*c);
 109 
 110                 if (dashes != null) {
 111                     recycleDashes = true;
 112                     dashLen = dashes.length;
 113                     dashes = rdrCtx.dasher.copyDashArray(dashes);
 114                     for (int i = 0; i < dashLen; i++) {
 115                         dashes[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         } else {
 141             // either tx is null or it's the identity. In either case
 142             // we don't transform the path.
 143             tx = null;
 144         }
 145 









 146         // Prepare the pipeline:
 147         PathConsumer2D pc = out;
 148 
 149         final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 150 
 151         if (DO_TRACE_PATH) {
 152             // trace Stroker:
 153             pc = transformerPC2D.traceStroker(pc);
 154         }
 155 
 156         if (MarlinConst.USE_SIMPLIFIER) {
 157             // Use simplifier after stroker before Renderer
 158             // to remove collinear segments (notably due to cap square)
 159             pc = rdrCtx.simplifier.init(pc);
 160         }
 161 
 162         // deltaTransformConsumer may adjust the clip rectangle:
 163         pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx);
 164 
 165         // stroker will adjust the clip rectangle (width / miter limit):
 166         pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
 167                 stroke.getLineJoin(), stroke.getMiterLimit(),
 168                 (dashes == null));
 169 
 170         // Curve Monotizer:
 171         rdrCtx.monotonizer.init(width);
 172 
 173         if (dashes != null) {
 174             if (!recycleDashes) {
 175                 dashLen = dashes.length;
 176             }
 177             if (DO_TRACE_PATH) {
 178                 pc = transformerPC2D.traceDasher(pc);
 179             }
 180             pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase,
 181                                     recycleDashes);
 182 
 183             if (DISABLE_2ND_STROKER_CLIPPING) {
 184                 // disable stoker clipping:
 185                 rdrCtx.stroker.disableClipping();
 186             }
 187 
 188         } else if (rdrCtx.doClip && (stroke.getEndCap() != Stroker.CAP_BUTT)) {


 214          */
 215         return pc;
 216     }
 217 
 218     private static boolean nearZero(final double num) {
 219         return Math.abs(num) < 2.0d * Math.ulp(num);
 220     }
 221 
 222     private static PathConsumer2D initRenderer(
 223             final RendererContext rdrCtx,
 224             final BasicStroke stroke,
 225             final BaseTransform tx,
 226             final Rectangle clip,
 227             final int piRule,
 228             final MarlinRenderer renderer)
 229     {
 230         if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
 231             // Define the initial clip bounds:
 232             final float[] clipRect = rdrCtx.clipRect;
 233 
 234             // Adjust the clipping rectangle with the renderer offsets
 235             final float rdrOffX = renderer.getOffsetX();
 236             final float rdrOffY = renderer.getOffsetY();
 237 
 238             // add a small rounding error:
 239             final float margin = 1e-3f;
 240 
 241             clipRect[0] = clip.y
 242                             - margin + rdrOffY;
 243             clipRect[1] = clip.y + clip.height
 244                             + margin + rdrOffY;
 245             clipRect[2] = clip.x
 246                             - margin + rdrOffX;
 247             clipRect[3] = clip.x + clip.width
 248                             + margin + rdrOffX;
 249 
 250             if (MarlinConst.DO_LOG_CLIP) {
 251                 MarlinUtils.logInfo("clipRect (clip): "
 252                                     + Arrays.toString(rdrCtx.clipRect));
 253             }
 254 
 255             // Enable clipping:
 256             rdrCtx.doClip = true;
 257         }
 258 
 259         if (stroke != null) {
 260             renderer.init(clip.x, clip.y, clip.width, clip.height,
 261                           MarlinConst.WIND_NON_ZERO);
 262 
 263             return initStroker(rdrCtx, stroke, stroke.getLineWidth(), tx, renderer);
 264         } else {
 265             // Filler:
 266             final int oprule = (piRule == PathIterator.WIND_EVEN_ODD) ?
 267                 MarlinConst.WIND_EVEN_ODD : MarlinConst.WIND_NON_ZERO;
 268 
 269             renderer.init(clip.x, clip.y, clip.width, clip.height, oprule);
 270 
 271             PathConsumer2D pc = renderer;
 272 
 273             final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
 274 
 275             if (DO_CLIP_FILL && rdrCtx.doClip) {



 276                 if (DO_TRACE_PATH) {
 277                     // trace Filler:
 278                     pc = rdrCtx.transformerPC2D.traceFiller(pc);
 279                 }
 280                 pc = rdrCtx.transformerPC2D.pathClipper(pc);
 281             }
 282 
 283             if (DO_TRACE_PATH) {
 284                 // trace Input:
 285                 pc = transformerPC2D.traceInput(pc);
 286             }
 287             return pc;
 288         }
 289     }
 290 
 291     public static MarlinRenderer setupRenderer(
 292             final RendererContext rdrCtx,
 293             final Shape shape,
 294             final BasicStroke stroke,
 295             final BaseTransform xform,
 296             final Rectangle rclip,
 297             final boolean antialiasedShape)
 298     {
 299         // Test if transform is identity:
 300         final BaseTransform tf = ((xform != null) && !xform.isIdentity()) ? xform : null;


< prev index next >