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 = 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( 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 { | 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.MarlinProperties; 36 import com.sun.marlin.DMarlinRenderer; 37 import com.sun.marlin.DPathConsumer2D; 38 import com.sun.marlin.DRendererContext; 39 import com.sun.marlin.DStroker; 40 import com.sun.marlin.DTransformingPathConsumer2D; 41 import com.sun.prism.BasicStroke; 42 43 public final class DMarlinPrismUtils { 44 45 private static final boolean FORCE_NO_AA = false; 46 47 static final double UPPER_BND = Float.MAX_VALUE / 2.0d; 48 static final double LOWER_BND = -UPPER_BND; 49 50 static final boolean DO_CLIP = MarlinProperties.isDoClip(); 51 static final boolean DO_TRACE = false; 52 53 /** 54 * Private constructor to prevent instantiation. 55 */ 56 private DMarlinPrismUtils() { 57 } 58 59 private static DPathConsumer2D initRenderer( 60 final DRendererContext rdrCtx, 61 final BasicStroke stroke, 62 final BaseTransform tx, 63 final Rectangle clip, 64 final int pirule, 65 final DMarlinRenderer renderer) 66 { 67 final int oprule = (stroke == null && pirule == PathIterator.WIND_EVEN_ODD) ? 68 DMarlinRenderer.WIND_EVEN_ODD : DMarlinRenderer.WIND_NON_ZERO; 69 70 // We use strokerat so that in Stroker and Dasher we can work only 71 // with the pre-transformation coordinates. This will repeat a lot of 72 // computations done in the path iterator, but the alternative is to 73 // work with transformed paths and compute untransformed coordinates 74 // as needed. This would be faster but I do not think the complexity 75 // of working with both untransformed and transformed coordinates in 76 // the same code is worth it. 77 // However, if a path's width is constant after a transformation, 78 // we can skip all this untransforming. 79 80 // As pathTo() will check transformed coordinates for invalid values 81 // (NaN / Infinity) to ignore such points, it is necessary to apply the 82 // transformation before the path processing. 83 BaseTransform strokerTx = null; 84 85 int dashLen = -1; 86 boolean recycleDashes = false; 87 double scale = 1.0d; 88 double width = 0.0f, dashphase = 0.0f; 89 double[] dashesD = null; 90 91 if (stroke != null) { 92 width = stroke.getLineWidth(); 93 final float[] dashes = stroke.getDashArray(); 94 dashphase = stroke.getDashPhase(); 95 96 // Ensure converting dashes to double precision: 97 if (dashes != null) { 98 recycleDashes = true; 99 dashLen = dashes.length; 100 dashesD = rdrCtx.dasher.copyDashArray(dashes); 101 } 102 103 if (tx != null && !tx.isIdentity()) { 104 final double a = tx.getMxx(); 105 final double b = tx.getMxy(); 106 final double c = tx.getMyx(); 107 final double d = tx.getMyy(); 108 109 // If the transform is a constant multiple of an orthogonal transformation 110 // then every length is just multiplied by a constant, so we just 111 // need to transform input paths to stroker and tell stroker 112 // the scaled width. This condition is satisfied if 113 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 114 // leave a bit of room for error. 115 if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) { 116 scale = Math.sqrt(a*a + c*c); 117 118 if (dashesD != null) { 119 for (int i = 0; i < dashLen; i++) { 120 dashesD[i] *= scale; 121 } 122 dashphase *= scale; 123 } 124 width *= scale; 125 126 // by now strokerat == null. Input paths to 127 // stroker (and maybe dasher) will have the full transform tx 128 // applied to them and nothing will happen to the output paths. 129 } else { 130 strokerTx = tx; 131 132 // by now strokerat == tx. Input paths to 133 // stroker (and maybe dasher) will have the full transform tx 134 // applied to them, then they will be normalized, and then 135 // the inverse of *only the non translation part of tx* will 136 // be applied to the normalized paths. This won't cause problems 137 // in stroker, because, suppose tx = T*A, where T is just the 138 // translation part of tx, and A is the rest. T*A has already 139 // been applied to Stroker/Dasher's input. Then Ainv will be 140 // applied. Ainv*T*A is not equal to T, but it is a translation, 141 // which means that none of stroker's assumptions about its 142 // input will be violated. After all this, A will be applied 143 // to stroker's output. 144 } 145 } 146 } 147 148 final DMarlinRenderer rdr = renderer.init(clip.x, clip.y, clip.width, clip.height, oprule); 149 DPathConsumer2D pc = rdr; 150 151 double rdrOffX = 0.0d, rdrOffY = 0.0d; 152 153 if (DO_CLIP && stroke != null) { 154 // Define the initial clip bounds: 155 final double[] clipRect = rdrCtx.clipRect; 156 clipRect[0] = clip.y; 157 clipRect[1] = clip.y + clip.height; 158 clipRect[2] = clip.x; 159 clipRect[3] = clip.x + clip.width; 160 161 // Get offsets: 162 rdrOffX = rdr.getOffsetX(); 163 rdrOffY = rdr.getOffsetY(); 164 165 // Enable clipping: 166 rdrCtx.doClip = true; 167 } 168 169 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D; 170 171 if (DO_TRACE) { 172 // trace Stroker: 173 pc = transformerPC2D.traceStroker(pc); 174 } 175 176 if (MarlinConst.USE_SIMPLIFIER) { 177 // Use simplifier after stroker before Renderer 178 // to remove collinear segments (notably due to cap square) 179 pc = rdrCtx.simplifier.init(pc); 180 } 181 182 // deltaTransformConsumer may adjust the clip rectangle: 183 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY); 184 185 if (stroke != null) { 186 // stroker will adjust the clip rectangle (width / miter limit): 187 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(), 188 stroke.getLineJoin(), stroke.getMiterLimit(), 189 scale, rdrOffX, rdrOffY); 190 191 if (dashesD != null) { 192 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes); 193 } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) { 194 if (DO_TRACE) { 195 pc = transformerPC2D.traceClosedPathDetector(pc); 196 } 197 198 // If no dash and clip is enabled: 199 // detect closedPaths (polygons) for caps 200 pc = transformerPC2D.detectClosedPath(pc); 201 } 202 } 203 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx); 204 205 if (DO_TRACE) { 206 // trace Input: 207 pc = transformerPC2D.traceInput(pc); 208 } 209 /* 210 * Pipeline seems to be: 211 * shape.getPathIterator(tx) 212 * -> (inverseDeltaTransformConsumer) 213 * -> (Dasher) 214 * -> Stroker 215 * -> (deltaTransformConsumer) 216 * 217 * -> (CollinearSimplifier) to remove redundant segments 218 * 219 * -> pc2d = Renderer (bounding box) 220 */ 221 return pc; 222 } 223 224 private static boolean nearZero(final double num) { 225 return Math.abs(num) < 2.0d * Math.ulp(num); 226 } 227 228 public static DMarlinRenderer setupRenderer( 386 387 // mark context as CLEAN: 388 rdrCtx.dirty = false; 389 } 390 391 private static void feedConsumer(final DRendererContext rdrCtx, 392 final Path2D p2d, 393 final BaseTransform xform, 394 final DPathConsumer2D pc2d) 395 { 396 // mark context as DIRTY: 397 rdrCtx.dirty = true; 398 399 final float[] coords = rdrCtx.float6; 400 401 // ported from DuctusRenderingEngine.feedConsumer() but simplified: 402 // - removed skip flag = !subpathStarted 403 // - removed pathClosed (ie subpathStarted not set to false) 404 boolean subpathStarted = false; 405 406 final float[] pCoords = p2d.getFloatCoordsNoClone(); 407 final byte[] pTypes = p2d.getCommandsNoClone(); 408 final int nsegs = p2d.getNumCommands(); 409 410 for (int i = 0, coff = 0; i < nsegs; i++) { 411 switch (pTypes[i]) { 412 case PathIterator.SEG_MOVETO: 413 if (xform == null) { 414 coords[0] = pCoords[coff]; 415 coords[1] = pCoords[coff+1]; 416 } else { 417 xform.transform(pCoords, coff, coords, 0, 1); 418 } 419 coff += 2; 420 /* Checking SEG_MOVETO coordinates if they are out of the 421 * [LOWER_BND, UPPER_BND] range. This check also handles NaN 422 * and Infinity values. Skipping next path segment in case of 423 * invalid data. 424 */ 425 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 426 coords[1] < UPPER_BND && coords[1] > LOWER_BND) 427 { |