2 * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #ifndef _TVG_SW_STROKER_H_
18 #define _TVG_SW_STROKER_H_
20 #include "tvgSwCommon.h"
23 /************************************************************************/
24 /* Internal Class Implementation */
25 /************************************************************************/
26 static constexpr auto SW_STROKE_TAG_POINT = 1;
27 static constexpr auto SW_STROKE_TAG_CUBIC = 2;
28 static constexpr auto SW_STROKE_TAG_BEGIN = 4;
29 static constexpr auto SW_STROKE_TAG_END = 8;
31 static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
33 return (SW_ANGLE_PI2 - (s) * SW_ANGLE_PI);
37 static void _growBorder(SwStrokeBorder* border, uint32_t newPts)
41 auto maxOld = border->maxPts;
42 auto maxNew = border->ptsCnt + newPts;
44 if (maxNew <= maxOld) return;
48 while (maxCur < maxNew)
49 maxCur += (maxCur >> 1) + 16;
51 border->pts = static_cast<SwPoint*>(realloc(border->pts, maxCur * sizeof(SwPoint)));
54 border->tags = static_cast<uint8_t*>(realloc(border->tags, maxCur * sizeof(uint8_t)));
57 border->maxPts = maxCur;
61 static void _borderClose(SwStrokeBorder* border, bool reverse)
63 assert(border && border->start >= 0);
65 uint32_t start = border->start;
66 uint32_t count = border->ptsCnt;
68 //Don't record empty paths!
69 if (count <= start + 1U) {
70 border->ptsCnt = start;
72 /* Copy the last point to the start of this sub-path,
73 since it contains the adjusted starting coordinates */
74 border->ptsCnt = --count;
75 border->pts[start] = border->pts[count];
79 auto pt1 = border->pts + start + 1;
80 auto pt2 = border->pts + count - 1;
91 auto tag1 = border->tags + start + 1;
92 auto tag2 = border->tags + count - 1;
103 border->tags[start] |= SW_STROKE_TAG_BEGIN;
104 border->tags[count - 1] |= SW_STROKE_TAG_END;
108 border->movable = false;
112 static void _borderCubicTo(SwStrokeBorder* border, SwPoint& ctrl1, SwPoint& ctrl2, SwPoint& to)
114 assert(border && border->start >= 0);
116 _growBorder(border, 3);
118 auto pt = border->pts + border->ptsCnt;
119 auto tag = border->tags + border->ptsCnt;
125 tag[0] = SW_STROKE_TAG_CUBIC;
126 tag[1] = SW_STROKE_TAG_CUBIC;
127 tag[2] = SW_STROKE_TAG_POINT;
131 border->movable = false;
135 static void _borderArcTo(SwStrokeBorder* border, SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff)
137 constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
138 SwPoint a = {radius, 0};
139 mathRotate(a, angleStart);
142 auto total = angleDiff;
143 auto angle = angleStart;
144 auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2;
148 if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE;
149 else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE;
151 auto next = angle + step;
153 if (theta < 0) theta = -theta;
158 SwPoint b = {radius, 0};
162 //compute first and second control points
163 auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
165 SwPoint a2 = {length, 0};
166 mathRotate(a2, angle + rotate);
169 SwPoint b2 = {length, 0};
170 mathRotate(b2, next - rotate);
174 _borderCubicTo(border, a2, b2, b);
176 //process the rest of the arc?
184 static void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable)
186 assert(border && border->start >= 0);
188 if (border->movable) {
190 border->pts[border->ptsCnt - 1] = to;
193 //don't add zero-length line_to
194 if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
196 _growBorder(border, 1);
197 border->pts[border->ptsCnt] = to;
198 border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT;
202 border->movable = movable;
206 static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
210 //close current open path if any?
211 if (border->start >= 0) _borderClose(border, false);
213 border->start = border->ptsCnt;
214 border->movable = false;
216 _borderLineTo(border, to, false);
220 static void _arcTo(SwStroke& stroke, int32_t side)
222 auto border = stroke.borders + side;
223 auto rotate = SIDE_TO_ROTATE(side);
224 auto total = mathDiff(stroke.angleIn, stroke.angleOut);
225 if (total == SW_ANGLE_PI) total = -rotate * 2;
227 _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total);
228 border->movable = false;
232 static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
234 constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
236 assert(MITER_LIMIT >= 0x10000);
238 auto border = stroke.borders + side;
241 if (stroke.join == StrokeJoin::Round) {
242 _arcTo(stroke, side);
244 //this is a mitered (pointed) or beveled (truncated) corner
245 auto rotate = SIDE_TO_ROTATE(side);
246 auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false;
251 auto theta = mathDiff(stroke.angleIn, stroke.angleOut);
252 if (theta == SW_ANGLE_PI) {
254 phi = stroke.angleIn;
257 phi = stroke.angleIn + theta + rotate;
260 thcos = mathCos(theta);
261 auto sigma = mathMultiply(MITER_LIMIT, thcos);
263 //is miter limit exceeded?
264 if (sigma < 0x10000L) bevel = true;
267 //this is a bevel (broken angle)
269 SwPoint delta = {stroke.width, 0};
270 mathRotate(delta, stroke.angleOut + rotate);
271 delta += stroke.center;
272 border->movable = false;
273 _borderLineTo(border, delta, false);
274 //this is a miter (intersection)
276 auto length = mathDivide(stroke.width, thcos);
277 SwPoint delta = {length, 0};
278 mathRotate(delta, phi);
279 delta += stroke.center;
280 _borderLineTo(border, delta, false);
282 /* Now add and end point
283 Only needed if not lineto (lineLength is zero for curves) */
284 if (lineLength == 0) {
285 delta = {stroke.width, 0};
286 mathRotate(delta, stroke.angleOut + rotate);
287 delta += stroke.center;
288 _borderLineTo(border, delta, false);
295 static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
297 auto border = stroke.borders + side;
298 auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
300 bool intersect = false;
302 /* Only intersect borders if between two line_to's and both
303 lines are long enough (line length is zero fur curves). */
304 if (border->movable && lineLength > 0) {
305 //compute minimum required length of lines
306 SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
307 if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
310 auto rotate = SIDE_TO_ROTATE(side);
313 delta = {stroke.width, 0};
314 mathRotate(delta, stroke.angleOut + rotate);
315 delta += stroke.center;
316 border->movable = false;
318 //compute median angle
319 auto phi = stroke.angleIn + theta;
320 auto thcos = mathCos(theta);
321 auto length = mathDivide(stroke.width, thcos);
323 mathRotate(delta, phi + rotate);
324 delta += stroke.center;
327 _borderLineTo(border, delta, false);
331 void _processCorner(SwStroke& stroke, SwFixed lineLength)
333 auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
335 //no specific corner processing is required if the turn is 0
336 if (turn == 0) return;
338 //when we turn to the right, the inside side is 0
341 //otherwise, the inside is 1
342 if (turn < 0) inside = 1;
345 _inside(stroke, inside, lineLength);
347 //process the outside
348 _outside(stroke, 1 - inside, lineLength);
352 void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
354 SwPoint delta = {stroke.width, 0};
355 mathRotate(delta, startAngle + SW_ANGLE_PI2);
357 auto pt = stroke.center + delta;
358 auto border = stroke.borders;
359 _borderMoveTo(border, pt);
361 pt = stroke.center - delta;
363 _borderMoveTo(border, pt);
365 /* Save angle, position and line length for last join
366 lineLength is zero for curves */
367 stroke.subPathAngle = startAngle;
368 stroke.firstPt = false;
369 stroke.subPathLineLength = lineLength;
373 static void _lineTo(SwStroke& stroke, const SwPoint& to)
375 auto delta = to - stroke.center;
377 //a zero-length lineto is a no-op; avoid creating a spurious corner
378 if (delta.zero()) return;
380 //compute length of line
381 auto lineLength = mathLength(delta);
382 auto angle = mathAtan(delta);
384 delta = {stroke.width, 0};
385 mathRotate(delta, angle + SW_ANGLE_PI2);
387 //process corner if necessary
388 if (stroke.firstPt) {
389 /* This is the first segment of a subpath. We need to add a point to each border
390 at their respective starting point locations. */
391 _firstSubPath(stroke, angle, lineLength);
393 //process the current corner
394 stroke.angleOut = angle;
395 _processCorner(stroke, lineLength);
398 //now add a line segment to both the inside and outside paths
399 auto border = stroke.borders;
403 auto pt = to + delta;
405 //the ends of lineto borders are movable
406 _borderLineTo(border, pt, true);
415 stroke.angleIn = angle;
417 stroke.lineLength = lineLength;
421 static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
423 /* if all control points are coincident, this is a no-op;
424 avoid creating a spurious corner */
425 if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) {
430 SwPoint bezStack[37]; //TODO: static?
431 auto firstArc = true;
432 auto limit = bezStack + 32;
437 arc[3] = stroke.center;
439 while (arc >= bezStack) {
440 SwFixed angleIn, angleOut, angleMid;
442 //initialize with current direction
443 angleIn = angleOut = angleMid = stroke.angleIn;
445 if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
446 if (stroke.firstPt) stroke.angleIn = angleIn;
454 //process corner if necessary
455 if (stroke.firstPt) {
456 _firstSubPath(stroke, angleIn, 0);
458 stroke.angleOut = angleIn;
459 _processCorner(stroke, 0);
461 } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) {
462 //if the deviation from one arc to the next is too great add a round corner
463 stroke.center = arc[3];
464 stroke.angleOut = angleIn;
465 stroke.join = StrokeJoin::Round;
467 _processCorner(stroke, 0);
469 //reinstate line join style
470 stroke.join = stroke.joinSaved;
473 //the arc's angle is small enough; we can add it directly to each border
474 auto theta1 = mathDiff(angleIn, angleMid) / 2;
475 auto theta2 = mathDiff(angleMid, angleOut) / 2;
476 auto phi1 = mathMean(angleIn, angleMid);
477 auto phi2 = mathMean(angleMid, angleOut);
478 auto length1 = mathDivide(stroke.width, mathCos(theta1));
479 auto length2 = mathDivide(stroke.width, mathCos(theta2));
482 //compute direction of original arc
483 if (stroke.handleWideStrokes) {
484 alpha0 = mathAtan(arc[0] - arc[3]);
487 auto border = stroke.borders;
492 auto rotate = SIDE_TO_ROTATE(side);
494 //compute control points
495 SwPoint _ctrl1 = {length1, 0};
496 mathRotate(_ctrl1, phi1 + rotate);
499 SwPoint _ctrl2 = {length2, 0};
500 mathRotate(_ctrl2, phi2 + rotate);
504 SwPoint _end = {stroke.width, 0};
505 mathRotate(_end, angleOut + rotate);
508 if (stroke.handleWideStrokes) {
510 /* determine whether the border radius is greater than the radius of
511 curvature of the original arc */
512 auto _start = border->pts[border->ptsCnt - 1];
513 auto alpha1 = mathAtan(_end - _start);
515 //is the direction of the border arc opposite to that of the original arc?
516 if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) {
518 //use the sine rule to find the intersection point
519 auto beta = mathAtan(arc[3] - _start);
520 auto gamma = mathAtan(arc[0] - _end);
521 auto bvec = _end - _start;
522 auto blen = mathLength(bvec);
523 auto sinA = abs(mathSin(alpha1 - gamma));
524 auto sinB = abs(mathSin(beta - gamma));
525 auto alen = mathMulDiv(blen, sinA, sinB);
527 SwPoint delta = {alen, 0};
528 mathRotate(delta, beta);
531 //circumnavigate the negative sector backwards
532 border->movable = false;
533 _borderLineTo(border, delta, false);
534 _borderLineTo(border, _end, false);
535 _borderCubicTo(border, _ctrl2, _ctrl1, _start);
537 //and then move to the endpoint
538 _borderLineTo(border, _end, false);
547 _borderCubicTo(border, _ctrl1, _ctrl2, _end);
552 stroke.angleIn = angleOut;
558 static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side)
560 if (stroke.cap == StrokeCap::Square) {
561 auto rotate = SIDE_TO_ROTATE(side);
562 auto border = stroke.borders + side;
564 SwPoint delta = {stroke.width, 0};
565 mathRotate(delta, angle);
567 SwPoint delta2 = {stroke.width, 0};
568 mathRotate(delta2, angle + rotate);
569 delta += stroke.center + delta2;
571 _borderLineTo(border, delta, false);
573 delta = {stroke.width, 0};
574 mathRotate(delta, angle);
576 delta2 = {stroke.width, 0};
577 mathRotate(delta2, angle - rotate);
579 delta += delta2 + stroke.center;
581 _borderLineTo(border, delta, false);
583 } else if (stroke.cap == StrokeCap::Round) {
585 stroke.angleIn = angle;
586 stroke.angleOut = angle + SW_ANGLE_PI;
587 _arcTo(stroke, side);
591 auto rotate = SIDE_TO_ROTATE(side);
592 auto border = stroke.borders + side;
594 SwPoint delta = {stroke.width, 0};
595 mathRotate(delta, angle + rotate);
597 delta += stroke.center;
599 _borderLineTo(border, delta, false);
601 delta = {stroke.width, 0};
602 mathRotate(delta, angle - rotate);
604 delta += stroke.center;
606 _borderLineTo(border, delta, false);
611 static void _addReverseLeft(SwStroke& stroke, bool opened)
613 auto right = stroke.borders + 0;
614 auto left = stroke.borders + 1;
615 assert(left->start >= 0);
617 auto newPts = left->ptsCnt - left->start;
619 if (newPts <= 0) return;
621 _growBorder(right, newPts);
623 auto dstPt = right->pts + right->ptsCnt;
624 auto dstTag = right->tags + right->ptsCnt;
625 auto srcPt = left->pts + left->ptsCnt - 1;
626 auto srcTag = left->tags + left->ptsCnt - 1;
628 while (srcPt >= left->pts + left->start) {
633 dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
635 //switch begin/end tags if necessary
636 auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
637 if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
638 dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
647 left->ptsCnt = left->start;
648 right->ptsCnt += newPts;
649 right->movable = false;
650 left->movable = false;
654 static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened)
656 /* We cannot process the first point because there is not enough
657 information regarding its corner/cap. Later, it will be processed
658 in the _endSubPath() */
660 stroke.firstPt = true;
662 stroke.openSubPath = opened;
664 /* Determine if we need to check whether the border radius is greater
665 than the radius of curvature of a curve, to handle this case specially.
666 This is only required if bevel joins or butt caps may be created because
667 round & miter joins and round & square caps cover the nagative sector
668 created with wide strokes. */
669 if ((stroke.join != StrokeJoin::Round) || (stroke.openSubPath && stroke.cap == StrokeCap::Butt))
670 stroke.handleWideStrokes = true;
672 stroke.handleWideStrokes = false;
674 stroke.ptStartSubPath = to;
679 static void _endSubPath(SwStroke& stroke)
681 if (stroke.openSubPath) {
682 auto right = stroke.borders;
685 /* all right, this is an opened path, we need to add a cap between
686 right & left, add the reverse of left, then add a final cap
687 between left & right */
688 _addCap(stroke, stroke.angleIn, 0);
690 //add reversed points from 'left' to 'right'
691 _addReverseLeft(stroke, true);
693 //now add the final cap
694 stroke.center = stroke.ptStartSubPath;
695 _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0);
697 /* now end the right subpath accordingly. The left one is rewind
698 and deosn't need further processing */
699 _borderClose(right, false);
702 //close the path if needed
703 if (stroke.center != stroke.ptStartSubPath)
704 _lineTo(stroke, stroke.ptStartSubPath);
707 stroke.angleOut = stroke.subPathAngle;
708 auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
710 //No specific corner processing is required if the turn is 0
713 //when we turn to the right, the inside is 0
716 //otherwise, the inside is 1
717 if (turn < 0) inside = 1;
719 _inside(stroke, inside, stroke.subPathLineLength); //inside
720 _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside
723 _borderClose(stroke.borders + 0, false);
724 _borderClose(stroke.borders + 1, true);
729 static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt)
733 auto count = border->ptsCnt;
734 auto tags = border->tags;
735 uint32_t _ptsCnt = 0;
736 uint32_t _cntrsCnt = 0;
741 if (tags[0] & SW_STROKE_TAG_BEGIN) {
742 if (inCntr) goto fail;
744 } else if (!inCntr) goto fail;
746 if (tags[0] & SW_STROKE_TAG_END) {
755 if (inCntr) goto fail;
756 border->valid = true;
758 cntrsCnt = _cntrsCnt;
768 static void _exportBorderOutline(SwStroke& stroke, SwOutline* outline, uint32_t side)
770 auto border = stroke.borders + side;
773 if (!border->valid) return;
775 memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint));
777 auto cnt = border->ptsCnt;
778 auto src = border->tags;
779 auto tags = outline->types + outline->ptsCnt;
780 auto cntrs = outline->cntrs + outline->cntrsCnt;
781 uint16_t idx = outline->ptsCnt;
785 if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
786 else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
787 else cout << "what type of stroke outline??" << endl;
789 if (*src & SW_STROKE_TAG_END) {
799 outline->ptsCnt = outline->ptsCnt + border->ptsCnt;
803 /************************************************************************/
804 /* External Class Implementation */
805 /************************************************************************/
807 void strokeFree(SwStroke* stroke)
812 if (stroke->borders[0].pts) free(stroke->borders[0].pts);
813 if (stroke->borders[0].tags) free(stroke->borders[0].tags);
814 if (stroke->borders[1].pts) free(stroke->borders[1].pts);
815 if (stroke->borders[1].tags) free(stroke->borders[1].tags);
821 void strokeReset(SwStroke& stroke, const Shape* sdata, const Matrix* transform)
825 //If x/y scale factor is identical, we can scale width size simply.
827 if (transform && fabsf(transform->e11 - transform->e22) < FLT_EPSILON) {
828 scale = transform->e11;
829 stroke.preScaled = true;
832 stroke.width = TO_SWCOORD(sdata->strokeWidth() * 0.5 * scale);
833 stroke.cap = sdata->strokeCap();
835 //Save line join: it can be temporarily changed when stroking curves...
836 stroke.joinSaved = stroke.join = sdata->strokeJoin();
838 stroke.borders[0].ptsCnt = 0;
839 stroke.borders[0].start = -1;
840 stroke.borders[0].valid = false;
842 stroke.borders[1].ptsCnt = 0;
843 stroke.borders[1].start = -1;
844 stroke.borders[1].valid = false;
848 bool strokeParseOutline(SwStroke& stroke, const SwOutline& outline)
852 for (uint32_t i = 0; i < outline.cntrsCnt; ++i) {
853 auto last = outline.cntrs[i]; //index of last point in contour
854 auto limit = outline.pts + last;
863 auto start = outline.pts[first];
865 auto pt = outline.pts + first;
867 auto types = outline.types + first;
870 auto type = types[0];
872 //A contour cannot start with a cubic control point
873 if (type == SW_CURVE_TYPE_CUBIC) return false;
875 _beginSubPath(stroke, start, outline.opened);
881 //emit a signel line_to
882 if (types[0] == SW_CURVE_TYPE_POINT) {
883 _lineTo(stroke, *pt);
886 if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false;
892 _cubicTo(stroke, pt[-2], pt[-1], pt[0]);
895 _cubicTo(stroke, pt[-2], pt[-1], start);
901 if (!stroke.firstPt) _endSubPath(stroke);
908 SwOutline* strokeExportOutline(SwStroke& stroke)
910 uint32_t count1, count2, count3, count4;
912 _getCounts(stroke.borders + 0, count1, count2);
913 _getCounts(stroke.borders + 1, count3, count4);
915 auto ptsCnt = count1 + count3;
916 auto cntrsCnt = count2 + count4;
918 auto outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline)));
921 outline->pts = static_cast<SwPoint*>(malloc(sizeof(SwPoint) * ptsCnt));
922 assert(outline->pts);
924 outline->types = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * ptsCnt));
925 assert(outline->types);
927 outline->cntrs = static_cast<uint32_t*>(malloc(sizeof(uint32_t) * cntrsCnt));
928 assert(outline->cntrs);
930 _exportBorderOutline(stroke, outline, 0); //left
931 _exportBorderOutline(stroke, outline, 1); //right
936 #endif /* _TVG_SW_STROKER_H_ */