9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 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.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 = 0f, dashphase = 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.0 * 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 245 * and Infinity values. Skipping next path segment in case of 246 * invalid data. 247 */ 248 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 326 subpathStarted = true; 327 } 328 } 329 break; 330 case PathIterator.SEG_CLOSE: 331 if (subpathStarted) { 332 pc2d.closePath(); 333 // do not set subpathStarted to false 334 // in case of missing moveTo() after close() 335 } 336 break; 337 default: 338 } 339 } 340 pc2d.pathDone(); 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]; | 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 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 = 0f, dashphase = 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.0 * 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 246 * and Infinity values. Skipping next path segment in case of 247 * invalid data. 248 */ 249 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND && 327 subpathStarted = true; 328 } 329 } 330 break; 331 case PathIterator.SEG_CLOSE: 332 if (subpathStarted) { 333 pc2d.closePath(); 334 // do not set subpathStarted to false 335 // in case of missing moveTo() after close() 336 } 337 break; 338 default: 339 } 340 } 341 pc2d.pathDone(); 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]; |