meerk40t 0.9.7020__py2.py3-none-any.whl → 0.9.7030__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meerk40t/core/cutcode/cutcode.py +1 -1
- meerk40t/core/cutplan.py +70 -2
- meerk40t/core/elements/element_treeops.py +9 -7
- meerk40t/core/elements/grid.py +8 -1
- meerk40t/core/elements/offset_mk.py +2 -1
- meerk40t/core/elements/shapes.py +378 -259
- meerk40t/core/node/node.py +6 -3
- meerk40t/core/planner.py +23 -0
- meerk40t/core/undos.py +1 -1
- meerk40t/core/wordlist.py +1 -0
- meerk40t/dxf/dxf_io.py +6 -0
- meerk40t/extra/mk_potrace.py +1959 -0
- meerk40t/extra/param_functions.py +1 -1
- meerk40t/extra/potrace.py +14 -10
- meerk40t/grbl/interpreter.py +1 -1
- meerk40t/gui/about.py +3 -5
- meerk40t/gui/basicops.py +3 -3
- meerk40t/gui/choicepropertypanel.py +1 -4
- meerk40t/gui/gui_mixins.py +4 -1
- meerk40t/gui/spoolerpanel.py +6 -9
- meerk40t/gui/themes.py +7 -1
- meerk40t/gui/wxmeerk40t.py +26 -0
- meerk40t/gui/wxmscene.py +93 -0
- meerk40t/image/imagetools.py +1 -1
- meerk40t/kernel/kernel.py +10 -4
- meerk40t/kernel/settings.py +2 -0
- meerk40t/lihuiyu/device.py +9 -3
- meerk40t/main.py +22 -5
- meerk40t/ruida/gui/gui.py +6 -6
- meerk40t/ruida/gui/ruidaoperationproperties.py +1 -10
- meerk40t/ruida/rdjob.py +3 -3
- meerk40t/tools/geomstr.py +88 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/RECORD +39 -38
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7020.dist-info → meerk40t-0.9.7030.dist-info}/zip-safe +0 -0
@@ -0,0 +1,1959 @@
|
|
1
|
+
import math
|
2
|
+
from typing import Optional, Tuple, Union
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
|
6
|
+
try:
|
7
|
+
from numba import njit
|
8
|
+
except ImportError:
|
9
|
+
|
10
|
+
def njit(*args, **kwargs):
|
11
|
+
return lambda f: f
|
12
|
+
|
13
|
+
|
14
|
+
POTRACE_TURNPOLICY_BLACK = 0
|
15
|
+
POTRACE_TURNPOLICY_WHITE = 1
|
16
|
+
POTRACE_TURNPOLICY_LEFT = 2
|
17
|
+
POTRACE_TURNPOLICY_RIGHT = 3
|
18
|
+
POTRACE_TURNPOLICY_MINORITY = 4
|
19
|
+
POTRACE_TURNPOLICY_MAJORITY = 5
|
20
|
+
POTRACE_TURNPOLICY_RANDOM = 6
|
21
|
+
|
22
|
+
# /* segment tags */
|
23
|
+
POTRACE_CURVETO = 1
|
24
|
+
POTRACE_CORNER = 2
|
25
|
+
|
26
|
+
INFTY = float("inf")
|
27
|
+
COS179 = math.cos(math.radians(179))
|
28
|
+
|
29
|
+
|
30
|
+
class Bitmap:
|
31
|
+
def __init__(self, data, blacklevel=0.5):
|
32
|
+
if hasattr(data, "dtype"):
|
33
|
+
if data.dtype != "bool":
|
34
|
+
data = data > (255 * blacklevel)
|
35
|
+
if hasattr(data, "mode"):
|
36
|
+
if data.mode != "L":
|
37
|
+
data = data.convert("L")
|
38
|
+
data = data.point(lambda e: 0 if (e / 255.0) < blacklevel else 255)
|
39
|
+
image = data.convert("1")
|
40
|
+
data = np.array(image)
|
41
|
+
self.data = data
|
42
|
+
self.invert()
|
43
|
+
|
44
|
+
def invert(self):
|
45
|
+
self.data = np.invert(self.data)
|
46
|
+
|
47
|
+
def trace(
|
48
|
+
self,
|
49
|
+
turdsize: int = 2,
|
50
|
+
turnpolicy: int = POTRACE_TURNPOLICY_MINORITY,
|
51
|
+
alphamax=1.0,
|
52
|
+
opticurve=True,
|
53
|
+
opttolerance=0.2,
|
54
|
+
):
|
55
|
+
bm = np.pad(self.data, [(0, 1), (0, 1)], mode="constant")
|
56
|
+
|
57
|
+
plist = bm_to_pathlist(bm, turdsize=turdsize, turnpolicy=turnpolicy)
|
58
|
+
process_path(
|
59
|
+
plist,
|
60
|
+
alphamax=alphamax,
|
61
|
+
opticurve=opticurve,
|
62
|
+
opttolerance=opttolerance,
|
63
|
+
)
|
64
|
+
return Path(plist)
|
65
|
+
|
66
|
+
|
67
|
+
class Path(list):
|
68
|
+
def __init__(self, plist):
|
69
|
+
list.__init__(self)
|
70
|
+
self.extend([Curve(p) for p in plist])
|
71
|
+
|
72
|
+
@property
|
73
|
+
def curves(self):
|
74
|
+
return self
|
75
|
+
|
76
|
+
@property
|
77
|
+
def curves_tree(self):
|
78
|
+
"""
|
79
|
+
Curves tree is currently non-implemented.
|
80
|
+
:return:
|
81
|
+
"""
|
82
|
+
return None
|
83
|
+
|
84
|
+
|
85
|
+
class Curve(list):
|
86
|
+
def __init__(self, p):
|
87
|
+
list.__init__(self)
|
88
|
+
last = None
|
89
|
+
self._path = p
|
90
|
+
self._curve = p._fcurve
|
91
|
+
for s in self._curve:
|
92
|
+
if s.tag == POTRACE_CORNER:
|
93
|
+
self.append(CornerSegment(s))
|
94
|
+
else:
|
95
|
+
self.append(BezierSegment(s))
|
96
|
+
last = s
|
97
|
+
self.start_point = last.c[2] if last is not None else None
|
98
|
+
|
99
|
+
@property
|
100
|
+
def decomposition_points(self):
|
101
|
+
return self._path.pt
|
102
|
+
|
103
|
+
@property
|
104
|
+
def segments(self):
|
105
|
+
return self
|
106
|
+
|
107
|
+
@property
|
108
|
+
def children(self):
|
109
|
+
"""
|
110
|
+
Curves tree is non-implemented.
|
111
|
+
:return:
|
112
|
+
"""
|
113
|
+
return None
|
114
|
+
|
115
|
+
|
116
|
+
class CornerSegment:
|
117
|
+
def __init__(self, s):
|
118
|
+
self._segment = s
|
119
|
+
|
120
|
+
@property
|
121
|
+
def c(self):
|
122
|
+
return self._segment.c[1]
|
123
|
+
|
124
|
+
@property
|
125
|
+
def end_point(self):
|
126
|
+
return self._segment.c[2]
|
127
|
+
|
128
|
+
@property
|
129
|
+
def is_corner(self):
|
130
|
+
return True
|
131
|
+
|
132
|
+
|
133
|
+
class BezierSegment:
|
134
|
+
def __init__(self, s):
|
135
|
+
self._segment = s
|
136
|
+
|
137
|
+
@property
|
138
|
+
def c1(self):
|
139
|
+
return self._segment.c[0]
|
140
|
+
|
141
|
+
@property
|
142
|
+
def c2(self):
|
143
|
+
return self._segment.c[1]
|
144
|
+
|
145
|
+
@property
|
146
|
+
def end_point(self):
|
147
|
+
return self._segment.c[2]
|
148
|
+
|
149
|
+
@property
|
150
|
+
def is_corner(self):
|
151
|
+
return False
|
152
|
+
|
153
|
+
|
154
|
+
# /* ---------------------------------------------------------------------- */
|
155
|
+
|
156
|
+
|
157
|
+
class _Curve:
|
158
|
+
def __init__(self, m):
|
159
|
+
self.segments = [_Segment() for _ in range(m)]
|
160
|
+
self.alphacurve = False
|
161
|
+
|
162
|
+
def __len__(self):
|
163
|
+
return len(self.segments)
|
164
|
+
|
165
|
+
@property
|
166
|
+
def n(self):
|
167
|
+
return len(self)
|
168
|
+
|
169
|
+
def __getitem__(self, item):
|
170
|
+
return self.segments[item]
|
171
|
+
|
172
|
+
|
173
|
+
class _Path:
|
174
|
+
def __init__(self, pt: list, area: int, sign: bool):
|
175
|
+
self.pt = pt # /* pt[len]: path as extracted from bitmap */
|
176
|
+
|
177
|
+
self.area = area
|
178
|
+
self.sign = sign
|
179
|
+
self.next = None
|
180
|
+
self.childlist = []
|
181
|
+
self.sibling = []
|
182
|
+
|
183
|
+
self._lon = [] # /* lon[len]: (i,lon[i]) = longest straight line from i */
|
184
|
+
|
185
|
+
self._x0 = 0 # /* origin for sums */
|
186
|
+
self._y0 = 0 # /* origin for sums */
|
187
|
+
self._sums = [] # / *sums[len + 1]: cache for fast summing * /
|
188
|
+
|
189
|
+
self._m = 0 # /* length of optimal polygon */
|
190
|
+
self._po = [] # /* po[m]: optimal polygon */
|
191
|
+
self._curve = [] # /* curve[m]: array of curve elements */
|
192
|
+
self._ocurve = [] # /* ocurve[om]: array of curve elements */
|
193
|
+
self._fcurve = [] # /* final curve: this points to either curve or ocurve.*/
|
194
|
+
|
195
|
+
def __len__(self):
|
196
|
+
return len(self.pt)
|
197
|
+
|
198
|
+
def init_curve(self, m):
|
199
|
+
pass
|
200
|
+
|
201
|
+
|
202
|
+
class _Point:
|
203
|
+
def __init__(self, x: float = 0, y: float = 0):
|
204
|
+
self.x = x
|
205
|
+
self.y = y
|
206
|
+
|
207
|
+
def __repr__(self):
|
208
|
+
return "Point(%f, %f)" % (self.x, self.y)
|
209
|
+
|
210
|
+
|
211
|
+
class _Segment:
|
212
|
+
def __init__(self):
|
213
|
+
self.tag = 0
|
214
|
+
self.c = [_Point(), _Point(), _Point()]
|
215
|
+
self.vertex = _Point()
|
216
|
+
self.alpha = 0.0
|
217
|
+
self.alpha0 = 0.0
|
218
|
+
self.beta = 0.0
|
219
|
+
|
220
|
+
|
221
|
+
class _Sums:
|
222
|
+
def __init__(self):
|
223
|
+
self.x = 0
|
224
|
+
self.y = 0
|
225
|
+
self.x2 = 0
|
226
|
+
self.xy = 0
|
227
|
+
self.y2 = 0
|
228
|
+
|
229
|
+
|
230
|
+
detrand_t = (
|
231
|
+
# /* non-linear sequence: constant term of inverse in GF(8),
|
232
|
+
# mod x^8+x^4+x^3+x+1 */
|
233
|
+
0,
|
234
|
+
1,
|
235
|
+
1,
|
236
|
+
0,
|
237
|
+
1,
|
238
|
+
0,
|
239
|
+
1,
|
240
|
+
1,
|
241
|
+
0,
|
242
|
+
1,
|
243
|
+
1,
|
244
|
+
0,
|
245
|
+
0,
|
246
|
+
1,
|
247
|
+
1,
|
248
|
+
1,
|
249
|
+
0,
|
250
|
+
0,
|
251
|
+
0,
|
252
|
+
1,
|
253
|
+
1,
|
254
|
+
1,
|
255
|
+
0,
|
256
|
+
1,
|
257
|
+
0,
|
258
|
+
1,
|
259
|
+
1,
|
260
|
+
0,
|
261
|
+
1,
|
262
|
+
0,
|
263
|
+
0,
|
264
|
+
0,
|
265
|
+
0,
|
266
|
+
0,
|
267
|
+
0,
|
268
|
+
1,
|
269
|
+
1,
|
270
|
+
1,
|
271
|
+
0,
|
272
|
+
1,
|
273
|
+
1,
|
274
|
+
0,
|
275
|
+
0,
|
276
|
+
1,
|
277
|
+
0,
|
278
|
+
0,
|
279
|
+
0,
|
280
|
+
0,
|
281
|
+
0,
|
282
|
+
1,
|
283
|
+
0,
|
284
|
+
0,
|
285
|
+
1,
|
286
|
+
1,
|
287
|
+
0,
|
288
|
+
0,
|
289
|
+
0,
|
290
|
+
1,
|
291
|
+
0,
|
292
|
+
1,
|
293
|
+
1,
|
294
|
+
1,
|
295
|
+
1,
|
296
|
+
1,
|
297
|
+
1,
|
298
|
+
0,
|
299
|
+
1,
|
300
|
+
1,
|
301
|
+
1,
|
302
|
+
1,
|
303
|
+
1,
|
304
|
+
1,
|
305
|
+
1,
|
306
|
+
0,
|
307
|
+
1,
|
308
|
+
1,
|
309
|
+
0,
|
310
|
+
1,
|
311
|
+
1,
|
312
|
+
1,
|
313
|
+
1,
|
314
|
+
0,
|
315
|
+
1,
|
316
|
+
0,
|
317
|
+
0,
|
318
|
+
0,
|
319
|
+
1,
|
320
|
+
1,
|
321
|
+
0,
|
322
|
+
0,
|
323
|
+
0,
|
324
|
+
0,
|
325
|
+
1,
|
326
|
+
0,
|
327
|
+
1,
|
328
|
+
1,
|
329
|
+
0,
|
330
|
+
0,
|
331
|
+
1,
|
332
|
+
1,
|
333
|
+
1,
|
334
|
+
0,
|
335
|
+
0,
|
336
|
+
1,
|
337
|
+
0,
|
338
|
+
1,
|
339
|
+
1,
|
340
|
+
1,
|
341
|
+
1,
|
342
|
+
1,
|
343
|
+
1,
|
344
|
+
1,
|
345
|
+
1,
|
346
|
+
1,
|
347
|
+
1,
|
348
|
+
1,
|
349
|
+
0,
|
350
|
+
1,
|
351
|
+
0,
|
352
|
+
0,
|
353
|
+
0,
|
354
|
+
0,
|
355
|
+
0,
|
356
|
+
0,
|
357
|
+
1,
|
358
|
+
0,
|
359
|
+
1,
|
360
|
+
0,
|
361
|
+
1,
|
362
|
+
0,
|
363
|
+
1,
|
364
|
+
0,
|
365
|
+
0,
|
366
|
+
1,
|
367
|
+
0,
|
368
|
+
0,
|
369
|
+
1,
|
370
|
+
0,
|
371
|
+
1,
|
372
|
+
1,
|
373
|
+
1,
|
374
|
+
0,
|
375
|
+
1,
|
376
|
+
0,
|
377
|
+
0,
|
378
|
+
0,
|
379
|
+
0,
|
380
|
+
1,
|
381
|
+
0,
|
382
|
+
0,
|
383
|
+
0,
|
384
|
+
0,
|
385
|
+
0,
|
386
|
+
0,
|
387
|
+
1,
|
388
|
+
0,
|
389
|
+
1,
|
390
|
+
0,
|
391
|
+
1,
|
392
|
+
0,
|
393
|
+
1,
|
394
|
+
0,
|
395
|
+
0,
|
396
|
+
1,
|
397
|
+
1,
|
398
|
+
0,
|
399
|
+
1,
|
400
|
+
0,
|
401
|
+
0,
|
402
|
+
0,
|
403
|
+
0,
|
404
|
+
0,
|
405
|
+
0,
|
406
|
+
1,
|
407
|
+
0,
|
408
|
+
0,
|
409
|
+
0,
|
410
|
+
0,
|
411
|
+
1,
|
412
|
+
1,
|
413
|
+
1,
|
414
|
+
1,
|
415
|
+
0,
|
416
|
+
1,
|
417
|
+
1,
|
418
|
+
0,
|
419
|
+
0,
|
420
|
+
1,
|
421
|
+
1,
|
422
|
+
0,
|
423
|
+
0,
|
424
|
+
1,
|
425
|
+
1,
|
426
|
+
0,
|
427
|
+
1,
|
428
|
+
1,
|
429
|
+
0,
|
430
|
+
0,
|
431
|
+
0,
|
432
|
+
1,
|
433
|
+
1,
|
434
|
+
1,
|
435
|
+
1,
|
436
|
+
0,
|
437
|
+
1,
|
438
|
+
0,
|
439
|
+
0,
|
440
|
+
0,
|
441
|
+
0,
|
442
|
+
1,
|
443
|
+
0,
|
444
|
+
1,
|
445
|
+
1,
|
446
|
+
1,
|
447
|
+
0,
|
448
|
+
0,
|
449
|
+
0,
|
450
|
+
1,
|
451
|
+
0,
|
452
|
+
1,
|
453
|
+
1,
|
454
|
+
0,
|
455
|
+
0,
|
456
|
+
1,
|
457
|
+
1,
|
458
|
+
1,
|
459
|
+
0,
|
460
|
+
1,
|
461
|
+
0,
|
462
|
+
0,
|
463
|
+
1,
|
464
|
+
1,
|
465
|
+
0,
|
466
|
+
0,
|
467
|
+
1,
|
468
|
+
1,
|
469
|
+
1,
|
470
|
+
0,
|
471
|
+
0,
|
472
|
+
1,
|
473
|
+
1,
|
474
|
+
1,
|
475
|
+
0,
|
476
|
+
0,
|
477
|
+
0,
|
478
|
+
0,
|
479
|
+
1,
|
480
|
+
0,
|
481
|
+
1,
|
482
|
+
0,
|
483
|
+
1,
|
484
|
+
0,
|
485
|
+
1,
|
486
|
+
0,
|
487
|
+
1,
|
488
|
+
0,
|
489
|
+
)
|
490
|
+
|
491
|
+
|
492
|
+
def detrand(x: int, y: int) -> int:
|
493
|
+
"""deterministically and efficiently hash (x,y) into a pseudo-random bit"""
|
494
|
+
# /* 0x04b3e375 and 0x05a8ef93 are chosen to contain every possible 5-bit sequence */
|
495
|
+
z = ((0x04B3E375 * x) ^ y) * 0x05A8EF93
|
496
|
+
z = (
|
497
|
+
detrand_t[z & 0xFF]
|
498
|
+
^ detrand_t[(z >> 8) & 0xFF]
|
499
|
+
^ detrand_t[(z >> 16) & 0xFF]
|
500
|
+
^ detrand_t[(z >> 24) & 0xFF]
|
501
|
+
)
|
502
|
+
return z
|
503
|
+
|
504
|
+
|
505
|
+
def majority(bm: np.array, x: int, y: int) -> int:
|
506
|
+
"""
|
507
|
+
/* return the "majority" value of bitmap bm at intersection (x,y). We
|
508
|
+
assume that the bitmap is balanced at "radius" 1. */
|
509
|
+
"""
|
510
|
+
for i in range(2, 5): # /* check at "radius" i */
|
511
|
+
ct = 0
|
512
|
+
for a in range(-i + 1, i - 2):
|
513
|
+
try:
|
514
|
+
ct += 1 if bm[y + i - 1][x + a] else -1
|
515
|
+
except IndexError:
|
516
|
+
pass
|
517
|
+
try:
|
518
|
+
ct += 1 if bm[y + a - 1][x + i - 1] else -1
|
519
|
+
except IndexError:
|
520
|
+
pass
|
521
|
+
try:
|
522
|
+
ct += 1 if bm[y - i][x + a - 1] else -1
|
523
|
+
except IndexError:
|
524
|
+
pass
|
525
|
+
try:
|
526
|
+
ct += 1 if bm[y + a][x - i] else -1
|
527
|
+
except IndexError:
|
528
|
+
pass
|
529
|
+
if ct > 0:
|
530
|
+
return 1
|
531
|
+
elif ct < 0:
|
532
|
+
return 0
|
533
|
+
return 0
|
534
|
+
|
535
|
+
|
536
|
+
"""
|
537
|
+
/* ---------------------------------------------------------------------- */
|
538
|
+
/* decompose image into paths */
|
539
|
+
"""
|
540
|
+
|
541
|
+
|
542
|
+
def xor_to_ref(bm: np.array, x: int, y: int, xa: int) -> None:
|
543
|
+
"""
|
544
|
+
/* efficiently invert bits [x,infty) and [xa,infty) in line y. Here xa
|
545
|
+
must be a multiple of BM_WORDBITS. */
|
546
|
+
"""
|
547
|
+
|
548
|
+
if x < xa:
|
549
|
+
bm[y, x:xa] ^= True
|
550
|
+
elif x != xa:
|
551
|
+
bm[y, xa:x] ^= True
|
552
|
+
|
553
|
+
|
554
|
+
def xor_path(bm: np.array, p: _Path) -> None:
|
555
|
+
"""
|
556
|
+
a path is represented as an array of points, which are thought to
|
557
|
+
lie on the corners of pixels (not on their centers). The path point
|
558
|
+
(x,y) is the lower left corner of the pixel (x,y). Paths are
|
559
|
+
represented by the len/pt components of a path_t object (which
|
560
|
+
also stores other information about the path) */
|
561
|
+
|
562
|
+
xor the given pixmap with the interior of the given path. Note: the
|
563
|
+
path must be within the dimensions of the pixmap.
|
564
|
+
"""
|
565
|
+
if len(p) <= 0: # /* a path of length 0 is silly, but legal */
|
566
|
+
return
|
567
|
+
|
568
|
+
y1 = p.pt[-1].y
|
569
|
+
xa = p.pt[0].x
|
570
|
+
for n in p.pt:
|
571
|
+
x, y = n.x, n.y
|
572
|
+
if y != y1:
|
573
|
+
# /* efficiently invert the rectangle [x,xa] x [y,y1] */
|
574
|
+
xor_to_ref(bm, x, min(y, y1), xa)
|
575
|
+
y1 = y
|
576
|
+
|
577
|
+
|
578
|
+
def findpath(bm: np.array, x0: int, y0: int, sign: bool, turnpolicy: int) -> _Path:
|
579
|
+
"""
|
580
|
+
/* compute a path in the given pixmap, separating black from white.
|
581
|
+
Start path at the point (x0,x1), which must be an upper left corner
|
582
|
+
of the path. Also compute the area enclosed by the path. Return a
|
583
|
+
new path_t object, or NULL on error (note that a legitimate path
|
584
|
+
cannot have length 0). Sign is required for correct interpretation
|
585
|
+
of turnpolicies. */"""
|
586
|
+
|
587
|
+
x = x0
|
588
|
+
y = y0
|
589
|
+
dirx = 0
|
590
|
+
diry = -1 # diry-1
|
591
|
+
pt = []
|
592
|
+
area = 0
|
593
|
+
|
594
|
+
while True: # /* while this path */
|
595
|
+
# /* add point to path */
|
596
|
+
pt.append(_Point(int(x), int(y)))
|
597
|
+
|
598
|
+
# /* move to next point */
|
599
|
+
x += dirx
|
600
|
+
y += diry
|
601
|
+
area += x * diry
|
602
|
+
|
603
|
+
# /* path complete? */
|
604
|
+
if x == x0 and y == y0:
|
605
|
+
break
|
606
|
+
|
607
|
+
# /* determine next direction */
|
608
|
+
cy = y + (diry - dirx - 1) // 2
|
609
|
+
cx = x + (dirx + diry - 1) // 2
|
610
|
+
try:
|
611
|
+
c = bm[cy][cx]
|
612
|
+
except IndexError:
|
613
|
+
c = 0
|
614
|
+
dy = y + (diry + dirx - 1) // 2
|
615
|
+
dx = x + (dirx - diry - 1) // 2
|
616
|
+
try:
|
617
|
+
d = bm[dy][dx]
|
618
|
+
except IndexError:
|
619
|
+
d = 0
|
620
|
+
|
621
|
+
if c and not d: # /* ambiguous turn */
|
622
|
+
if (
|
623
|
+
turnpolicy == POTRACE_TURNPOLICY_RIGHT
|
624
|
+
or (turnpolicy == POTRACE_TURNPOLICY_BLACK and sign)
|
625
|
+
or (turnpolicy == POTRACE_TURNPOLICY_WHITE and not sign)
|
626
|
+
or (turnpolicy == POTRACE_TURNPOLICY_RANDOM and detrand(x, y))
|
627
|
+
or (turnpolicy == POTRACE_TURNPOLICY_MAJORITY and majority(bm, x, y))
|
628
|
+
or (
|
629
|
+
turnpolicy == POTRACE_TURNPOLICY_MINORITY and not majority(bm, x, y)
|
630
|
+
)
|
631
|
+
):
|
632
|
+
tmp = dirx # /* right turn */
|
633
|
+
dirx = diry
|
634
|
+
diry = -tmp
|
635
|
+
else:
|
636
|
+
tmp = dirx # /* left turn */
|
637
|
+
dirx = -diry
|
638
|
+
diry = tmp
|
639
|
+
elif c: # /* right turn */
|
640
|
+
tmp = dirx
|
641
|
+
dirx = diry
|
642
|
+
diry = -tmp
|
643
|
+
elif not d: # /* left turn */
|
644
|
+
tmp = dirx
|
645
|
+
dirx = -diry
|
646
|
+
diry = tmp
|
647
|
+
|
648
|
+
# /* allocate new path object */
|
649
|
+
return _Path(pt, area, sign)
|
650
|
+
|
651
|
+
|
652
|
+
@njit()
|
653
|
+
def findnext(bm: np.array) -> Optional[Tuple[Union[int], int]]:
|
654
|
+
"""
|
655
|
+
/* find the next set pixel in a row <= y. Pixels are searched first
|
656
|
+
left-to-right, then top-down. In other words, (x,y)<(x',y') if y>y'
|
657
|
+
or y=y' and x<x'. If found, return 0 and store pixel in
|
658
|
+
(*xp,*yp). Else return 1. Note that this function assumes that
|
659
|
+
excess bytes have been cleared with bm_clearexcess. */
|
660
|
+
"""
|
661
|
+
w = np.nonzero(bm)
|
662
|
+
if len(w[0]) == 0:
|
663
|
+
return None
|
664
|
+
|
665
|
+
q = np.where(w[0] == w[0][-1])
|
666
|
+
y = w[0][q]
|
667
|
+
x = w[1][q]
|
668
|
+
return y[0], x[0]
|
669
|
+
|
670
|
+
|
671
|
+
def setbbox_path(p: _Path):
|
672
|
+
"""
|
673
|
+
/* Find the bounding box of a given path. Path is assumed to be of
|
674
|
+
non-zero length. */
|
675
|
+
"""
|
676
|
+
y0 = float("inf")
|
677
|
+
y1 = 0
|
678
|
+
x0 = float("inf")
|
679
|
+
x1 = 0
|
680
|
+
for k in range(len(p)):
|
681
|
+
x = p.pt[k].x
|
682
|
+
y = p.pt[k].y
|
683
|
+
|
684
|
+
if x < x0:
|
685
|
+
x0 = x
|
686
|
+
if x > x1:
|
687
|
+
x1 = x
|
688
|
+
if y < y0:
|
689
|
+
y0 = y
|
690
|
+
if y > y1:
|
691
|
+
y1 = y
|
692
|
+
return x0, y0, x1, y1
|
693
|
+
|
694
|
+
|
695
|
+
def pathlist_to_tree(plist: list, bm: np.array) -> None:
|
696
|
+
"""
|
697
|
+
/* Give a tree structure to the given path list, based on "insideness"
|
698
|
+
testing. I.e., path A is considered "below" path B if it is inside
|
699
|
+
path B. The input pathlist is assumed to be ordered so that "outer"
|
700
|
+
paths occur before "inner" paths. The tree structure is stored in
|
701
|
+
the "childlist" and "sibling" components of the path_t
|
702
|
+
structure. The linked list structure is also changed so that
|
703
|
+
negative path components are listed immediately after their
|
704
|
+
positive parent. Note: some backends may ignore the tree
|
705
|
+
structure, others may use it e.g. to group path components. We
|
706
|
+
assume that in the input, point 0 of each path is an "upper left"
|
707
|
+
corner of the path, as returned by bm_to_pathlist. This makes it
|
708
|
+
easy to find an "interior" point. The bm argument should be a
|
709
|
+
bitmap of the correct size (large enough to hold all the paths),
|
710
|
+
and will be used as scratch space. Return 0 on success or -1 on
|
711
|
+
error with errno set. */
|
712
|
+
"""
|
713
|
+
# path_t *p, *p1
|
714
|
+
# path_t *heap, *heap1
|
715
|
+
# path_t *cur
|
716
|
+
# path_t *head
|
717
|
+
# path_t **plist_hook #/* for fast appending to linked list */
|
718
|
+
# path_t **hook_in, **hook_out #/* for fast appending to linked list */
|
719
|
+
# bbox_t bbox
|
720
|
+
|
721
|
+
bm = bm.copy()
|
722
|
+
|
723
|
+
# /* save original "next" pointers */
|
724
|
+
|
725
|
+
for p in plist:
|
726
|
+
p.sibling = p.next
|
727
|
+
p.childlist = None
|
728
|
+
|
729
|
+
heap = plist
|
730
|
+
"""
|
731
|
+
/* the heap holds a list of lists of paths. Use "childlist" field
|
732
|
+
for outer list, "next" field for inner list. Each of the sublists
|
733
|
+
is to be turned into a tree. This code is messy, but it is
|
734
|
+
actually fast. Each path is rendered exactly once. We use the
|
735
|
+
heap to get a tail recursive algorithm: the heap holds a list of
|
736
|
+
pathlists which still need to be transformed. */
|
737
|
+
"""
|
738
|
+
while heap:
|
739
|
+
# /* unlink first sublist */
|
740
|
+
cur = heap
|
741
|
+
heap = heap.childlist
|
742
|
+
cur.childlist = None
|
743
|
+
|
744
|
+
# /* unlink first path */
|
745
|
+
head = cur
|
746
|
+
cur = cur.next
|
747
|
+
head.next = None
|
748
|
+
|
749
|
+
# /* render path */
|
750
|
+
xor_path(bm, head)
|
751
|
+
x0, y0, x1, y1 = setbbox_path(head)
|
752
|
+
|
753
|
+
"""
|
754
|
+
/* now do insideness test for each element of cur; append it to
|
755
|
+
head->childlist if it's inside head, else append it to
|
756
|
+
head->next. */
|
757
|
+
"""
|
758
|
+
# hook_in= head.childlist
|
759
|
+
# hook_out= head.next
|
760
|
+
# list_forall_unlink(p, cur):
|
761
|
+
# if p.priv.pt[0].y <= bbox.y0:
|
762
|
+
# list_insert_beforehook(p, hook_out)
|
763
|
+
# # /* append the remainder of the list to hook_out */
|
764
|
+
# hook_out = cur
|
765
|
+
# break
|
766
|
+
# if (BM_GET(bm, p.priv.pt[0].x, p.priv.pt[0].y-1)):
|
767
|
+
# list_insert_beforehook(p, hook_in)
|
768
|
+
# else:
|
769
|
+
# list_insert_beforehook(p, hook_out)
|
770
|
+
|
771
|
+
# /* clear bm */
|
772
|
+
# clear_bm_with_bbox(bm, &bbox)
|
773
|
+
#
|
774
|
+
# #/* now schedule head->childlist and head->next for further processing */
|
775
|
+
# if head.next:
|
776
|
+
# head.next.childlist = heap
|
777
|
+
# heap = head.next
|
778
|
+
# if head.childlist:
|
779
|
+
# head.childlist.childlist = heap
|
780
|
+
# heap = head->childlist
|
781
|
+
#
|
782
|
+
# #/* copy sibling structure from "next" to "sibling" component */
|
783
|
+
# p = plist
|
784
|
+
# while p:
|
785
|
+
# p1 = p.sibling
|
786
|
+
# p.sibling = p.next
|
787
|
+
# p = p1
|
788
|
+
#
|
789
|
+
# """
|
790
|
+
# /* reconstruct a new linked list ("next") structure from tree
|
791
|
+
# ("childlist", "sibling") structure. This code is slightly messy,
|
792
|
+
# because we use a heap to make it tail recursive: the heap
|
793
|
+
# contains a list of childlists which still need to be
|
794
|
+
# processed. */"""
|
795
|
+
# heap = plist
|
796
|
+
# if heap:
|
797
|
+
# heap.next = None #/* heap is a linked list of childlists */
|
798
|
+
# plist = None
|
799
|
+
# plist_hook = plist
|
800
|
+
# while heap:
|
801
|
+
# heap1 = heap.next
|
802
|
+
# for (p=heap; p; p=p->sibling):
|
803
|
+
# #/* p is a positive path */
|
804
|
+
# #/* append to linked list */
|
805
|
+
# list_insert_beforehook(p, plist_hook)
|
806
|
+
#
|
807
|
+
# #/* go through its children */
|
808
|
+
# for (p1=p->childlist; p1; p1=p1->sibling):
|
809
|
+
# #/* append to linked list */
|
810
|
+
|
811
|
+
|
812
|
+
# list_insert_beforehook(p1, plist_hook);
|
813
|
+
# #/* append its childlist to heap, if non-empty */
|
814
|
+
# if (p1.childlist):
|
815
|
+
# list_append(path_t, heap1, p1->childlist)
|
816
|
+
# heap = heap1
|
817
|
+
|
818
|
+
|
819
|
+
def bm_to_pathlist(
|
820
|
+
bm: np.array, turdsize: int = 2, turnpolicy: int = POTRACE_TURNPOLICY_MINORITY
|
821
|
+
) -> list:
|
822
|
+
"""
|
823
|
+
/* Decompose the given bitmap into paths. Returns a linked list of
|
824
|
+
path_t objects with the fields len, pt, area, sign filled
|
825
|
+
in. Returns 0 on success with plistp set, or -1 on error with errno
|
826
|
+
set. */
|
827
|
+
"""
|
828
|
+
plist = [] # /* linked list of path objects */
|
829
|
+
original = bm.copy()
|
830
|
+
|
831
|
+
"""/* be sure the byte padding on the right is set to 0, as the fast
|
832
|
+
pixel search below relies on it */"""
|
833
|
+
# /* iterate through components */
|
834
|
+
while True:
|
835
|
+
n = findnext(bm)
|
836
|
+
if n is None:
|
837
|
+
break
|
838
|
+
y, x = n
|
839
|
+
# /* calculate the sign by looking at the original */
|
840
|
+
sign = original[y][x]
|
841
|
+
# /* calculate the path */
|
842
|
+
path = findpath(bm, x, y + 1, sign, turnpolicy)
|
843
|
+
if path is None:
|
844
|
+
raise ValueError
|
845
|
+
|
846
|
+
# /* update buffered image */
|
847
|
+
xor_path(bm, path)
|
848
|
+
|
849
|
+
# /* if it's a turd, eliminate it, else append it to the list */
|
850
|
+
if path.area > turdsize:
|
851
|
+
plist.append(path)
|
852
|
+
|
853
|
+
# pathlist_to_tree(plist, original)
|
854
|
+
return plist
|
855
|
+
|
856
|
+
|
857
|
+
# END DECOMPOSE SECTION.
|
858
|
+
|
859
|
+
# /* auxiliary functions */
|
860
|
+
|
861
|
+
|
862
|
+
def sign(x):
|
863
|
+
if x > 0:
|
864
|
+
return 1
|
865
|
+
if x < 0:
|
866
|
+
return -1
|
867
|
+
else:
|
868
|
+
return 0
|
869
|
+
|
870
|
+
|
871
|
+
def mod(a: int, n: int) -> int:
|
872
|
+
"""Note: the "mod" macro works correctly for
|
873
|
+
negative a. Also note that the test for a>=n, while redundant,
|
874
|
+
speeds up the mod function by 70% in the average case (significant
|
875
|
+
since the program spends about 16% of its time here - or 40%
|
876
|
+
without the test)."""
|
877
|
+
return a % n if a >= n else a if a >= 0 else n - 1 - (-1 - a) % n
|
878
|
+
|
879
|
+
|
880
|
+
def floordiv(a: int, n: int):
|
881
|
+
"""
|
882
|
+
The "floordiv" macro returns the largest integer <= a/n,
|
883
|
+
and again this works correctly for negative a, as long as
|
884
|
+
a,n are integers and n>0.
|
885
|
+
"""
|
886
|
+
return a // n if a >= 0 else -1 - (-1 - a) // n
|
887
|
+
|
888
|
+
|
889
|
+
def interval(t: float, a: _Point, b: _Point):
|
890
|
+
return _Point(a.x + t * (b.x - a.x), a.y + t * (b.y - a.y))
|
891
|
+
|
892
|
+
|
893
|
+
def dorth_infty(p0: _Point, p2: _Point):
|
894
|
+
"""
|
895
|
+
return a direction that is 90 degrees counterclockwise from p2-p0,
|
896
|
+
but then restricted to one of the major wind directions (n, nw, w, etc)
|
897
|
+
"""
|
898
|
+
return _Point(-sign(p2.y - p0.y), sign(p2.x - p0.x))
|
899
|
+
|
900
|
+
|
901
|
+
def dpara(p0: _Point, p1: _Point, p2: _Point) -> float:
|
902
|
+
"""
|
903
|
+
/* return (p1-p0)x(p2-p0), the area of the parallelogram */
|
904
|
+
"""
|
905
|
+
x1 = p1.x - p0.x
|
906
|
+
y1 = p1.y - p0.y
|
907
|
+
x2 = p2.x - p0.x
|
908
|
+
y2 = p2.y - p0.y
|
909
|
+
return x1 * y2 - x2 * y1
|
910
|
+
|
911
|
+
|
912
|
+
def ddenom(p0: _Point, p2: _Point) -> float:
|
913
|
+
"""
|
914
|
+
ddenom/dpara have the property that the square of radius 1 centered
|
915
|
+
at p1 intersects the line p0p2 iff |dpara(p0,p1,p2)| <= ddenom(p0,p2)
|
916
|
+
"""
|
917
|
+
r = dorth_infty(p0, p2)
|
918
|
+
return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y)
|
919
|
+
|
920
|
+
|
921
|
+
def cyclic(a: int, b: int, c: int) -> int:
|
922
|
+
"""
|
923
|
+
/* return 1 if a <= b < c < a, in a cyclic sense (mod n) */
|
924
|
+
"""
|
925
|
+
if a <= c:
|
926
|
+
return a <= b < c
|
927
|
+
else:
|
928
|
+
return a <= b or b < c
|
929
|
+
|
930
|
+
|
931
|
+
def pointslope(pp: _Path, i: int, j: int, ctr: _Point, dir: _Point) -> None:
|
932
|
+
"""
|
933
|
+
determine the center and slope of the line i..j. Assume i<j. Needs
|
934
|
+
"sum" components of p to be set.
|
935
|
+
"""
|
936
|
+
|
937
|
+
# /* assume i<j */
|
938
|
+
|
939
|
+
n = len(pp)
|
940
|
+
sums = pp._sums
|
941
|
+
|
942
|
+
r = 0 # /* rotations from i to j */
|
943
|
+
|
944
|
+
while j >= n:
|
945
|
+
j -= n
|
946
|
+
r += 1
|
947
|
+
|
948
|
+
while i >= n:
|
949
|
+
i -= n
|
950
|
+
r -= 1
|
951
|
+
|
952
|
+
while j < 0:
|
953
|
+
j += n
|
954
|
+
r -= 1
|
955
|
+
|
956
|
+
while i < 0:
|
957
|
+
i += n
|
958
|
+
r += 1
|
959
|
+
|
960
|
+
x = sums[j + 1].x - sums[i].x + r * sums[n].x
|
961
|
+
y = sums[j + 1].y - sums[i].y + r * sums[n].y
|
962
|
+
x2 = sums[j + 1].x2 - sums[i].x2 + r * sums[n].x2
|
963
|
+
xy = sums[j + 1].xy - sums[i].xy + r * sums[n].xy
|
964
|
+
y2 = sums[j + 1].y2 - sums[i].y2 + r * sums[n].y2
|
965
|
+
k = j + 1 - i + r * n
|
966
|
+
|
967
|
+
ctr.x = x / k
|
968
|
+
ctr.y = y / k
|
969
|
+
|
970
|
+
a = float(x2 - x * x / k) / k
|
971
|
+
b = float(xy - x * y / k) / k
|
972
|
+
c = float(y2 - y * y / k) / k
|
973
|
+
|
974
|
+
lambda2 = (
|
975
|
+
a + c + math.sqrt((a - c) * (a - c) + 4 * b * b)
|
976
|
+
) / 2 # /* larger e.value */
|
977
|
+
|
978
|
+
# /* now find e.vector for lambda2 */
|
979
|
+
a -= lambda2
|
980
|
+
c -= lambda2
|
981
|
+
|
982
|
+
if math.fabs(a) >= math.fabs(c):
|
983
|
+
l = math.sqrt(a * a + b * b)
|
984
|
+
if l != 0:
|
985
|
+
dir.x = -b / l
|
986
|
+
dir.y = a / l
|
987
|
+
else:
|
988
|
+
l = math.sqrt(c * c + b * b)
|
989
|
+
if l != 0:
|
990
|
+
dir.x = -c / l
|
991
|
+
dir.y = b / l
|
992
|
+
if l == 0:
|
993
|
+
# sometimes this can happen when k=4:
|
994
|
+
# the two eigenvalues coincide */
|
995
|
+
dir.x = 0
|
996
|
+
dir.y = 0
|
997
|
+
|
998
|
+
|
999
|
+
"""
|
1000
|
+
/* the type of (affine) quadratic forms, represented as symmetric 3x3
|
1001
|
+
matrices. The value of the quadratic form at a vector (x,y) is v^t
|
1002
|
+
Q v, where v = (x,y,1)^t. */
|
1003
|
+
"""
|
1004
|
+
|
1005
|
+
|
1006
|
+
def quadform(Q: list, w: _Point) -> float:
|
1007
|
+
"""Apply quadratic form Q to vector w = (w.x,w.y)"""
|
1008
|
+
v = (w.x, w.y, 1.0)
|
1009
|
+
sum = 0.0
|
1010
|
+
for i in range(3):
|
1011
|
+
for j in range(3):
|
1012
|
+
sum += v[i] * Q[i][j] * v[j]
|
1013
|
+
return sum
|
1014
|
+
|
1015
|
+
|
1016
|
+
def xprod(p1x, p1y, p2x, p2y) -> float:
|
1017
|
+
"""calculate p1 x p2"""
|
1018
|
+
return p1x * p2y - p1y * p2x
|
1019
|
+
|
1020
|
+
|
1021
|
+
def cprod(p0: _Point, p1: _Point, p2: _Point, p3: _Point) -> float:
|
1022
|
+
"""calculate (p1-p0)x(p3-p2)"""
|
1023
|
+
x1 = p1.x - p0.x
|
1024
|
+
y1 = p1.y - p0.y
|
1025
|
+
x2 = p3.x - p2.x
|
1026
|
+
y2 = p3.y - p2.y
|
1027
|
+
return x1 * y2 - x2 * y1
|
1028
|
+
|
1029
|
+
|
1030
|
+
def iprod(p0: _Point, p1: _Point, p2: _Point) -> float:
|
1031
|
+
"""calculate (p1-p0)*(p2-p0)"""
|
1032
|
+
x1 = p1.x - p0.x
|
1033
|
+
y1 = p1.y - p0.y
|
1034
|
+
x2 = p2.x - p0.x
|
1035
|
+
y2 = p2.y - p0.y
|
1036
|
+
return x1 * x2 + y1 * y2
|
1037
|
+
|
1038
|
+
|
1039
|
+
def iprod1(p0: _Point, p1: _Point, p2: _Point, p3: _Point) -> float:
|
1040
|
+
"""calculate (p1-p0)*(p3-p2)"""
|
1041
|
+
x1 = p1.x - p0.x
|
1042
|
+
y1 = p1.y - p0.y
|
1043
|
+
x2 = p3.x - p2.x
|
1044
|
+
y2 = p3.y - p2.y
|
1045
|
+
return x1 * x2 + y1 * y2
|
1046
|
+
|
1047
|
+
|
1048
|
+
def sq(x: float) -> float:
|
1049
|
+
return x * x
|
1050
|
+
|
1051
|
+
|
1052
|
+
def ddist(p: _Point, q: _Point) -> float:
|
1053
|
+
"""calculate distance between two points"""
|
1054
|
+
return math.sqrt(sq(p.x - q.x) + sq(p.y - q.y))
|
1055
|
+
|
1056
|
+
|
1057
|
+
def bezier(t: float, p0: _Point, p1: _Point, p2: _Point, p3: _Point) -> _Point:
|
1058
|
+
"""calculate point of a bezier curve"""
|
1059
|
+
s = 1 - t
|
1060
|
+
|
1061
|
+
"""
|
1062
|
+
Note: a good optimizing compiler (such as gcc-3) reduces the
|
1063
|
+
following to 16 multiplications, using common subexpression
|
1064
|
+
elimination.
|
1065
|
+
"""
|
1066
|
+
return _Point(
|
1067
|
+
s * s * s * p0.x
|
1068
|
+
+ 3 * (s * s * t) * p1.x
|
1069
|
+
+ 3 * (t * t * s) * p2.x
|
1070
|
+
+ t * t * t * p3.x,
|
1071
|
+
s * s * s * p0.y
|
1072
|
+
+ 3 * (s * s * t) * p1.y
|
1073
|
+
+ 3 * (t * t * s) * p2.y
|
1074
|
+
+ t * t * t * p3.y,
|
1075
|
+
)
|
1076
|
+
|
1077
|
+
|
1078
|
+
def tangent(
|
1079
|
+
p0: _Point, p1: _Point, p2: _Point, p3: _Point, q0: _Point, q1: _Point
|
1080
|
+
) -> float:
|
1081
|
+
"""calculate the point t in [0..1] on the (convex) bezier curve
|
1082
|
+
(p0,p1,p2,p3) which is tangent to q1-q0. Return -1.0 if there is no
|
1083
|
+
solution in [0..1]."""
|
1084
|
+
|
1085
|
+
# (1-t)^2 A + 2(1-t)t B + t^2 C = 0
|
1086
|
+
# a t^2 + b t + c = 0
|
1087
|
+
|
1088
|
+
A = cprod(p0, p1, q0, q1)
|
1089
|
+
B = cprod(p1, p2, q0, q1)
|
1090
|
+
C = cprod(p2, p3, q0, q1)
|
1091
|
+
|
1092
|
+
a = A - 2 * B + C
|
1093
|
+
b = -2 * A + 2 * B
|
1094
|
+
c = A
|
1095
|
+
|
1096
|
+
d = b * b - 4 * a * c
|
1097
|
+
|
1098
|
+
if a == 0 or d < 0:
|
1099
|
+
return -1.0
|
1100
|
+
|
1101
|
+
s = math.sqrt(d)
|
1102
|
+
|
1103
|
+
r1 = (-b + s) / (2 * a)
|
1104
|
+
r2 = (-b - s) / (2 * a)
|
1105
|
+
|
1106
|
+
if 0 <= r1 <= 1:
|
1107
|
+
return r1
|
1108
|
+
elif 0 <= r2 <= 1:
|
1109
|
+
return r2
|
1110
|
+
else:
|
1111
|
+
return -1.0
|
1112
|
+
|
1113
|
+
|
1114
|
+
"""
|
1115
|
+
/* ---------------------------------------------------------------------- */
|
1116
|
+
/* Stage 1: determine the straight subpaths (Sec. 2.2.1). Fill in the
|
1117
|
+
"lon" component of a path object (based on pt/len). For each i,
|
1118
|
+
lon[i] is the furthest index such that a straight line can be drawn
|
1119
|
+
from i to lon[i]. Return 1 on error with errno set, else 0. */
|
1120
|
+
|
1121
|
+
/* this algorithm depends on the fact that the existence of straight
|
1122
|
+
subpaths is a triplewise property. I.e., there exists a straight
|
1123
|
+
line through squares i0,...,in iff there exists a straight line
|
1124
|
+
through i,j,k, for all i0<=i<j<k<=in. (Proof?) */
|
1125
|
+
|
1126
|
+
/* this implementation of calc_lon is O(n^2). It replaces an older
|
1127
|
+
O(n^3) version. A "constraint" means that future points must
|
1128
|
+
satisfy xprod(constraint[0], cur) >= 0 and xprod(constraint[1],
|
1129
|
+
cur) <= 0. */
|
1130
|
+
|
1131
|
+
/* Remark for Potrace 1.1: the current implementation of calc_lon is
|
1132
|
+
more complex than the implementation found in Potrace 1.0, but it
|
1133
|
+
is considerably faster. The introduction of the "nc" data structure
|
1134
|
+
means that we only have to test the constraints for "corner"
|
1135
|
+
points. On a typical input file, this speeds up the calc_lon
|
1136
|
+
function by a factor of 31.2, thereby decreasing its time share
|
1137
|
+
within the overall Potrace algorithm from 72.6% to 7.82%, and
|
1138
|
+
speeding up the overall algorithm by a factor of 3.36. On another
|
1139
|
+
input file, calc_lon was sped up by a factor of 6.7, decreasing its
|
1140
|
+
time share from 51.4% to 13.61%, and speeding up the overall
|
1141
|
+
algorithm by a factor of 1.78. In any case, the savings are
|
1142
|
+
substantial. */
|
1143
|
+
|
1144
|
+
"""
|
1145
|
+
|
1146
|
+
|
1147
|
+
# ----------------------------------------------------------------------
|
1148
|
+
|
1149
|
+
|
1150
|
+
def _calc_sums(path: _Path) -> int:
|
1151
|
+
"""Preparation: fill in the sum* fields of a path (used for later
|
1152
|
+
rapid summing). Return 0 on success, 1 with errno set on
|
1153
|
+
failure."""
|
1154
|
+
n = len(path)
|
1155
|
+
path._sums = [_Sums() for i in range(len(path) + 1)]
|
1156
|
+
|
1157
|
+
# origin
|
1158
|
+
path._x0 = path.pt[0].x
|
1159
|
+
path._y0 = path.pt[0].y
|
1160
|
+
|
1161
|
+
# /* preparatory computation for later fast summing */
|
1162
|
+
path._sums[0].x2 = 0
|
1163
|
+
path._sums[0].xy = 0
|
1164
|
+
path._sums[0].y2 = 0
|
1165
|
+
path._sums[0].x = 0
|
1166
|
+
path._sums[0].y = 0
|
1167
|
+
for i in range(n):
|
1168
|
+
x = path.pt[i].x - path._x0
|
1169
|
+
y = path.pt[i].y - path._y0
|
1170
|
+
path._sums[i + 1].x = path._sums[i].x + x
|
1171
|
+
path._sums[i + 1].y = path._sums[i].y + y
|
1172
|
+
path._sums[i + 1].x2 = path._sums[i].x2 + float(x * x)
|
1173
|
+
path._sums[i + 1].xy = path._sums[i].xy + float(x * y)
|
1174
|
+
path._sums[i + 1].y2 = path._sums[i].y2 + float(y * y)
|
1175
|
+
return 0
|
1176
|
+
|
1177
|
+
|
1178
|
+
def _calc_lon(pp: _Path) -> int:
|
1179
|
+
"""initialize the nc data structure. Point from each point to the
|
1180
|
+
furthest future point to which it is connected by a vertical or
|
1181
|
+
horizontal segment. We take advantage of the fact that there is
|
1182
|
+
always a direction change at 0 (due to the path decomposition
|
1183
|
+
algorithm). But even if this were not so, there is no harm, as
|
1184
|
+
in practice, correctness does not depend on the word "furthest"
|
1185
|
+
above.
|
1186
|
+
returns 0 on success, 1 on error with errno set
|
1187
|
+
"""
|
1188
|
+
|
1189
|
+
pt = pp.pt
|
1190
|
+
n = len(pp)
|
1191
|
+
ct = [0, 0, 0, 0]
|
1192
|
+
pivk = [None] * n # pivk[n]
|
1193
|
+
nc = [None] * n # nc[n]: next corner
|
1194
|
+
|
1195
|
+
k = 0
|
1196
|
+
for i in range(n - 1, -1, -1):
|
1197
|
+
if pt[i].x != pt[k].x and pt[i].y != pt[k].y:
|
1198
|
+
k = i + 1 # /* necessarily i<n-1 in this case */
|
1199
|
+
nc[i] = k
|
1200
|
+
|
1201
|
+
pp._lon = [None] * n
|
1202
|
+
|
1203
|
+
# determine pivot points: for each i, let pivk[i] be the furthest k
|
1204
|
+
# such that all j with i<j<k lie on a line connecting i,k.
|
1205
|
+
|
1206
|
+
for i in range(n - 1, -1, -1):
|
1207
|
+
ct[0] = ct[1] = ct[2] = ct[3] = 0
|
1208
|
+
|
1209
|
+
# keep track of "directions" that have occurred
|
1210
|
+
dir = int(
|
1211
|
+
(3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + (pt[mod(i + 1, n)].y - pt[i].y))
|
1212
|
+
// 2
|
1213
|
+
)
|
1214
|
+
ct[dir] += 1
|
1215
|
+
|
1216
|
+
constraint0x = 0
|
1217
|
+
constraint0y = 0
|
1218
|
+
constraint1x = 0
|
1219
|
+
constraint1y = 0
|
1220
|
+
|
1221
|
+
# find the next k such that no straight line from i to k
|
1222
|
+
k = nc[i]
|
1223
|
+
k1 = i
|
1224
|
+
while True:
|
1225
|
+
break_inner_loop_and_continue = False
|
1226
|
+
dir = int(3 + 3 * sign(pt[k].x - pt[k1].x) + sign(pt[k].y - pt[k1].y)) // 2
|
1227
|
+
ct[dir] += 1
|
1228
|
+
|
1229
|
+
# if all four "directions" have occurred, cut this path
|
1230
|
+
if ct[0] and ct[1] and ct[2] and ct[3]:
|
1231
|
+
pivk[i] = k1
|
1232
|
+
break_inner_loop_and_continue = True
|
1233
|
+
break # goto foundk;
|
1234
|
+
|
1235
|
+
cur_x = pt[k].x - pt[i].x
|
1236
|
+
cur_y = pt[k].y - pt[i].y
|
1237
|
+
|
1238
|
+
if (
|
1239
|
+
xprod(constraint0x, constraint0y, cur_x, cur_y) < 0
|
1240
|
+
or xprod(constraint1x, constraint1y, cur_x, cur_y) > 0
|
1241
|
+
):
|
1242
|
+
break
|
1243
|
+
# see if current constraint is violated
|
1244
|
+
# else, update constraint
|
1245
|
+
if abs(cur_x) <= 1 and abs(cur_y) <= 1:
|
1246
|
+
pass # /* no constraint */
|
1247
|
+
else:
|
1248
|
+
off_x = cur_x + (1 if (cur_y >= 0 and (cur_y > 0 or cur_x < 0)) else -1)
|
1249
|
+
off_y = cur_y + (1 if (cur_x <= 0 and (cur_x < 0 or cur_y < 0)) else -1)
|
1250
|
+
if xprod(constraint0x, constraint0y, off_x, off_y) >= 0:
|
1251
|
+
constraint0x = off_x
|
1252
|
+
constraint0y = off_y
|
1253
|
+
off_x = cur_x + (1 if (cur_y <= 0 and (cur_y < 0 or cur_x < 0)) else -1)
|
1254
|
+
off_y = cur_y + (1 if (cur_x >= 0 and (cur_x > 0 or cur_y < 0)) else -1)
|
1255
|
+
if xprod(constraint1x, constraint1y, off_x, off_y) <= 0:
|
1256
|
+
constraint1x = off_x
|
1257
|
+
constraint1y = off_y
|
1258
|
+
k1 = k
|
1259
|
+
k = nc[k1]
|
1260
|
+
if not cyclic(k, i, k1):
|
1261
|
+
break
|
1262
|
+
if break_inner_loop_and_continue:
|
1263
|
+
# This previously was a goto to the end of the for i statement.
|
1264
|
+
continue
|
1265
|
+
# constraint_viol:
|
1266
|
+
"""k1 was the last "corner" satisfying the current constraint, and
|
1267
|
+
k is the first one violating it. We now need to find the last
|
1268
|
+
point along k1..k which satisfied the constraint."""
|
1269
|
+
# dk: direction of k-k1
|
1270
|
+
dk_x = sign(pt[k].x - pt[k1].x)
|
1271
|
+
dk_y = sign(pt[k].y - pt[k1].y)
|
1272
|
+
cur_x = pt[k1].x - pt[i].x
|
1273
|
+
cur_y = pt[k1].y - pt[i].y
|
1274
|
+
"""find largest integer j such that xprod(constraint[0], cur+j*dk) >= 0
|
1275
|
+
and xprod(constraint[1], cur+j*dk) <= 0. Use bilinearity of xprod. */"""
|
1276
|
+
a = xprod(constraint0x, constraint0y, cur_x, cur_y)
|
1277
|
+
b = xprod(constraint0x, constraint0y, dk_x, dk_y)
|
1278
|
+
c = xprod(constraint1x, constraint1y, cur_x, cur_y)
|
1279
|
+
d = xprod(constraint1x, constraint1y, dk_x, dk_y)
|
1280
|
+
"""find largest integer j such that a+j*b>=0 and c+j*d<=0. This
|
1281
|
+
can be solved with integer arithmetic."""
|
1282
|
+
j = INFTY
|
1283
|
+
if b < 0:
|
1284
|
+
j = floordiv(a, -b)
|
1285
|
+
if d > 0:
|
1286
|
+
j = min(j, floordiv(-c, d))
|
1287
|
+
pivk[i] = mod(k1 + j, n)
|
1288
|
+
# foundk:
|
1289
|
+
# /* for i */
|
1290
|
+
|
1291
|
+
"""/* clean up: for each i, let lon[i] be the largest k such that for
|
1292
|
+
all i' with i<=i'<k, i'<k<=pivk[i']. */"""
|
1293
|
+
|
1294
|
+
j = pivk[n - 1]
|
1295
|
+
pp._lon[n - 1] = j
|
1296
|
+
for i in range(n - 2, -1, -1):
|
1297
|
+
if cyclic(i + 1, pivk[i], j):
|
1298
|
+
j = pivk[i]
|
1299
|
+
pp._lon[i] = j
|
1300
|
+
|
1301
|
+
i = n - 1
|
1302
|
+
while cyclic(mod(i + 1, n), j, pp._lon[i]):
|
1303
|
+
pp._lon[i] = j
|
1304
|
+
i -= 1
|
1305
|
+
return 0
|
1306
|
+
|
1307
|
+
|
1308
|
+
"""
|
1309
|
+
/* ---------------------------------------------------------------------- */
|
1310
|
+
/* Stage 2: calculate the optimal polygon (Sec. 2.2.2-2.2.4). */
|
1311
|
+
"""
|
1312
|
+
|
1313
|
+
|
1314
|
+
def penalty3(pp: _Path, i: int, j: int) -> float:
|
1315
|
+
"""Auxiliary function: calculate the penalty of an edge from i to j in
|
1316
|
+
the given path. This needs the "lon" and "sum*" data."""
|
1317
|
+
n = len(pp)
|
1318
|
+
pt = pp.pt
|
1319
|
+
sums = pp._sums
|
1320
|
+
|
1321
|
+
# /* assume 0<=i<j<=n */
|
1322
|
+
|
1323
|
+
r = 0 # /* rotations from i to j */
|
1324
|
+
if j >= n:
|
1325
|
+
j -= n
|
1326
|
+
r = 1
|
1327
|
+
|
1328
|
+
# /* critical inner loop: the "if" gives a 4.6 percent speedup */
|
1329
|
+
if r == 0:
|
1330
|
+
x = sums[j + 1].x - sums[i].x
|
1331
|
+
y = sums[j + 1].y - sums[i].y
|
1332
|
+
x2 = sums[j + 1].x2 - sums[i].x2
|
1333
|
+
xy = sums[j + 1].xy - sums[i].xy
|
1334
|
+
y2 = sums[j + 1].y2 - sums[i].y2
|
1335
|
+
k = j + 1 - i
|
1336
|
+
else:
|
1337
|
+
x = sums[j + 1].x - sums[i].x + sums[n].x
|
1338
|
+
y = sums[j + 1].y - sums[i].y + sums[n].y
|
1339
|
+
x2 = sums[j + 1].x2 - sums[i].x2 + sums[n].x2
|
1340
|
+
xy = sums[j + 1].xy - sums[i].xy + sums[n].xy
|
1341
|
+
y2 = sums[j + 1].y2 - sums[i].y2 + sums[n].y2
|
1342
|
+
k = j + 1 - i + n
|
1343
|
+
|
1344
|
+
px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x
|
1345
|
+
py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y
|
1346
|
+
ey = pt[j].x - pt[i].x
|
1347
|
+
ex = -(pt[j].y - pt[i].y)
|
1348
|
+
|
1349
|
+
a = (x2 - 2 * x * px) / k + px * px
|
1350
|
+
b = (xy - x * py - y * px) / k + px * py
|
1351
|
+
c = (y2 - 2 * y * py) / k + py * py
|
1352
|
+
|
1353
|
+
s = ex * ex * a + 2 * ex * ey * b + ey * ey * c
|
1354
|
+
return math.sqrt(s)
|
1355
|
+
|
1356
|
+
|
1357
|
+
def _bestpolygon(pp: _Path) -> int:
|
1358
|
+
"""
|
1359
|
+
/* find the optimal polygon. Fill in the m and po components. Return 1
|
1360
|
+
on failure with errno set, else 0. Non-cyclic version: assumes i=0
|
1361
|
+
is in the polygon. Fixme: implement cyclic version. */
|
1362
|
+
"""
|
1363
|
+
n = len(pp)
|
1364
|
+
pen = [None] * (n + 1) # /* pen[n+1]: penalty vector */
|
1365
|
+
prev = [None] * (n + 1) # /* prev[n+1]: best path pointer vector */
|
1366
|
+
clip0 = [None] * n # /* clip0[n]: longest segment pointer, non-cyclic */
|
1367
|
+
clip1 = [None] * (n + 1) # /* clip1[n+1]: backwards segment pointer, non-cyclic */
|
1368
|
+
seg0 = [None] * (n + 1) # /* seg0[m+1]: forward segment bounds, m<=n */
|
1369
|
+
seg1 = [None] * (n + 1) # /* seg1[m+1]: backward segment bounds, m<=n */
|
1370
|
+
|
1371
|
+
# /* calculate clipped paths */
|
1372
|
+
for i in range(n):
|
1373
|
+
c = mod(pp._lon[mod(i - 1, n)] - 1, n)
|
1374
|
+
if c == i:
|
1375
|
+
c = mod(i + 1, n)
|
1376
|
+
if c < i:
|
1377
|
+
clip0[i] = n
|
1378
|
+
else:
|
1379
|
+
clip0[i] = c
|
1380
|
+
|
1381
|
+
# /* calculate backwards path clipping, non-cyclic. j <= clip0[i] iff
|
1382
|
+
# clip1[j] <= i, for i,j=0..n. */
|
1383
|
+
j = 1
|
1384
|
+
for i in range(n):
|
1385
|
+
while j <= clip0[i]:
|
1386
|
+
clip1[j] = i
|
1387
|
+
j += 1
|
1388
|
+
|
1389
|
+
# calculate seg0[j] = longest path from 0 with j segments */
|
1390
|
+
i = 0
|
1391
|
+
j = 0
|
1392
|
+
while i < n:
|
1393
|
+
seg0[j] = i
|
1394
|
+
i = clip0[i]
|
1395
|
+
j += 1
|
1396
|
+
seg0[j] = n
|
1397
|
+
m = j
|
1398
|
+
|
1399
|
+
# calculate seg1[j] = longest path to n with m-j segments */
|
1400
|
+
i = n
|
1401
|
+
for j in range(m, 0, -1):
|
1402
|
+
seg1[j] = i
|
1403
|
+
i = clip1[i]
|
1404
|
+
seg1[0] = 0
|
1405
|
+
|
1406
|
+
"""now find the shortest path with m segments, based on penalty3 */
|
1407
|
+
/* note: the outer 2 loops jointly have at most n iterations, thus
|
1408
|
+
the worst-case behavior here is quadratic. In practice, it is
|
1409
|
+
close to linear since the inner loop tends to be short. */
|
1410
|
+
"""
|
1411
|
+
pen[0] = 0
|
1412
|
+
for j in range(1, m + 1):
|
1413
|
+
for i in range(seg1[j], seg0[j] + 1):
|
1414
|
+
best = -1
|
1415
|
+
for k in range(seg0[j - 1], clip1[i] - 1, -1):
|
1416
|
+
thispen = penalty3(pp, k, i) + pen[k]
|
1417
|
+
if best < 0 or thispen < best:
|
1418
|
+
prev[i] = k
|
1419
|
+
best = thispen
|
1420
|
+
pen[i] = best
|
1421
|
+
|
1422
|
+
pp._m = m
|
1423
|
+
pp._po = [None] * m
|
1424
|
+
|
1425
|
+
# /* read off shortest path */
|
1426
|
+
i = n
|
1427
|
+
j = m - 1
|
1428
|
+
while i > 0:
|
1429
|
+
i = prev[i]
|
1430
|
+
pp._po[j] = i
|
1431
|
+
j -= 1
|
1432
|
+
return 0
|
1433
|
+
|
1434
|
+
|
1435
|
+
"""
|
1436
|
+
/* ---------------------------------------------------------------------- */
|
1437
|
+
/* Stage 3: vertex adjustment (Sec. 2.3.1). */
|
1438
|
+
|
1439
|
+
"""
|
1440
|
+
|
1441
|
+
|
1442
|
+
def _adjust_vertices(pp: _Path) -> int:
|
1443
|
+
"""
|
1444
|
+
/* Adjust vertices of optimal polygon: calculate the intersection of
|
1445
|
+
the two "optimal" line segments, then move it into the unit square
|
1446
|
+
if it lies outside. Return 1 with errno set on error; 0 on
|
1447
|
+
success. */
|
1448
|
+
"""
|
1449
|
+
m = pp._m
|
1450
|
+
po = pp._po
|
1451
|
+
n = len(pp)
|
1452
|
+
pt = pp.pt # point_t
|
1453
|
+
x0 = pp._x0
|
1454
|
+
y0 = pp._y0
|
1455
|
+
|
1456
|
+
ctr = [_Point() for i in range(m)] # /* ctr[m] */
|
1457
|
+
dir = [_Point() for i in range(m)] # /* dir[m] */
|
1458
|
+
q = [
|
1459
|
+
[[0.0 for a in range(3)] for b in range(3)] for c in range(m)
|
1460
|
+
] # quadform_t/* q[m] */
|
1461
|
+
v = [0.0, 0.0, 0.0]
|
1462
|
+
s = _Point(0, 0)
|
1463
|
+
pp._curve = _Curve(m)
|
1464
|
+
|
1465
|
+
# /* calculate "optimal" point-slope representation for each line segment */
|
1466
|
+
for i in range(m):
|
1467
|
+
j = po[mod(i + 1, m)]
|
1468
|
+
j = mod(j - po[i], n) + po[i]
|
1469
|
+
pointslope(pp, po[i], j, ctr[i], dir[i])
|
1470
|
+
|
1471
|
+
# /* represent each line segment as a singular quadratic form;
|
1472
|
+
# the distance of a point (x,y) from the line segment will be
|
1473
|
+
# (x,y,1)Q(x,y,1)^t, where Q=q[i]. */
|
1474
|
+
for i in range(m):
|
1475
|
+
d = sq(dir[i].x) + sq(dir[i].y)
|
1476
|
+
if d == 0.0:
|
1477
|
+
for j in range(3):
|
1478
|
+
for k in range(3):
|
1479
|
+
q[i][j][k] = 0
|
1480
|
+
else:
|
1481
|
+
v[0] = dir[i].y
|
1482
|
+
v[1] = -dir[i].x
|
1483
|
+
v[2] = -v[1] * ctr[i].y - v[0] * ctr[i].x
|
1484
|
+
for l in range(3):
|
1485
|
+
for k in range(3):
|
1486
|
+
q[i][l][k] = v[l] * v[k] / d
|
1487
|
+
|
1488
|
+
"""/* now calculate the "intersections" of consecutive segments.
|
1489
|
+
Instead of using the actual intersection, we find the point
|
1490
|
+
within a given unit square which minimizes the square distance to
|
1491
|
+
the two lines. */"""
|
1492
|
+
Q = [[0.0 for a in range(3)] for b in range(3)]
|
1493
|
+
for i in range(m):
|
1494
|
+
# double min, cand; #/* minimum and candidate for minimum of quad. form */
|
1495
|
+
# double xmin, ymin; #/* coordinates of minimum */
|
1496
|
+
|
1497
|
+
# /* let s be the vertex, in coordinates relative to x0/y0 */
|
1498
|
+
s.x = pt[po[i]].x - x0
|
1499
|
+
s.y = pt[po[i]].y - y0
|
1500
|
+
|
1501
|
+
# /* intersect segments i-1 and i */
|
1502
|
+
|
1503
|
+
j = mod(i - 1, m)
|
1504
|
+
|
1505
|
+
# /* add quadratic forms */
|
1506
|
+
for l in range(3):
|
1507
|
+
for k in range(3):
|
1508
|
+
Q[l][k] = q[j][l][k] + q[i][l][k]
|
1509
|
+
|
1510
|
+
while True:
|
1511
|
+
# /* minimize the quadratic form Q on the unit square */
|
1512
|
+
# /* find intersection */
|
1513
|
+
|
1514
|
+
det = Q[0][0] * Q[1][1] - Q[0][1] * Q[1][0]
|
1515
|
+
w = None
|
1516
|
+
if det != 0.0:
|
1517
|
+
w = _Point(
|
1518
|
+
(-Q[0][2] * Q[1][1] + Q[1][2] * Q[0][1]) / det,
|
1519
|
+
(Q[0][2] * Q[1][0] - Q[1][2] * Q[0][0]) / det,
|
1520
|
+
)
|
1521
|
+
break
|
1522
|
+
|
1523
|
+
# /* matrix is singular - lines are parallel. Add another,
|
1524
|
+
# orthogonal axis, through the center of the unit square */
|
1525
|
+
if Q[0][0] > Q[1][1]:
|
1526
|
+
v[0] = -Q[0][1]
|
1527
|
+
v[1] = Q[0][0]
|
1528
|
+
elif Q[1][1]:
|
1529
|
+
v[0] = -Q[1][1]
|
1530
|
+
v[1] = Q[1][0]
|
1531
|
+
else:
|
1532
|
+
v[0] = 1
|
1533
|
+
v[1] = 0
|
1534
|
+
d = sq(v[0]) + sq(v[1])
|
1535
|
+
v[2] = -v[1] * s.y - v[0] * s.x
|
1536
|
+
for l in range(3):
|
1537
|
+
for k in range(3):
|
1538
|
+
Q[l][k] += v[l] * v[k] / d
|
1539
|
+
dx = math.fabs(w.x - s.x)
|
1540
|
+
dy = math.fabs(w.y - s.y)
|
1541
|
+
if dx <= 0.5 and dy <= 0.5:
|
1542
|
+
pp._curve[i].vertex.x = w.x + x0
|
1543
|
+
pp._curve[i].vertex.y = w.y + y0
|
1544
|
+
continue
|
1545
|
+
|
1546
|
+
# /* the minimum was not in the unit square; now minimize quadratic
|
1547
|
+
# on boundary of square */
|
1548
|
+
min = quadform(Q, s)
|
1549
|
+
xmin = s.x
|
1550
|
+
ymin = s.y
|
1551
|
+
|
1552
|
+
if Q[0][0] != 0.0:
|
1553
|
+
for z in range(2): # /* value of the y-coordinate */
|
1554
|
+
w.y = s.y - 0.5 + z
|
1555
|
+
w.x = -(Q[0][1] * w.y + Q[0][2]) / Q[0][0]
|
1556
|
+
dx = math.fabs(w.x - s.x)
|
1557
|
+
cand = quadform(Q, w)
|
1558
|
+
if dx <= 0.5 and cand < min:
|
1559
|
+
min = cand
|
1560
|
+
xmin = w.x
|
1561
|
+
ymin = w.y
|
1562
|
+
if Q[1][1] != 0.0:
|
1563
|
+
for z in range(2): # /* value of the x-coordinate */
|
1564
|
+
w.x = s.x - 0.5 + z
|
1565
|
+
w.y = -(Q[1][0] * w.x + Q[1][2]) / Q[1][1]
|
1566
|
+
dy = math.fabs(w.y - s.y)
|
1567
|
+
cand = quadform(Q, w)
|
1568
|
+
if dy <= 0.5 and cand < min:
|
1569
|
+
min = cand
|
1570
|
+
xmin = w.x
|
1571
|
+
ymin = w.y
|
1572
|
+
# /* check four corners */
|
1573
|
+
for l in range(2):
|
1574
|
+
for k in range(2):
|
1575
|
+
w = _Point(s.x - 0.5 + l, s.y - 0.5 + k)
|
1576
|
+
cand = quadform(Q, w)
|
1577
|
+
if cand < min:
|
1578
|
+
min = cand
|
1579
|
+
xmin = w.x
|
1580
|
+
ymin = w.y
|
1581
|
+
pp._curve[i].vertex.x = xmin + x0
|
1582
|
+
pp._curve[i].vertex.y = ymin + y0
|
1583
|
+
return 0
|
1584
|
+
|
1585
|
+
|
1586
|
+
"""
|
1587
|
+
/* ---------------------------------------------------------------------- */
|
1588
|
+
/* Stage 4: smoothing and corner analysis (Sec. 2.3.3) */
|
1589
|
+
"""
|
1590
|
+
|
1591
|
+
|
1592
|
+
def reverse(curve: _Curve) -> None:
|
1593
|
+
"""/* reverse orientation of a path */"""
|
1594
|
+
m = curve.n
|
1595
|
+
i = 0
|
1596
|
+
j = m - 1
|
1597
|
+
while i < j:
|
1598
|
+
tmp = curve[i].vertex
|
1599
|
+
curve[i].vertex = curve[j].vertex
|
1600
|
+
curve[j].vertex = tmp
|
1601
|
+
i += 1
|
1602
|
+
j -= 1
|
1603
|
+
|
1604
|
+
|
1605
|
+
# /* Always succeeds */
|
1606
|
+
|
1607
|
+
|
1608
|
+
def _smooth(curve: _Curve, alphamax: float) -> None:
|
1609
|
+
m = curve.n
|
1610
|
+
|
1611
|
+
# /* examine each vertex and find its best fit */
|
1612
|
+
for i in range(m):
|
1613
|
+
j = mod(i + 1, m)
|
1614
|
+
k = mod(i + 2, m)
|
1615
|
+
p4 = interval(1 / 2.0, curve[k].vertex, curve[j].vertex)
|
1616
|
+
|
1617
|
+
denom = ddenom(curve[i].vertex, curve[k].vertex)
|
1618
|
+
if denom != 0.0:
|
1619
|
+
dd = dpara(curve[i].vertex, curve[j].vertex, curve[k].vertex) / denom
|
1620
|
+
dd = math.fabs(dd)
|
1621
|
+
alpha = (1 - 1.0 / dd) if dd > 1 else 0
|
1622
|
+
alpha = alpha / 0.75
|
1623
|
+
else:
|
1624
|
+
alpha = 4 / 3.0
|
1625
|
+
curve[j].alpha0 = alpha # /* remember "original" value of alpha */
|
1626
|
+
|
1627
|
+
if alpha >= alphamax: # /* pointed corner */
|
1628
|
+
curve[j].tag = POTRACE_CORNER
|
1629
|
+
curve[j].c[1] = curve[j].vertex
|
1630
|
+
curve[j].c[2] = p4
|
1631
|
+
else:
|
1632
|
+
if alpha < 0.55:
|
1633
|
+
alpha = 0.55
|
1634
|
+
elif alpha > 1:
|
1635
|
+
alpha = 1
|
1636
|
+
p2 = interval(0.5 + 0.5 * alpha, curve[i].vertex, curve[j].vertex)
|
1637
|
+
p3 = interval(0.5 + 0.5 * alpha, curve[k].vertex, curve[j].vertex)
|
1638
|
+
curve[j].tag = POTRACE_CURVETO
|
1639
|
+
curve[j].c[0] = p2
|
1640
|
+
curve[j].c[1] = p3
|
1641
|
+
curve[j].c[2] = p4
|
1642
|
+
curve[j].alpha = alpha # /* store the "cropped" value of alpha */
|
1643
|
+
curve[j].beta = 0.5
|
1644
|
+
curve.alphacurve = True
|
1645
|
+
|
1646
|
+
|
1647
|
+
"""
|
1648
|
+
/* ---------------------------------------------------------------------- */
|
1649
|
+
/* Stage 5: Curve optimization (Sec. 2.4) */
|
1650
|
+
"""
|
1651
|
+
|
1652
|
+
|
1653
|
+
class opti_t:
|
1654
|
+
def __init__(self):
|
1655
|
+
self.pen = 0 # /* penalty */
|
1656
|
+
self.c = [_Point(0, 0), _Point(0, 0)] # /* curve parameters */
|
1657
|
+
self.t = 0 # /* curve parameters */
|
1658
|
+
self.s = 0 # /* curve parameters */
|
1659
|
+
self.alpha = 0 # /* curve parameter */
|
1660
|
+
|
1661
|
+
|
1662
|
+
def opti_penalty(
|
1663
|
+
pp: _Path,
|
1664
|
+
i: int,
|
1665
|
+
j: int,
|
1666
|
+
res: opti_t,
|
1667
|
+
opttolerance: float,
|
1668
|
+
convc: int,
|
1669
|
+
areac: float,
|
1670
|
+
) -> int:
|
1671
|
+
"""
|
1672
|
+
/* calculate best fit from i+.5 to j+.5. Assume i<j (cyclically).
|
1673
|
+
Return 0 and set badness and parameters (alpha, beta), if
|
1674
|
+
possible. Return 1 if impossible. */
|
1675
|
+
"""
|
1676
|
+
|
1677
|
+
m = pp._curve.n
|
1678
|
+
|
1679
|
+
# /* check convexity, corner-freeness, and maximum bend < 179 degrees */
|
1680
|
+
|
1681
|
+
if i == j: # sanity - a full loop can never be an opticurve
|
1682
|
+
return 1
|
1683
|
+
|
1684
|
+
k = i
|
1685
|
+
i1 = mod(i + 1, m)
|
1686
|
+
k1 = mod(k + 1, m)
|
1687
|
+
conv = convc[k1]
|
1688
|
+
if conv == 0:
|
1689
|
+
return 1
|
1690
|
+
d = ddist(pp._curve[i].vertex, pp._curve[i1].vertex)
|
1691
|
+
k = k1
|
1692
|
+
while k != j:
|
1693
|
+
k1 = mod(k + 1, m)
|
1694
|
+
k2 = mod(k + 2, m)
|
1695
|
+
if convc[k1] != conv:
|
1696
|
+
return 1
|
1697
|
+
if (
|
1698
|
+
sign(
|
1699
|
+
cprod(
|
1700
|
+
pp._curve[i].vertex,
|
1701
|
+
pp._curve[i1].vertex,
|
1702
|
+
pp._curve[k1].vertex,
|
1703
|
+
pp._curve[k2].vertex,
|
1704
|
+
)
|
1705
|
+
)
|
1706
|
+
!= conv
|
1707
|
+
):
|
1708
|
+
return 1
|
1709
|
+
if (
|
1710
|
+
iprod1(
|
1711
|
+
pp._curve[i].vertex,
|
1712
|
+
pp._curve[i1].vertex,
|
1713
|
+
pp._curve[k1].vertex,
|
1714
|
+
pp._curve[k2].vertex,
|
1715
|
+
)
|
1716
|
+
< d * ddist(pp._curve[k1].vertex, pp._curve[k2].vertex) * COS179
|
1717
|
+
):
|
1718
|
+
return 1
|
1719
|
+
k = k1
|
1720
|
+
|
1721
|
+
# /* the curve we're working in: */
|
1722
|
+
p0 = pp._curve[mod(i, m)].c[2]
|
1723
|
+
p1 = pp._curve[mod(i + 1, m)].vertex
|
1724
|
+
p2 = pp._curve[mod(j, m)].vertex
|
1725
|
+
p3 = pp._curve[mod(j, m)].c[2]
|
1726
|
+
|
1727
|
+
# /* determine its area */
|
1728
|
+
area = areac[j] - areac[i]
|
1729
|
+
area -= dpara(pp._curve[0].vertex, pp._curve[i].c[2], pp._curve[j].c[2]) / 2
|
1730
|
+
if i >= j:
|
1731
|
+
area += areac[m]
|
1732
|
+
|
1733
|
+
# /* find intersection o of p0p1 and p2p3. Let t,s such that
|
1734
|
+
# o =interval(t,p0,p1) = interval(s,p3,p2). Let A be the area of the
|
1735
|
+
# triangle (p0,o,p3). */
|
1736
|
+
|
1737
|
+
A1 = dpara(p0, p1, p2)
|
1738
|
+
A2 = dpara(p0, p1, p3)
|
1739
|
+
A3 = dpara(p0, p2, p3)
|
1740
|
+
# /* A4 = dpara(p1, p2, p3); */
|
1741
|
+
A4 = A1 + A3 - A2
|
1742
|
+
|
1743
|
+
if A2 == A1: # this should never happen
|
1744
|
+
return 1
|
1745
|
+
|
1746
|
+
t = A3 / (A3 - A4)
|
1747
|
+
s = A2 / (A2 - A1)
|
1748
|
+
A = A2 * t / 2.0
|
1749
|
+
|
1750
|
+
if A == 0.0: # this should never happen
|
1751
|
+
return 1
|
1752
|
+
|
1753
|
+
R = area / A # /* relative area */
|
1754
|
+
alpha = 2 - math.sqrt(4 - R / 0.3) # /* overall alpha for p0-o-p3 curve */
|
1755
|
+
|
1756
|
+
res.c[0] = interval(t * alpha, p0, p1)
|
1757
|
+
res.c[1] = interval(s * alpha, p3, p2)
|
1758
|
+
res.alpha = alpha
|
1759
|
+
res.t = t
|
1760
|
+
res.s = s
|
1761
|
+
|
1762
|
+
p1 = res.c[0]
|
1763
|
+
p1 = _Point(p1.x, p1.y)
|
1764
|
+
p2 = res.c[1] # /* the proposed curve is now (p0,p1,p2,p3) */
|
1765
|
+
p2 = _Point(p2.x, p2.y)
|
1766
|
+
|
1767
|
+
res.pen = 0
|
1768
|
+
|
1769
|
+
# /* calculate penalty */
|
1770
|
+
# /* check tangency with edges */
|
1771
|
+
k = mod(i + 1, m)
|
1772
|
+
while k != j:
|
1773
|
+
k1 = mod(k + 1, m)
|
1774
|
+
t = tangent(p0, p1, p2, p3, pp._curve[k].vertex, pp._curve[k1].vertex)
|
1775
|
+
if t < -0.5:
|
1776
|
+
return 1
|
1777
|
+
pt = bezier(t, p0, p1, p2, p3)
|
1778
|
+
d = ddist(pp._curve[k].vertex, pp._curve[k1].vertex)
|
1779
|
+
if d == 0.0: # /* this should never happen */
|
1780
|
+
return 1
|
1781
|
+
d1 = dpara(pp._curve[k].vertex, pp._curve[k1].vertex, pt) / d
|
1782
|
+
if math.fabs(d1) > opttolerance:
|
1783
|
+
return 1
|
1784
|
+
if (
|
1785
|
+
iprod(pp._curve[k].vertex, pp._curve[k1].vertex, pt) < 0
|
1786
|
+
or iprod(pp._curve[k1].vertex, pp._curve[k].vertex, pt) < 0
|
1787
|
+
):
|
1788
|
+
return 1
|
1789
|
+
res.pen += sq(d1)
|
1790
|
+
k = k1
|
1791
|
+
|
1792
|
+
# /* check corners */
|
1793
|
+
k = i
|
1794
|
+
while k != j:
|
1795
|
+
k1 = mod(k + 1, m)
|
1796
|
+
t = tangent(p0, p1, p2, p3, pp._curve[k].c[2], pp._curve[k1].c[2])
|
1797
|
+
if t < -0.5:
|
1798
|
+
return 1
|
1799
|
+
pt = bezier(t, p0, p1, p2, p3)
|
1800
|
+
d = ddist(pp._curve[k].c[2], pp._curve[k1].c[2])
|
1801
|
+
if d == 0.0: # /* this should never happen */
|
1802
|
+
return 1
|
1803
|
+
d1 = dpara(pp._curve[k].c[2], pp._curve[k1].c[2], pt) / d
|
1804
|
+
d2 = dpara(pp._curve[k].c[2], pp._curve[k1].c[2], pp._curve[k1].vertex) / d
|
1805
|
+
d2 *= 0.75 * pp._curve[k1].alpha
|
1806
|
+
if d2 < 0:
|
1807
|
+
d1 = -d1
|
1808
|
+
d2 = -d2
|
1809
|
+
if d1 < d2 - opttolerance:
|
1810
|
+
return 1
|
1811
|
+
if d1 < d2:
|
1812
|
+
res.pen += sq(d1 - d2)
|
1813
|
+
k = k1
|
1814
|
+
return 0
|
1815
|
+
|
1816
|
+
|
1817
|
+
def _opticurve(pp: _Path, opttolerance: float) -> int:
|
1818
|
+
"""
|
1819
|
+
optimize the path p, replacing sequences of Bezier segments by a
|
1820
|
+
single segment when possible. Return 0 on success, 1 with errno set
|
1821
|
+
on failure.
|
1822
|
+
"""
|
1823
|
+
m = pp._curve.n
|
1824
|
+
pt = [0] * (m + 1) # /* pt[m+1] */
|
1825
|
+
pen = [0.0] * (m + 1) # /* pen[m+1] */
|
1826
|
+
len = [0] * (m + 1) # /* len[m+1] */
|
1827
|
+
opt = [None] * (m + 1) # /* opt[m+1] */
|
1828
|
+
|
1829
|
+
convc = [0.0] * m # /* conv[m]: pre-computed convexities */
|
1830
|
+
areac = [0.0] * (m + 1) # /* cumarea[m+1]: cache for fast area computation */
|
1831
|
+
|
1832
|
+
# /* pre-calculate convexity: +1 = right turn, -1 = left turn, 0 = corner */
|
1833
|
+
for i in range(m):
|
1834
|
+
if pp._curve[i].tag == POTRACE_CURVETO:
|
1835
|
+
convc[i] = sign(
|
1836
|
+
dpara(
|
1837
|
+
pp._curve[mod(i - 1, m)].vertex,
|
1838
|
+
pp._curve[i].vertex,
|
1839
|
+
pp._curve[mod(i + 1, m)].vertex,
|
1840
|
+
)
|
1841
|
+
)
|
1842
|
+
else:
|
1843
|
+
convc[i] = 0
|
1844
|
+
|
1845
|
+
# /* pre-calculate areas */
|
1846
|
+
area = 0.0
|
1847
|
+
areac[0] = 0.0
|
1848
|
+
p0 = pp._curve[0].vertex
|
1849
|
+
for i in range(m):
|
1850
|
+
i1 = mod(i + 1, m)
|
1851
|
+
if pp._curve[i1].tag == POTRACE_CURVETO:
|
1852
|
+
alpha = pp._curve[i1].alpha
|
1853
|
+
area += (
|
1854
|
+
0.3
|
1855
|
+
* alpha
|
1856
|
+
* (4 - alpha)
|
1857
|
+
* dpara(pp._curve[i].c[2], pp._curve[i1].vertex, pp._curve[i1].c[2])
|
1858
|
+
/ 2
|
1859
|
+
)
|
1860
|
+
area += dpara(p0, pp._curve[i].c[2], pp._curve[i1].c[2]) / 2
|
1861
|
+
areac[i + 1] = area
|
1862
|
+
pt[0] = -1
|
1863
|
+
pen[0] = 0
|
1864
|
+
len[0] = 0
|
1865
|
+
|
1866
|
+
# /* Fixme: we always start from a fixed point
|
1867
|
+
# -- should find the best curve cyclically */
|
1868
|
+
|
1869
|
+
o = None
|
1870
|
+
for j in range(1, m + 1):
|
1871
|
+
# /* calculate best path from 0 to j */
|
1872
|
+
pt[j] = j - 1
|
1873
|
+
pen[j] = pen[j - 1]
|
1874
|
+
len[j] = len[j - 1] + 1
|
1875
|
+
for i in range(j - 2, -1, -1):
|
1876
|
+
if o is None:
|
1877
|
+
o = opti_t()
|
1878
|
+
if opti_penalty(pp, i, mod(j, m), o, opttolerance, convc, areac):
|
1879
|
+
break
|
1880
|
+
if len[j] > len[i] + 1 or (
|
1881
|
+
len[j] == len[i] + 1 and pen[j] > pen[i] + o.pen
|
1882
|
+
):
|
1883
|
+
opt[j] = o
|
1884
|
+
pt[j] = i
|
1885
|
+
pen[j] = pen[i] + o.pen
|
1886
|
+
len[j] = len[i] + 1
|
1887
|
+
o = None
|
1888
|
+
om = len[m]
|
1889
|
+
pp._ocurve = _Curve(om)
|
1890
|
+
s = [None] * om
|
1891
|
+
t = [None] * om
|
1892
|
+
|
1893
|
+
j = m
|
1894
|
+
for i in range(om - 1, -1, -1):
|
1895
|
+
if pt[j] == j - 1:
|
1896
|
+
pp._ocurve[i].tag = pp._curve[mod(j, m)].tag
|
1897
|
+
pp._ocurve[i].c[0] = pp._curve[mod(j, m)].c[0]
|
1898
|
+
pp._ocurve[i].c[1] = pp._curve[mod(j, m)].c[1]
|
1899
|
+
pp._ocurve[i].c[2] = pp._curve[mod(j, m)].c[2]
|
1900
|
+
pp._ocurve[i].vertex = pp._curve[mod(j, m)].vertex
|
1901
|
+
pp._ocurve[i].alpha = pp._curve[mod(j, m)].alpha
|
1902
|
+
pp._ocurve[i].alpha0 = pp._curve[mod(j, m)].alpha0
|
1903
|
+
pp._ocurve[i].beta = pp._curve[mod(j, m)].beta
|
1904
|
+
s[i] = t[i] = 1.0
|
1905
|
+
else:
|
1906
|
+
pp._ocurve[i].tag = POTRACE_CURVETO
|
1907
|
+
pp._ocurve[i].c[0] = opt[j].c[0]
|
1908
|
+
pp._ocurve[i].c[1] = opt[j].c[1]
|
1909
|
+
pp._ocurve[i].c[2] = pp._curve[mod(j, m)].c[2]
|
1910
|
+
pp._ocurve[i].vertex = interval(
|
1911
|
+
opt[j].s, pp._curve[mod(j, m)].c[2], pp._curve[mod(j, m)].vertex
|
1912
|
+
)
|
1913
|
+
pp._ocurve[i].alpha = opt[j].alpha
|
1914
|
+
pp._ocurve[i].alpha0 = opt[j].alpha
|
1915
|
+
s[i] = opt[j].s
|
1916
|
+
t[i] = opt[j].t
|
1917
|
+
j = pt[j]
|
1918
|
+
|
1919
|
+
# /* calculate beta parameters */
|
1920
|
+
for i in range(om):
|
1921
|
+
i1 = mod(i + 1, om)
|
1922
|
+
pp._ocurve[i].beta = s[i] / (s[i] + t[i1])
|
1923
|
+
pp._ocurve.alphacurve = True
|
1924
|
+
return 0
|
1925
|
+
|
1926
|
+
|
1927
|
+
# /* ---------------------------------------------------------------------- */
|
1928
|
+
|
1929
|
+
|
1930
|
+
def process_path(
|
1931
|
+
plist: list,
|
1932
|
+
alphamax=1.0,
|
1933
|
+
opticurve=True,
|
1934
|
+
opttolerance=0.2,
|
1935
|
+
) -> int:
|
1936
|
+
"""/* return 0 on success, 1 on error with errno set. */"""
|
1937
|
+
|
1938
|
+
def TRY(x):
|
1939
|
+
if x:
|
1940
|
+
raise ValueError
|
1941
|
+
|
1942
|
+
# /* call downstream function with each path */
|
1943
|
+
for p in plist:
|
1944
|
+
TRY(_calc_sums(p))
|
1945
|
+
TRY(_calc_lon(p))
|
1946
|
+
TRY(_bestpolygon(p))
|
1947
|
+
TRY(_adjust_vertices(p))
|
1948
|
+
if not p.sign: # /* reverse orientation of negative paths */
|
1949
|
+
reverse(p._curve)
|
1950
|
+
_smooth(p._curve, alphamax)
|
1951
|
+
if opticurve:
|
1952
|
+
TRY(_opticurve(p, opttolerance))
|
1953
|
+
p._fcurve = p._ocurve
|
1954
|
+
else:
|
1955
|
+
p._fcurve = p._curve
|
1956
|
+
return 0
|
1957
|
+
|
1958
|
+
|
1959
|
+
# END TRACE SECTION.
|