svg-path-simplify 0.0.8 → 0.0.9
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.
- package/README.md +25 -5
- package/dist/svg-path-simplify.esm.js +576 -494
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +576 -494
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +576 -494
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +86 -29
- package/package.json +1 -1
- package/src/detect_input.js +17 -10
- package/src/index.js +3 -0
- package/src/pathData_simplify_cubic.js +113 -106
- package/src/pathData_simplify_cubic_extrapolate.js +25 -11
- package/src/pathSimplify-main.js +89 -182
- package/src/svgii/geometry_flatness.js +29 -36
- package/src/svgii/pathData_analyze.js +4 -0
- package/src/svgii/pathData_convert.js +26 -17
- package/src/svgii/pathData_interpolate.js +65 -0
- package/src/svgii/pathData_parse.js +25 -9
- package/src/svgii/pathData_parse_els.js +18 -12
- package/src/svgii/pathData_remove_collinear.js +31 -28
- package/src/svgii/pathData_remove_orphaned.js +5 -4
- package/src/svgii/pathData_remove_zerolength.js +8 -4
- package/src/svgii/pathData_reorder.js +6 -2
- package/src/svgii/pathData_simplify_refineCorners.js +160 -0
- package/src/svgii/{simplify_refineExtremes.js → pathData_simplify_refineExtremes.js} +78 -43
- package/src/svgii/pathData_split.js +42 -15
- package/src/svgii/pathData_stringify.js +3 -12
- package/src/svgii/rounding.js +16 -14
- package/src/svgii/svg_cleanup.js +1 -1
- package/src/pathData_simplify_cubic_arr.js +0 -50
- package/src/svgii/simplify.js +0 -248
- package/src/svgii/simplify_bezier.js +0 -470
- package/src/svgii/simplify_linetos.js +0 -93
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import { pathDataArcsToCubics, pathDataQuadraticToCubic, quadratic2Cubic, pathDataToRelative, pathDataToAbsolute, pathDataToLonghands, pathDataToShorthands, pathDataToQuadratic, cubicToQuad, arcToBezier, pathDataToVerbose, convertArrayPathData, revertPathDataToArray, cubicToArc } from './pathData_convert.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { getAngle, bezierhasExtreme, getDistance, getSquareDistance, pointAtT, checkLineIntersection, interpolate, getPointOnEllipse, commandIsFlat, getPathDataVertices } from "./geometry";
|
|
5
|
-
|
|
6
|
-
import { getPathArea, getPolygonArea, getRelativeAreaDiff, getBezierAreaAccuracy } from "./geometry_area.js";
|
|
7
|
-
|
|
8
|
-
import { renderPoint } from './visualize.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Function to simplify cubic Bézier sequences
|
|
14
|
-
* thresh defines a threshold based on the
|
|
15
|
-
* segment size
|
|
16
|
-
* tolerance describes a percentage based deviation
|
|
17
|
-
* comparing unoptimized against combined segment areas
|
|
18
|
-
*/
|
|
19
|
-
export function simplifyBezierSequence(chunk, tolerance = 7.5, keepDetails = true, forceCubic = false) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//console.log('forceCubic simplifyBezierSequence', forceCubic);
|
|
23
|
-
//tolerance = 20
|
|
24
|
-
|
|
25
|
-
// t value for control point extrapolation
|
|
26
|
-
const t = 1.333;
|
|
27
|
-
|
|
28
|
-
// collect simplified path data commands
|
|
29
|
-
let simplified = [];
|
|
30
|
-
let Clen = chunk.length;
|
|
31
|
-
let { type, p0, cp1, cp2 = null, p, values } = chunk[0];
|
|
32
|
-
|
|
33
|
-
// get original chunk area for error detection
|
|
34
|
-
let pathDataChunk = [{ type: 'M', values: [p0.x, p0.y] }, ...chunk];
|
|
35
|
-
|
|
36
|
-
// unoptimized area
|
|
37
|
-
let area0 = getPathArea(pathDataChunk);
|
|
38
|
-
|
|
39
|
-
let p0_1, cp1_1, cp2_1, p_1;
|
|
40
|
-
let cp1_2, cp2_2, p_2;
|
|
41
|
-
let areaDiff, cp1_cubic, cp2_cubic;
|
|
42
|
-
|
|
43
|
-
let indexEnd = chunk.length - 1;
|
|
44
|
-
let indexMid = chunk.length > 2 ? Math.ceil(chunk.length / 2) - 1 : 1
|
|
45
|
-
|
|
46
|
-
// compare accuracy
|
|
47
|
-
let accurate = false;
|
|
48
|
-
let areaDiff1 = 10000
|
|
49
|
-
let areaDiff2 = 10000
|
|
50
|
-
let areaDiff3 = 10000
|
|
51
|
-
let comAreaDiff1, comAreaDiff2, comAreaDiff3;
|
|
52
|
-
let ptMid, cp1_cubic_1, cp2_cubic_1, cp1_cubic_2, cp2_cubic_2, cp1_cubic_3, cp2_cubic_3;
|
|
53
|
-
let areaCptPoly
|
|
54
|
-
|
|
55
|
-
let log = []
|
|
56
|
-
let c_3_1 = 0, c_3_2 = 0, c_2_1 = 0, c_2_2 = 0;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* try to replace single cubics
|
|
61
|
-
* with quadratic commands
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
//forceCubic = true;
|
|
65
|
-
//if (area0 < 0.01) forceCubic = true;
|
|
66
|
-
|
|
67
|
-
if (!forceCubic && Clen === 1 && type === 'C') {
|
|
68
|
-
|
|
69
|
-
let thresh = area0 * 4;
|
|
70
|
-
//check flatness
|
|
71
|
-
let flatness = commandIsFlat([p0, cp1, cp2, p])
|
|
72
|
-
areaCptPoly = flatness.area
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (flatness.flat) {
|
|
76
|
-
console.log('is flat cubic!');
|
|
77
|
-
console.log(flatness, 'thresh');
|
|
78
|
-
|
|
79
|
-
simplified = [{ type: 'L', values: [p.x, p.y] }];
|
|
80
|
-
log.push('cubic to quadratic')
|
|
81
|
-
return simplified
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// quadratic controlpoint
|
|
86
|
-
let cpQ = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
87
|
-
simplified = [chunk[0]];
|
|
88
|
-
|
|
89
|
-
if (cpQ) {
|
|
90
|
-
comAreaDiff1 = getBezierAreaAccuracy([p0, cpQ, p], area0, areaCptPoly, tolerance).areaDiff
|
|
91
|
-
|
|
92
|
-
// can be converted to quadratic
|
|
93
|
-
if (comAreaDiff1 < tolerance) {
|
|
94
|
-
simplified = [{ type: 'Q', values: [cpQ.x, cpQ.y, p.x, p.y] }];
|
|
95
|
-
log.push('cubic to quadratic')
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return simplified
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (Clen > 1) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
//console.log(Clen, area0);
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* normalize quadratic
|
|
109
|
-
* to cubics
|
|
110
|
-
*/
|
|
111
|
-
|
|
112
|
-
// convert quadratic to cubic
|
|
113
|
-
|
|
114
|
-
if (type === 'Q') {
|
|
115
|
-
chunk.forEach((com, i) => {
|
|
116
|
-
let c1 = quadratic2Cubic(com.p0, com.values);
|
|
117
|
-
|
|
118
|
-
//console.log('com Q', com, c1);
|
|
119
|
-
//let dQ = `M ${com.p0.x} ${com.p0.y} Q ${com.values.join(' ')}`
|
|
120
|
-
|
|
121
|
-
let cp1 = { x: c1.values[0], y: c1.values[1] };
|
|
122
|
-
let cp2 = { x: c1.values[2], y: c1.values[3] };
|
|
123
|
-
|
|
124
|
-
//chunk[i] = {type:'C', values:[cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, com.values[2], com.values[3]]};
|
|
125
|
-
chunk[i].type = 'C'
|
|
126
|
-
chunk[i].cp1 = cp1;
|
|
127
|
-
chunk[i].cp2 = cp2;
|
|
128
|
-
chunk[i].values = [cp1.x, cp1.y, cp2.x, cp2.y, com.p.x, com.p.y];
|
|
129
|
-
|
|
130
|
-
//let d = `M ${com.p0.x} ${com.p0.y} C ${[cp1.x, cp1.y, cp2.x, cp2.y, com.p.x, com.p.y].join(' ')}`
|
|
131
|
-
//renderPoint(svg1, p, 'orange')
|
|
132
|
-
//console.log('dQ', dQ+d);
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
type = 'C';
|
|
136
|
-
//console.log('chunk Q', chunk);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
p0_1 = chunk[1].p0;
|
|
141
|
-
cp1_1 = chunk[1].cp1;
|
|
142
|
-
cp2_1 = type === 'C' ? chunk[1].cp2 : null;
|
|
143
|
-
p_1 = chunk[1].p;
|
|
144
|
-
|
|
145
|
-
//get end points
|
|
146
|
-
p_2 = chunk[indexEnd].p;
|
|
147
|
-
cp1_2 = chunk[indexEnd].cp1;
|
|
148
|
-
cp2_2 = type === 'C' ? chunk[indexEnd].cp2 : chunk[indexEnd].cp1;
|
|
149
|
-
|
|
150
|
-
areaCptPoly = getPolygonArea([p0, cp1, cp2_1, p_2])
|
|
151
|
-
//console.log('cp2_2',p0_1, p_1, cp2_2, cp1_2, p_2, areaCptPoly);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* check flatness of chunk
|
|
156
|
-
* beziers might be linots
|
|
157
|
-
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//console.log('forceCubic', forceCubic);
|
|
161
|
-
|
|
162
|
-
//forceCubic= true
|
|
163
|
-
if (!forceCubic) {
|
|
164
|
-
|
|
165
|
-
//renderPoint(svg1, p, 'cyan' )
|
|
166
|
-
|
|
167
|
-
let chunkPoints = chunk.map(com => { return [com.p0, com.cp1, com.cp2, com.p] }).flat()
|
|
168
|
-
let { flat, ratio } = commandIsFlat(chunkPoints)
|
|
169
|
-
//console.log('chunkPoints', chunkPoints, flat);
|
|
170
|
-
|
|
171
|
-
/*
|
|
172
|
-
if (flat) {
|
|
173
|
-
let last = chunkPoints.slice(-1)[0]
|
|
174
|
-
simplified.push({ type: 'L', values: [last.x, last.y] });
|
|
175
|
-
//console.log('chunk flat', simplified, last);
|
|
176
|
-
log.push('all commands are flat')
|
|
177
|
-
return simplified
|
|
178
|
-
}else{
|
|
179
|
-
}
|
|
180
|
-
*/
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// 3 or more subsequent bezier segments
|
|
188
|
-
if (Clen > 2) {
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Cubics to Arcs:
|
|
192
|
-
* educated guess -
|
|
193
|
-
* check if control points build a right angle
|
|
194
|
-
*/
|
|
195
|
-
let { com, isArc, area } = cubicToArc(p0, cp1, cp2_2, p_2);
|
|
196
|
-
areaDiff = getRelativeAreaDiff(area0, area)
|
|
197
|
-
//console.log('flat', chunkPoints, flatness);
|
|
198
|
-
//renderPoint(svg1, p, 'cyan')
|
|
199
|
-
|
|
200
|
-
// arc approximations should be more precise - otherwise we prefer cubics
|
|
201
|
-
if (isArc && areaDiff < tolerance * 0.75) {
|
|
202
|
-
simplified = [com];
|
|
203
|
-
//renderPoint(svg1, p, 'orange')
|
|
204
|
-
log.push('cubic to arc')
|
|
205
|
-
return simplified
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* more than 2 segments
|
|
211
|
-
* try to interpolate tangents from
|
|
212
|
-
* mid control point tangents
|
|
213
|
-
*/
|
|
214
|
-
|
|
215
|
-
// get mid segment and get tangent intersection
|
|
216
|
-
let p_m = chunk[indexMid].p;
|
|
217
|
-
//console.log('indexMid', indexMid, chunk.length);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// get mit segments cps
|
|
221
|
-
let cpMid_1 = type === 'C' ? chunk[indexMid].cp2 : chunk[indexMid].cp1;
|
|
222
|
-
let cp1_Int = checkLineIntersection(p_m, cpMid_1, p0, cp1, false);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (cp1_Int) {
|
|
226
|
-
let cp2_Int = checkLineIntersection(p_m, cpMid_1, p_2, cp2_2, false);
|
|
227
|
-
cp1_cubic = cp1_Int;
|
|
228
|
-
cp2_cubic = cp2_Int;
|
|
229
|
-
|
|
230
|
-
//renderPoint(svg1, cpMid_1, 'orange')
|
|
231
|
-
//renderPoint(svg1, cp1_cubic, 'magenta')
|
|
232
|
-
//renderPoint(svg1, cp2_cubic, 'cyan')
|
|
233
|
-
|
|
234
|
-
// extrapolate control points
|
|
235
|
-
cp1_cubic_1 = pointAtT([p0, cp1_cubic], t);
|
|
236
|
-
cp2_cubic_1 = pointAtT([p_2, cp2_cubic], t);
|
|
237
|
-
|
|
238
|
-
// test accuracy
|
|
239
|
-
comAreaDiff1 = getBezierAreaAccuracy([p0, cp1_cubic_1, cp2_cubic_1, p_2], area0, areaCptPoly, tolerance);
|
|
240
|
-
accurate = comAreaDiff1.accurate;
|
|
241
|
-
areaDiff1 = comAreaDiff1.areaDiff
|
|
242
|
-
areaDiff = areaDiff1;
|
|
243
|
-
|
|
244
|
-
//console.log('3.1: ', areaDiff1);
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* 2nd try
|
|
251
|
-
* odd - calculate interpolated mid tangents
|
|
252
|
-
*/
|
|
253
|
-
if (!accurate) {
|
|
254
|
-
let controlPoints = type === 'C' ? [p0_1, cp1_1, cp2_1, p_1] : [p0_1, cp1_1, p_1];
|
|
255
|
-
|
|
256
|
-
// interpolate mid point in mid segment and get cpts
|
|
257
|
-
ptMid = pointAtT(controlPoints, 0.5, true, true);
|
|
258
|
-
|
|
259
|
-
let cp1_mid = type === 'C' ? ptMid.cpts[2] : ptMid.cpts[0];
|
|
260
|
-
cp1_cubic_2 = checkLineIntersection(ptMid, cp1_mid, cp1, p0, false);
|
|
261
|
-
cp2_cubic_2 = checkLineIntersection(ptMid, cp1_mid, cp2_2, p_2, false);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// extrapolate control points
|
|
265
|
-
cp1_cubic_2 = pointAtT([p0, cp1_cubic_2], t);
|
|
266
|
-
cp2_cubic_2 = pointAtT([p_2, cp2_cubic_2], t);
|
|
267
|
-
|
|
268
|
-
// test accuracy
|
|
269
|
-
comAreaDiff2 = getBezierAreaAccuracy([p0, cp1_cubic_2, cp2_cubic_2, p_2], area0, areaCptPoly, tolerance);
|
|
270
|
-
accurate = comAreaDiff2.accurate;
|
|
271
|
-
areaDiff2 = comAreaDiff2.areaDiff
|
|
272
|
-
//console.log('3.2: ', areaDiff2);
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// final
|
|
277
|
-
cp1_cubic = areaDiff1 < areaDiff2 ? cp1_cubic_1 : cp1_cubic_2
|
|
278
|
-
cp2_cubic = areaDiff1 < areaDiff2 ? cp2_cubic_1 : cp2_cubic_2
|
|
279
|
-
areaDiff = areaDiff1 < areaDiff2 ? areaDiff1 : areaDiff2
|
|
280
|
-
log.push(areaDiff1 < areaDiff2 ? '3.1 is better' : '3.2 is better')
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (areaDiff < tolerance) {
|
|
284
|
-
//renderPoint(svg1, p, 'magenta')
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// combine 2 cubic segments
|
|
291
|
-
else if (Clen === 2) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
cp2_1 = chunk[0].cp2;
|
|
295
|
-
cp2_2 = chunk[1].cp2;
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Approach 1:
|
|
299
|
-
* get combined control points
|
|
300
|
-
* by extrapolating mid tangent intersection
|
|
301
|
-
*/
|
|
302
|
-
|
|
303
|
-
// Get cp intersection point
|
|
304
|
-
let cpI, cp1_cubicInter, cp2_cubicInter;
|
|
305
|
-
|
|
306
|
-
cpI = checkLineIntersection(p0, cp1, cp2_2, p_2, false);
|
|
307
|
-
if (cpI) {
|
|
308
|
-
//console.log('2 cubics:', p, cp2_1, p0, cpI);
|
|
309
|
-
cp1_cubicInter = checkLineIntersection(p, cp2_1, p0, cpI, false);
|
|
310
|
-
cp2_cubicInter = checkLineIntersection(p, cp1_2, p_2, cpI, false);
|
|
311
|
-
|
|
312
|
-
// extrapolate control points
|
|
313
|
-
cp1_cubic_1 = pointAtT([p0, cp1_cubicInter], t);
|
|
314
|
-
cp2_cubic_1 = pointAtT([p_2, cp2_cubicInter], t);
|
|
315
|
-
|
|
316
|
-
// get area to detect sign changes
|
|
317
|
-
comAreaDiff1 = getBezierAreaAccuracy([p0, cp1_cubic_1, cp2_cubic_1, p_2], area0, areaCptPoly, tolerance);
|
|
318
|
-
|
|
319
|
-
accurate = comAreaDiff1.accurate;
|
|
320
|
-
areaDiff1 = comAreaDiff1.areaDiff
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
//console.log('2.1: ', areaDiff1, cp1_cubic_1, cp2_cubic_1);
|
|
324
|
-
//renderPoint(svg1, cp1_cubic_1, 'cyan')
|
|
325
|
-
//renderPoint(svg1, cp2_cubic_1, 'orange')
|
|
326
|
-
//renderPoint(svg1, p, 'magenta')
|
|
327
|
-
|
|
328
|
-
if (comAreaDiff1 < tolerance) {
|
|
329
|
-
//renderPoint(svg1, p, 'blue')
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* If Approach 1 is too imprecise:
|
|
335
|
-
* Approach 2:
|
|
336
|
-
* add segments' cp tangents lengths for
|
|
337
|
-
* combined control points
|
|
338
|
-
*/
|
|
339
|
-
|
|
340
|
-
if (!accurate) {
|
|
341
|
-
|
|
342
|
-
// 1 distances between "tangent handles"
|
|
343
|
-
let t0Length = getDistance(p0, cp1_1);
|
|
344
|
-
let t1Length = getDistance(p_1, cp2_2);
|
|
345
|
-
|
|
346
|
-
// new average tangent length
|
|
347
|
-
let t2Length = t0Length + t1Length;
|
|
348
|
-
let tRat0 = t2Length / t0Length;
|
|
349
|
-
let tRat1 = t2Length / t1Length;
|
|
350
|
-
|
|
351
|
-
// extrapolate cp tangents
|
|
352
|
-
cp1_cubic_2 = pointAtT([p0, cp1_1], tRat0);
|
|
353
|
-
cp2_cubic_2 = pointAtT([p_1, cp2_2], tRat1);
|
|
354
|
-
|
|
355
|
-
// accuracy
|
|
356
|
-
comAreaDiff2 = getBezierAreaAccuracy([p0, cp1_cubic_2, cp2_cubic_2, p_2], area0, areaCptPoly, tolerance);
|
|
357
|
-
accurate = comAreaDiff2.accurate;
|
|
358
|
-
areaDiff2 = comAreaDiff2.areaDiff
|
|
359
|
-
|
|
360
|
-
// renderPoint(svg1, cp1_cubic_2, 'cyan')
|
|
361
|
-
// renderPoint(svg1, cp2_cubic_2, 'orange')
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* 3rd try
|
|
367
|
-
* take larger segment as reference
|
|
368
|
-
*/
|
|
369
|
-
|
|
370
|
-
if (!accurate) {
|
|
371
|
-
|
|
372
|
-
//[p0, cp1, cp2, p] = chunk[0];
|
|
373
|
-
//console.log('chunk[0]', chunk[0]);
|
|
374
|
-
|
|
375
|
-
cp1 = chunk[0].cp1
|
|
376
|
-
cp2 = chunk[0].cp2
|
|
377
|
-
p = chunk[0].p
|
|
378
|
-
|
|
379
|
-
let controlPoints = [p0, cp1, cp2, p]
|
|
380
|
-
|
|
381
|
-
// interpolate mid point in mid segment and get cpts
|
|
382
|
-
ptMid = pointAtT(controlPoints, 0.5, true, true);
|
|
383
|
-
|
|
384
|
-
let cp1_mid = type === 'C' ? ptMid.cpts[2] : ptMid.cpts[0];
|
|
385
|
-
cp1_cubic_3 = checkLineIntersection(ptMid, cp1_mid, cp1, p0, false);
|
|
386
|
-
cp2_cubic_3 = checkLineIntersection(ptMid, cp1_mid, cp2_2, p_2, false);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// extrapolate control points
|
|
390
|
-
cp1_cubic_3 = pointAtT([p0, cp1_cubic_3], t);
|
|
391
|
-
cp2_cubic_3 = pointAtT([p_2, cp2_cubic_3], t);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
// test accuracy
|
|
395
|
-
comAreaDiff2 = getBezierAreaAccuracy([p0, cp1_cubic_3, cp2_cubic_3, p_2], area0, areaCptPoly, tolerance);
|
|
396
|
-
accurate = comAreaDiff2.accurate;
|
|
397
|
-
areaDiff3 = comAreaDiff2.areaDiff
|
|
398
|
-
|
|
399
|
-
if (areaDiff3 < tolerance && areaDiff3 < areaDiff2) {
|
|
400
|
-
cp1_cubic_2 = cp1_cubic_3
|
|
401
|
-
cp2_cubic_2 = cp2_cubic_3
|
|
402
|
-
areaDiff2 = areaDiff3
|
|
403
|
-
//console.log('2.3');
|
|
404
|
-
log.push(areaDiff3 < areaDiff2 ? '2.3 is better' : '2.2 is better', areaDiff3, areaDiff2)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// final
|
|
410
|
-
cp1_cubic = areaDiff1 < areaDiff2 ? cp1_cubic_1 : cp1_cubic_2
|
|
411
|
-
cp2_cubic = areaDiff1 < areaDiff2 ? cp2_cubic_1 : cp2_cubic_2
|
|
412
|
-
areaDiff = areaDiff1 < areaDiff2 ? areaDiff1 : areaDiff2
|
|
413
|
-
|
|
414
|
-
//cp1_cubic = cp1_cubic_1
|
|
415
|
-
//cp2_cubic = cp2_cubic_1
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (areaDiff < tolerance) {
|
|
419
|
-
/*
|
|
420
|
-
let polyArea = getPolygonArea([p0,cp1_cubic, cp2_cubic, p_2 ])
|
|
421
|
-
|
|
422
|
-
//console.log('area0', area0, areaDiff, comAreaDiff2, comAreaDiff3, comAreaDiff1);
|
|
423
|
-
console.log('area0', area0, 'areaDiff', areaDiff, 'tolerance', tolerance, comAreaDiff1, comAreaDiff2, comAreaDiff3, 'polyArea', polyArea);
|
|
424
|
-
renderPoint(svg1, p, 'blue')
|
|
425
|
-
renderPoint(svg1, cp1_cubic, 'magenta')
|
|
426
|
-
renderPoint(svg1, cp2_cubic, 'orange')
|
|
427
|
-
*/
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
log.push(areaDiff1 < areaDiff2 ? '2.1 is better' : '2.2 is better', areaDiff1, areaDiff2)
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// no cpts - return original
|
|
437
|
-
if (!cp1_cubic || !cp2_cubic) {
|
|
438
|
-
//console.log('no cpts', [...chunk]);
|
|
439
|
-
return [...chunk];
|
|
440
|
-
//return [...chunk];
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// !!! CAN be simplified
|
|
445
|
-
if (areaDiff < tolerance) {
|
|
446
|
-
//console.log('!!! IS simplified!!!', area0, areaDiff, tolerance);
|
|
447
|
-
simplified.push({ type: 'C', values: [cp1_cubic.x, cp1_cubic.y, cp2_cubic.x, cp2_cubic.y, p_2.x, p_2.y] });
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// !!! no way to simplify
|
|
451
|
-
else {
|
|
452
|
-
//simplified = [...chunk];
|
|
453
|
-
simplified = chunk;
|
|
454
|
-
//console.log('not simplified!!!', areaDiff, 'area0:', area0, 'areaSimple', areaSimple, tolerance);
|
|
455
|
-
/*
|
|
456
|
-
let d = comSimple.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ')
|
|
457
|
-
let d0 = pathDataChunk.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ')
|
|
458
|
-
*/
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
let diffCom = pathDataChunk.length - simplified.length
|
|
463
|
-
log.push('saved:' + diffCom, 'results:' + [c_3_1, c_3_2, c_2_1, c_2_2].join(', '))
|
|
464
|
-
|
|
465
|
-
//console.log(log);
|
|
466
|
-
|
|
467
|
-
return simplified;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { getAngle, bezierhasExtreme, getPathDataVertices, svgArcToCenterParam, getSquareDistance, commandIsFlat } from "./geometry";
|
|
2
|
-
import { renderPoint } from "./visualize";
|
|
3
|
-
//import { renderPoint, renderPath } from "./visualize";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export function simplifyLinetoSequence(chunk, thresh = 0.1) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let valuesL = chunk[0].values.slice(-2).map(val => +val.toFixed(8))
|
|
12
|
-
let p0 = chunk[0].p0
|
|
13
|
-
//let p0 = { x: valuesL[0], y: valuesL[1] }
|
|
14
|
-
let p = p0;
|
|
15
|
-
let simplified = [];
|
|
16
|
-
|
|
17
|
-
//console.log('chunk lineto', chunk);
|
|
18
|
-
//renderPoint(svg1, p0, 'orange')
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
for (let i = 1, len = chunk.length; i < len; i++) {
|
|
22
|
-
let com = chunk[i - 1];
|
|
23
|
-
valuesL = com.values.slice(-2).map(val => +val.toFixed(8))
|
|
24
|
-
p = { x: valuesL[0], y: valuesL[1] }
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// zero length
|
|
28
|
-
if ((p.x === p0.x && p.y === p0.y)) {
|
|
29
|
-
console.log('zero length', com);
|
|
30
|
-
p0 = p
|
|
31
|
-
continue
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// check flatness
|
|
35
|
-
let comN = chunk[i];
|
|
36
|
-
let valuesNL = comN.values.slice(-2)
|
|
37
|
-
let pN = { x: valuesNL[0], y: valuesNL[1] }
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// check if adjacent linetos are flat
|
|
42
|
-
let flatness = commandIsFlat([p0, p, pN])
|
|
43
|
-
let isFlatN = flatness.flat;
|
|
44
|
-
|
|
45
|
-
//renderPoint(svg1, pN, 'blue', '0.5%')
|
|
46
|
-
|
|
47
|
-
/*
|
|
48
|
-
if (!isFlatN) {
|
|
49
|
-
renderPoint(svg1, p, 'orange', '0.75%')
|
|
50
|
-
console.log( flatness, thresh);
|
|
51
|
-
renderPoint(svg1, p0, 'cyan', '1%', '0.5')
|
|
52
|
-
renderPoint(svg1, pN, 'magenta', '0.5%')
|
|
53
|
-
}
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
// next lineto is flat – don't add command
|
|
57
|
-
if (isFlatN) {
|
|
58
|
-
|
|
59
|
-
// check angles
|
|
60
|
-
let ang1 = getAngle(p0, p, true)
|
|
61
|
-
let ang2 = getAngle(p, pN, true)
|
|
62
|
-
let angDiff = Math.abs(ang1 - ang2)
|
|
63
|
-
//*180/Math.PI
|
|
64
|
-
//console.log(angDiff, flatness, thresh);
|
|
65
|
-
|
|
66
|
-
if (angDiff < Math.PI / 4) {
|
|
67
|
-
//renderPoint(svg1, p0, 'cyan', '1%', '0.5')
|
|
68
|
-
//renderPoint(svg1, p, 'magenta', '0.5%')
|
|
69
|
-
//p0 = p
|
|
70
|
-
continue
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//console.log('flat', flatness, 'thresh', thresh, dist, p0, p);
|
|
76
|
-
// update p0
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
p0 = p
|
|
81
|
-
|
|
82
|
-
simplified.push(com)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// always add last command in chunk
|
|
87
|
-
simplified.push(chunk[chunk.length - 1])
|
|
88
|
-
|
|
89
|
-
//simplified.push(...chunk)
|
|
90
|
-
|
|
91
|
-
return simplified;
|
|
92
|
-
|
|
93
|
-
}
|