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.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_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 MarlinPrismUtils() {
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 PathConsumer2D initStroker(
68 final RendererContext rdrCtx,
69 final BasicStroke stroke,
70 final float lineWidth,
71 final 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 scale = 1.0f;
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 // Get renderer offsets:
144 float rdrOffX = 0.0f, rdrOffY = 0.0f;
145
146 if (rdrCtx.doClip && (tx != null)) {
147 final MarlinRenderer renderer = (MarlinRenderer)out;
148 rdrOffX = renderer.getOffsetX();
149 rdrOffY = renderer.getOffsetY();
150 }
151
152 // Prepare the pipeline:
153 PathConsumer2D pc = out;
154
155 final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
156
157 if (DO_TRACE_PATH) {
158 // trace Stroker:
159 pc = transformerPC2D.traceStroker(pc);
160 }
161
162 if (MarlinConst.USE_SIMPLIFIER) {
163 // Use simplifier after stroker before Renderer
164 // to remove collinear segments (notably due to cap square)
165 pc = rdrCtx.simplifier.init(pc);
166 }
167
168 // deltaTransformConsumer may adjust the clip rectangle:
169 pc = transformerPC2D.deltaTransformConsumer(pc, strokerTx, rdrOffX, rdrOffY);
170
171 // stroker will adjust the clip rectangle (width / miter limit):
172 pc = rdrCtx.stroker.init(pc, width, stroke.getEndCap(),
173 stroke.getLineJoin(), stroke.getMiterLimit(),
174 scale, rdrOffX, rdrOffY);
175
176 if (dashes != null) {
177 if (!recycleDashes) {
178 dashLen = dashes.length;
179 }
180 pc = rdrCtx.dasher.init(pc, dashes, dashLen, dashphase, recycleDashes);
181 } else if (rdrCtx.doClip && (stroke.getEndCap() != Stroker.CAP_BUTT)) {
182 if (DO_TRACE_PATH) {
183 pc = transformerPC2D.traceClosedPathDetector(pc);
184 }
185
186 // If no dash and clip is enabled:
187 // detect closedPaths (polygons) for caps
188 pc = transformerPC2D.detectClosedPath(pc);
189 }
190 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
191
192 if (DO_TRACE_PATH) {
193 // trace Input:
194 pc = transformerPC2D.traceInput(pc);
195 }
196 /*
197 * Pipeline seems to be:
198 * shape.getPathIterator(tx)
199 * -> (inverseDeltaTransformConsumer)
200 * -> (Dasher)
201 * -> Stroker
202 * -> (deltaTransformConsumer)
203 *
204 * -> (CollinearSimplifier) to remove redundant segments
205 *
206 * -> pc2d = Renderer (bounding box)
207 */
208 return pc;
209 }
210
211 private static PathConsumer2D initRenderer(
212 final RendererContext rdrCtx,
213 final BasicStroke stroke,
214 final BaseTransform tx,
215 final Rectangle clip,
216 final int piRule,
217 final MarlinRenderer renderer)
218 {
219 if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {
220 // Define the initial clip bounds:
221 final float[] clipRect = rdrCtx.clipRect;
222
223 clipRect[0] = clip.y;
224 clipRect[1] = clip.y + clip.height;
225 clipRect[2] = clip.x;
226 clipRect[3] = clip.x + clip.width;
227
228 // Enable clipping:
229 rdrCtx.doClip = true;
230 }
290 return r;
291 }
292
293 public static void strokeTo(
294 final RendererContext rdrCtx,
295 final Shape shape,
296 final BasicStroke stroke,
297 final float lineWidth,
298 final PathConsumer2D out)
299 {
300 final PathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out);
301
302 if (shape instanceof Path2D) {
303 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
304 } else {
305 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
306 }
307 }
308
309 private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi,
310 final PathConsumer2D pc2d)
311 {
312 // mark context as DIRTY:
313 rdrCtx.dirty = true;
314
315 final float[] coords = rdrCtx.float6;
316
317 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
318 // - removed skip flag = !subpathStarted
319 // - removed pathClosed (ie subpathStarted not set to false)
320 boolean subpathStarted = false;
321
322 for (; !pi.isDone(); pi.next()) {
323 switch (pi.currentSegment(coords)) {
324 case PathIterator.SEG_MOVETO:
325 /* Checking SEG_MOVETO coordinates if they are out of the
326 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
327 * and Infinity values. Skipping next path segment in case of
328 * invalid data.
329 */
330 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
331 coords[1] < UPPER_BND && coords[1] > LOWER_BND)
411 break;
412 case PathIterator.SEG_CLOSE:
413 if (subpathStarted) {
414 pc2d.closePath();
415 // do not set subpathStarted to false
416 // in case of missing moveTo() after close()
417 }
418 break;
419 default:
420 }
421 }
422 pc2d.pathDone();
423
424 // mark context as CLEAN:
425 rdrCtx.dirty = false;
426 }
427
428 private static void feedConsumer(final RendererContext rdrCtx,
429 final Path2D p2d,
430 final BaseTransform xform,
431 final PathConsumer2D pc2d)
432 {
433 // mark context as DIRTY:
434 rdrCtx.dirty = true;
435
436 final float[] coords = rdrCtx.float6;
437
438 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
439 // - removed skip flag = !subpathStarted
440 // - removed pathClosed (ie subpathStarted not set to false)
441 boolean subpathStarted = false;
442
443 final float[] pCoords = p2d.getFloatCoordsNoClone();
444 final byte[] pTypes = p2d.getCommandsNoClone();
445 final int nsegs = p2d.getNumCommands();
446
447 for (int i = 0, coff = 0; i < nsegs; i++) {
448 switch (pTypes[i]) {
449 case PathIterator.SEG_MOVETO:
450 if (xform == null) {
451 coords[0] = pCoords[coff];
452 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.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 */
62 private MarlinPrismUtils() {
63 }
64
65 private static PathConsumer2D initStroker(
66 final RendererContext rdrCtx,
67 final BasicStroke stroke,
68 final float lineWidth,
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;
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)) {
197 if (DO_TRACE_PATH) {
198 pc = transformerPC2D.traceClosedPathDetector(pc);
199 }
200
201 // If no dash and clip is enabled:
202 // detect closedPaths (polygons) for caps
203 pc = transformerPC2D.detectClosedPath(pc);
204 }
205 pc = transformerPC2D.inverseDeltaTransformConsumer(pc, strokerTx);
206
207 if (DO_TRACE_PATH) {
208 // trace Input:
209 pc = transformerPC2D.traceInput(pc);
210 }
211 /*
212 * Pipeline seems to be:
213 * shape.getPathIterator(tx)
214 * -> (inverseDeltaTransformConsumer)
215 * -> (Dasher)
216 * -> Stroker
217 * -> (deltaTransformConsumer)
218 *
219 * -> (CollinearSimplifier) to remove redundant segments
220 *
221 * -> pc2d = Renderer (bounding box)
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 }
309 return r;
310 }
311
312 public static void strokeTo(
313 final RendererContext rdrCtx,
314 final Shape shape,
315 final BasicStroke stroke,
316 final float lineWidth,
317 final PathConsumer2D out)
318 {
319 final PathConsumer2D pc2d = initStroker(rdrCtx, stroke, lineWidth, null, out);
320
321 if (shape instanceof Path2D) {
322 feedConsumer(rdrCtx, (Path2D)shape, null, pc2d);
323 } else {
324 feedConsumer(rdrCtx, shape.getPathIterator(null), pc2d);
325 }
326 }
327
328 private static void feedConsumer(final RendererContext rdrCtx, final PathIterator pi,
329 PathConsumer2D pc2d)
330 {
331 if (MarlinConst.USE_PATH_SIMPLIFIER) {
332 // Use path simplifier at the first step
333 // to remove useless points
334 pc2d = rdrCtx.pathSimplifier.init(pc2d);
335 }
336
337 // mark context as DIRTY:
338 rdrCtx.dirty = true;
339
340 final float[] coords = rdrCtx.float6;
341
342 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
343 // - removed skip flag = !subpathStarted
344 // - removed pathClosed (ie subpathStarted not set to false)
345 boolean subpathStarted = false;
346
347 for (; !pi.isDone(); pi.next()) {
348 switch (pi.currentSegment(coords)) {
349 case PathIterator.SEG_MOVETO:
350 /* Checking SEG_MOVETO coordinates if they are out of the
351 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
352 * and Infinity values. Skipping next path segment in case of
353 * invalid data.
354 */
355 if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
356 coords[1] < UPPER_BND && coords[1] > LOWER_BND)
436 break;
437 case PathIterator.SEG_CLOSE:
438 if (subpathStarted) {
439 pc2d.closePath();
440 // do not set subpathStarted to false
441 // in case of missing moveTo() after close()
442 }
443 break;
444 default:
445 }
446 }
447 pc2d.pathDone();
448
449 // mark context as CLEAN:
450 rdrCtx.dirty = false;
451 }
452
453 private static void feedConsumer(final RendererContext rdrCtx,
454 final Path2D p2d,
455 final BaseTransform xform,
456 PathConsumer2D pc2d)
457 {
458 if (MarlinConst.USE_PATH_SIMPLIFIER) {
459 // Use path simplifier at the first step
460 // to remove useless points
461 pc2d = rdrCtx.pathSimplifier.init(pc2d);
462 }
463
464 // mark context as DIRTY:
465 rdrCtx.dirty = true;
466
467 final float[] coords = rdrCtx.float6;
468
469 // ported from DuctusRenderingEngine.feedConsumer() but simplified:
470 // - removed skip flag = !subpathStarted
471 // - removed pathClosed (ie subpathStarted not set to false)
472 boolean subpathStarted = false;
473
474 final float[] pCoords = p2d.getFloatCoordsNoClone();
475 final byte[] pTypes = p2d.getCommandsNoClone();
476 final int nsegs = p2d.getNumCommands();
477
478 for (int i = 0, coff = 0; i < nsegs; i++) {
479 switch (pTypes[i]) {
480 case PathIterator.SEG_MOVETO:
481 if (xform == null) {
482 coords[0] = pCoords[coff];
483 coords[1] = pCoords[coff+1];
|