1 /*
2 * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
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
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 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_CLIP_FILL = true;
52
53 static final boolean DO_TRACE_PATH = false;
54
55 static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
56
57 /**
58 * Private constructor to prevent instantiation.
59 */
60 private DMarlinPrismUtils() {
61 }
62
63 private static boolean nearZero(final double num) {
64 return Math.abs(num) < 2.0d * Math.ulp(num);
65 }
66
67 private static DPathConsumer2D initStroker(
68 final DRendererContext rdrCtx,
69 final BasicStroke stroke,
70 final float lineWidth,
71 final BaseTransform tx,
72 final DPathConsumer2D 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 double scale = 1.0d;
126
127 // by now strokerat == null. Input paths to
128 // stroker (and maybe dasher) will have the full transform tx
129 // applied to them and nothing will happen to the output paths.
130 } else {
131 strokerTx = tx;
132
133 // by now strokerat == tx. Input paths to
134 // stroker (and maybe dasher) will have the full transform tx
135 // applied to them, then they will be normalized, and then
136 // the inverse of *only the non translation part of tx* will
137 // be applied to the normalized paths. This won't cause problems
138 // in stroker, because, suppose tx = T*A, where T is just the
139 // translation part of tx, and A is the rest. T*A has already
140 // been applied to Stroker/Dasher's input. Then Ainv will be
141 // applied. Ainv*T*A is not equal to T, but it is a translation,
142 // which means that none of stroker's assumptions about its
143 // input will be violated. After all this, A will be applied
144 // to stroker's output.
145 }
146 }
147
148 // Get renderer offsets:
149 double rdrOffX = 0.0d, rdrOffY = 0.0d;
150
151 if (rdrCtx.doClip && (tx != null)) {
152 final DMarlinRenderer renderer = (DMarlinRenderer)out;
153 rdrOffX = renderer.getOffsetX();
154 rdrOffY = renderer.getOffsetY();
155 }
156
157 // Prepare the pipeline:
158 DPathConsumer2D pc = out;
159
160 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
161
162 if (DO_TRACE_PATH) {
163 // trace Stroker:
164 pc = transformerPC2D.traceStroker(pc);
165 }
166
167 if (MarlinConst.USE_SIMPLIFIER) {
168 // Use simplifier after stroker before Renderer
169 // to remove collinear segments (notably due to cap square)
170 pc = rdrCtx.simplifier.init(pc);
171 }
172
173 // deltaTransformConsumer may adjust the clip rectangle:
174 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY);
175
176 // stroker will adjust the clip rectangle (width / miter limit):
177 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
178 stroke.getLineJoin(), stroke.getMiterLimit(),
179 scale, rdrOffX, rdrOffY);
180
181 if (dashesD != null) {
182 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase, recycleDashes);
183 } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) {
184 if (DO_TRACE_PATH) {
185 pc = transformerPC2D.traceClosedPathDetector(pc);
186 }
187
188 // If no dash and clip is enabled:
189 // detect closedPaths (polygons) for caps
190 pc = transformerPC2D.detectClosedPath(pc);
191 }
192 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
193
194 if (DO_TRACE_PATH) {
195 // trace Input:
196 pc = transformerPC2D.traceInput(pc);
197 }
198 /*
199 * Pipeline seems to be:
200 * shape.getPathIterator(tx)
201 * -> (inverseDeltaTransformConsumer)
202 * -> (Dasher)
203 * -> Stroker
204 * -> (deltaTransformConsumer)
205 *
206 * -> (CollinearSimplifier) to remove redundant segments
207 *
208 * -> pc2d = Renderer (bounding box)
209 */
210 return pc;
211 }
212
213 private static DPathConsumer2D initRenderer(
214 final DRendererContext rdrCtx,
215 final BasicStroke stroke,
216 final BaseTransform tx,
217 final Rectangle clip,
218 final int piRule,
219 final DMarlinRenderer renderer)
220 {
221 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
222 // Define the initial clip bounds:
223 final double[] clipRect = rdrCtx.clipRect;
224
225 clipRect[0] = clip.y;
226 clipRect[1] = clip.y + clip.height;
227 clipRect[2] = clip.x;
228 clipRect[3] = clip.x + clip.width;
229
230 // Enable clipping:
231 rdrCtx.doClip = true;
232 }
292 return r;
293 }
294
295 public static void strokeTo(
296 final DRendererContext rdrCtx,
297 final Shape shape,
298 final BasicStroke stroke,
299 final float lineWidth,
300 final DPathConsumer2D out)
301 {
302 final DPathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out);
303
304 if (shape instanceof Path2D) {
305 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
306 } else {
307 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
308 }
309 }
310
311 private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi,
312 final DPathConsumer2D pc2d)
313 {
314 // mark context as DIRTY:
315 rdrCtx.dirty = true;
316
317 final float[] coords = rdrCtx.float6;
318
319 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
320 // - removed skip flag = !subpathStarted
321 // - removed pathClosed (ie subpathStarted not set to false)
322 boolean subpathStarted = false;
323
324 for (; !pi.isDone(); pi.next()) {
325 switch (pi.currentSegment(coords)) {
326 case PathIterator.SEG_MOVETO:
327 /* Checking SEG_MOVETO coordinates if they are out of the
328 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
329 * and Infinity values. Skipping next path segment in case of
330 * invalid data.
331 */
332 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
333 coords[1] < UPPER_BND && coords[1] > LOWER_BND)
413 break;
414 case PathIterator.SEG_CLOSE:
415 if (subpathStarted) {
416 pc2d.closePath();
417 // do not set subpathStarted to false
418 // in case of missing moveTo() after close()
419 }
420 break;
421 default:
422 }
423 }
424 pc2d.pathDone();
425
426 // mark context as CLEAN:
427 rdrCtx.dirty = false;
428 }
429
430 private static void feedConsumer(final DRendererContext rdrCtx,
431 final Path2D p2d,
432 final BaseTransform xform,
433 final DPathConsumer2D pc2d)
434 {
435 // mark context as DIRTY:
436 rdrCtx.dirty = true;
437
438 final float[] coords = rdrCtx.float6;
439
440 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
441 // - removed skip flag = !subpathStarted
442 // - removed pathClosed (ie subpathStarted not set to false)
443 boolean subpathStarted = false;
444
445 final float[] pCoords = p2d.getFloatCoordsNoClone();
446 final byte[] pTypes = p2d.getCommandsNoClone();
447 final int nsegs = p2d.getNumCommands();
448
449 for (int i = 0, coff = 0; i < nsegs; i++) {
450 switch (pTypes[i]) {
451 case PathIterator.SEG_MOVETO:
452 if (xform == null) {
453 coords[0] = pCoords[coff];
454 coords[1] = pCoords[coff+1];
|
1 /*
2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
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
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 // 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 */
62 private DMarlinPrismUtils() {
63 }
64
65 private static DPathConsumer2D initStroker(
66 final DRendererContext rdrCtx,
67 final BasicStroke stroke,
68 final float lineWidth,
69 BaseTransform tx,
70 final DPathConsumer2D 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 double scale = 1.0d;
124
125 // by now strokerat == null. Input paths to
126 // stroker (and maybe dasher) will have the full transform tx
127 // applied to them and nothing will happen to the output paths.
128 } else {
129 strokerTx = tx;
130
131 // by now strokerat == tx. Input paths to
132 // stroker (and maybe dasher) will have the full transform tx
133 // applied to them, then they will be normalized, and then
134 // the inverse of *only the non translation part of tx* will
135 // be applied to the normalized paths. This won't cause problems
136 // in stroker, because, suppose tx = T*A, where T is just the
137 // translation part of tx, and A is the rest. T*A has already
138 // been applied to Stroker/Dasher's input. Then Ainv will be
139 // applied. Ainv*T*A is not equal to T, but it is a translation,
140 // which means that none of stroker's assumptions about its
141 // input will be violated. After all this, A will be applied
142 // to stroker's output.
143 }
144 } else {
145 // either tx is null or it's the identity. In either case
146 // we don't transform the path.
147 tx = null;
148 }
149
150 // Get renderer offsets:
151 double rdrOffX = 0.0d, rdrOffY = 0.0d;
152
153 if (rdrCtx.doClip && (tx != null)) {
154 final DMarlinRenderer renderer = (DMarlinRenderer)out;
155 rdrOffX = renderer.getOffsetX();
156 rdrOffY = renderer.getOffsetY();
157 }
158
159 // Prepare the pipeline:
160 DPathConsumer2D pc = out;
161
162 final DTransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
163
164 if (DO_TRACE_PATH) {
165 // trace Stroker:
166 pc = transformerPC2D.traceStroker(pc);
167 }
168
169 if (MarlinConst.USE_SIMPLIFIER) {
170 // Use simplifier after stroker before Renderer
171 // to remove collinear segments (notably due to cap square)
172 pc = rdrCtx.simplifier.init(pc);
173 }
174
175 // deltaTransformConsumer may adjust the clip rectangle:
176 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY);
177
178 // stroker will adjust the clip rectangle (width / miter limit):
179 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
180 stroke.getLineJoin(), stroke.getMiterLimit(),
181 scale, rdrOffX, rdrOffY, (dashesD == null));
182
183 // Curve Monotizer:
184 rdrCtx.monotonizer.init(width);
185
186 if (dashesD != null) {
187 if (DO_TRACE_PATH) {
188 pc = transformerPC2D.traceDasher(pc);
189 }
190 pc = rdrCtx.dasher.init(pc, dashesD, dashLen, dashphase,
191 recycleDashes);
192
193 if (DISABLE_2ND_STROKER_CLIPPING) {
194 // disable stoker clipping:
195 rdrCtx.stroker.disableClipping();
196 }
197
198 } else if (rdrCtx.doClip && (stroke.getEndCap() != DStroker.CAP_BUTT)) {
199 if (DO_TRACE_PATH) {
200 pc = transformerPC2D.traceClosedPathDetector(pc);
201 }
202
203 // If no dash and clip is enabled:
204 // detect closedPaths (polygons) for caps
205 pc = transformerPC2D.detectClosedPath(pc);
206 }
207 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
208
209 if (DO_TRACE_PATH) {
210 // trace Input:
211 pc = transformerPC2D.traceInput(pc);
212 }
213 /*
214 * Pipeline seems to be:
215 * shape.getPathIterator(tx)
216 * -> (inverseDeltaTransformConsumer)
217 * -> (Dasher)
218 * -> Stroker
219 * -> (deltaTransformConsumer)
220 *
221 * -> (CollinearSimplifier) to remove redundant segments
222 *
223 * -> pc2d = Renderer (bounding box)
224 */
225 return pc;
226 }
227
228 private static boolean nearZero(final double num) {
229 return Math.abs(num) < 2.0d * Math.ulp(num);
230 }
231
232 private static DPathConsumer2D initRenderer(
233 final DRendererContext rdrCtx,
234 final BasicStroke stroke,
235 final BaseTransform tx,
236 final Rectangle clip,
237 final int piRule,
238 final DMarlinRenderer renderer)
239 {
240 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
241 // Define the initial clip bounds:
242 final double[] clipRect = rdrCtx.clipRect;
243
244 clipRect[0] = clip.y;
245 clipRect[1] = clip.y + clip.height;
246 clipRect[2] = clip.x;
247 clipRect[3] = clip.x + clip.width;
248
249 // Enable clipping:
250 rdrCtx.doClip = true;
251 }
311 return r;
312 }
313
314 public static void strokeTo(
315 final DRendererContext rdrCtx,
316 final Shape shape,
317 final BasicStroke stroke,
318 final float lineWidth,
319 final DPathConsumer2D out)
320 {
321 final DPathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out);
322
323 if (shape instanceof Path2D) {
324 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
325 } else {
326 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
327 }
328 }
329
330 private static void feedConsumer(final DRendererContext rdrCtx, final PathIterator pi,
331 DPathConsumer2D pc2d)
332 {
333 if (MarlinConst.USE_PATH_SIMPLIFIER) {
334 // Use path simplifier at the first step
335 // to remove useless points
336 pc2d = rdrCtx.pathSimplifier.init(pc2d);
337 }
338
339 // mark context as DIRTY:
340 rdrCtx.dirty = true;
341
342 final float[] coords = rdrCtx.float6;
343
344 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
345 // - removed skip flag = !subpathStarted
346 // - removed pathClosed (ie subpathStarted not set to false)
347 boolean subpathStarted = false;
348
349 for (; !pi.isDone(); pi.next()) {
350 switch (pi.currentSegment(coords)) {
351 case PathIterator.SEG_MOVETO:
352 /* Checking SEG_MOVETO coordinates if they are out of the
353 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
354 * and Infinity values. Skipping next path segment in case of
355 * invalid data.
356 */
357 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
358 coords[1] < UPPER_BND && coords[1] > LOWER_BND)
438 break;
439 case PathIterator.SEG_CLOSE:
440 if (subpathStarted) {
441 pc2d.closePath();
442 // do not set subpathStarted to false
443 // in case of missing moveTo() after close()
444 }
445 break;
446 default:
447 }
448 }
449 pc2d.pathDone();
450
451 // mark context as CLEAN:
452 rdrCtx.dirty = false;
453 }
454
455 private static void feedConsumer(final DRendererContext rdrCtx,
456 final Path2D p2d,
457 final BaseTransform xform,
458 DPathConsumer2D pc2d)
459 {
460 if (MarlinConst.USE_PATH_SIMPLIFIER) {
461 // Use path simplifier at the first step
462 // to remove useless points
463 pc2d = rdrCtx.pathSimplifier.init(pc2d);
464 }
465
466 // mark context as DIRTY:
467 rdrCtx.dirty = true;
468
469 final float[] coords = rdrCtx.float6;
470
471 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
472 // - removed skip flag = !subpathStarted
473 // - removed pathClosed (ie subpathStarted not set to false)
474 boolean subpathStarted = false;
475
476 final float[] pCoords = p2d.getFloatCoordsNoClone();
477 final byte[] pTypes = p2d.getCommandsNoClone();
478 final int nsegs = p2d.getNumCommands();
479
480 for (int i = 0, coff = 0; i < nsegs; i++) {
481 switch (pTypes[i]) {
482 case PathIterator.SEG_MOVETO:
483 if (xform == null) {
484 coords[0] = pCoords[coff];
485 coords[1] = pCoords[coff+1];
|