/src/libass/libass/ass_outline.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2016 Vabishchevich Nikolay <vabnick@gmail.com> |
3 | | * |
4 | | * This file is part of libass. |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include "config.h" |
20 | | #include "ass_compat.h" |
21 | | |
22 | | #include "ass_utils.h" |
23 | | #include "ass_outline.h" |
24 | | |
25 | | |
26 | | |
27 | | /* |
28 | | * \brief Initialize ASS_Outline to an empty state |
29 | | * Equivalent to zeroing of outline object and doesn't free any memory. |
30 | | */ |
31 | | void ass_outline_clear(ASS_Outline *outline) |
32 | 108k | { |
33 | 108k | outline->points = NULL; |
34 | 108k | outline->segments = NULL; |
35 | | |
36 | 108k | outline->n_points = outline->max_points = 0; |
37 | 108k | outline->n_segments = outline->max_segments = 0; |
38 | 108k | } |
39 | | |
40 | | /* |
41 | | * \brief Initialize ASS_Outline and allocate memory |
42 | | */ |
43 | | bool ass_outline_alloc(ASS_Outline *outline, size_t max_points, size_t max_segments) |
44 | 70.9k | { |
45 | 70.9k | assert(max_points && max_segments); |
46 | 70.9k | if (max_points > SIZE_MAX / sizeof(ASS_Vector)) { |
47 | 0 | ass_outline_clear(outline); |
48 | 0 | return false; |
49 | 0 | } |
50 | 70.9k | outline->points = malloc(sizeof(ASS_Vector) * max_points); |
51 | 70.9k | outline->segments = malloc(max_segments); |
52 | 70.9k | if (!outline->points || !outline->segments) { |
53 | 0 | ass_outline_free(outline); |
54 | 0 | return false; |
55 | 0 | } |
56 | | |
57 | 70.9k | outline->max_points = max_points; |
58 | 70.9k | outline->max_segments = max_segments; |
59 | 70.9k | outline->n_points = outline->n_segments = 0; |
60 | 70.9k | return true; |
61 | 70.9k | } |
62 | | |
63 | | /* |
64 | | * \brief Free previously initialized ASS_Outline |
65 | | * Outline state after the call is the same as after ass_outline_clear(). |
66 | | * Outline pointer can be NULL. |
67 | | */ |
68 | | void ass_outline_free(ASS_Outline *outline) |
69 | 94.5k | { |
70 | 94.5k | if (!outline) |
71 | 0 | return; |
72 | | |
73 | 94.5k | free(outline->points); |
74 | 94.5k | free(outline->segments); |
75 | | |
76 | 94.5k | ass_outline_clear(outline); |
77 | 94.5k | } |
78 | | |
79 | | |
80 | | static bool valid_point(const FT_Vector *pt) |
81 | 208k | { |
82 | 208k | return labs(pt->x) <= OUTLINE_MAX && labs(pt->y) <= OUTLINE_MAX; |
83 | 208k | } |
84 | | |
85 | | /* |
86 | | * \brief Convert FT_Ouline into ASS_Outline |
87 | | * Outline should be preallocated to a sufficient size. |
88 | | */ |
89 | | bool ass_outline_convert(ASS_Outline *outline, const FT_Outline *source) |
90 | 8.14k | { |
91 | 8.14k | enum Status { |
92 | 8.14k | S_ON, S_Q, S_C1, S_C2 |
93 | 8.14k | }; |
94 | | |
95 | 19.7k | for (int i = 0, j = 0; i < source->n_contours; i++) { |
96 | 11.5k | ASS_Vector pt; |
97 | 11.5k | int skip_last = 0; |
98 | 11.5k | enum Status st; |
99 | 11.5k | char seg; |
100 | | |
101 | 11.5k | int last = source->contours[i]; |
102 | 11.5k | if (j > last || last >= source->n_points) |
103 | 0 | return false; |
104 | | |
105 | | // skip degenerate 2-point contours from broken fonts |
106 | 11.5k | if (last - j < 2) { |
107 | 0 | j = last + 1; |
108 | 0 | continue; |
109 | 0 | } |
110 | | |
111 | 11.5k | if (!valid_point(source->points + j)) |
112 | 0 | return false; |
113 | 11.5k | switch (FT_CURVE_TAG(source->tags[j])) { |
114 | 9.87k | case FT_CURVE_TAG_ON: |
115 | 9.87k | st = S_ON; |
116 | 9.87k | break; |
117 | | |
118 | 1.70k | case FT_CURVE_TAG_CONIC: |
119 | 1.70k | if (!valid_point(source->points + last)) |
120 | 0 | return false; |
121 | 1.70k | pt.x = source->points[last].x; |
122 | 1.70k | pt.y = -source->points[last].y; |
123 | 1.70k | switch (FT_CURVE_TAG(source->tags[last])) { |
124 | 1.28k | case FT_CURVE_TAG_ON: |
125 | 1.28k | skip_last = 1; |
126 | 1.28k | last--; |
127 | 1.28k | break; |
128 | | |
129 | 421 | case FT_CURVE_TAG_CONIC: |
130 | 421 | pt.x = (pt.x + source->points[j].x) >> 1; |
131 | 421 | pt.y = (pt.y - source->points[j].y) >> 1; |
132 | 421 | break; |
133 | | |
134 | 0 | default: |
135 | 0 | return false; |
136 | 1.70k | } |
137 | 1.70k | assert(outline->n_points < outline->max_points); |
138 | 1.70k | outline->points[outline->n_points++] = pt; |
139 | 1.70k | st = S_Q; |
140 | 1.70k | break; |
141 | | |
142 | 0 | default: |
143 | 0 | return false; |
144 | 11.5k | } |
145 | 11.5k | pt.x = source->points[j].x; |
146 | 11.5k | pt.y = -source->points[j].y; |
147 | 11.5k | assert(outline->n_points < outline->max_points); |
148 | 11.5k | outline->points[outline->n_points++] = pt; |
149 | | |
150 | 206k | for (j++; j <= last; j++) { |
151 | 194k | if (!valid_point(source->points + j)) |
152 | 0 | return false; |
153 | 194k | switch (FT_CURVE_TAG(source->tags[j])) { |
154 | 121k | case FT_CURVE_TAG_ON: |
155 | 121k | switch (st) { |
156 | 63.4k | case S_ON: |
157 | 63.4k | seg = OUTLINE_LINE_SEGMENT; |
158 | 63.4k | break; |
159 | | |
160 | 58.2k | case S_Q: |
161 | 58.2k | seg = OUTLINE_QUADRATIC_SPLINE; |
162 | 58.2k | break; |
163 | | |
164 | 0 | case S_C2: |
165 | 0 | seg = OUTLINE_CUBIC_SPLINE; |
166 | 0 | break; |
167 | | |
168 | 0 | default: |
169 | 0 | return false; |
170 | 121k | } |
171 | 121k | assert(outline->n_segments < outline->max_segments); |
172 | 121k | outline->segments[outline->n_segments++] = seg; |
173 | 121k | st = S_ON; |
174 | 121k | break; |
175 | | |
176 | 73.0k | case FT_CURVE_TAG_CONIC: |
177 | 73.0k | switch (st) { |
178 | 61.0k | case S_ON: |
179 | 61.0k | st = S_Q; |
180 | 61.0k | break; |
181 | | |
182 | 11.9k | case S_Q: |
183 | 11.9k | assert(outline->n_segments < outline->max_segments); |
184 | 11.9k | outline->segments[outline->n_segments++] = OUTLINE_QUADRATIC_SPLINE; |
185 | 11.9k | pt.x = (pt.x + source->points[j].x) >> 1; |
186 | 11.9k | pt.y = (pt.y - source->points[j].y) >> 1; |
187 | 11.9k | assert(outline->n_points < outline->max_points); |
188 | 11.9k | outline->points[outline->n_points++] = pt; |
189 | 11.9k | break; |
190 | | |
191 | 0 | default: |
192 | 0 | return false; |
193 | 73.0k | } |
194 | 73.0k | break; |
195 | | |
196 | 73.0k | case FT_CURVE_TAG_CUBIC: |
197 | 0 | switch (st) { |
198 | 0 | case S_ON: |
199 | 0 | st = S_C1; |
200 | 0 | break; |
201 | | |
202 | 0 | case S_C1: |
203 | 0 | st = S_C2; |
204 | 0 | break; |
205 | | |
206 | 0 | default: |
207 | 0 | return false; |
208 | 0 | } |
209 | 0 | break; |
210 | | |
211 | 0 | default: |
212 | 0 | return false; |
213 | 194k | } |
214 | 194k | pt.x = source->points[j].x; |
215 | 194k | pt.y = -source->points[j].y; |
216 | 194k | assert(outline->n_points < outline->max_points); |
217 | 194k | outline->points[outline->n_points++] = pt; |
218 | 194k | } |
219 | | |
220 | 11.5k | switch (st) { |
221 | 7.04k | case S_ON: |
222 | 7.04k | seg = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END; |
223 | 7.04k | break; |
224 | | |
225 | 4.53k | case S_Q: |
226 | 4.53k | seg = OUTLINE_QUADRATIC_SPLINE | OUTLINE_CONTOUR_END; |
227 | 4.53k | break; |
228 | | |
229 | 0 | case S_C2: |
230 | 0 | seg = OUTLINE_CUBIC_SPLINE | OUTLINE_CONTOUR_END; |
231 | 0 | break; |
232 | | |
233 | 0 | default: |
234 | 0 | return false; |
235 | 11.5k | } |
236 | 11.5k | assert(outline->n_segments < outline->max_segments); |
237 | 11.5k | outline->segments[outline->n_segments++] = seg; |
238 | 11.5k | j += skip_last; |
239 | 11.5k | } |
240 | 8.14k | return true; |
241 | 8.14k | } |
242 | | |
243 | | /* |
244 | | * \brief Add a rectangle to the outline |
245 | | * Outline should be preallocated to a sufficient size |
246 | | * and coordinates should be in the allowable range. |
247 | | */ |
248 | | void ass_outline_add_rect(ASS_Outline *outline, |
249 | | int32_t x0, int32_t y0, int32_t x1, int32_t y1) |
250 | 19 | { |
251 | 19 | assert(outline->n_points + 4 <= outline->max_points); |
252 | 19 | assert(outline->n_segments + 4 <= outline->max_segments); |
253 | 19 | assert(abs(x0) <= OUTLINE_MAX && abs(y0) <= OUTLINE_MAX); |
254 | 19 | assert(abs(x1) <= OUTLINE_MAX && abs(y1) <= OUTLINE_MAX); |
255 | 19 | assert(!outline->n_segments || |
256 | 19 | (outline->segments[outline->n_segments - 1] & OUTLINE_CONTOUR_END)); |
257 | | |
258 | 19 | size_t pos = outline->n_points; |
259 | 19 | outline->points[pos + 0].x = outline->points[pos + 3].x = x0; |
260 | 19 | outline->points[pos + 1].x = outline->points[pos + 2].x = x1; |
261 | 19 | outline->points[pos + 0].y = outline->points[pos + 1].y = y0; |
262 | 19 | outline->points[pos + 2].y = outline->points[pos + 3].y = y1; |
263 | 19 | outline->n_points = pos + 4; |
264 | | |
265 | 19 | pos = outline->n_segments; |
266 | 19 | outline->segments[pos + 0] = OUTLINE_LINE_SEGMENT; |
267 | 19 | outline->segments[pos + 1] = OUTLINE_LINE_SEGMENT; |
268 | 19 | outline->segments[pos + 2] = OUTLINE_LINE_SEGMENT; |
269 | 19 | outline->segments[pos + 3] = OUTLINE_LINE_SEGMENT | OUTLINE_CONTOUR_END; |
270 | 19 | outline->n_segments = pos + 4; |
271 | 19 | } |
272 | | |
273 | | |
274 | | /* |
275 | | * \brief Add a single point to the outline |
276 | | * Outline should be allocated and will be enlarged if needed. |
277 | | * Also adds outline segment if segment parameter is nonzero. |
278 | | */ |
279 | | bool ass_outline_add_point(ASS_Outline *outline, ASS_Vector pt, char segment) |
280 | 820k | { |
281 | 820k | assert(outline->max_points); |
282 | 820k | if (abs(pt.x) > OUTLINE_MAX || abs(pt.y) > OUTLINE_MAX) |
283 | 0 | return false; |
284 | | |
285 | 820k | if (outline->n_points >= outline->max_points) { |
286 | 10.0k | size_t new_size = 2 * outline->max_points; |
287 | 10.0k | if (!ASS_REALLOC_ARRAY(outline->points, new_size)) |
288 | 0 | return false; |
289 | 10.0k | outline->max_points = new_size; |
290 | 10.0k | } |
291 | 820k | outline->points[outline->n_points] = pt; |
292 | 820k | outline->n_points++; |
293 | | |
294 | 820k | return !segment || ass_outline_add_segment(outline, segment); |
295 | 820k | } |
296 | | |
297 | | /* |
298 | | * \brief Add a segment to the outline |
299 | | * Outline should be allocated and will be enlarged if needed. |
300 | | */ |
301 | | bool ass_outline_add_segment(ASS_Outline *outline, char segment) |
302 | 588k | { |
303 | 588k | assert(outline->max_segments); |
304 | 588k | if (outline->n_segments >= outline->max_segments) { |
305 | 9.90k | size_t new_size = 2 * outline->max_segments; |
306 | 9.90k | if (!ASS_REALLOC_ARRAY(outline->segments, new_size)) |
307 | 0 | return false; |
308 | 9.90k | outline->max_segments = new_size; |
309 | 9.90k | } |
310 | 588k | outline->segments[outline->n_segments] = segment; |
311 | 588k | outline->n_segments++; |
312 | 588k | return true; |
313 | 588k | } |
314 | | |
315 | | /* |
316 | | * \brief Close last contour |
317 | | */ |
318 | | void ass_outline_close_contour(ASS_Outline *outline) |
319 | 22.8k | { |
320 | 22.8k | assert(outline->n_segments); |
321 | 22.8k | assert(!(outline->segments[outline->n_segments - 1] & ~OUTLINE_COUNT_MASK)); |
322 | 22.8k | outline->segments[outline->n_segments - 1] |= OUTLINE_CONTOUR_END; |
323 | 22.8k | } |
324 | | |
325 | | |
326 | | /* |
327 | | * \brief Inplace rotate outline by 90 degrees and translate by offs |
328 | | */ |
329 | | bool ass_outline_rotate_90(ASS_Outline *outline, ASS_Vector offs) |
330 | 0 | { |
331 | 0 | assert(abs(offs.x) <= INT32_MAX - OUTLINE_MAX); |
332 | 0 | assert(abs(offs.y) <= INT32_MAX - OUTLINE_MAX); |
333 | 0 | for (size_t i = 0; i < outline->n_points; i++) { |
334 | 0 | ASS_Vector pt = { offs.x + outline->points[i].y, |
335 | 0 | offs.y - outline->points[i].x }; |
336 | 0 | if (abs(pt.x) > OUTLINE_MAX || abs(pt.y) > OUTLINE_MAX) |
337 | 0 | return false; |
338 | 0 | outline->points[i] = pt; |
339 | 0 | } |
340 | 0 | return true; |
341 | 0 | } |
342 | | |
343 | | /* |
344 | | * \brief Scale outline by {2^scale_ord_x, 2^scale_ord_y} |
345 | | * Result outline should be uninitialized or empty. |
346 | | * Source outline can be NULL. |
347 | | */ |
348 | | bool ass_outline_scale_pow2(ASS_Outline *outline, const ASS_Outline *source, |
349 | | int scale_ord_x, int scale_ord_y) |
350 | 8.02k | { |
351 | 8.02k | if (!source || !source->n_points) { |
352 | 0 | ass_outline_clear(outline); |
353 | 0 | return true; |
354 | 0 | } |
355 | | |
356 | 8.02k | int32_t lim_x = OUTLINE_MAX; |
357 | 8.02k | if (scale_ord_x > 0) |
358 | 0 | lim_x = scale_ord_x < 32 ? lim_x >> scale_ord_x : 0; |
359 | 8.02k | else |
360 | 8.02k | scale_ord_x = FFMAX(scale_ord_x, -32); |
361 | | |
362 | 8.02k | int32_t lim_y = OUTLINE_MAX; |
363 | 8.02k | if (scale_ord_y > 0) |
364 | 0 | lim_y = scale_ord_y < 32 ? lim_y >> scale_ord_y : 0; |
365 | 8.02k | else |
366 | 8.02k | scale_ord_y = FFMAX(scale_ord_y, -32); |
367 | | |
368 | 8.02k | if (!lim_x || !lim_y) { |
369 | 0 | ass_outline_clear(outline); |
370 | 0 | return false; |
371 | 0 | } |
372 | | |
373 | 8.02k | if (!ass_outline_alloc(outline, source->n_points, source->n_segments)) |
374 | 0 | return false; |
375 | | |
376 | 8.02k | int sx = scale_ord_x + 32; |
377 | 8.02k | int sy = scale_ord_y + 32; |
378 | 8.02k | const ASS_Vector *pt = source->points; |
379 | 224k | for (size_t i = 0; i < source->n_points; i++) { |
380 | 216k | if (abs(pt[i].x) > lim_x || abs(pt[i].y) > lim_y) { |
381 | 0 | ass_outline_free(outline); |
382 | 0 | return false; |
383 | 0 | } |
384 | | // that's equivalent to pt[i].x << scale_ord_x, |
385 | | // but works even for negative coordinate and/or shift amount |
386 | 216k | outline->points[i].x = pt[i].x * ((int64_t) 1 << sx) >> 32; |
387 | 216k | outline->points[i].y = pt[i].y * ((int64_t) 1 << sy) >> 32; |
388 | 216k | } |
389 | 8.02k | memcpy(outline->segments, source->segments, source->n_segments); |
390 | 8.02k | outline->n_points = source->n_points; |
391 | 8.02k | outline->n_segments = source->n_segments; |
392 | 8.02k | return true; |
393 | 8.02k | } |
394 | | |
395 | | /* |
396 | | * \brief Transform outline by 2x3 matrix |
397 | | * Result outline should be uninitialized or empty. |
398 | | * Source outline can be NULL. |
399 | | */ |
400 | | bool ass_outline_transform_2d(ASS_Outline *outline, const ASS_Outline *source, |
401 | | const double m[2][3]) |
402 | 52.7k | { |
403 | 52.7k | if (!source || !source->n_points) { |
404 | 14.2k | ass_outline_clear(outline); |
405 | 14.2k | return true; |
406 | 14.2k | } |
407 | | |
408 | 38.5k | if (!ass_outline_alloc(outline, source->n_points, source->n_segments)) |
409 | 0 | return false; |
410 | | |
411 | 38.5k | const ASS_Vector *pt = source->points; |
412 | 1.60M | for (size_t i = 0; i < source->n_points; i++) { |
413 | 1.57M | double v[2]; |
414 | 4.71M | for (int k = 0; k < 2; k++) |
415 | 3.14M | v[k] = m[k][0] * pt[i].x + m[k][1] * pt[i].y + m[k][2]; |
416 | | |
417 | 1.57M | if (!(fabs(v[0]) < OUTLINE_MAX && fabs(v[1]) < OUTLINE_MAX)) { |
418 | 0 | ass_outline_free(outline); |
419 | 0 | return false; |
420 | 0 | } |
421 | 1.57M | outline->points[i].x = ass_lrint(v[0]); |
422 | 1.57M | outline->points[i].y = ass_lrint(v[1]); |
423 | 1.57M | } |
424 | 38.5k | memcpy(outline->segments, source->segments, source->n_segments); |
425 | 38.5k | outline->n_points = source->n_points; |
426 | 38.5k | outline->n_segments = source->n_segments; |
427 | 38.5k | return true; |
428 | 38.5k | } |
429 | | |
430 | | /* |
431 | | * \brief Apply perspective transform by 3x3 matrix to the outline |
432 | | * Result outline should be uninitialized or empty. |
433 | | * Source outline can be NULL. |
434 | | */ |
435 | | bool ass_outline_transform_3d(ASS_Outline *outline, const ASS_Outline *source, |
436 | | const double m[3][3]) |
437 | 2 | { |
438 | 2 | if (!source || !source->n_points) { |
439 | 0 | ass_outline_clear(outline); |
440 | 0 | return true; |
441 | 0 | } |
442 | | |
443 | 2 | if (!ass_outline_alloc(outline, source->n_points, source->n_segments)) |
444 | 0 | return false; |
445 | | |
446 | 2 | const ASS_Vector *pt = source->points; |
447 | 30 | for (size_t i = 0; i < source->n_points; i++) { |
448 | 28 | double v[3]; |
449 | 112 | for (int k = 0; k < 3; k++) |
450 | 84 | v[k] = m[k][0] * pt[i].x + m[k][1] * pt[i].y + m[k][2]; |
451 | | |
452 | 28 | double w = 1 / FFMAX(v[2], 0.1); |
453 | 28 | v[0] *= w; |
454 | 28 | v[1] *= w; |
455 | | |
456 | 28 | if (!(fabs(v[0]) < OUTLINE_MAX && fabs(v[1]) < OUTLINE_MAX)) { |
457 | 0 | ass_outline_free(outline); |
458 | 0 | return false; |
459 | 0 | } |
460 | 28 | outline->points[i].x = ass_lrint(v[0]); |
461 | 28 | outline->points[i].y = ass_lrint(v[1]); |
462 | 28 | } |
463 | 2 | memcpy(outline->segments, source->segments, source->n_segments); |
464 | 2 | outline->n_points = source->n_points; |
465 | 2 | outline->n_segments = source->n_segments; |
466 | 2 | return true; |
467 | 2 | } |
468 | | |
469 | | /* |
470 | | * \brief Find minimal X-coordinate of control points after perspective transform |
471 | | */ |
472 | | void ass_outline_update_min_transformed_x(const ASS_Outline *outline, |
473 | | const double m[3][3], |
474 | 10.8k | int32_t *min_x) { |
475 | 10.8k | const ASS_Vector *pt = outline->points; |
476 | 311k | for (size_t i = 0; i < outline->n_points; i++) { |
477 | 300k | double z = m[2][0] * pt[i].x + m[2][1] * pt[i].y + m[2][2]; |
478 | 300k | double x = (m[0][0] * pt[i].x + m[0][1] * pt[i].y + m[0][2]) / FFMAX(z, 0.1); |
479 | 300k | if (isnan(x)) |
480 | 0 | continue; |
481 | 300k | int32_t ix = ass_lrint(FFMINMAX(x, -OUTLINE_MAX, OUTLINE_MAX)); |
482 | 300k | *min_x = FFMIN(*min_x, ix); |
483 | 300k | } |
484 | 10.8k | } |
485 | | |
486 | | /* |
487 | | * \brief Update bounding box of control points |
488 | | */ |
489 | | void ass_outline_update_cbox(const ASS_Outline *outline, ASS_Rect *cbox) |
490 | 33.4k | { |
491 | 1.07M | for (size_t i = 0; i < outline->n_points; i++) |
492 | 1.04M | rectangle_update(cbox, |
493 | 1.04M | outline->points[i].x, outline->points[i].y, |
494 | 1.04M | outline->points[i].x, outline->points[i].y); |
495 | 33.4k | } |
496 | | |
497 | | |
498 | | /* |
499 | | * Outline Stroke Algorithm |
500 | | * |
501 | | * Goal: |
502 | | * Given source outline, construct two border outlines, such as that |
503 | | * for any point inside any border outline (nonzero winding rule) |
504 | | * minimal distance to points of source outline (same rule) |
505 | | * is less than 1 with given precision, and for any point outside |
506 | | * both border outlines minimal distance is more than approximately 1. |
507 | | * Distance here is defined in normal space that is scaled by [1 / xbord, 1 / ybord]. |
508 | | * Correspondingly distance is equal to hypot(dx / xbord, dy / ybord) and |
509 | | * approximate allowable error is eps / max(xbord, ybord). |
510 | | * |
511 | | * Two border outlines correspond to ±1 offset curves and |
512 | | * are required in case of self-intersecting source outline. |
513 | | * |
514 | | * Each of source segment that can be line segment, quadratic or cubic spline, |
515 | | * and also connection between them is stroked mostly independently. |
516 | | * Line segments can be offset quite straightforwardly. |
517 | | * For splines algorithm first tries to offset individual points, |
518 | | * then estimates error of such approximation and subdivide recursively if necessary. |
519 | | * |
520 | | * List of border cases handled by this algorithm: |
521 | | * 1) Too close points lead to random derivatives or even division by zero. |
522 | | * Algorithm solves that by merging such points into one. |
523 | | * 2) Degenerate cases--near zero derivative at some spline points. |
524 | | * Algorithm adds circular cap in such cases. |
525 | | * 3) Negative curvature--offset amount is larger than radius of curvature. |
526 | | * Algorithm checks if produced splines can potentially have self-intersection |
527 | | * and handles them accordingly. It's mostly done by skipping |
528 | | * problematic spline and replacing it with polyline that covers only |
529 | | * positive winging part of mathematical offset curve. |
530 | | * |
531 | | * Error estimation for splines is done by analyzing offset spline. |
532 | | * Offset spline is the difference between result and source spline in normal space. |
533 | | * Such spline should consist of vectors with length 1 and orthogonal to source spline. |
534 | | * Correspondingly error estimator have radial and angular part. |
535 | | * |
536 | | * Useful facts about B-splines. |
537 | | * 1) Derivative of B-spline of order N is B-spline of order N-1. |
538 | | * 2) Multiplication of B-splines of order N and M is B-spline of order N+M. |
539 | | * 3) B-spline is fully contained inside convex hull of its control points. |
540 | | * |
541 | | * So, for radial error it's possible to check only control points of |
542 | | * offset spline multiplication by itself. And for angular error it's |
543 | | * possible to check control points of cross and dot product between |
544 | | * offset spline and derivative spline. |
545 | | */ |
546 | | |
547 | | |
548 | | typedef struct { |
549 | | ASS_DVector v; |
550 | | double len; |
551 | | } Normal; |
552 | | |
553 | | typedef struct { |
554 | | ASS_Outline *result[2]; // result outlines |
555 | | size_t contour_first[2]; // start position of last contours |
556 | | double xbord, ybord; // border sizes |
557 | | double xscale, yscale; // inverse border sizes |
558 | | int eps; // allowable error in coordinate space |
559 | | |
560 | | // true if where are points in current contour |
561 | | bool contour_start; |
562 | | // skip flags for first and last point |
563 | | int first_skip, last_skip; |
564 | | // normal at first and last point |
565 | | ASS_DVector first_normal, last_normal; |
566 | | // first and last points of current contour |
567 | | ASS_Vector first_point, last_point; |
568 | | |
569 | | // cosinus of maximal angle that do not require cap |
570 | | double merge_cos; |
571 | | // cosinus of maximal angle of circular arc that can be approximated with quadratic spline |
572 | | double split_cos; |
573 | | // maximal distance between control points in normal space that triggers handling of degenerates |
574 | | double min_len; |
575 | | // constant that used in exact radial error checking in quadratic case |
576 | | double err_q; |
577 | | // constant that used in approximate radial error checking in cubic case |
578 | | double err_c; |
579 | | // tangent of maximal angular error |
580 | | double err_a; |
581 | | } StrokerState; |
582 | | |
583 | | /** |
584 | | * \brief 2D vector dot product |
585 | | */ |
586 | | static inline double vec_dot(ASS_DVector vec1, ASS_DVector vec2) |
587 | 237k | { |
588 | 237k | return vec1.x * vec2.x + vec1.y * vec2.y; |
589 | 237k | } |
590 | | |
591 | | /** |
592 | | * \brief 2D vector cross product |
593 | | */ |
594 | | static inline double vec_crs(ASS_DVector vec1, ASS_DVector vec2) |
595 | 166k | { |
596 | 166k | return vec1.x * vec2.y - vec1.y * vec2.x; |
597 | 166k | } |
598 | | |
599 | | /** |
600 | | * \brief 2D vector length |
601 | | */ |
602 | | static inline double vec_len(ASS_DVector vec) |
603 | 226k | { |
604 | 226k | return sqrt(vec.x * vec.x + vec.y * vec.y); |
605 | 226k | } |
606 | | |
607 | | |
608 | | /** |
609 | | * \brief Add point to one or two border outlines |
610 | | * \param str stroker state |
611 | | * \param pt source point |
612 | | * \param offs offset in normal space |
613 | | * \param segment outline segment type |
614 | | * \param dir destination outline flags |
615 | | * \return false on allocation failure |
616 | | */ |
617 | | static bool emit_point(StrokerState *str, ASS_Vector pt, |
618 | | ASS_DVector offs, char segment, int dir) |
619 | 764k | { |
620 | 764k | int32_t dx = (int32_t) (str->xbord * offs.x); |
621 | 764k | int32_t dy = (int32_t) (str->ybord * offs.y); |
622 | | |
623 | 764k | if (dir & 1) { |
624 | 434k | ASS_Vector res = { pt.x + dx, pt.y + dy }; |
625 | 434k | if (!ass_outline_add_point(str->result[0], res, segment)) |
626 | 0 | return false; |
627 | 434k | } |
628 | 764k | if (dir & 2) { |
629 | 385k | ASS_Vector res = { pt.x - dx, pt.y - dy }; |
630 | 385k | if (!ass_outline_add_point(str->result[1], res, segment)) |
631 | 0 | return false; |
632 | 385k | } |
633 | 764k | return true; |
634 | 764k | } |
635 | | |
636 | | /** |
637 | | * \brief Replace first point of current contour |
638 | | * \param str stroker state |
639 | | * \param pt source point |
640 | | * \param offs offset in normal space |
641 | | * \param dir destination outline flags |
642 | | */ |
643 | | static void fix_first_point(StrokerState *str, ASS_Vector pt, |
644 | | ASS_DVector offs, int dir) |
645 | 1.86k | { |
646 | 1.86k | int32_t dx = (int32_t) (str->xbord * offs.x); |
647 | 1.86k | int32_t dy = (int32_t) (str->ybord * offs.y); |
648 | | |
649 | 1.86k | if (dir & 1) { |
650 | 1.22k | ASS_Vector res = { pt.x + dx, pt.y + dy }; |
651 | 1.22k | ASS_Outline *ol = str->result[0]; |
652 | 1.22k | ol->points[str->contour_first[0]] = res; |
653 | 1.22k | } |
654 | 1.86k | if (dir & 2) { |
655 | 902 | ASS_Vector res = { pt.x - dx, pt.y - dy }; |
656 | 902 | ASS_Outline *ol = str->result[1]; |
657 | 902 | ol->points[str->contour_first[1]] = res; |
658 | 902 | } |
659 | 1.86k | } |
660 | | |
661 | | /** |
662 | | * \brief Helper function for circular arc construction |
663 | | * \param str stroker state |
664 | | * \param pt center point |
665 | | * \param normal0 first normal |
666 | | * \param normal1 last normal |
667 | | * \param mul precalculated coefficients |
668 | | * \param level subdivision level |
669 | | * \param dir destination outline flags |
670 | | * \return false on allocation failure |
671 | | */ |
672 | | static bool process_arc(StrokerState *str, ASS_Vector pt, |
673 | | ASS_DVector normal0, ASS_DVector normal1, |
674 | | const double *mul, int level, int dir) |
675 | 180k | { |
676 | 180k | ASS_DVector center; |
677 | 180k | center.x = (normal0.x + normal1.x) * mul[level]; |
678 | 180k | center.y = (normal0.y + normal1.y) * mul[level]; |
679 | 180k | if (level) |
680 | 48.9k | return process_arc(str, pt, normal0, center, mul, level - 1, dir) && |
681 | 48.9k | process_arc(str, pt, center, normal1, mul, level - 1, dir); |
682 | 131k | return emit_point(str, pt, normal0, OUTLINE_QUADRATIC_SPLINE, dir) && |
683 | 131k | emit_point(str, pt, center, 0, dir); |
684 | 180k | } |
685 | | |
686 | | /** |
687 | | * \brief Construct circular arc |
688 | | * \param str stroker state |
689 | | * \param pt center point |
690 | | * \param normal0 first normal |
691 | | * \param normal1 last normal |
692 | | * \param c dot product between normal0 and normal1 |
693 | | * \param dir destination outline flags |
694 | | * \return false on allocation failure |
695 | | */ |
696 | | static bool draw_arc(StrokerState *str, ASS_Vector pt, |
697 | | ASS_DVector normal0, ASS_DVector normal1, double c, int dir) |
698 | 71.8k | { |
699 | 71.8k | enum { max_subdiv = 15 }; |
700 | 71.8k | double mul[max_subdiv + 1]; |
701 | | |
702 | 71.8k | ASS_DVector center; |
703 | 71.8k | bool small_angle = true; |
704 | 71.8k | if (c < 0) { |
705 | 10.8k | double mul = dir & 2 ? -sqrt(0.5) : sqrt(0.5); |
706 | 10.8k | mul /= sqrt(1 - c); |
707 | 10.8k | center.x = (normal1.y - normal0.y) * mul; |
708 | 10.8k | center.y = (normal0.x - normal1.x) * mul; |
709 | 10.8k | c = sqrt(FFMAX(0, 0.5 + 0.5 * c)); |
710 | 10.8k | small_angle = false; |
711 | 10.8k | } |
712 | | |
713 | 71.8k | int pos = max_subdiv; |
714 | 120k | while (c < str->split_cos && pos) { |
715 | 48.8k | mul[pos] = sqrt(0.5) / sqrt(1 + c); |
716 | 48.8k | c = (1 + c) * mul[pos]; |
717 | 48.8k | pos--; |
718 | 48.8k | } |
719 | 71.8k | mul[pos] = 1 / (1 + c); |
720 | 71.8k | return small_angle ? |
721 | 61.0k | process_arc(str, pt, normal0, normal1, mul + pos, max_subdiv - pos, dir) : |
722 | 71.8k | process_arc(str, pt, normal0, center, mul + pos, max_subdiv - pos, dir) && |
723 | 10.8k | process_arc(str, pt, center, normal1, mul + pos, max_subdiv - pos, dir); |
724 | 71.8k | } |
725 | | |
726 | | /** |
727 | | * \brief Construct full circle |
728 | | * \param str stroker state |
729 | | * \param pt center point |
730 | | * \param dir destination outline flags |
731 | | * \return false on allocation failure |
732 | | */ |
733 | | static bool draw_circle(StrokerState *str, ASS_Vector pt, int dir) |
734 | 3 | { |
735 | 3 | enum { max_subdiv = 15 }; |
736 | 3 | double mul[max_subdiv + 1], c = 0; |
737 | | |
738 | 3 | int pos = max_subdiv; |
739 | 6 | while (c < str->split_cos && pos) { |
740 | 3 | mul[pos] = sqrt(0.5) / sqrt(1 + c); |
741 | 3 | c = (1 + c) * mul[pos]; |
742 | 3 | pos--; |
743 | 3 | } |
744 | 3 | mul[pos] = 1 / (1 + c); |
745 | | |
746 | 3 | ASS_DVector normal[4] = { |
747 | 3 | { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } |
748 | 3 | }; |
749 | 3 | return process_arc(str, pt, normal[0], normal[1], mul + pos, max_subdiv - pos, dir) && |
750 | 3 | process_arc(str, pt, normal[1], normal[2], mul + pos, max_subdiv - pos, dir) && |
751 | 3 | process_arc(str, pt, normal[2], normal[3], mul + pos, max_subdiv - pos, dir) && |
752 | 3 | process_arc(str, pt, normal[3], normal[0], mul + pos, max_subdiv - pos, dir); |
753 | 3 | } |
754 | | |
755 | | /** |
756 | | * \brief Start new segment and add circular cap if necessary |
757 | | * \param str stroker state |
758 | | * \param pt start point of the new segment |
759 | | * \param normal normal at start of the new segment |
760 | | * \param dir destination outline flags |
761 | | * \return false on allocation failure |
762 | | */ |
763 | | static bool start_segment(StrokerState *str, ASS_Vector pt, |
764 | | ASS_DVector normal, int dir) |
765 | 154k | { |
766 | 154k | if (str->contour_start) { |
767 | 11.4k | str->contour_start = false; |
768 | 11.4k | str->first_skip = str->last_skip = 0; |
769 | 11.4k | str->first_normal = str->last_normal = normal; |
770 | 11.4k | str->first_point = pt; |
771 | 11.4k | return true; |
772 | 11.4k | } |
773 | | |
774 | 142k | ASS_DVector prev = str->last_normal; |
775 | 142k | double c = vec_dot(prev, normal); |
776 | 142k | if (c > str->merge_cos) { // merge without cap |
777 | 70.8k | double mul = 1 / (1 + c); |
778 | 70.8k | str->last_normal.x = (str->last_normal.x + normal.x) * mul; |
779 | 70.8k | str->last_normal.y = (str->last_normal.y + normal.y) * mul; |
780 | 70.8k | return true; |
781 | 70.8k | } |
782 | 71.8k | str->last_normal = normal; |
783 | | |
784 | | // check for negative curvature |
785 | 71.8k | double s = vec_crs(prev, normal); |
786 | 71.8k | int skip_dir = s < 0 ? 1 : 2; |
787 | 71.8k | if (dir & skip_dir) { |
788 | 71.8k | if (!emit_point(str, pt, prev, OUTLINE_LINE_SEGMENT, ~str->last_skip & skip_dir)) |
789 | 0 | return false; |
790 | 71.8k | ASS_DVector zero_normal = {0, 0}; |
791 | 71.8k | if (!emit_point(str, pt, zero_normal, OUTLINE_LINE_SEGMENT, skip_dir)) |
792 | 0 | return false; |
793 | 71.8k | } |
794 | 71.8k | str->last_skip = skip_dir; |
795 | | |
796 | 71.8k | dir &= ~skip_dir; |
797 | 71.8k | return !dir || draw_arc(str, pt, prev, normal, c, dir); |
798 | 71.8k | } |
799 | | |
800 | | /** |
801 | | * \brief Same as emit_point() but also updates skip flags |
802 | | */ |
803 | | static bool emit_first_point(StrokerState *str, ASS_Vector pt, char segment, int dir) |
804 | 153k | { |
805 | 153k | str->last_skip &= ~dir; |
806 | 153k | return emit_point(str, pt, str->last_normal, segment, dir); |
807 | 153k | } |
808 | | |
809 | | /** |
810 | | * \brief Prepare to skip part of curve |
811 | | * \param str stroker state |
812 | | * \param pt start point of the skipped part |
813 | | * \param dir destination outline flags |
814 | | * \param first true if the skipped part is at start of the segment |
815 | | * \return false on allocation failure |
816 | | */ |
817 | | static bool prepare_skip(StrokerState *str, ASS_Vector pt, int dir, bool first) |
818 | 55.4k | { |
819 | 55.4k | if (first) |
820 | 2.72k | str->first_skip |= dir; |
821 | 52.7k | else if (!emit_point(str, pt, str->last_normal, OUTLINE_LINE_SEGMENT, ~str->last_skip & dir)) |
822 | 0 | return false; |
823 | 55.4k | str->last_skip |= dir; |
824 | 55.4k | return true; |
825 | 55.4k | } |
826 | | |
827 | | /** |
828 | | * \brief Process source line segment |
829 | | * \param str stroker state |
830 | | * \param pt1 end point of the line segment |
831 | | * \param dir destination outline flags |
832 | | * \return false on allocation failure |
833 | | */ |
834 | | static bool add_line(StrokerState *str, ASS_Vector pt1, int dir) |
835 | 82.1k | { |
836 | 82.1k | int32_t dx = pt1.x - str->last_point.x; |
837 | 82.1k | int32_t dy = pt1.y - str->last_point.y; |
838 | 82.1k | if (dx > -str->eps && dx < str->eps && dy > -str->eps && dy < str->eps) |
839 | 11.8k | return true; |
840 | | |
841 | 70.3k | ASS_DVector deriv = { dy * str->yscale, -dx * str->xscale }; |
842 | 70.3k | double scale = 1 / vec_len(deriv); |
843 | 70.3k | ASS_DVector normal = { deriv.x * scale, deriv.y * scale }; |
844 | 70.3k | if (!start_segment(str, str->last_point, normal, dir)) |
845 | 0 | return false; |
846 | 70.3k | if (!emit_first_point(str, str->last_point, OUTLINE_LINE_SEGMENT, dir)) |
847 | 0 | return false; |
848 | 70.3k | str->last_normal = normal; |
849 | 70.3k | str->last_point = pt1; |
850 | 70.3k | return true; |
851 | 70.3k | } |
852 | | |
853 | | |
854 | | /** |
855 | | * \brief Estimate error for quadratic spline |
856 | | * \param str stroker state |
857 | | * \param c dot product between normal[0] and normal[1] |
858 | | * \param s cross product between normal[0] and normal[1] |
859 | | * \param normal first and last spline normal |
860 | | * \param result best offset for central spline point |
861 | | * \return false if error is too large |
862 | | */ |
863 | | static bool estimate_quadratic_error(StrokerState *str, double c, double s, |
864 | | const Normal *normal, ASS_DVector *result) |
865 | 88.1k | { |
866 | | // check radial error |
867 | 88.1k | if (!((3 + c) * (3 + c) < str->err_q * (1 + c))) |
868 | 4.17k | return false; |
869 | | |
870 | 83.9k | double mul = 1 / (1 + c); |
871 | 83.9k | double l0 = 2 * normal[0].len, l1 = 2 * normal[1].len; |
872 | 83.9k | double dot0 = l0 + normal[1].len * c, crs0 = (l0 * mul - normal[1].len) * s; |
873 | 83.9k | double dot1 = l1 + normal[0].len * c, crs1 = (l1 * mul - normal[0].len) * s; |
874 | | // check angular error |
875 | 83.9k | if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1)) |
876 | 377 | return false; |
877 | | |
878 | 83.6k | result->x = (normal[0].v.x + normal[1].v.x) * mul; |
879 | 83.6k | result->y = (normal[0].v.y + normal[1].v.y) * mul; |
880 | 83.6k | return true; |
881 | 83.9k | } |
882 | | |
883 | | /** |
884 | | * \brief Helper function for quadratic spline construction |
885 | | * \param str stroker state |
886 | | * \param pt array of 3 source spline points |
887 | | * \param deriv array of 2 differences between adjacent points in normal space |
888 | | * \param normal first and last spline normal |
889 | | * \param dir destination outline flags |
890 | | * \param first true if the current part is at start of the segment |
891 | | * \return false on allocation failure |
892 | | */ |
893 | | static bool process_quadratic(StrokerState *str, const ASS_Vector *pt, |
894 | | const ASS_DVector *deriv, const Normal *normal, |
895 | | int dir, bool first) |
896 | 94.8k | { |
897 | 94.8k | double c = vec_dot(normal[0].v, normal[1].v); |
898 | 94.8k | double s = vec_crs(normal[0].v, normal[1].v); |
899 | 94.8k | int check_dir = dir, skip_dir = s < 0 ? 1 : 2; |
900 | 94.8k | if (dir & skip_dir) { |
901 | 86.1k | double abs_s = fabs(s); |
902 | 86.1k | double f0 = normal[0].len * c + normal[1].len; |
903 | 86.1k | double f1 = normal[1].len * c + normal[0].len; |
904 | 86.1k | double g0 = normal[0].len * abs_s; |
905 | 86.1k | double g1 = normal[1].len * abs_s; |
906 | | // check for self-intersection |
907 | 86.1k | if (f0 < abs_s && f1 < abs_s) { |
908 | 62.1k | double d2 = (f0 * normal[1].len + f1 * normal[0].len) / 2; |
909 | 62.1k | if (d2 < g0 && d2 < g1) { |
910 | 55.4k | if (!prepare_skip(str, pt[0], skip_dir, first)) |
911 | 0 | return false; |
912 | 55.4k | if (f0 < 0 || f1 < 0) { |
913 | 0 | ASS_DVector zero_normal = {0, 0}; |
914 | 0 | if (!emit_point(str, pt[0], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir) || |
915 | 0 | !emit_point(str, pt[2], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir)) |
916 | 0 | return false; |
917 | 55.4k | } else { |
918 | 55.4k | double mul = f0 / abs_s; |
919 | 55.4k | ASS_DVector offs = { normal[0].v.x * mul, normal[0].v.y * mul }; |
920 | 55.4k | if (!emit_point(str, pt[0], offs, OUTLINE_LINE_SEGMENT, skip_dir)) |
921 | 0 | return false; |
922 | 55.4k | } |
923 | 55.4k | dir &= ~skip_dir; |
924 | 55.4k | if (!dir) { |
925 | 6.66k | str->last_normal = normal[1].v; |
926 | 6.66k | return true; |
927 | 6.66k | } |
928 | 55.4k | } |
929 | 55.5k | check_dir ^= skip_dir; |
930 | 55.5k | } else if (c + g0 < 1 && c + g1 < 1) |
931 | 0 | check_dir ^= skip_dir; |
932 | 86.1k | } |
933 | | |
934 | 88.1k | ASS_DVector result; |
935 | 88.1k | if (check_dir && estimate_quadratic_error(str, c, s, normal, &result)) { |
936 | 83.6k | if (!emit_first_point(str, pt[0], OUTLINE_QUADRATIC_SPLINE, check_dir)) |
937 | 0 | return false; |
938 | 83.6k | if (!emit_point(str, pt[1], result, 0, check_dir)) |
939 | 0 | return false; |
940 | 83.6k | dir &= ~check_dir; |
941 | 83.6k | if (!dir) { |
942 | 76.9k | str->last_normal = normal[1].v; |
943 | 76.9k | return true; |
944 | 76.9k | } |
945 | 83.6k | } |
946 | | |
947 | 11.2k | ASS_Vector next[5]; |
948 | 11.2k | next[1].x = pt[0].x + pt[1].x; |
949 | 11.2k | next[1].y = pt[0].y + pt[1].y; |
950 | 11.2k | next[3].x = pt[1].x + pt[2].x; |
951 | 11.2k | next[3].y = pt[1].y + pt[2].y; |
952 | 11.2k | next[2].x = (next[1].x + next[3].x + 2) >> 2; |
953 | 11.2k | next[2].y = (next[1].y + next[3].y + 2) >> 2; |
954 | 11.2k | next[1].x >>= 1; |
955 | 11.2k | next[1].y >>= 1; |
956 | 11.2k | next[3].x >>= 1; |
957 | 11.2k | next[3].y >>= 1; |
958 | 11.2k | next[0] = pt[0]; |
959 | 11.2k | next[4] = pt[2]; |
960 | | |
961 | 11.2k | ASS_DVector next_deriv[3]; |
962 | 11.2k | next_deriv[0].x = deriv[0].x / 2; |
963 | 11.2k | next_deriv[0].y = deriv[0].y / 2; |
964 | 11.2k | next_deriv[2].x = deriv[1].x / 2; |
965 | 11.2k | next_deriv[2].y = deriv[1].y / 2; |
966 | 11.2k | next_deriv[1].x = (next_deriv[0].x + next_deriv[2].x) / 2; |
967 | 11.2k | next_deriv[1].y = (next_deriv[0].y + next_deriv[2].y) / 2; |
968 | | |
969 | 11.2k | double len = vec_len(next_deriv[1]); |
970 | 11.2k | if (len < str->min_len) { // check degenerate case |
971 | 7 | if (!emit_first_point(str, next[0], OUTLINE_LINE_SEGMENT, dir)) |
972 | 0 | return false; |
973 | 7 | if (!start_segment(str, next[2], normal[1].v, dir)) |
974 | 0 | return false; |
975 | 7 | str->last_skip &= ~dir; |
976 | 7 | return emit_point(str, next[2], normal[1].v, OUTLINE_LINE_SEGMENT, dir); |
977 | 7 | } |
978 | | |
979 | 11.2k | double scale = 1 / len; |
980 | 11.2k | Normal next_normal[3] = { |
981 | 11.2k | { normal[0].v, normal[0].len / 2 }, |
982 | 11.2k | { { next_deriv[1].x * scale, next_deriv[1].y * scale }, len }, |
983 | 11.2k | { normal[1].v, normal[1].len / 2 } |
984 | 11.2k | }; |
985 | 11.2k | return process_quadratic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) && |
986 | 11.2k | process_quadratic(str, next + 2, next_deriv + 1, next_normal + 1, dir, false); |
987 | 11.2k | } |
988 | | |
989 | | /** |
990 | | * \brief Process source quadratic spline |
991 | | * \param str stroker state |
992 | | * \param pt1 middle control point |
993 | | * \param pt2 final spline point |
994 | | * \param dir destination outline flags |
995 | | * \return false on allocation failure |
996 | | */ |
997 | | static bool add_quadratic(StrokerState *str, ASS_Vector pt1, ASS_Vector pt2, int dir) |
998 | 73.7k | { |
999 | 73.7k | int32_t dx0 = pt1.x - str->last_point.x; |
1000 | 73.7k | int32_t dy0 = pt1.y - str->last_point.y; |
1001 | 73.7k | if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) |
1002 | 697 | return add_line(str, pt2, dir); |
1003 | | |
1004 | 73.0k | int32_t dx1 = pt2.x - pt1.x; |
1005 | 73.0k | int32_t dy1 = pt2.y - pt1.y; |
1006 | 73.0k | if (dx1 > -str->eps && dx1 < str->eps && dy1 > -str->eps && dy1 < str->eps) |
1007 | 628 | return add_line(str, pt2, dir); |
1008 | | |
1009 | 72.4k | ASS_Vector pt[3] = { str->last_point, pt1, pt2 }; |
1010 | 72.4k | str->last_point = pt2; |
1011 | | |
1012 | 72.4k | ASS_DVector deriv[2] = { |
1013 | 72.4k | { dy0 * str->yscale, -dx0 * str->xscale }, |
1014 | 72.4k | { dy1 * str->yscale, -dx1 * str->xscale } |
1015 | 72.4k | }; |
1016 | 72.4k | double len0 = vec_len(deriv[0]), scale0 = 1 / len0; |
1017 | 72.4k | double len1 = vec_len(deriv[1]), scale1 = 1 / len1; |
1018 | 72.4k | Normal normal[2] = { |
1019 | 72.4k | { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 }, |
1020 | 72.4k | { { deriv[1].x * scale1, deriv[1].y * scale1 }, len1 } |
1021 | 72.4k | }; |
1022 | | |
1023 | 72.4k | bool first = str->contour_start; |
1024 | 72.4k | return start_segment(str, pt[0], normal[0].v, dir) && |
1025 | 72.4k | process_quadratic(str, pt, deriv, normal, dir, first); |
1026 | 73.0k | } |
1027 | | |
1028 | | |
1029 | | enum { |
1030 | | FLAG_INTERSECTION = 1, |
1031 | | FLAG_ZERO_0 = 2, |
1032 | | FLAG_ZERO_1 = 4, |
1033 | | FLAG_CLIP_0 = 8, |
1034 | | FLAG_CLIP_1 = 16, |
1035 | | FLAG_DIR_2 = 32, |
1036 | | |
1037 | | FLAG_COUNT = 6, |
1038 | | |
1039 | | MASK_INTERSECTION = FLAG_INTERSECTION << FLAG_COUNT, |
1040 | | MASK_ZERO_0 = FLAG_ZERO_0 << FLAG_COUNT, |
1041 | | MASK_ZERO_1 = FLAG_ZERO_1 << FLAG_COUNT, |
1042 | | MASK_CLIP_0 = FLAG_CLIP_0 << FLAG_COUNT, |
1043 | | MASK_CLIP_1 = FLAG_CLIP_1 << FLAG_COUNT, |
1044 | | }; |
1045 | | |
1046 | | /** |
1047 | | * \brief Estimate error for cubic spline |
1048 | | * \param str stroker state |
1049 | | * \param c dot product between normal[0] and normal[1] |
1050 | | * \param s cross product between normal[0] and normal[1] |
1051 | | * \param dc dot products between normals and central points difference in normal space |
1052 | | * \param dc cross products between normals and central points difference in normal space |
1053 | | * \param normal first and last spline normal |
1054 | | * \param result best offsets for central spline points (second & third) |
1055 | | * \return flags for destination outlines that do not require subdivision |
1056 | | */ |
1057 | | static int estimate_cubic_error(StrokerState *str, double c, double s, |
1058 | | const double *dc, const double *ds, |
1059 | | const Normal *normal, ASS_DVector *result, |
1060 | | int check_flags, int dir) |
1061 | 0 | { |
1062 | 0 | double t = (ds[0] + ds[1]) / (dc[0] + dc[1]), c1 = 1 + c, ss = s * s; |
1063 | 0 | double ts = t * s, tt = t * t, ttc = tt * c1, ttcc = ttc * c1; |
1064 | |
|
1065 | 0 | const double w = 0.4; |
1066 | 0 | double f0[] = { |
1067 | 0 | 10 * w * (c - 1) + 9 * w * tt * c, |
1068 | 0 | 2 * (c - 1) + 3 * tt + 2 * ts, |
1069 | 0 | 2 * (c - 1) + 3 * tt - 2 * ts, |
1070 | 0 | }; |
1071 | 0 | double f1[] = { |
1072 | 0 | 18 * w * (ss - ttc * c), |
1073 | 0 | 2 * ss - 6 * ttc - 2 * ts * (c + 4), |
1074 | 0 | 2 * ss - 6 * ttc + 2 * ts * (c + 4), |
1075 | 0 | }; |
1076 | 0 | double f2[] = { |
1077 | 0 | 9 * w * (ttcc - ss) * c, |
1078 | 0 | 3 * ss + 3 * ttcc + 6 * ts * c1, |
1079 | 0 | 3 * ss + 3 * ttcc - 6 * ts * c1, |
1080 | 0 | }; |
1081 | |
|
1082 | 0 | double aa = 0, ab = 0; |
1083 | 0 | double ch = sqrt(c1 / 2); |
1084 | 0 | double inv_ro0 = 1.5 * ch * (ch + 1); |
1085 | 0 | for (int i = 0; i < 3; i++) { |
1086 | 0 | double a = 2 * f2[i] + f1[i] * inv_ro0; |
1087 | 0 | double b = f2[i] - f0[i] * inv_ro0 * inv_ro0; |
1088 | 0 | aa += a * a; ab += a * b; |
1089 | 0 | } |
1090 | 0 | double ro = ab / (aa * inv_ro0 + 1e-9); // best fit |
1091 | |
|
1092 | 0 | double err2 = 0; |
1093 | 0 | for (int i = 0; i < 3; i++) { |
1094 | 0 | double err = f0[i] + ro * (f1[i] + ro * f2[i]); |
1095 | 0 | err2 += err * err; |
1096 | 0 | } |
1097 | 0 | if (!(err2 < str->err_c)) // check radial error |
1098 | 0 | return 0; |
1099 | | |
1100 | 0 | double r = ro * c1 - 1; |
1101 | 0 | double ro0 = t * r - ro * s; |
1102 | 0 | double ro1 = t * r + ro * s; |
1103 | |
|
1104 | 0 | int check_dir = check_flags & FLAG_DIR_2 ? 2 : 1; |
1105 | 0 | if (dir & check_dir) { |
1106 | 0 | double test_s = s, test0 = ro0, test1 = ro1; |
1107 | 0 | if (check_flags & FLAG_DIR_2) { |
1108 | 0 | test_s = -test_s; |
1109 | 0 | test0 = -test0; |
1110 | 0 | test1 = -test1; |
1111 | 0 | } |
1112 | 0 | int flags = 0; |
1113 | 0 | if (2 * test_s * r < dc[0] + dc[1]) flags |= FLAG_INTERSECTION; |
1114 | 0 | if (normal[0].len - test0 < 0) flags |= FLAG_ZERO_0; |
1115 | 0 | if (normal[1].len + test1 < 0) flags |= FLAG_ZERO_1; |
1116 | 0 | if (normal[0].len + dc[0] + test_s - test1 * c < 0) flags |= FLAG_CLIP_0; |
1117 | 0 | if (normal[1].len + dc[1] + test_s + test0 * c < 0) flags |= FLAG_CLIP_1; |
1118 | 0 | if ((flags ^ check_flags) & (check_flags >> FLAG_COUNT)) { |
1119 | 0 | dir &= ~check_dir; |
1120 | 0 | if (!dir) |
1121 | 0 | return 0; |
1122 | 0 | } |
1123 | 0 | } |
1124 | | |
1125 | 0 | double d0c = 2 * dc[0], d0s = 2 * ds[0]; |
1126 | 0 | double d1c = 2 * dc[1], d1s = 2 * ds[1]; |
1127 | 0 | double dot0 = d0c + 3 * normal[0].len, crs0 = d0s + 3 * ro0 * normal[0].len; |
1128 | 0 | double dot1 = d1c + 3 * normal[1].len, crs1 = d1s + 3 * ro1 * normal[1].len; |
1129 | | // check angular error (stage 1) |
1130 | 0 | if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1)) |
1131 | 0 | return 0; |
1132 | | |
1133 | 0 | double cl0 = c * normal[0].len, sl0 = +s * normal[0].len; |
1134 | 0 | double cl1 = c * normal[1].len, sl1 = -s * normal[1].len; |
1135 | 0 | dot0 = d0c - ro0 * d0s + cl0 + ro1 * sl0 + cl1 / 3; |
1136 | 0 | dot1 = d1c - ro1 * d1s + cl1 + ro0 * sl1 + cl0 / 3; |
1137 | 0 | crs0 = d0s + ro0 * d0c - sl0 + ro1 * cl0 - sl1 / 3; |
1138 | 0 | crs1 = d1s + ro1 * d1c - sl1 + ro0 * cl1 - sl0 / 3; |
1139 | | // check angular error (stage 2) |
1140 | 0 | if (!(fabs(crs0) < str->err_a * dot0 && fabs(crs1) < str->err_a * dot1)) |
1141 | 0 | return 0; |
1142 | | |
1143 | 0 | result[0].x = normal[0].v.x + normal[0].v.y * ro0; |
1144 | 0 | result[0].y = normal[0].v.y - normal[0].v.x * ro0; |
1145 | 0 | result[1].x = normal[1].v.x + normal[1].v.y * ro1; |
1146 | 0 | result[1].y = normal[1].v.y - normal[1].v.x * ro1; |
1147 | 0 | return dir; |
1148 | 0 | } |
1149 | | |
1150 | | /** |
1151 | | * \brief Helper function for cubic spline construction |
1152 | | * \param str stroker state |
1153 | | * \param pt array of 4 source spline points |
1154 | | * \param deriv array of 3 differences between adjacent points in normal space |
1155 | | * \param normal first and last spline normal |
1156 | | * \param dir destination outline flags |
1157 | | * \param first true if the current part is at start of the segment |
1158 | | * \return false on allocation failure |
1159 | | */ |
1160 | | static bool process_cubic(StrokerState *str, const ASS_Vector *pt, |
1161 | | const ASS_DVector *deriv, const Normal *normal, |
1162 | | int dir, bool first) |
1163 | 0 | { |
1164 | 0 | double c = vec_dot(normal[0].v, normal[1].v); |
1165 | 0 | double s = vec_crs(normal[0].v, normal[1].v); |
1166 | 0 | double dc[] = { vec_dot(normal[0].v, deriv[1]), vec_dot(normal[1].v, deriv[1]) }; |
1167 | 0 | double ds[] = { vec_crs(normal[0].v, deriv[1]), vec_crs(normal[1].v, deriv[1]) }; |
1168 | 0 | double f0 = normal[0].len * c + normal[1].len + dc[1]; |
1169 | 0 | double f1 = normal[1].len * c + normal[0].len + dc[0]; |
1170 | 0 | double g0 = normal[0].len * s - ds[1]; |
1171 | 0 | double g1 = normal[1].len * s + ds[0]; |
1172 | |
|
1173 | 0 | double abs_s = s; |
1174 | 0 | int check_dir = dir, skip_dir = 2; |
1175 | 0 | int flags = FLAG_INTERSECTION | FLAG_DIR_2; |
1176 | 0 | if (s < 0) { |
1177 | 0 | abs_s = -s; |
1178 | 0 | skip_dir = 1; |
1179 | 0 | flags = 0; |
1180 | 0 | g0 = -g0; |
1181 | 0 | g1 = -g1; |
1182 | 0 | } |
1183 | |
|
1184 | 0 | if (!(dc[0] + dc[1] > 0)) |
1185 | 0 | check_dir = 0; |
1186 | 0 | else if (dir & skip_dir) { |
1187 | 0 | if (f0 < abs_s && f1 < abs_s) { // check for self-intersection |
1188 | 0 | double d2 = (f0 + dc[1]) * normal[1].len + (f1 + dc[0]) * normal[0].len; |
1189 | 0 | d2 = (d2 + vec_dot(deriv[1], deriv[1])) / 2; |
1190 | 0 | if (d2 < g0 && d2 < g1) { |
1191 | 0 | double q = sqrt(d2 / (2 - d2)); |
1192 | 0 | double h0 = (f0 * q + g0) * normal[1].len; |
1193 | 0 | double h1 = (f1 * q + g1) * normal[0].len; |
1194 | 0 | q *= (4.0 / 3) * d2; |
1195 | 0 | if (h0 > q && h1 > q) { |
1196 | 0 | if (!prepare_skip(str, pt[0], skip_dir, first)) |
1197 | 0 | return false; |
1198 | 0 | if (f0 < 0 || f1 < 0) { |
1199 | 0 | ASS_DVector zero_normal = {0, 0}; |
1200 | 0 | if (!emit_point(str, pt[0], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir) || |
1201 | 0 | !emit_point(str, pt[3], zero_normal, OUTLINE_LINE_SEGMENT, skip_dir)) |
1202 | 0 | return false; |
1203 | 0 | } else { |
1204 | 0 | double mul = f0 / abs_s; |
1205 | 0 | ASS_DVector offs = { normal[0].v.x * mul, normal[0].v.y * mul }; |
1206 | 0 | if (!emit_point(str, pt[0], offs, OUTLINE_LINE_SEGMENT, skip_dir)) |
1207 | 0 | return false; |
1208 | 0 | } |
1209 | 0 | dir &= ~skip_dir; |
1210 | 0 | if (!dir) { |
1211 | 0 | str->last_normal = normal[1].v; |
1212 | 0 | return true; |
1213 | 0 | } |
1214 | 0 | } |
1215 | 0 | } |
1216 | 0 | check_dir ^= skip_dir; |
1217 | 0 | } else { |
1218 | 0 | if (ds[0] < 0) |
1219 | 0 | flags ^= MASK_INTERSECTION; |
1220 | 0 | if (ds[1] < 0) |
1221 | 0 | flags ^= MASK_INTERSECTION | FLAG_INTERSECTION; |
1222 | 0 | bool parallel = flags & MASK_INTERSECTION; |
1223 | 0 | int badness = parallel ? 0 : 1; |
1224 | 0 | if (c + g0 < 1) { |
1225 | 0 | if (parallel) { |
1226 | 0 | flags ^= MASK_ZERO_0 | FLAG_ZERO_0; |
1227 | 0 | if (c < 0) |
1228 | 0 | flags ^= MASK_CLIP_0; |
1229 | 0 | if (f0 > abs_s) |
1230 | 0 | flags ^= FLAG_ZERO_0 | FLAG_CLIP_0; |
1231 | 0 | } |
1232 | 0 | badness++; |
1233 | 0 | } else { |
1234 | 0 | flags ^= MASK_INTERSECTION | FLAG_INTERSECTION; |
1235 | 0 | if (!parallel) { |
1236 | 0 | flags ^= MASK_ZERO_0; |
1237 | 0 | if (c > 0) |
1238 | 0 | flags ^= MASK_CLIP_0; |
1239 | 0 | } |
1240 | 0 | } |
1241 | 0 | if (c + g1 < 1) { |
1242 | 0 | if (parallel) { |
1243 | 0 | flags ^= MASK_ZERO_1 | FLAG_ZERO_1; |
1244 | 0 | if (c < 0) |
1245 | 0 | flags ^= MASK_CLIP_1; |
1246 | 0 | if (f1 > abs_s) |
1247 | 0 | flags ^= FLAG_ZERO_1 | FLAG_CLIP_1; |
1248 | 0 | } |
1249 | 0 | badness++; |
1250 | 0 | } else { |
1251 | 0 | flags ^= MASK_INTERSECTION; |
1252 | 0 | if (!parallel) { |
1253 | 0 | flags ^= MASK_ZERO_1; |
1254 | 0 | if (c > 0) |
1255 | 0 | flags ^= MASK_CLIP_1; |
1256 | 0 | } |
1257 | 0 | } |
1258 | 0 | if (badness > 2) |
1259 | 0 | check_dir ^= skip_dir; |
1260 | 0 | } |
1261 | 0 | } |
1262 | | |
1263 | 0 | ASS_DVector result[2]; |
1264 | 0 | if (check_dir) |
1265 | 0 | check_dir = estimate_cubic_error(str, c, s, dc, ds, |
1266 | 0 | normal, result, flags, check_dir); |
1267 | 0 | if (check_dir) { |
1268 | 0 | if (!emit_first_point(str, pt[0], OUTLINE_CUBIC_SPLINE, check_dir)) |
1269 | 0 | return false; |
1270 | 0 | if (!emit_point(str, pt[1], result[0], 0, check_dir) || |
1271 | 0 | !emit_point(str, pt[2], result[1], 0, check_dir)) |
1272 | 0 | return false; |
1273 | 0 | dir &= ~check_dir; |
1274 | 0 | if (!dir) { |
1275 | 0 | str->last_normal = normal[1].v; |
1276 | 0 | return true; |
1277 | 0 | } |
1278 | 0 | } |
1279 | | |
1280 | 0 | ASS_Vector next[7], center; |
1281 | 0 | next[1].x = pt[0].x + pt[1].x; |
1282 | 0 | next[1].y = pt[0].y + pt[1].y; |
1283 | 0 | center.x = pt[1].x + pt[2].x + 2; |
1284 | 0 | center.y = pt[1].y + pt[2].y + 2; |
1285 | 0 | next[5].x = pt[2].x + pt[3].x; |
1286 | 0 | next[5].y = pt[2].y + pt[3].y; |
1287 | 0 | next[2].x = next[1].x + center.x; |
1288 | 0 | next[2].y = next[1].y + center.y; |
1289 | 0 | next[4].x = center.x + next[5].x; |
1290 | 0 | next[4].y = center.y + next[5].y; |
1291 | 0 | next[3].x = (next[2].x + next[4].x - 1) >> 3; |
1292 | 0 | next[3].y = (next[2].y + next[4].y - 1) >> 3; |
1293 | 0 | next[2].x >>= 2; |
1294 | 0 | next[2].y >>= 2; |
1295 | 0 | next[4].x >>= 2; |
1296 | 0 | next[4].y >>= 2; |
1297 | 0 | next[1].x >>= 1; |
1298 | 0 | next[1].y >>= 1; |
1299 | 0 | next[5].x >>= 1; |
1300 | 0 | next[5].y >>= 1; |
1301 | 0 | next[0] = pt[0]; |
1302 | 0 | next[6] = pt[3]; |
1303 | |
|
1304 | 0 | ASS_DVector next_deriv[5], center_deriv; |
1305 | 0 | next_deriv[0].x = deriv[0].x / 2; |
1306 | 0 | next_deriv[0].y = deriv[0].y / 2; |
1307 | 0 | center_deriv.x = deriv[1].x / 2; |
1308 | 0 | center_deriv.y = deriv[1].y / 2; |
1309 | 0 | next_deriv[4].x = deriv[2].x / 2; |
1310 | 0 | next_deriv[4].y = deriv[2].y / 2; |
1311 | 0 | next_deriv[1].x = (next_deriv[0].x + center_deriv.x) / 2; |
1312 | 0 | next_deriv[1].y = (next_deriv[0].y + center_deriv.y) / 2; |
1313 | 0 | next_deriv[3].x = (center_deriv.x + next_deriv[4].x) / 2; |
1314 | 0 | next_deriv[3].y = (center_deriv.y + next_deriv[4].y) / 2; |
1315 | 0 | next_deriv[2].x = (next_deriv[1].x + next_deriv[3].x) / 2; |
1316 | 0 | next_deriv[2].y = (next_deriv[1].y + next_deriv[3].y) / 2; |
1317 | |
|
1318 | 0 | double len = vec_len(next_deriv[2]); |
1319 | 0 | if (len < str->min_len) { // check degenerate case |
1320 | 0 | Normal next_normal[4]; |
1321 | 0 | next_normal[0].v = normal[0].v; |
1322 | 0 | next_normal[0].len = normal[0].len / 2; |
1323 | 0 | next_normal[3].v = normal[1].v; |
1324 | 0 | next_normal[3].len = normal[1].len / 2; |
1325 | |
|
1326 | 0 | next_deriv[1].x += next_deriv[2].x; |
1327 | 0 | next_deriv[1].y += next_deriv[2].y; |
1328 | 0 | next_deriv[3].x += next_deriv[2].x; |
1329 | 0 | next_deriv[3].y += next_deriv[2].y; |
1330 | 0 | next_deriv[2].x = next_deriv[2].y = 0; |
1331 | |
|
1332 | 0 | double len1 = vec_len(next_deriv[1]); |
1333 | 0 | if (len1 < str->min_len) { |
1334 | 0 | next_normal[1] = normal[0]; |
1335 | 0 | } else { |
1336 | 0 | double scale = 1 / len1; |
1337 | 0 | next_normal[1].v.x = next_deriv[1].x * scale; |
1338 | 0 | next_normal[1].v.y = next_deriv[1].y * scale; |
1339 | 0 | next_normal[1].len = len1; |
1340 | 0 | } |
1341 | |
|
1342 | 0 | double len2 = vec_len(next_deriv[3]); |
1343 | 0 | if (len2 < str->min_len) { |
1344 | 0 | next_normal[2] = normal[1]; |
1345 | 0 | } else { |
1346 | 0 | double scale = 1 / len2; |
1347 | 0 | next_normal[2].v.x = next_deriv[3].x * scale; |
1348 | 0 | next_normal[2].v.y = next_deriv[3].y * scale; |
1349 | 0 | next_normal[2].len = len2; |
1350 | 0 | } |
1351 | |
|
1352 | 0 | if (len1 < str->min_len) { |
1353 | 0 | if (!emit_first_point(str, next[0], OUTLINE_LINE_SEGMENT, dir)) |
1354 | 0 | return false; |
1355 | 0 | } else { |
1356 | 0 | if (!process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first)) |
1357 | 0 | return false; |
1358 | 0 | } |
1359 | 0 | if (!start_segment(str, next[2], next_normal[2].v, dir)) |
1360 | 0 | return false; |
1361 | 0 | if (len2 < str->min_len) { |
1362 | 0 | if (!emit_first_point(str, next[3], OUTLINE_LINE_SEGMENT, dir)) |
1363 | 0 | return false; |
1364 | 0 | } else { |
1365 | 0 | if (!process_cubic(str, next + 3, next_deriv + 2, next_normal + 2, dir, false)) |
1366 | 0 | return false; |
1367 | 0 | } |
1368 | 0 | return true; |
1369 | 0 | } |
1370 | | |
1371 | 0 | double scale = 1 / len; |
1372 | 0 | Normal next_normal[3] = { |
1373 | 0 | { normal[0].v, normal[0].len / 2 }, |
1374 | 0 | { { next_deriv[2].x * scale, next_deriv[2].y * scale }, len }, |
1375 | 0 | { normal[1].v, normal[1].len / 2 } |
1376 | 0 | }; |
1377 | 0 | return process_cubic(str, next + 0, next_deriv + 0, next_normal + 0, dir, first) && |
1378 | 0 | process_cubic(str, next + 3, next_deriv + 2, next_normal + 1, dir, false); |
1379 | 0 | } |
1380 | | |
1381 | | /** |
1382 | | * \brief Process source cubic spline |
1383 | | * \param str stroker state |
1384 | | * \param pt1 first middle control point |
1385 | | * \param pt2 second middle control point |
1386 | | * \param pt3 final spline point |
1387 | | * \param dir destination outline flags |
1388 | | * \return false on allocation failure |
1389 | | */ |
1390 | | static bool add_cubic(StrokerState *str, ASS_Vector pt1, ASS_Vector pt2, ASS_Vector pt3, int dir) |
1391 | 0 | { |
1392 | 0 | int flags = 9; |
1393 | |
|
1394 | 0 | int32_t dx0 = pt1.x - str->last_point.x; |
1395 | 0 | int32_t dy0 = pt1.y - str->last_point.y; |
1396 | 0 | if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) { |
1397 | 0 | dx0 = pt2.x - str->last_point.x; |
1398 | 0 | dy0 = pt2.y - str->last_point.y; |
1399 | 0 | if (dx0 > -str->eps && dx0 < str->eps && dy0 > -str->eps && dy0 < str->eps) |
1400 | 0 | return add_line(str, pt3, dir); |
1401 | 0 | flags ^= 1; |
1402 | 0 | } |
1403 | | |
1404 | 0 | int32_t dx2 = pt3.x - pt2.x; |
1405 | 0 | int32_t dy2 = pt3.y - pt2.y; |
1406 | 0 | if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) { |
1407 | 0 | dx2 = pt3.x - pt1.x; |
1408 | 0 | dy2 = pt3.y - pt1.y; |
1409 | 0 | if (dx2 > -str->eps && dx2 < str->eps && dy2 > -str->eps && dy2 < str->eps) |
1410 | 0 | return add_line(str, pt3, dir); |
1411 | 0 | flags ^= 4; |
1412 | 0 | } |
1413 | | |
1414 | 0 | if (flags == 12) |
1415 | 0 | return add_line(str, pt3, dir); |
1416 | | |
1417 | 0 | ASS_Vector pt[4] = { str->last_point, pt1, pt2, pt3 }; |
1418 | 0 | str->last_point = pt3; |
1419 | |
|
1420 | 0 | int32_t dx1 = pt[flags >> 2].x - pt[flags & 3].x; |
1421 | 0 | int32_t dy1 = pt[flags >> 2].y - pt[flags & 3].y; |
1422 | |
|
1423 | 0 | ASS_DVector deriv[3] = { |
1424 | 0 | { dy0 * str->yscale, -dx0 * str->xscale }, |
1425 | 0 | { dy1 * str->yscale, -dx1 * str->xscale }, |
1426 | 0 | { dy2 * str->yscale, -dx2 * str->xscale } |
1427 | 0 | }; |
1428 | 0 | double len0 = vec_len(deriv[0]), scale0 = 1 / len0; |
1429 | 0 | double len2 = vec_len(deriv[2]), scale2 = 1 / len2; |
1430 | 0 | Normal normal[2] = { |
1431 | 0 | { { deriv[0].x * scale0, deriv[0].y * scale0 }, len0 }, |
1432 | 0 | { { deriv[2].x * scale2, deriv[2].y * scale2 }, len2 } |
1433 | 0 | }; |
1434 | |
|
1435 | 0 | bool first = str->contour_start; |
1436 | 0 | return start_segment(str, pt[0], normal[0].v, dir) && |
1437 | 0 | process_cubic(str, pt, deriv, normal, dir, first); |
1438 | 0 | } |
1439 | | |
1440 | | |
1441 | | /** |
1442 | | * \brief Process contour closing |
1443 | | * \param str stroker state |
1444 | | * \param dir destination outline flags |
1445 | | * \return false on allocation failure |
1446 | | */ |
1447 | | static bool close_contour(StrokerState *str, int dir) |
1448 | 11.4k | { |
1449 | 11.4k | if (str->contour_start) { |
1450 | 3 | if ((dir & 3) == 3) |
1451 | 3 | dir = 1; |
1452 | 3 | if (!draw_circle(str, str->last_point, dir)) |
1453 | 0 | return false; |
1454 | 11.4k | } else { |
1455 | 11.4k | if (!add_line(str, str->first_point, dir)) |
1456 | 0 | return false; |
1457 | 11.4k | if (!start_segment(str, str->first_point, str->first_normal, dir)) |
1458 | 0 | return false; |
1459 | 11.4k | if (!emit_point(str, str->first_point, str->first_normal, OUTLINE_LINE_SEGMENT, |
1460 | 11.4k | ~str->last_skip & dir & str->first_skip)) |
1461 | 0 | return false; |
1462 | 11.4k | if (str->last_normal.x != str->first_normal.x || |
1463 | 9.54k | str->last_normal.y != str->first_normal.y) |
1464 | 1.86k | fix_first_point(str, str->first_point, str->last_normal, |
1465 | 1.86k | ~str->last_skip & dir & ~str->first_skip); |
1466 | 11.4k | str->contour_start = true; |
1467 | 11.4k | } |
1468 | 11.4k | if (dir & 1) |
1469 | 11.4k | ass_outline_close_contour(str->result[0]); |
1470 | 11.4k | if (dir & 2) |
1471 | 11.4k | ass_outline_close_contour(str->result[1]); |
1472 | 11.4k | str->contour_first[0] = str->result[0]->n_points; |
1473 | 11.4k | str->contour_first[1] = str->result[1]->n_points; |
1474 | 11.4k | return true; |
1475 | 11.4k | } |
1476 | | |
1477 | | |
1478 | | /* |
1479 | | * Stroke an outline glyph in x/y direction. |
1480 | | * \param result first result outline |
1481 | | * \param result1 second result outline |
1482 | | * \param path source outline |
1483 | | * \param xbord border size in X direction |
1484 | | * \param ybord border size in Y direction |
1485 | | * \param eps approximate allowable error |
1486 | | * \return false on allocation failure |
1487 | | */ |
1488 | | bool ass_outline_stroke(ASS_Outline *result, ASS_Outline *result1, |
1489 | | const ASS_Outline *path, int xbord, int ybord, int eps) |
1490 | 8.02k | { |
1491 | 8.02k | ass_outline_alloc(result, 2 * path->n_points, 2 * path->n_segments); |
1492 | 8.02k | ass_outline_alloc(result1, 2 * path->n_points, 2 * path->n_segments); |
1493 | 8.02k | if (!result->max_points || !result1->max_points) |
1494 | 0 | return false; |
1495 | | |
1496 | 8.02k | const int dir = 3; |
1497 | 8.02k | int rad = FFMAX(xbord, ybord); |
1498 | 8.02k | assert(rad >= eps && rad <= OUTLINE_MAX); |
1499 | | |
1500 | 8.02k | StrokerState str; |
1501 | 8.02k | str.result[0] = result; |
1502 | 8.02k | str.result[1] = result1; |
1503 | 8.02k | str.contour_first[0] = 0; |
1504 | 8.02k | str.contour_first[1] = 0; |
1505 | 8.02k | str.xbord = xbord; |
1506 | 8.02k | str.ybord = ybord; |
1507 | 8.02k | str.xscale = 1.0 / FFMAX(eps, xbord); |
1508 | 8.02k | str.yscale = 1.0 / FFMAX(eps, ybord); |
1509 | 8.02k | str.eps = eps; |
1510 | | |
1511 | 8.02k | str.contour_start = true; |
1512 | 8.02k | double rel_err = (double) eps / rad; |
1513 | 8.02k | str.merge_cos = 1 - rel_err; |
1514 | 8.02k | double e = sqrt(2 * rel_err); |
1515 | 8.02k | str.split_cos = 1 + 8 * rel_err - 4 * (1 + rel_err) * e; |
1516 | 8.02k | str.min_len = rel_err / 4; |
1517 | 8.02k | str.err_q = 8 * (1 + rel_err) * (1 + rel_err); |
1518 | 8.02k | str.err_c = 390 * rel_err * rel_err; |
1519 | 8.02k | str.err_a = e; |
1520 | | |
1521 | 8.02k | #ifndef NDEBUG |
1522 | 224k | for (size_t i = 0; i < path->n_points; i++) |
1523 | 216k | assert(abs(path->points[i].x) <= OUTLINE_MAX && abs(path->points[i].y) <= OUTLINE_MAX); |
1524 | 8.02k | #endif |
1525 | | |
1526 | 8.02k | ASS_Vector *start = path->points, *cur = start; |
1527 | 151k | for (size_t i = 0; i < path->n_segments; i++) { |
1528 | 143k | if (start == cur) |
1529 | 11.4k | str.last_point = *start; |
1530 | | |
1531 | 143k | int n = path->segments[i] & OUTLINE_COUNT_MASK; |
1532 | 143k | cur += n; |
1533 | | |
1534 | 143k | ASS_Vector *end = cur; |
1535 | 143k | if (path->segments[i] & OUTLINE_CONTOUR_END) { |
1536 | 11.4k | end = start; |
1537 | 11.4k | start = cur; |
1538 | 11.4k | } |
1539 | | |
1540 | 143k | switch (n) { |
1541 | 69.4k | case OUTLINE_LINE_SEGMENT: |
1542 | 69.4k | if (!add_line(&str, *end, dir)) |
1543 | 0 | return false; |
1544 | 69.4k | break; |
1545 | | |
1546 | 73.7k | case OUTLINE_QUADRATIC_SPLINE: |
1547 | 73.7k | if (!add_quadratic(&str, cur[-1], *end, dir)) |
1548 | 0 | return false; |
1549 | 73.7k | break; |
1550 | | |
1551 | 73.7k | case OUTLINE_CUBIC_SPLINE: |
1552 | 0 | if (!add_cubic(&str, cur[-2], cur[-1], *end, dir)) |
1553 | 0 | return false; |
1554 | 0 | break; |
1555 | | |
1556 | 0 | default: |
1557 | 0 | return false; |
1558 | 143k | } |
1559 | | |
1560 | 143k | if (start == cur && !close_contour(&str, dir)) |
1561 | 0 | return false; |
1562 | 143k | } |
1563 | 8.02k | assert(start == cur && cur == path->points + path->n_points); |
1564 | 8.02k | return true; |
1565 | 8.02k | } |