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