From b4cf6660b749dccb686ac4c7f82fa10f71d041a0 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Thu, 28 May 2020 20:37:25 +0900 Subject: [PATCH 01/16] test: recover sample build. introduced by mistake. Change-Id: I8fd88054da1e86cace02931791a646a182ab6721 --- test/makefile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/makefile b/test/makefile index 174b93f..3f314e8 100644 --- a/test/makefile +++ b/test/makefile @@ -1,13 +1,13 @@ all: -# gcc -o testShape testShape.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testMultiShapes testMultiShapes.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testBoundary testBoundary.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testPath testPath.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testPathCopy testPathCopy.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testBlending testBlending.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testUpdate testUpdate.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testDirectUpdate testDirectUpdate.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testScene testScene.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testTransform testTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -# gcc -o testSceneTransform testSceneTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testShape testShape.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testMultiShapes testMultiShapes.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testBoundary testBoundary.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testPath testPath.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testPathCopy testPathCopy.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testBlending testBlending.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testUpdate testUpdate.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testDirectUpdate testDirectUpdate.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testScene testScene.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testTransform testTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testSceneTransform testSceneTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` gcc -o testStroke testStroke.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` -- 2.7.4 From 674483845305bc2fdd862cdd7e188220f0e4d1d7 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 29 May 2020 13:33:46 +0900 Subject: [PATCH 02/16] sw_engine: Fix build error (no match type) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit ../src/lib/sw_engine/tvgSwStroke.cpp:282:72: error: no match for ‘operator<’ (operand types are ‘long int’ and ‘const SwPoint’) if (border->ptsCnt > 0 && abs(diff.x) < EPSILON && abs(diff.y) < EPSILON) return; Change-Id: I426f8980ba718e3dc908dc32a62fb897b5b5fbbf --- src/lib/sw_engine/tvgSwStroke.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index 2f810a1..acaf2c6 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -269,7 +269,7 @@ static void _growBorder(SwStrokeBorder* border, uint32_t newPts) static void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable) { - constexpr SwPoint EPSILON = 2; + constexpr SwCoord EPSILON = 2; assert(border && border->start >= 0); -- 2.7.4 From 41dbd9774a73d3c04de1f3c86a4444763bb54b1b Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Fri, 29 May 2020 11:47:55 +0900 Subject: [PATCH 03/16] sw_engine: implement stroke cubicto, arcto, lineto Change-Id: I59e95b1031ebfaf54e966cab334e045613ca3830 --- src/lib/sw_engine/meson.build | 1 + src/lib/sw_engine/tvgSwCommon.h | 39 ++- src/lib/sw_engine/tvgSwMath.cpp | 407 ++++++++++++++++++++++ src/lib/sw_engine/tvgSwRle.cpp | 30 +- src/lib/sw_engine/tvgSwStroke.cpp | 711 +++++++++++++++++++++++--------------- 5 files changed, 885 insertions(+), 303 deletions(-) create mode 100644 src/lib/sw_engine/tvgSwMath.cpp diff --git a/src/lib/sw_engine/meson.build b/src/lib/sw_engine/meson.build index b666471..f4998f4 100644 --- a/src/lib/sw_engine/meson.build +++ b/src/lib/sw_engine/meson.build @@ -1,5 +1,6 @@ source_file = [ 'tvgSwCommon.h', + 'tvgSwMath.cpp', 'tvgSwRenderer.h', 'tvgSwRaster.cpp', 'tvgSwRenderer.cpp', diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index 66d6fe0..b942ca1 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -27,10 +27,6 @@ constexpr auto SW_CURVE_TYPE_CUBIC = 1; constexpr auto SW_OUTLINE_FILL_WINDING = 0; constexpr auto SW_OUTLINE_FILL_EVEN_ODD = 1; -constexpr auto SW_STROKE_TAG_ON = 1; -constexpr auto SW_STROKE_TAG_BEGIN = 4; -constexpr auto SW_STROKE_TAG_END = 8; - using SwCoord = signed long; using SwFixed = signed long long; @@ -59,6 +55,20 @@ struct SwPoint bool operator!=(const SwPoint& rhs) const { return (x != rhs.x || y != rhs.y); } + + bool zero() + { + if (x == 0 && y == 0) return true; + else return false; + } + + bool small() + { + //2 is epsilon... + if (abs(x) < 2 && abs(y) < 2) return true; + else return false; + } + }; struct SwSize @@ -141,6 +151,13 @@ struct SwShape SwBBox bbox; }; + +constexpr static SwFixed ANGLE_PI = (180L << 16); +constexpr static SwFixed ANGLE_2PI = (ANGLE_PI << 1); +constexpr static SwFixed ANGLE_PI2 = (ANGLE_PI >> 1); +constexpr static SwFixed ANGLE_PI4 = (ANGLE_PI >> 2); + + static inline SwPoint TO_SWPOINT(const Point* pt) { return {SwCoord(pt->x * 64), SwCoord(pt->y * 64)}; @@ -153,6 +170,20 @@ static inline SwCoord TO_SWCOORD(float val) } +int64_t mathMultiply(int64_t a, int64_t b); +int64_t mathDivide(int64_t a, int64_t b); +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c); +void mathRotate(SwPoint& pt, SwFixed angle); +SwFixed mathTan(SwFixed angle); +SwFixed mathAtan(const SwPoint& pt); +SwFixed mathCos(SwFixed angle); +SwFixed mathSin(SwFixed angle); +void mathSplitCubic(SwPoint* base); +SwFixed mathDiff(SwFixed angle1, SwFixed angle2); +SwFixed mathLength(SwPoint& pt); +bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); +SwFixed mathMean(SwFixed angle1, SwFixed angle2); + void shapeReset(SwShape& sdata); bool shapeGenOutline(const Shape& shape, SwShape& sdata); bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip); diff --git a/src/lib/sw_engine/tvgSwMath.cpp b/src/lib/sw_engine/tvgSwMath.cpp new file mode 100644 index 0000000..919fe1e --- /dev/null +++ b/src/lib/sw_engine/tvgSwMath.cpp @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _TVG_SW_MATH_H_ +#define _TVG_SW_MATH_H_ + +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +constexpr auto CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 + +//this table was generated for SW_FT_PI = 180L << 16, i.e. degrees +constexpr static auto ATAN_MAX = 23; +constexpr static SwFixed ATAN_TBL[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, + 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, + 57L, 29L, 14L, 7L, 4L, 2L, 1L}; + +static inline SwCoord SATURATE(const SwCoord x) +{ + return (x >> (sizeof(SwCoord) * 8 - 1)); +} + + +static inline SwFixed PAD_ROUND(const SwFixed x, int32_t n) +{ + return (((x) + ((n)/2)) & ~((n)-1)); +} + + +static SwCoord _downscale(SwCoord x) +{ + //multiply a give value by the CORDIC shrink factor + + abs(x); + int64_t t = (x * static_cast(CORDIC_FACTOR)) + 0x100000000UL; + x = static_cast(t >> 32); + if (x < 0) x = -x; + return x; +} + + +static int32_t _normalize(SwPoint& pt) +{ + /* the highest bit in overflow-safe vector components + MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ + constexpr auto SAFE_MSB = 29; + + auto v = pt; + + //High order bit(MSB) + //clz: count leading zero’s + auto shift = 31 - __builtin_clz(abs(v.x) | abs(v.y)); + + if (shift <= SAFE_MSB) { + shift = SAFE_MSB - shift; + pt.x = static_cast((unsigned long)v.x << shift); + pt.y = static_cast((unsigned long)v.y << shift); + } else { + shift -= SAFE_MSB; + pt.x = v.x >> shift; + pt.y = v.y >> shift; + shift = -shift; + } + return shift; +} + + +static void _polarize(SwPoint& pt) +{ + auto v = pt; + SwFixed theta; + + //Get the vector into [-PI/4, PI/4] sector + if (v.y > v.x) { + if (v.y > -v.x) { + auto tmp = v.y; + v.y = -v.x; + v.x = tmp; + theta = ANGLE_PI2; + } else { + theta = v.y > 0 ? ANGLE_PI : -ANGLE_PI; + v.x = -v.x; + v.y = -v.y; + } + } else { + if (v.y < -v.x) { + theta = -ANGLE_PI2; + auto tmp = -v.y; + v.y = v.x; + v.x = tmp; + } else { + theta = 0; + } + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + //Pseudorotations. with right shifts + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (v.y > 0) { + auto tmp = v.x + ((v.y + j) >> i); + v.y = v.y - ((v.x + j) >> i); + v.x = tmp; + theta += *atan++; + } else { + auto tmp = v.x - ((v.y + j) >> i); + v.y = v.y + ((v.x + j) >> i); + v.x = tmp; + theta -= *atan++; + } + } + + //round theta + if (theta >= 0) theta = PAD_ROUND(theta, 32); + else theta = -PAD_ROUND(-theta, 32); + + pt.x = v.x; + pt.y = theta; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwFixed mathMean(SwFixed angle1, SwFixed angle2) +{ + return angle1 + mathDiff(angle1, angle2) / 2; +} + + +bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) +{ + auto d1 = base[2] - base[3]; + auto d2 = base[1] - base[2]; + auto d3 = base[0] - base[1]; + + if (d1.small()) { + if (d2.small()) { + if (d3.small()) { + //basically a point. + //do nothing to retain original direction + } else { + angleIn = angleMid = angleOut = mathAtan(d3); + } + } else { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d2); + } else { + angleIn = angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } else { + if (d2.small()) { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d1); + } else { + angleIn = mathAtan(d1); + angleOut = mathAtan(d3); + angleMid = mathMean(angleIn, angleOut); + } + } else { + if (d3.small()) { + angleIn = mathAtan(d1); + angleMid = angleOut = mathAtan(d2); + } else { + angleIn = mathAtan(d1); + angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } + + auto theta1 = abs(mathDiff(angleIn, angleMid)); + auto theta2 = abs(mathDiff(angleMid, angleOut)); + + if ((theta1 < (ANGLE_PI / 8)) && (theta2 < (ANGLE_PI / 8))) return true; + else return false; +} + + +int64_t mathMultiply(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t c = (a * b + 0x8000L ) >> 16; + return (s > 0) ? c : -c; +} + + +int64_t mathDivide(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; + return (s < 0 ? -q : q); +} + + +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + if (c < 0) { + c = -c; + s = -s; + } + int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL; + + return (s > 0 ? -d : d); +} + + +void mathRotate(SwPoint& pt, SwFixed angle) +{ + if (angle == 0 || (pt.x == 0 && pt.y == 0)) return; + + auto v = pt; + auto shift = _normalize(v); + auto theta = angle; + + //Rotate inside [-PI/4, PI/4] sector + while (theta < -ANGLE_PI4) { + auto tmp = v.y; + v.y = -v.x; + v.x = tmp; + theta += ANGLE_PI2; + } + + while (theta > ANGLE_PI4) { + auto tmp = -v.y; + v.y = v.x; + v.x = tmp; + theta -= ANGLE_PI2; + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (theta < 0) { + auto tmp = v.x + ((v.y + j) >> i); + v.y = v.y - ((v.x + j) >> i); + v.x = tmp; + theta += *atan++; + }else { + auto tmp = v.x - ((v.y + j) >> i); + v.y = v.y + ((v.x + j) >> i); + v.x = tmp; + theta -= *atan++; + } + } + + v.x = _downscale(v.x); + v.y = _downscale(v.y); + + if (shift > 0) { + auto half = static_cast(1L << (shift - 1)); + v.x = (v.x + half + SATURATE(v.x)) >> shift; + v.y = (v.y + half + SATURATE(v.y)) >> shift; + } else { + shift = -shift; + v.x = static_cast((unsigned long)v.x << shift); + v.y = static_cast((unsigned long)v.y << shift); + } +} + +SwFixed mathTan(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + mathRotate(v, angle); + return mathDivide(v.y, v.x); +} + + +SwFixed mathAtan(const SwPoint& pt) +{ + if (pt.x == 0 && pt.y == 0) return 0; + + auto v = pt; + _normalize(v); + _polarize(v); + + return v.y; +} + + +SwFixed mathSin(SwFixed angle) +{ + return mathCos(ANGLE_PI2 - angle); +} + + +SwFixed mathCos(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + mathRotate(v, angle); + return (v.x + 0x80L) >> 8; +} + + +SwFixed mathLength(SwPoint& pt) +{ + auto v = pt; + + //trivial case + if (v.x == 0) return abs(v.y); + if (v.y == 0) return abs(v.x); + + //general case + auto shift = _normalize(v); + _polarize(v); + v.x = _downscale(v.x); + + if (shift > 0) return (v.x + (1 << (shift -1))) >> shift; + return static_cast((uint32_t)v.x << -shift); +} + + +void mathSplitCubic(SwPoint* base) +{ + assert(base); + + SwCoord a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = (base[0].x + c) / 2; + base[5].x = b = (base[3].x + d) / 2; + c = (c + d) / 2; + base[2].x = a = (a + c) / 2; + base[4].x = b = (b + c) / 2; + base[3].x = (a + b) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = (base[0].y + c) / 2; + base[5].y = b = (base[3].y + d) / 2; + c = (c + d) / 2; + base[2].y = a = (a + c) / 2; + base[4].y = b = (b + c) / 2; + base[3].y = (a + b) / 2; +} + + +SwFixed mathDiff(SwFixed angle1, SwFixed angle2) +{ + auto delta = angle2 - angle1; + + delta %= ANGLE_2PI; + if (delta < 0) delta += ANGLE_2PI; + if (delta > ANGLE_PI) delta -= ANGLE_2PI; + + return delta; +} +#endif /* _TVG_SW_MATH_H_ */ \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp index 3a4b8ed..7bbc326 100644 --- a/src/lib/sw_engine/tvgSwRle.cpp +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -490,34 +490,6 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) } -static void _splitCubic(SwPoint* base) -{ - assert(base); - - SwCoord a, b, c, d; - - base[6].x = base[3].x; - c = base[1].x; - d = base[2].x; - base[1].x = a = (base[0].x + c) / 2; - base[5].x = b = (base[3].x + d) / 2; - c = (c + d) / 2; - base[2].x = a = (a + c) / 2; - base[4].x = b = (b + c) / 2; - base[3].x = (a + b) / 2; - - base[6].y = base[3].y; - c = base[1].y; - d = base[2].y; - base[1].y = a = (base[0].y + c) / 2; - base[5].y = b = (base[3].y + d) / 2; - c = (c + d) / 2; - base[2].y = a = (a + c) / 2; - base[4].y = b = (b + c) / 2; - base[3].y = (a + b) / 2; -} - - static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) { auto arc = rw.bezStack; @@ -579,7 +551,7 @@ static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, goto draw; } split: - _splitCubic(arc); + mathSplitCubic(arc); arc += 3; continue; diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index acaf2c6..a0a2573 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -23,271 +23,532 @@ /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ -constexpr auto CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 -constexpr static SwFixed ANGLE_PI = (180L << 16); -constexpr static SwFixed ANGLE_2PI = (ANGLE_PI << 1); -constexpr static SwFixed ANGLE_PI2 = (ANGLE_PI >> 1); -constexpr static SwFixed ANGLE_PI4 = (ANGLE_PI >> 2); +static constexpr auto SW_STROKE_TAG_ON = 1; +static constexpr auto SW_STROKE_TAG_CUBIC = 2; +static constexpr auto SW_STROKE_TAG_BEGIN = 4; +static constexpr auto SW_STROKE_TAG_END = 8; -//this table was generated for SW_FT_PI = 180L << 16, i.e. degrees -constexpr static auto ATAN_MAX = 23; -constexpr static SwFixed ATAN_TBL[] = { - 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, - 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, - 57L, 29L, 14L, 7L, 4L, 2L, 1L}; +static inline SwFixed SIDE_TO_ROTATE(const int32_t s) +{ + return (ANGLE_PI2 - (s) * ANGLE_PI); +} -static inline SwCoord SATURATE(const SwCoord x) +static void _growBorder(SwStrokeBorder* border, uint32_t newPts) { - return (x >> (sizeof(long) * 8 - 1)); + auto maxOld = border->maxPts; + auto maxNew = border->ptsCnt + newPts; + + if (maxNew <= maxOld) return; + + auto maxCur = maxOld; + + while (maxCur < maxNew) + maxCur += (maxCur >> 1) + 16; + + border->pts = static_cast(realloc(border->pts, maxCur * sizeof(SwPoint))); + assert(border->pts); + + border->tags = static_cast(realloc(border->tags, maxCur * sizeof(uint8_t))); + assert(border->tags); + + border->maxPts = maxCur; + + printf("realloc border!!! (%u => %u)\n", maxOld, maxCur); } -static inline SwFixed SIDE_TO_ROTATE(int32_t s) +static void _borderClose(SwStrokeBorder* border, bool reverse) { - return (ANGLE_PI2 - (s) * ANGLE_PI); + assert(border && border->start >= 0); + + uint32_t start = border->start; + uint32_t count = border->ptsCnt; + + //Don't record empty paths! + if (count <= start + 1U) { + border->ptsCnt = start; + } else { + /* Copy the last point to the start of this sub-path, + since it contains the adjusted starting coordinates */ + border->ptsCnt = --count; + border->pts[start] = border->pts[count]; + + if (reverse) { + //reverse the points + auto pt1 = border->pts + start + 1; + auto pt2 = border->pts + count - 1; + + while (pt1 < pt2) { + auto tmp = *pt1; + *pt1 = *pt2; + *pt2 = tmp; + ++pt1; + --pt2; + } + + //reverse the tags + auto tag1 = border->tags + start + 1; + auto tag2 = border->tags + count - 1; + + while (tag1 < tag2) { + auto tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + ++tag1; + --tag2; + } + } + + border->tags[start] |= SW_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_STROKE_TAG_END; + } + + border->start = -1; + border->movable = false; } -static int64_t _multiply(int64_t a, int64_t b) +static void _borderCubicTo(SwStrokeBorder* border, SwPoint& ctrl1, SwPoint& ctrl2, SwPoint& to) { - int32_t s = 1; + assert(border->start >= 0); - //move sign - if (a < 0) { - a = -a; - s = -s; - } - //move sign - if (b < 0) { - b = -b; - s = -s; - } - int64_t c = (a * b + 0x8000L ) >> 16; - return (s > 0) ? c : -c; + _growBorder(border, 3); + + auto pt = border->pts + border->ptsCnt; + auto tag = border->tags + border->ptsCnt; + + pt[0] = ctrl1; + pt[1] = ctrl2; + pt[2] = to; + + tag[0] = SW_STROKE_TAG_CUBIC; + tag[1] = SW_STROKE_TAG_CUBIC; + tag[2] = SW_STROKE_TAG_ON; + + border->ptsCnt += 3; + border->movable = false; } -static int64_t _divide(int64_t a, int64_t b) +static void _borderArcTo(SwStrokeBorder* border, SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff) { - int32_t s = 1; + constexpr auto ARC_CUBIC_ANGLE = ANGLE_PI / 2; + SwPoint a = {radius, 0}; + mathRotate(a, angleStart); + a += center; - //move sign - if (a < 0) { - a = -a; - s = -s; - } - //move sign - if (b < 0) { - b = -b; - s = -s; + auto total = angleDiff; + auto angle = angleStart; + auto rotate = (angleDiff >= 0) ? ANGLE_PI2 : -ANGLE_PI2; + + while (total != 0) { + auto step = total; + if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE; + else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE; + + auto next = angle + step; + auto theta = step; + if (theta < 0) theta = -theta; + + theta >>= 1; + + //compute end point + SwPoint b = {radius, 0}; + mathRotate(b, next); + b += center; + + //compute first and second control points + auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3); + + SwPoint a2 = {length, 0}; + mathRotate(a2, angle + rotate); + a2 += a; + + SwPoint b2 = {length, 0}; + mathRotate(b2, next - rotate); + b2 += b; + + //add cubic arc + _borderCubicTo(border, a2, b2, b); + + //process the rest of the arc? + a = b; + total -= step; + angle = next; } - int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; - return (s < 0 ? -q : q); } -static SwFixed _angleDiff(SwFixed angle1, SwFixed angle2) +static void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable) { - auto delta = angle2 - angle1; + assert(border && border->start >= 0); - delta %= ANGLE_2PI; - if (delta < 0) delta += ANGLE_2PI; - if (delta > ANGLE_PI) delta -= ANGLE_2PI; + if (border->movable) { + //move last point + border->pts[border->ptsCnt - 1] = to; + } else { + //don't add zero-length line_to + auto diff = border->pts[border->ptsCnt - 1] - to; + if (border->ptsCnt > 0 && diff.small()) return; - return delta; + _growBorder(border, 1); + border->pts[border->ptsCnt] = to; + border->tags[border->ptsCnt] = SW_STROKE_TAG_ON; + border->ptsCnt += 1; + } + + border->movable = movable; } -static void _trigDownscale(SwPoint& pt) +static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to) { - //multiply a give value by the CORDIC shrink factor + assert(border); - auto s = pt; + //close current open path if any? + if (border->start >= 0) + _borderClose(border, false); - //abs - if (pt.x < 0) pt.x = -pt.x; - if (pt.y < 0) pt.y = -pt.y; + border->start = border->ptsCnt; + border->movable = false; + + _borderLineTo(border, to, false); +} - int64_t vx = (pt.x * static_cast(CORDIC_FACTOR)) + 0x100000000UL; - int64_t vy = (pt.y * static_cast(CORDIC_FACTOR)) + 0x100000000UL; - pt.x = static_cast(vx >> 32); - pt.y = static_cast(vy >> 32); +static void _arcTo(SwStroke& stroke, int32_t side) +{ + auto border = stroke.borders + side; + auto rotate = SIDE_TO_ROTATE(side); + auto total = mathDiff(stroke.angleIn, stroke.angleOut); + if (total == ANGLE_PI) total = -rotate * 2; - if (s.x < 0) pt.x = -pt.x; - if (s.y < 0) pt.y = -pt.y; + _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total); + border->movable = false; } -static int32_t _trigPrenorm(SwPoint& pt) +static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) { - /* the highest bit in overflow-safe vector components - MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ - constexpr auto TRIG_SAFE_MSB = 29; + constexpr SwFixed MITER_LIMIT = 4 * (1 << 16); - auto v = pt; + assert(MITER_LIMIT >= 0x10000); - //High order bit(MSB) - //clz: count leading zero’s - auto shift = 31 - __builtin_clz(abs(v.x) | abs(v.y)); + auto border = stroke.borders + side; + assert(border); - if (shift <= TRIG_SAFE_MSB) { - shift = TRIG_SAFE_MSB - shift; - pt.x = static_cast((unsigned long)v.x << shift); - pt.y = static_cast((unsigned long)v.y << shift); + if (stroke.join == StrokeJoin::Round) { + _arcTo(stroke, side); } else { - shift -= TRIG_SAFE_MSB; - pt.x = v.x >> shift; - pt.y = v.y >> shift; - shift = -shift; + //this is a mitered (pointed) or beveled (truncated) corner + auto rotate = SIDE_TO_ROTATE(side); + auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false; + SwFixed phi = 0; + SwFixed thcos = 0; + + if (!bevel) { + auto theta = mathDiff(stroke.angleIn, stroke.angleOut); + if (theta == ANGLE_PI) { + theta = rotate; + phi = stroke.angleIn; + } else { + theta /= 2; + phi = stroke.angleIn + theta + rotate; + } + + thcos = mathCos(theta); + auto sigma = mathMultiply(MITER_LIMIT, thcos); + + //is miter limit exceeded? + if (sigma < 0x10000L) bevel = true; + } + + //this is a bevel (broken angle) + if (bevel) { + SwPoint delta = {stroke.width, 0}; + mathRotate(delta, stroke.angleOut + rotate); + delta += stroke.center; + border->movable = false; + _borderLineTo(border, delta, false); + //this is a miter (intersection) + } else { + auto length = mathDivide(stroke.width, thcos); + SwPoint delta = {length, 0}; + mathRotate(delta, phi); + delta += stroke.center; + _borderLineTo(border, delta, false); + + /* Now add and end point + Only needed if not lineto (lineLength is zero for curves) */ + if (lineLength == 0) { + delta = {stroke.width, 0}; + mathRotate(delta, stroke.angleOut + rotate); + delta += stroke.center; + _borderLineTo(border, delta, false); + } + } } - return shift; } -static void _trigPseudoRotate(SwPoint& pt, SwFixed theta) +static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength) { - auto v = pt; - - //Rotate inside [-PI/4, PI/4] sector - while (theta < -ANGLE_PI4) { - auto temp = v.y; - v.y = -v.x; - v.x = temp; - theta += ANGLE_PI2; - } + auto border = stroke.borders + side; + auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2; + SwPoint delta; + bool intersect; - while (theta > ANGLE_PI4) { - auto temp = -v.y; - v.y = v.x; - v.x = temp; - theta -= ANGLE_PI2; + /* Only intersect borders if between two line_to's and both + lines are long enough (line length is zero fur curves). */ + if (!border->movable || lineLength == 0) { + intersect = false; + } else { + //compute minimum required length of lines + SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta))); + if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true; } - auto atan = ATAN_TBL; - uint32_t i; - SwFixed j; - - for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { - if (theta < 0) { - auto temp = v.x + ((v.y + j) >> i); - v.y = v.y - ((v.x + j) >> i); - v.x = temp; - theta += *atan++; - }else { - auto temp = v.x - ((v.y + j) >> i); - v.y = v.y + ((v.x + j) >> i); - v.x = temp; - theta -= *atan++; - } + auto rotate = SIDE_TO_ROTATE(side); + + if (!intersect) { + delta = {stroke.width, 0}; + mathRotate(delta, stroke.angleOut + rotate); + delta += stroke.center; + border->movable = false; + } else { + //compute median angle + delta = {mathDivide(stroke.width, mathCos(theta)), 0}; + mathRotate(delta, stroke.angleIn + theta + rotate); + delta += stroke.center; } - pt = v; + _borderLineTo(border, delta, false); } -static void _rotate(SwPoint& pt, SwFixed angle) +void _processCorner(SwStroke& stroke, SwFixed lineLength) { - if (angle == 0 || (pt.x == 0 && pt.y == 0)) return; + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); - auto v = pt; - auto shift = _trigPrenorm(v); - _trigPseudoRotate(v, angle); - _trigDownscale(v); + //no specific corner processing is required if the turn is 0 + if (turn == 0) return; - if (shift > 0) { - auto half = static_cast(1L << (shift - 1)); - v.x = (v.x + half + SATURATE(v.x)) >> shift; - v.y = (v.y + half + SATURATE(v.y)) >> shift; - } else { - shift = -shift; - v.x = static_cast((unsigned long)v.x << shift); - v.y = static_cast((unsigned long)v.y << shift); - } -} + //when we turn to the right, the inside side is 0 + int32_t inside = 0; + //otherwise, the inside is 1 + if (turn < 0) inside = 1; -static SwFixed _tan(SwFixed angle) -{ - SwPoint v = {CORDIC_FACTOR >> 8, 0}; - _rotate(v, angle); - return _divide(v.y, v.x); + //process the inside + _inside(stroke, inside, lineLength); + + //process the outside + _outside(stroke, 1 - inside, lineLength); } -static SwFixed _cos(SwFixed angle) +void _subPathStart(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) { - SwPoint v = {CORDIC_FACTOR >> 8, 0}; - _rotate(v, angle); - return (v.x + 0x80L) >> 8; + SwPoint delta = {stroke.width, 0}; + mathRotate(delta, startAngle + ANGLE_PI2); + + auto pt = stroke.center + delta; + auto border = stroke.borders; + _borderMoveTo(border, pt); + + pt = stroke.center - delta; + ++border; + _borderMoveTo(border, pt); + + /* Save angle, position and line length for last join + lineLength is zero for curves */ + stroke.subPathAngle = startAngle; + stroke.firstPt = false; + stroke.subPathLineLength = lineLength; } static void _lineTo(SwStroke& stroke, const SwPoint& to) { + auto delta = to - stroke.center; + + //a zero-length lineto is a no-op; avoid creating a spurious corner + if (delta.zero()) return; + + //compute length of line + auto lineLength = mathLength(delta); + auto angle = mathAtan(delta); + + delta = {stroke.width, 0}; + mathRotate(delta, angle + ANGLE_PI2); + + //process corner if necessary + if (stroke.firstPt) { + /* This is the first segment of a subpath. We need to add a point to each border + at their respective starting point locations. */ + _subPathStart(stroke, angle, lineLength); + } else { + //process the current corner + stroke.angleOut = angle; + _processCorner(stroke, lineLength); + } + + //now add a line segment to both the inside and outside paths + auto border = stroke.borders; + auto side = 1; + + while (side >= 0) { + auto pt = to + delta; + + //the ends of lineto borders are movable + _borderLineTo(border, pt, true); + + delta.x = -delta.x; + delta.y = -delta.y; + --side; + ++border; + } + + stroke.angleIn = angle; + stroke.center = to; + stroke.lineLength = lineLength; } static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) { + /* if all control points are coincident, this is a no-op; + avoid creating a spurious corner */ + if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) { + stroke.center = to; + return; + } -} + SwPoint bezStack[37]; //TODO: static? + auto firstArc = true; + auto limit = bezStack + 32; + auto arc = bezStack; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = stroke.center; + + while (arc >= bezStack) { + SwFixed angleIn, angleOut, angleMid; + + //initialize with current direction + angleIn = angleOut = angleMid = stroke.angleIn; + + if (arc < limit && mathSmallCubic(arc, angleIn, angleMid, angleOut)) { + if (stroke.firstPt) stroke.angleIn = angleIn; + mathSplitCubic(arc); + arc += 3; + continue; + } + if (firstArc) { + firstArc = false; + //process corner if necessary + if (stroke.firstPt) { + _subPathStart(stroke, angleIn, 0); + } else { + stroke.angleOut = angleIn; + _processCorner(stroke, 0); + } + } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (ANGLE_PI / 8)) { + //if the deviation from one arc to the next is too great add a round corner + stroke.center = arc[3]; + stroke.angleOut = angleIn; + stroke.join = StrokeJoin::Round; -static void _arcTo(SwStroke& stroke, int32_t side) -{ + _processCorner(stroke, 0); -} + //reinstate line join style + stroke.join = stroke.joinSaved; + } + //the arc's angle is small enough; we can add it directly to each border + auto theta1 = mathDiff(angleIn, angleMid) / 2; + auto theta2 = mathDiff(angleMid, angleOut) / 2; + auto phi1 = mathMean(angleIn, angleMid); + auto phi2 = mathMean(angleMid, angleOut); + auto length1 = mathDivide(stroke.width, mathCos(theta1)); + auto length2 = mathDivide(stroke.width, mathCos(theta2)); + SwFixed alpha0 = 0; + + //compute direction of original arc + if (stroke.handleWideStrokes) { + alpha0 = mathAtan(arc[0] - arc[3]); + } -static void _growBorder(SwStrokeBorder* border, uint32_t newPts) -{ - auto maxOld = border->maxPts; - auto maxNew = border->ptsCnt + newPts; + auto border = stroke.borders; + int32_t side = 0; - if (maxNew <= maxOld) return; + while (side <= 1) + { + auto rotate = SIDE_TO_ROTATE(side); - auto maxCur = maxOld; + //compute control points + SwPoint _ctrl1 = {length1, 0}; + mathRotate(_ctrl1, phi1 + rotate); + _ctrl1 += arc[2]; - while (maxCur < maxNew) - maxCur += (maxCur >> 1) + 16; + SwPoint _ctrl2 = {length2, 0}; + mathRotate(_ctrl2, phi2 + rotate); + _ctrl2 += arc[1]; - border->pts = static_cast(realloc(border->pts, maxCur * sizeof(SwPoint))); - assert(border->pts); + //compute end point + SwPoint _end = {stroke.width, 0}; + mathRotate(_end, angleOut + rotate); + _end += arc[0]; - border->tags = static_cast(realloc(border->tags, maxCur * sizeof(uint8_t))); - assert(border->tags); + if (stroke.handleWideStrokes) { - border->maxPts = maxCur; + /* determine whether the border radius is greater than the radius of + curvature of the original arc */ + auto _start = border->pts[border->ptsCnt - 1]; + auto alpha1 = mathAtan(_end - _start); - printf("realloc border!!! (%u => %u)\n", maxOld, maxCur); -} + //is the direction of the border arc opposite to that of the original arc? + if (abs(mathDiff(alpha0, alpha1)) > ANGLE_PI / 2) { + //use the sine rule to find the intersection point + auto beta = mathAtan(arc[3] - _start); + auto gamma = mathAtan(arc[0] - _end); + auto bvec = _end - _start; + auto blen = mathLength(bvec); + auto sinA = abs(mathSin(alpha1 - gamma)); + auto sinB = abs(mathSin(beta - gamma)); + auto alen = mathMulDiv(blen, sinA, sinB); + SwPoint delta = {alen, 0}; + mathRotate(delta, beta); + delta += _start; -static void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable) -{ - constexpr SwCoord EPSILON = 2; + //circumnavigate the negative sector backwards + border->movable = false; + _borderLineTo(border, delta, false); + _borderLineTo(border, _end, false); + _borderCubicTo(border, _ctrl2, _ctrl1, _start); - assert(border && border->start >= 0); + //and thenmove to the endpoint + _borderLineTo(border, _end, false); - if (border->movable) { - //move last point - border->pts[border->ptsCnt - 1] = to; - } else { - //don't add zero-length line_to - auto diff = border->pts[border->ptsCnt - 1] - to; - if (border->ptsCnt > 0 && abs(diff.x) < EPSILON && abs(diff.y) < EPSILON) return; + continue; + } - _growBorder(border, 1); - border->pts[border->ptsCnt] = to; - border->tags[border->ptsCnt] = SW_STROKE_TAG_ON; - border->ptsCnt += 1; + //else fall through + } + _borderCubicTo(border, _ctrl1, _ctrl2, _end); + ++side; + ++border; + } + arc -= 3; + stroke.angleIn = angleOut; } - - border->movable = movable; + stroke.center = to; } @@ -298,20 +559,20 @@ static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) auto border = stroke.borders + side; SwPoint delta = {stroke.width, 0}; - _rotate(delta, angle); + mathRotate(delta, angle); SwPoint delta2 = {stroke.width, 0}; - _rotate(delta2, angle + rotate); + mathRotate(delta2, angle + rotate); delta += stroke.center + delta2; _borderLineTo(border, delta, false); delta = {stroke.width, 0}; - _rotate(delta, angle); + mathRotate(delta, angle); delta2 = {stroke.width, 0}; - _rotate(delta2, angle - rotate); + mathRotate(delta2, angle - rotate); delta += delta2 + stroke.center; @@ -329,14 +590,14 @@ static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) auto border = stroke.borders + side; SwPoint delta = {stroke.width, 0}; - _rotate(delta, angle + rotate); + mathRotate(delta, angle + rotate); delta += stroke.center; _borderLineTo(border, delta, false); delta = {stroke.width, 0}; - _rotate(delta, angle - rotate); + mathRotate(delta, angle - rotate); delta += stroke.center; @@ -389,88 +650,6 @@ static void _addReverseLeft(SwStroke& stroke, bool opened) } -static void _closeBorder(SwStrokeBorder* border, bool reverse) -{ - assert(border && border->start >= 0); - - uint32_t start = border->start; - uint32_t count = border->ptsCnt; - - //Don't record empty paths! - if (count <= start + 1U) { - border->ptsCnt = start; - } else { - /* Copy the last point to the start of this sub-path, - since it contains the adjusted starting coordinates */ - border->ptsCnt = --count; - border->pts[start] = border->pts[count]; - - if (reverse) { - //reverse the points - auto pt1 = border->pts + start + 1; - auto pt2 = border->pts + count - 1; - - for (; pt1 < pt2; pt1++, pt2--) { - auto tmp = *pt1; - *pt1 = *pt2; - *pt2 = tmp; - } - - //reverse the tags - auto tag1 = border->tags + start + 1; - auto tag2 = border->tags + count - 1; - - for (; tag1 < tag2; tag1++, tag2++) { - auto tmp = *tag1; - *tag1 = *tag2; - *tag2 = tmp; - } - } - - border->tags[start] |= SW_STROKE_TAG_BEGIN; - border->tags[count - 1] |= SW_STROKE_TAG_END; - } - - border->start = -1; - border->movable = false; -} - - -static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength) -{ - auto border = stroke.borders + side; - auto theta = _angleDiff(stroke.angleIn, stroke.angleOut) / 2; - SwPoint delta; - bool intersect; - - /* Only intersect borders if between two line_to's and both - lines are long enough (line length is zero fur curves). */ - if (!border->movable || lineLength == 0) { - intersect = false; - } else { - //compute minimum required length of lines - SwFixed minLength = abs(_multiply(stroke.width, _tan(theta))); - if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true; - } - - auto rotate = SIDE_TO_ROTATE(side); - - if (!intersect) { - delta = {stroke.width, 0}; - _rotate(delta, stroke.angleOut + rotate); - delta += stroke.center; - border->movable = false; - } else { - //compute median angle - delta = {_divide(stroke.width, _cos(theta)), 0}; - _rotate(delta, stroke.angleIn + theta + rotate); - delta += stroke.center; - } - - _borderLineTo(border, delta, false); -} - - static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened) { cout << "stroke opened? = " << opened << endl; @@ -518,7 +697,7 @@ static void _endSubPath(SwStroke& stroke) /* now end the right subpath accordingly. The left one is rewind and deosn't need further processing */ - _closeBorder(right, false); + _borderClose(right, false); } else { //close the path if needed @@ -527,7 +706,7 @@ static void _endSubPath(SwStroke& stroke) //process the corner stroke.angleOut = stroke.subPathAngle; - auto turn = _angleDiff(stroke.angleIn, stroke.angleOut); + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); //No specific corner processing is required if the turn is 0 if (turn != 0) { @@ -542,8 +721,8 @@ static void _endSubPath(SwStroke& stroke) _inside(stroke, 1 - inside, stroke.subPathLineLength); //outside } - _closeBorder(stroke.borders + 0, false); - _closeBorder(stroke.borders + 1, true); + _borderClose(stroke.borders + 0, false); + _borderClose(stroke.borders + 1, true); } } @@ -572,14 +751,6 @@ void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) { _deleteRle(stroke); -#if 0 - miterLimit = 4 * (1 >> 16); - - /* ensure miter limit has sensible value */ - if ( stroker->miter_limit < 0x10000 ) - stroker->miter_limit = 0x10000; -#endif - stroke.width = TO_SWCOORD(width * 0.5f); stroke.cap = cap; -- 2.7.4 From 1686af7643cdfbe02859ab6323396f439d228756 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Mon, 1 Jun 2020 20:27:43 +0900 Subject: [PATCH 04/16] sw_engine: implement stroke rle part Current stroke fails to merged shapes case... you can test with testStroke example Change-Id: I488af728949cba1d01b88723eb1dc4c49bac6c9b --- src/lib/sw_engine/tvgSwCommon.h | 11 +-- src/lib/sw_engine/tvgSwMath.cpp | 101 +++++++++++++----------- src/lib/sw_engine/tvgSwRaster.cpp | 26 ++++-- src/lib/sw_engine/tvgSwRenderer.cpp | 12 ++- src/lib/sw_engine/tvgSwRle.cpp | 24 +++--- src/lib/sw_engine/tvgSwShape.cpp | 80 +++++++++++-------- src/lib/sw_engine/tvgSwStroke.cpp | 153 ++++++++++++++++++++++++++++++------ test/testStroke.cpp | 5 +- 8 files changed, 271 insertions(+), 141 deletions(-) diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index b942ca1..cfb15bd 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -121,8 +121,6 @@ struct SwStrokeBorder struct SwStroke { - SwRleData* rle; - SwFixed angleIn; SwFixed angleOut; SwPoint center; @@ -146,8 +144,9 @@ struct SwStroke struct SwShape { SwOutline* outline; - SwRleData* rle; SwStroke* stroke; + SwRleData* rle; + SwRleData* strokeRle; SwBBox bbox; }; @@ -196,11 +195,13 @@ void shapeFree(SwShape* sdata); void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join); bool strokeParseOutline(SwStroke& stroke, SwOutline& outline); +SwOutline* strokeExportOutline(SwStroke& stroke); void strokeFree(SwStroke* stroke); -SwRleData* rleRender(const SwShape& sdata, const SwSize& clip); -SwRleData* rleStrokeRender(const SwShape& sdata); +SwRleData* rleRender(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip); +void rleFree(SwRleData* rle); bool rasterShape(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +bool rasterStroke(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a); #endif /* _TVG_SW_COMMON_H_ */ diff --git a/src/lib/sw_engine/tvgSwMath.cpp b/src/lib/sw_engine/tvgSwMath.cpp index 919fe1e..d2fcc4e 100644 --- a/src/lib/sw_engine/tvgSwMath.cpp +++ b/src/lib/sw_engine/tvgSwMath.cpp @@ -24,7 +24,7 @@ /* Internal Class Implementation */ /************************************************************************/ -constexpr auto CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 +constexpr SwCoord CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 //this table was generated for SW_FT_PI = 180L << 16, i.e. degrees constexpr static auto ATAN_MAX = 23; @@ -45,15 +45,14 @@ static inline SwFixed PAD_ROUND(const SwFixed x, int32_t n) } -static SwCoord _downscale(SwCoord x) +static SwCoord _downscale(SwFixed x) { //multiply a give value by the CORDIC shrink factor - - abs(x); - int64_t t = (x * static_cast(CORDIC_FACTOR)) + 0x100000000UL; - x = static_cast(t >> 32); - if (x < 0) x = -x; - return x; + auto s = abs(x); + int64_t t = (s * static_cast(CORDIC_FACTOR)) + 0x100000000UL; + s = static_cast(t >> 32); + if (x < 0) s = -s; + return s; } @@ -139,6 +138,47 @@ static void _polarize(SwPoint& pt) } +static void _rotate(SwPoint& pt, SwFixed theta) +{ + auto v = pt; + + //Rotate inside [-PI/4, PI/4] sector + while (theta < -ANGLE_PI4) { + auto tmp = v.y; + v.y = -v.x; + v.x = tmp; + theta += ANGLE_PI2; + } + + while (theta > ANGLE_PI4) { + auto tmp = -v.y; + v.y = v.x; + v.x = tmp; + theta -= ANGLE_PI2; + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (theta < 0) { + auto tmp = v.x + ((v.y + j) >> i); + v.y = v.y - ((v.x + j) >> i); + v.x = tmp; + theta += *atan++; + }else { + auto tmp = v.x - ((v.y + j) >> i); + v.y = v.y + ((v.x + j) >> i); + v.x = tmp; + theta -= *atan++; + } + } + + pt = v; +} + + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -267,57 +307,26 @@ void mathRotate(SwPoint& pt, SwFixed angle) auto shift = _normalize(v); auto theta = angle; - //Rotate inside [-PI/4, PI/4] sector - while (theta < -ANGLE_PI4) { - auto tmp = v.y; - v.y = -v.x; - v.x = tmp; - theta += ANGLE_PI2; - } - - while (theta > ANGLE_PI4) { - auto tmp = -v.y; - v.y = v.x; - v.x = tmp; - theta -= ANGLE_PI2; - } - - auto atan = ATAN_TBL; - uint32_t i; - SwFixed j; - - for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { - if (theta < 0) { - auto tmp = v.x + ((v.y + j) >> i); - v.y = v.y - ((v.x + j) >> i); - v.x = tmp; - theta += *atan++; - }else { - auto tmp = v.x - ((v.y + j) >> i); - v.y = v.y + ((v.x + j) >> i); - v.x = tmp; - theta -= *atan++; - } - } + _rotate(v, theta); v.x = _downscale(v.x); v.y = _downscale(v.y); if (shift > 0) { auto half = static_cast(1L << (shift - 1)); - v.x = (v.x + half + SATURATE(v.x)) >> shift; - v.y = (v.y + half + SATURATE(v.y)) >> shift; + pt.x = (v.x + half + SATURATE(v.x)) >> shift; + pt.y = (v.y + half + SATURATE(v.y)) >> shift; } else { shift = -shift; - v.x = static_cast((unsigned long)v.x << shift); - v.y = static_cast((unsigned long)v.y << shift); + pt.x = static_cast((unsigned long)v.x << shift); + pt.y = static_cast((unsigned long)v.y << shift); } } SwFixed mathTan(SwFixed angle) { SwPoint v = {CORDIC_FACTOR >> 8, 0}; - mathRotate(v, angle); + _rotate(v, angle); return mathDivide(v.y, v.x); } @@ -343,7 +352,7 @@ SwFixed mathSin(SwFixed angle) SwFixed mathCos(SwFixed angle) { SwPoint v = {CORDIC_FACTOR >> 8, 0}; - mathRotate(v, angle); + _rotate(v, angle); return (v.x + 0x80L) >> 8; } diff --git a/src/lib/sw_engine/tvgSwRaster.cpp b/src/lib/sw_engine/tvgSwRaster.cpp index ba7c6a3..78d507b 100644 --- a/src/lib/sw_engine/tvgSwRaster.cpp +++ b/src/lib/sw_engine/tvgSwRaster.cpp @@ -76,18 +76,13 @@ _rasterSolid(uint32_t* dst, uint32_t len, uint32_t color, uint32_t cov) } -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -bool rasterShape(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static bool +_rasterRle(Surface& surface, SwRleData* rle, uint32_t color, uint8_t a) { - SwRleData* rle = sdata.rle; if (!rle) return false; auto span = rle->spans; auto stride = surface.stride; - auto color = COLOR_ARGB_JOIN(r, g, b, a); for (uint32_t i = 0; i < rle->size; ++i) { assert(span); @@ -103,4 +98,21 @@ bool rasterShape(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t return true; } + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool rasterShape(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return _rasterRle(surface, sdata.rle, COLOR_ARGB_JOIN(r, g, b, a), a); +} + + +bool rasterStroke(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return _rasterRle(surface, sdata.strokeRle, COLOR_ARGB_JOIN(r, g, b, a), a); +} + + #endif /* _TVG_SW_RASTER_CPP_ */ \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index bf97c9b..3ec882e 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -63,16 +63,14 @@ bool SwRenderer::render(const Shape& shape, void *data) if (!sdata) return false; size_t r, g, b, a; - shape.fill(&r, &g, &b, &a); - size_t sa; - shape.strokeColor(nullptr, nullptr, nullptr, &sa); + shape.fill(&r, &g, &b, &a); + if (a > 0) rasterShape(surface, *sdata, r, g, b, a); - //invisible? - if (a == 0 && sa == 0) return false; + shape.strokeColor(&r, &g, &b, &a); + if (a > 0) rasterStroke(surface, *sdata, r, g, b, a); - //TODO: Threading - return rasterShape(surface, *sdata, r, g, b, a); + return true; } diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp index 7bbc326..b22b233 100644 --- a/src/lib/sw_engine/tvgSwRle.cpp +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -612,9 +612,7 @@ static bool _decomposeOutline(RleWorker& rw) goto close; } } - - //FIXME: Close the contour with a line segment? - //_lineTo(rw, UPSCALE(outline->pts[first])); + _lineTo(rw, UPSCALE(outline->pts[first])); close: first = last + 1; } @@ -646,13 +644,12 @@ static bool _genRle(RleWorker& rw) /* External Class Implementation */ /************************************************************************/ -SwRleData* rleRender(const SwShape& sdata, const SwSize& clip) +SwRleData* rleRender(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip) { //Please adjust when you out of cell memory (default: 16384L) constexpr auto RENDER_POOL_SIZE = 166641L; constexpr auto BAND_SIZE = 40; - auto outline = sdata.outline; assert(outline); assert(outline->cntrs && outline->pts); assert(outline->ptsCnt == outline->cntrs[outline->cntrsCnt - 1] + 1); @@ -671,11 +668,11 @@ SwRleData* rleRender(const SwShape& sdata, const SwSize& clip) rw.area = 0; rw.cover = 0; rw.invalid = true; - rw.cellMin = sdata.bbox.min; - rw.cellMax = sdata.bbox.max; + rw.cellMin = bbox.min; + rw.cellMax = bbox.max; rw.cellXCnt = rw.cellMax.x - rw.cellMin.x; rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; - rw.outline = outline; + rw.outline = const_cast(outline); rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 rw.bandShoot = 0; rw.clip = clip; @@ -770,12 +767,13 @@ error: } -SwRleData* rleStrokeRender(const SwShape& sdata) +void rleFree(SwRleData* rle) { - auto stroke = sdata.stroke; - assert(stroke); - - return nullptr; + if (!rle) return; + if (rle->spans) free(rle->spans); + free(rle); } + + #endif /* _TVG_SW_RLE_H_ */ diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index f1ab06e..e8e1996 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -64,6 +64,17 @@ static void _growOutlinePoint(SwOutline& outline, uint32_t n) } +static void _freeOutline(SwOutline* outline) +{ + if (!outline) return; + + if (outline->cntrs) free(outline->cntrs); + if (outline->pts) free(outline->pts); + if (outline->types) free(outline->types); + free(outline); +} + + static void _outlineEnd(SwOutline& outline) { _growOutlineContour(outline, 1); @@ -153,23 +164,22 @@ static void _outlineClose(SwOutline& outline) } -static void _initBBox(SwShape& sdata) +static void _initBBox(SwBBox& bbox) { - sdata.bbox.min.x = sdata.bbox.min.y = 0; - sdata.bbox.max.x = sdata.bbox.max.y = 0; + bbox.min.x = bbox.min.y = 0; + bbox.max.x = bbox.max.y = 0; } -static bool _updateBBox(SwShape& sdata) +static bool _updateBBox(SwOutline* outline, SwBBox& bbox) { - auto outline = sdata.outline; - assert(outline); + if (!outline) return false; auto pt = outline->pts; assert(pt); if (outline->ptsCnt <= 0) { - _initBBox(sdata); + _initBBox(bbox); return false; } @@ -187,10 +197,10 @@ static bool _updateBBox(SwShape& sdata) if (yMin > pt->y) yMin = pt->y; if (yMax < pt->y) yMax = pt->y; } - sdata.bbox.min.x = xMin >> 6; - sdata.bbox.max.x = (xMax + 63) >> 6; - sdata.bbox.min.y = yMin >> 6; - sdata.bbox.max.y = (yMax + 63) >> 6; + bbox.min.x = xMin >> 6; + bbox.max.x = (xMax + 63) >> 6; + bbox.min.y = yMin >> 6; + bbox.max.y = (yMax + 63) >> 6; if (xMax - xMin < 1 || yMax - yMin < 1) return false; @@ -213,16 +223,6 @@ static bool _checkValid(SwShape& sdata, const SwSize& clip) } -static void _deleteRle(SwShape& sdata) -{ - if (!sdata.rle) return; - if (sdata.rle->spans) free(sdata.rle->spans); - free(sdata.rle); - sdata.rle = nullptr; -} - - - /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -245,10 +245,10 @@ void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTrans bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip) { - if (!_updateBBox(sdata)) goto end; + if (!_updateBBox(sdata.outline, sdata.bbox)) goto end; if (!_checkValid(sdata, clip)) goto end; - sdata.rle = rleRender(sdata, clip); + sdata.rle = rleRender(sdata.outline, sdata.bbox, clip); end: if (sdata.rle) return true; @@ -259,12 +259,7 @@ end: void shapeDelOutline(SwShape& sdata) { auto outline = sdata.outline; - if (!outline) return; - - if (outline->cntrs) free(outline->cntrs); - if (outline->pts) free(outline->pts); - if (outline->types) free(outline->types); - free(outline); + _freeOutline(outline); sdata.outline = nullptr; } @@ -272,8 +267,9 @@ void shapeDelOutline(SwShape& sdata) void shapeReset(SwShape& sdata) { shapeDelOutline(sdata); - _deleteRle(sdata); - _initBBox(sdata); + rleFree(sdata.rle); + sdata.rle = nullptr; + _initBBox(sdata.bbox); } @@ -366,8 +362,13 @@ void shapeFree(SwShape* sdata) assert(sdata); shapeDelOutline(*sdata); - _deleteRle(*sdata); - strokeFree(sdata->stroke); + rleFree(sdata->rle); + + if (sdata->stroke) { + rleFree(sdata->strokeRle); + strokeFree(sdata->stroke); + } + free(sdata); } @@ -377,8 +378,9 @@ void shapeResetStroke(const Shape& shape, SwShape& sdata) if (!sdata.stroke) sdata.stroke = static_cast(calloc(1, sizeof(SwStroke))); auto stroke = sdata.stroke; assert(stroke); - strokeReset(*stroke, shape.strokeWidth(), shape.strokeCap(), shape.strokeJoin()); + rleFree(sdata.strokeRle); + sdata.strokeRle = nullptr; } @@ -392,6 +394,16 @@ bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip) if (!strokeParseOutline(*sdata.stroke, *sdata.outline)) return false; + auto outline = strokeExportOutline(*sdata.stroke); + if (!outline) return false; + + SwBBox bbox; + _updateBBox(outline, bbox); + + sdata.strokeRle = rleRender(outline, bbox, clip); + + _freeOutline(outline); + return true; } diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index a0a2573..8477d7c 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -23,7 +23,7 @@ /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ -static constexpr auto SW_STROKE_TAG_ON = 1; +static constexpr auto SW_STROKE_TAG_POINT = 1; static constexpr auto SW_STROKE_TAG_CUBIC = 2; static constexpr auto SW_STROKE_TAG_BEGIN = 4; static constexpr auto SW_STROKE_TAG_END = 8; @@ -36,6 +36,8 @@ static inline SwFixed SIDE_TO_ROTATE(const int32_t s) static void _growBorder(SwStrokeBorder* border, uint32_t newPts) { + assert(border); + auto maxOld = border->maxPts; auto maxNew = border->ptsCnt + newPts; @@ -53,8 +55,6 @@ static void _growBorder(SwStrokeBorder* border, uint32_t newPts) assert(border->tags); border->maxPts = maxCur; - - printf("realloc border!!! (%u => %u)\n", maxOld, maxCur); } @@ -111,7 +111,7 @@ static void _borderClose(SwStrokeBorder* border, bool reverse) static void _borderCubicTo(SwStrokeBorder* border, SwPoint& ctrl1, SwPoint& ctrl2, SwPoint& to) { - assert(border->start >= 0); + assert(border && border->start >= 0); _growBorder(border, 3); @@ -124,9 +124,10 @@ static void _borderCubicTo(SwStrokeBorder* border, SwPoint& ctrl1, SwPoint& ctrl tag[0] = SW_STROKE_TAG_CUBIC; tag[1] = SW_STROKE_TAG_CUBIC; - tag[2] = SW_STROKE_TAG_ON; + tag[2] = SW_STROKE_TAG_POINT; border->ptsCnt += 3; + border->movable = false; } @@ -188,13 +189,13 @@ static void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable) //move last point border->pts[border->ptsCnt - 1] = to; } else { + //don't add zero-length line_to - auto diff = border->pts[border->ptsCnt - 1] - to; - if (border->ptsCnt > 0 && diff.small()) return; + if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return; _growBorder(border, 1); border->pts[border->ptsCnt] = to; - border->tags[border->ptsCnt] = SW_STROKE_TAG_ON; + border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT; border->ptsCnt += 1; } @@ -207,8 +208,7 @@ static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to) assert(border); //close current open path if any? - if (border->start >= 0) - _borderClose(border, false); + if (border->start >= 0) _borderClose(border, false); border->start = border->ptsCnt; border->movable = false; @@ -318,8 +318,11 @@ static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength) border->movable = false; } else { //compute median angle - delta = {mathDivide(stroke.width, mathCos(theta)), 0}; - mathRotate(delta, stroke.angleIn + theta + rotate); + auto phi = stroke.angleIn + theta; + auto thcos = mathCos(theta); + auto length = mathDivide(stroke.width, thcos); + delta = {length, 0}; + mathRotate(delta, phi + rotate); delta += stroke.center; } @@ -652,11 +655,9 @@ static void _addReverseLeft(SwStroke& stroke, bool opened) static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened) { - cout << "stroke opened? = " << opened << endl; - /* We cannot process the first point because there is not enought information regarding its corner/cap. Later, it will be processed - in the _strokeEndSubPath() */ + in the _endSubPath() */ stroke.firstPt = true; stroke.center = to; @@ -712,13 +713,13 @@ static void _endSubPath(SwStroke& stroke) if (turn != 0) { //when we turn to the right, the inside is 0 - auto inside = 0; + int32_t inside = 0; //otherwise, the inside is 1 if (turn < 0) inside = 1; _inside(stroke, inside, stroke.subPathLineLength); //inside - _inside(stroke, 1 - inside, stroke.subPathLineLength); //outside + _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside } _borderClose(stroke.borders + 0, false); @@ -727,14 +728,81 @@ static void _endSubPath(SwStroke& stroke) } -static void _deleteRle(SwStroke& stroke) +static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt) +{ + assert(border); + + auto count = border->ptsCnt; + auto tags = border->tags; + uint32_t _ptsCnt = 0; + uint32_t _cntrsCnt = 0; + bool inCntr = false; + + while (count > 0) { + + if (tags[0] & SW_STROKE_TAG_BEGIN) { + if (inCntr) goto fail; + inCntr = true; + } else if (!inCntr) goto fail; + + if (tags[0] & SW_STROKE_TAG_END) { + inCntr = false; + ++_cntrsCnt; + } + --count; + ++_ptsCnt; + ++tags; + } + + if (inCntr) goto fail; + border->valid = true; + ptsCnt = _ptsCnt; + cntrsCnt = _cntrsCnt; + + return; + +fail: + ptsCnt = 0; + cntrsCnt = 0; +} + + +static void _exportBorderOutline(SwStroke& stroke, SwOutline* outline, uint32_t side) { - if (!stroke.rle) return; - if (stroke.rle->spans) free(stroke.rle->spans); - free(stroke.rle); - stroke.rle = nullptr; + auto border = stroke.borders + side; + assert(border); + + if (!border->valid) return; + + memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint)); + + auto cnt = border->ptsCnt; + auto src = border->tags; + auto tags = outline->types + outline->ptsCnt; + auto cntrs = outline->cntrs + outline->cntrsCnt; + uint16_t idx = outline->ptsCnt; + + while (cnt > 0) { + + if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT; + else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC; + else cout << "what type of stroke outline??" << endl; + + if (*src & SW_STROKE_TAG_END) { + *cntrs = idx; + ++cntrs; + ++outline->cntrsCnt; + } + + ++src; + ++tags; + ++idx; + --cnt; + } + outline->ptsCnt = outline->ptsCnt + border->ptsCnt; } + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -742,16 +810,20 @@ static void _deleteRle(SwStroke& stroke) void strokeFree(SwStroke* stroke) { if (!stroke) return; - _deleteRle(*stroke); + + //free borders + if (stroke->borders[0].pts) free(stroke->borders[0].pts); + if (stroke->borders[0].tags) free(stroke->borders[0].tags); + if (stroke->borders[1].pts) free(stroke->borders[1].pts); + if (stroke->borders[1].tags) free(stroke->borders[1].tags); + free(stroke); } void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) { - _deleteRle(stroke); - - stroke.width = TO_SWCOORD(width * 0.5f); + stroke.width = TO_SWCOORD(width * 0.5); stroke.cap = cap; //Save line join: it can be temporarily changed when stroking curves... @@ -826,4 +898,33 @@ bool strokeParseOutline(SwStroke& stroke, SwOutline& outline) } +SwOutline* strokeExportOutline(SwStroke& stroke) +{ + uint32_t count1, count2, count3, count4; + + _getCounts(stroke.borders + 0, count1, count2); + _getCounts(stroke.borders + 1, count3, count4); + + auto ptsCnt = count1 + count3; + auto cntrsCnt = count2 + count4; + + auto outline = static_cast(calloc(1, sizeof(SwOutline))); + assert(outline); + + outline->pts = static_cast(malloc(sizeof(SwPoint) * ptsCnt)); + assert(outline->pts); + + outline->types = static_cast(malloc(sizeof(uint8_t) * ptsCnt)); + assert(outline->types); + + outline->cntrs = static_cast(malloc(sizeof(uint32_t) * cntrsCnt)); + assert(outline->cntrs); + + _exportBorderOutline(stroke, outline, 0); //left + _exportBorderOutline(stroke, outline, 1); //right + + return outline; +} + + #endif /* _TVG_SW_STROKER_H_ */ diff --git a/test/testStroke.cpp b/test/testStroke.cpp index 1f865d6..d978b6f 100644 --- a/test/testStroke.cpp +++ b/test/testStroke.cpp @@ -23,11 +23,10 @@ void tvgtest() //Prepare a Shape (Rectangle + Rectangle + Circle + Circle) auto shape1 = tvg::Shape::gen(); - shape1->appendRect(0, 0, 200, 200, 0); //x, y, w, h, cornerRadius + shape1->appendRect(50, 50, 200, 200, 0); //x, y, w, h, cornerRadius shape1->appendRect(100, 100, 300, 300, 100); //x, y, w, h, cornerRadius shape1->appendCircle(400, 400, 100, 100); //cx, cy, radiusW, radiusH - shape1->appendCircle(400, 500, 170, 100); //cx, cy, radiusW, radiusH - shape1->fill(255, 255, 0, 255); //r, g, b, a + shape1->fill(50, 50, 50, 255); //r, g, b, a //Stroke Style shape1->stroke(255, 255, 255, 255); //color: r, g, b, a -- 2.7.4 From bab258e004505faa25eeec0ba285141ed4716f8d Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Tue, 2 Jun 2020 20:04:44 +0900 Subject: [PATCH 05/16] test: revise stroke example Change-Id: I92a40e905544fd8fb41df88810eabce7429b1b7f --- test/testStroke.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/test/testStroke.cpp b/test/testStroke.cpp index d978b6f..a4678dc 100644 --- a/test/testStroke.cpp +++ b/test/testStroke.cpp @@ -21,23 +21,63 @@ void tvgtest() auto canvas = tvg::SwCanvas::gen(); canvas->target(buffer, WIDTH, WIDTH, HEIGHT); - //Prepare a Shape (Rectangle + Rectangle + Circle + Circle) + //Shape 1 auto shape1 = tvg::Shape::gen(); - shape1->appendRect(50, 50, 200, 200, 0); //x, y, w, h, cornerRadius - shape1->appendRect(100, 100, 300, 300, 100); //x, y, w, h, cornerRadius - shape1->appendCircle(400, 400, 100, 100); //cx, cy, radiusW, radiusH - shape1->fill(50, 50, 50, 255); //r, g, b, a + shape1->appendRect(50, 50, 200, 200, 0); + shape1->fill(50, 50, 50, 255); + shape1->stroke(255, 255, 255, 255); //color: r, g, b, a + shape1->stroke(tvg::StrokeJoin::Bevel); //default is Bevel + shape1->stroke(10); //width: 10px - //Stroke Style - shape1->stroke(255, 255, 255, 255); //color: r, g, b, a - shape1->stroke(5); //width: 5px -// shape1->strokeJoin(tvg::StrokeJoin::Miter); -// shape1->strokeLineCap(tvg::StrokeLineCap::Butt); + canvas->push(move(shape1)); -// uint32_t dash[] = {3, 1, 5, 1}; //dash pattern -// shape1->strokeDash(dash, 4); + //Shape 2 + auto shape2 = tvg::Shape::gen(); + shape2->appendRect(300, 50, 200, 200, 0); + shape2->fill(50, 50, 50, 255); + shape2->stroke(255, 255, 255, 255); + shape2->stroke(tvg::StrokeJoin::Round); + shape2->stroke(10); + + canvas->push(move(shape2)); + + //Shape 3 + auto shape3 = tvg::Shape::gen(); + shape3->appendRect(550, 50, 200, 200, 0); + shape3->fill(50, 50, 50, 255); + shape3->stroke(255, 255, 255, 255); + shape3->stroke(tvg::StrokeJoin::Miter); + shape3->stroke(10); + + canvas->push(move(shape3)); + + //Shape 4 + auto shape4 = tvg::Shape::gen(); + shape4->appendCircle(150, 450, 100, 100); + shape4->fill(50, 50, 50, 255); + shape4->stroke(255, 255, 255, 255); + shape4->stroke(1); + + canvas->push(move(shape4)); + + //Shape 5 + auto shape5 = tvg::Shape::gen(); + shape5->appendCircle(400, 450, 100, 100); + shape5->fill(50, 50, 50, 255); + shape5->stroke(255, 255, 255, 255); + shape5->stroke(2); + + canvas->push(move(shape5)); + + //Shape 6 + auto shape6 = tvg::Shape::gen(); + shape6->appendCircle(650, 450, 100, 100); + shape6->fill(50, 50, 50, 255); + shape6->stroke(255, 255, 255, 255); + shape6->stroke(4); + + canvas->push(move(shape6)); - canvas->push(move(shape1)); canvas->draw(); canvas->sync(); -- 2.7.4 From 8614a2efee4b8066c819db2c714f11c341254588 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Tue, 2 Jun 2020 20:25:54 +0900 Subject: [PATCH 06/16] sw_engine: fix stroke join round result. a trivial reversed value was returned that brought the inverted arc drawing... Change-Id: I928f05b3400772a367d1653496d385354032cbad --- src/lib/sw_engine/tvgSwMath.cpp | 2 +- src/lib/sw_engine/tvgSwStroke.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/sw_engine/tvgSwMath.cpp b/src/lib/sw_engine/tvgSwMath.cpp index d2fcc4e..80c7f8e 100644 --- a/src/lib/sw_engine/tvgSwMath.cpp +++ b/src/lib/sw_engine/tvgSwMath.cpp @@ -295,7 +295,7 @@ int64_t mathMulDiv(int64_t a, int64_t b, int64_t c) } int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL; - return (s > 0 ? -d : d); + return (s > 0 ? d : -d); } diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index 8477d7c..e048ff5 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -134,7 +134,7 @@ static void _borderCubicTo(SwStrokeBorder* border, SwPoint& ctrl1, SwPoint& ctrl static void _borderArcTo(SwStrokeBorder* border, SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff) { - constexpr auto ARC_CUBIC_ANGLE = ANGLE_PI / 2; + constexpr SwFixed ARC_CUBIC_ANGLE = ANGLE_PI / 2; SwPoint a = {radius, 0}; mathRotate(a, angleStart); a += center; -- 2.7.4 From ef9f31577e0fbf94a0135b827e84750be5673f2b Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Tue, 2 Jun 2020 20:58:50 +0900 Subject: [PATCH 07/16] common stroke: retype the stroke width from size_t to float Change-Id: I812d06d2037d66408c41d50f7c1ff7ba605558bd --- inc/tizenvg.h | 4 ++-- src/lib/tvgShape.cpp | 6 +++--- src/lib/tvgShapeImpl.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/inc/tizenvg.h b/inc/tizenvg.h index 05f1927..c113975 100644 --- a/inc/tizenvg.h +++ b/inc/tizenvg.h @@ -141,7 +141,7 @@ public: int appendPath(const PathCommand* cmds, size_t cmdCnt, const Point* pts, size_t ptsCnt) noexcept; //Stroke - int stroke(size_t width) noexcept; + int stroke(float width) noexcept; int stroke(size_t r, size_t g, size_t b, size_t a) noexcept; int stroke(const size_t* dashPattern, size_t cnt) noexcept; int stroke(StrokeCap cap) noexcept; @@ -161,7 +161,7 @@ public: int fill(size_t* r, size_t* g, size_t* b, size_t* a) const noexcept; int bounds(float* x, float* y, float* w, float* h) const noexcept override; - size_t strokeWidth() const noexcept; + float strokeWidth() const noexcept; int strokeColor(size_t* r, size_t* g, size_t* b, size_t* a) const noexcept; size_t strokeDash(const size_t** dashPattern) const noexcept; StrokeCap strokeCap() const noexcept; diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index a17c53b..fe95eac 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -280,7 +280,7 @@ int Shape::bounds(float* x, float* y, float* w, float* h) const noexcept } -int Shape::stroke(size_t width) noexcept +int Shape::stroke(float width) noexcept { auto impl = pImpl.get(); assert(impl); @@ -291,12 +291,12 @@ int Shape::stroke(size_t width) noexcept } -size_t Shape::strokeWidth() const noexcept +float Shape::strokeWidth() const noexcept { auto impl = pImpl.get(); assert(impl); - if (!impl->stroke) return -1; + if (!impl->stroke) return 0; return impl->stroke->width; } diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index 682c29b..05be16d 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -30,7 +30,7 @@ struct ShapeFill struct ShapeStroke { - size_t width = 0; + float width = 0; size_t color[4] = {0, 0, 0, 0}; size_t* dashPattern = nullptr; size_t dashCnt = 0; @@ -153,7 +153,7 @@ struct Shape::Impl return 0; } - bool strokeWidth(size_t width) + bool strokeWidth(float width) { //TODO: Size Exception? -- 2.7.4 From 7ee25cd78334a24bb6b465ae5db46bb4dbf403cb Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 11:15:40 +0900 Subject: [PATCH 08/16] sw_engine: fix mistached c style alloc/free these are allocated by c style mem alloc. Thus, they should be freed with free() Change-Id: I320fff4d5a5bce2374ace6495a9f96c3e1034cfc --- src/lib/sw_engine/tvgSwStroke.cpp | 1 + src/lib/tvgShapePath.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index e048ff5..be18e05 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -838,6 +838,7 @@ void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) stroke.borders[1].valid = false; } + bool strokeParseOutline(SwStroke& stroke, SwOutline& outline) { uint32_t first = 0; diff --git a/src/lib/tvgShapePath.h b/src/lib/tvgShapePath.h index 68205f4..7e020e8 100644 --- a/src/lib/tvgShapePath.h +++ b/src/lib/tvgShapePath.h @@ -36,8 +36,8 @@ struct ShapePath ~ShapePath() { - if (cmds) delete(cmds); - if (pts) delete(pts); + if (cmds) free(cmds); + if (pts) free(pts); } void reserveCmd(size_t cmdCnt) -- 2.7.4 From 3bb272877a273c01cde42c35f313128a538642dd Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 11:27:34 +0900 Subject: [PATCH 09/16] sw_engine: fix a missing of variable initializing. Change-Id: I6451b07709fbc56441363e8f0ee0f4647404ae10 --- src/lib/sw_engine/tvgSwRle.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp index b22b233..1d5577b 100644 --- a/src/lib/sw_engine/tvgSwRle.cpp +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -672,6 +672,7 @@ SwRleData* rleRender(const SwOutline* outline, const SwBBox& bbox, const SwSize& rw.cellMax = bbox.max; rw.cellXCnt = rw.cellMax.x - rw.cellMin.x; rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; + rw.ySpan = 0; rw.outline = const_cast(outline); rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 rw.bandShoot = 0; -- 2.7.4 From 2b53b8018a0275e55f1caca48ab36db66ad3f291 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 11:35:03 +0900 Subject: [PATCH 10/16] test stroke: remove duplicated engine initialize call. Change-Id: Ia592a45581eae4fd5c85e6ba4d9d0eef835b14c1 --- test/testStroke.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/testStroke.cpp b/test/testStroke.cpp index a4678dc..b17c3a1 100644 --- a/test/testStroke.cpp +++ b/test/testStroke.cpp @@ -14,9 +14,6 @@ void tvgtest() //Initialize TizenVG Engine tvg::Engine::init(); - //Initialize TizenVG Engine - tvg::Engine::init(); - //Create a Canvas auto canvas = tvg::SwCanvas::gen(); canvas->target(buffer, WIDTH, WIDTH, HEIGHT); -- 2.7.4 From ad6b74dd1392a2272f71e63e3fba7ede8c5fd83e Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 11:41:40 +0900 Subject: [PATCH 11/16] test scene: remove duplicated engine initialization. Change-Id: I5a2f972544e47578552b0c49367749ce2d01c5f2 --- test/testSceneTransform.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/testSceneTransform.cpp b/test/testSceneTransform.cpp index 38bfb0f..a66996a 100644 --- a/test/testSceneTransform.cpp +++ b/test/testSceneTransform.cpp @@ -13,9 +13,6 @@ tvg::Scene* pScene2 = nullptr; void tvgtest() { - //Initialize TizenVG Engine - tvg::Engine::init(); - //Create a Canvas canvas = tvg::SwCanvas::gen(); canvas->target(buffer, WIDTH, WIDTH, HEIGHT); @@ -96,9 +93,6 @@ void tvgtest() canvas->draw(); canvas->sync(); - - //Terminate TizenVG Engine - tvg::Engine::term(); } void transit_cb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) -- 2.7.4 From 01b550497c1121d88831d844302ecf6eda1b92d1 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Tue, 2 Jun 2020 21:00:50 +0900 Subject: [PATCH 12/16] sw_engine: support stroke transformation properly. also updated transform test cases. Yet, this engine is not well optimized, If they are too mch sluggish, you can use ELM_FPS envrionment lowing down the fps when you launch test cases... ex) $ELM_FPS=30 ./testSceneTransform Change-Id: I1871d5bedee010d5d6a3d877d95e257120796e8b --- src/lib/sw_engine/tvgSwRenderer.cpp | 33 +++++++++++++++++++-------------- test/testDirectUpdate.cpp | 3 +++ test/testSceneTransform.cpp | 12 ++++++++---- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 3ec882e..91c0013 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -93,29 +93,34 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* if (flags == RenderUpdateFlag::None) return sdata; - //invisible? - size_t a, sa; - shape.fill(nullptr, nullptr, nullptr, &a); - shape.strokeColor(nullptr, nullptr, nullptr, &sa); - if (a == 0 && sa == 0) return sdata; - //TODO: Threading SwSize clip = {static_cast(surface.w), static_cast(surface.h)}; //Shape if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) { - shapeReset(*sdata); - if (!shapeGenOutline(shape, *sdata)) return sdata; - if (transform) shapeTransformOutline(shape, *sdata, *transform); - if (!shapeGenRle(shape, *sdata, clip)) return sdata; + + size_t alpha = 0; + shape.fill(nullptr, nullptr, nullptr, &alpha); + + if (alpha > 0) { + shapeReset(*sdata); + if (!shapeGenOutline(shape, *sdata)) return sdata; + if (transform) shapeTransformOutline(shape, *sdata, *transform); + if (!shapeGenRle(shape, *sdata, clip)) return sdata; + } } //Stroke - if (flags & RenderUpdateFlag::Stroke) { - shapeResetStroke(shape, *sdata); - if (shape.strokeWidth() > 0.01) { - if (!shapeGenStrokeRle(shape, *sdata, clip)) return sdata; + if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { + + if (shape.strokeWidth() > 0.5) { + shapeResetStroke(shape, *sdata); + size_t alpha = 0; + shape.strokeColor(nullptr, nullptr, nullptr, &alpha); + if (alpha > 0) { + if (!shapeGenStrokeRle(shape, *sdata, clip)) return sdata; + } } } diff --git a/test/testDirectUpdate.cpp b/test/testDirectUpdate.cpp index 799d135..e1a4c60 100644 --- a/test/testDirectUpdate.cpp +++ b/test/testDirectUpdate.cpp @@ -27,6 +27,8 @@ void tvgtest() //fill property will be retained shape->fill(127, 255, 255, 255); + shape->stroke(0, 0, 255, 255); + shape->stroke(1); canvas->push(move(shape)); @@ -44,6 +46,7 @@ void transit_cb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progres pShape->reset(); //reset path pShape->appendRect(-100 + (800 * progress), -100 + (800 * progress), 200, 200, (100 * progress)); + pShape->stroke(30 * progress); //Update shape for drawing (this may work asynchronously) canvas->update(pShape); diff --git a/test/testSceneTransform.cpp b/test/testSceneTransform.cpp index a66996a..975f4fa 100644 --- a/test/testSceneTransform.cpp +++ b/test/testSceneTransform.cpp @@ -25,19 +25,21 @@ void tvgtest() //Prepare Round Rectangle (Scene1) auto shape1 = tvg::Shape::gen(); shape1->appendRect(-235, -250, 400, 400, 50); //x, y, w, h, cornerRadius - shape1->fill(0, 255, 0, 255); //r, g, b, a + shape1->fill(0, 255, 0, 255); //r, g, b, a + shape1->stroke(5); //width + shape1->stroke(255, 255, 255, 255); //r, g, b, a scene->push(move(shape1)); //Prepare Circle (Scene1) auto shape2 = tvg::Shape::gen(); shape2->appendCircle(-165, -150, 200, 200); //cx, cy, radiusW, radiusH - shape2->fill(255, 255, 0, 255); //r, g, b, a + shape2->fill(255, 255, 0, 255); //r, g, b, a scene->push(move(shape2)); //Prepare Ellipse (Scene1) auto shape3 = tvg::Shape::gen(); - shape3->appendCircle(265, 250, 150, 100); //cx, cy, radiusW, radiusH - shape3->fill(0, 255, 255, 255); //r, g, b, a + shape3->appendCircle(265, 250, 150, 100); //cx, cy, radiusW, radiusH + shape3->fill(0, 255, 255, 255); //r, g, b, a scene->push(move(shape3)); scene->translate(350, 350); @@ -64,6 +66,8 @@ void tvgtest() shape4->lineTo(-53, -5.5); shape4->close(); shape4->fill(0, 0, 127, 127); + shape4->stroke(3); //width + shape4->stroke(0, 0, 255, 255); //r, g, b, a scene2->push(move(shape4)); //Circle (Scene2) -- 2.7.4 From f335779ce52f39755828fe70d6996fa087803b06 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 19:08:18 +0900 Subject: [PATCH 13/16] sw_engine stroke: stabilizing line drawing. Also added StrokeLine test Change-Id: I91143039823d744bf9287534227927556a2f51e1 --- .gitignore | 1 + src/lib/sw_engine/tvgSwRle.cpp | 2 +- src/lib/sw_engine/tvgSwShape.cpp | 1 + src/lib/sw_engine/tvgSwStroke.cpp | 9 ++-- test/makefile | 1 + test/testStrokeLine.cpp | 101 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 test/testStrokeLine.cpp diff --git a/.gitignore b/.gitignore index 1353d08..ba439cb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ testScene testTransform testSceneTransform testStroke +testStrokeLine diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp index 1d5577b..2757c0d 100644 --- a/src/lib/sw_engine/tvgSwRle.cpp +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -612,7 +612,7 @@ static bool _decomposeOutline(RleWorker& rw) goto close; } } - _lineTo(rw, UPSCALE(outline->pts[first])); + _lineTo(rw, start); close: first = last + 1; } diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index e8e1996..a4fa7fb 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -316,6 +316,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) auto outline = sdata.outline; if (!outline) outline = static_cast(calloc(1, sizeof(SwOutline))); assert(outline); + outline->opened = true; _growOutlinePoint(*outline, outlinePtsCnt); _growOutlineContour(*outline, outlineCntrsCnt); diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index be18e05..06ec989 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -635,15 +635,14 @@ static void _addReverseLeft(SwStroke& stroke, bool opened) } else { //switch begin/end tags if necessary auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); - if (ttag == (SW_STROKE_TAG_BEGIN || SW_STROKE_TAG_END)) { - dstTag[0] ^= (SW_STROKE_TAG_BEGIN || SW_STROKE_TAG_END); - } + if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END) + dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); } --srcPt; --srcTag; - --dstPt; - --dstTag; + ++dstPt; + ++dstTag; } left->ptsCnt = left->start; diff --git a/test/makefile b/test/makefile index 3f314e8..2aba05d 100644 --- a/test/makefile +++ b/test/makefile @@ -11,3 +11,4 @@ all: gcc -o testTransform testTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` gcc -o testSceneTransform testSceneTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` gcc -o testStroke testStroke.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testStrokeLine testStrokeLine.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` diff --git a/test/testStrokeLine.cpp b/test/testStrokeLine.cpp new file mode 100644 index 0000000..8687b85 --- /dev/null +++ b/test/testStrokeLine.cpp @@ -0,0 +1,101 @@ +#include +#include + +using namespace std; + +#define WIDTH 800 +#define HEIGHT 800 + +static uint32_t buffer[WIDTH * HEIGHT]; + +void tvgtest() +{ + //Initialize TizenVG Engine + tvg::Engine::init(); + + //Create a Canvas + auto canvas = tvg::SwCanvas::gen(); + canvas->target(buffer, WIDTH, WIDTH, HEIGHT); + + for (int i = 0; i < 10; ++i) { + auto shape = tvg::Shape::gen(); + shape->moveTo(50, 50 + (25 * i)); + shape->lineTo(750, 50 + (25 * i)); + shape->stroke(255, 255, 255, 255); //color: r, g, b, a + shape->stroke(i + 1); //stroke width + shape->stroke(tvg::StrokeCap::Round); //default is Square + canvas->push(move(shape)); + } + + auto shape1 = tvg::Shape::gen(); + shape1->moveTo(20, 350); + shape1->lineTo(250, 350); + shape1->lineTo(220, 500); + shape1->lineTo(70, 470); + shape1->lineTo(70, 330); + shape1->stroke(255, 0, 0, 255); + shape1->stroke(10); + shape1->stroke(tvg::StrokeJoin::Round); + shape1->stroke(tvg::StrokeCap::Round); + canvas->push(move(shape1)); + + auto shape2 = tvg::Shape::gen(); + shape2->moveTo(270, 350); + shape2->lineTo(500, 350); + shape2->lineTo(470, 500); + shape2->lineTo(320, 470); + shape2->lineTo(320, 330); + shape2->stroke(255, 255, 0, 255); + shape2->stroke(10); + shape2->stroke(tvg::StrokeJoin::Bevel); + shape2->stroke(tvg::StrokeCap::Square); + canvas->push(move(shape2)); + + auto shape3 = tvg::Shape::gen(); + shape3->moveTo(520, 350); + shape3->lineTo(750, 350); + shape3->lineTo(720, 500); + shape3->lineTo(570, 470); + shape3->lineTo(570, 330); + shape3->stroke(0, 255, 0, 255); + shape3->stroke(10); + shape3->stroke(tvg::StrokeJoin::Miter); + shape3->stroke(tvg::StrokeCap::Butt); + canvas->push(move(shape3)); + + canvas->draw(); + canvas->sync(); + + //Terminate TizenVG Engine + tvg::Engine::term(); +} + +void +win_del(void *data, Evas_Object *o, void *ev) +{ + elm_exit(); +} + +int main(int argc, char **argv) +{ + tvgtest(); + + //Show the result using EFL... + elm_init(argc, argv); + + Eo* win = elm_win_util_standard_add(NULL, "TizenVG Test"); + evas_object_smart_callback_add(win, "delete,request", win_del, 0); + + Eo* img = evas_object_image_filled_add(evas_object_evas_get(win)); + evas_object_image_size_set(img, WIDTH, HEIGHT); + evas_object_image_data_set(img, buffer); + evas_object_size_hint_weight_set(img, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(img); + + elm_win_resize_object_add(win, img); + evas_object_geometry_set(win, 0, 0, WIDTH, HEIGHT); + evas_object_show(win); + + elm_run(); + elm_shutdown(); +} -- 2.7.4 From 98edb4112bf586a66f9bb095360eae1e9836adc8 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 19:16:44 +0900 Subject: [PATCH 14/16] sw_engine shape: ++ comment for later optimization Change-Id: Ie6cd622748b88e2bce0c9d9a79cc4528a95c9c5c --- src/lib/sw_engine/tvgSwShape.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index a4fa7fb..30b79c8 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -245,6 +245,8 @@ void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTrans bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip) { + /* OPTIMIZE ME: We may avoid this bounding box calculation in this stage + if this shape has stroke and stroke bbox can be used here... */ if (!_updateBBox(sdata.outline, sdata.bbox)) goto end; if (!_checkValid(sdata, clip)) goto end; -- 2.7.4 From dc5f9f7430df751f8f65755afe020d24149b74a9 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Thu, 4 Jun 2020 17:49:10 +0900 Subject: [PATCH 15/16] common: retyped the color value size_t -> uint8_t since it's range is 0 - 255. Change-Id: I16e0569341c4a94acab9488d076f235bf90ff4db --- inc/tizenvg.h | 8 ++++---- src/lib/sw_engine/tvgSwRenderer.cpp | 6 +++--- src/lib/tvgShape.cpp | 8 ++++---- src/lib/tvgShapeImpl.h | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/inc/tizenvg.h b/inc/tizenvg.h index c113975..905c3f0 100644 --- a/inc/tizenvg.h +++ b/inc/tizenvg.h @@ -142,13 +142,13 @@ public: //Stroke int stroke(float width) noexcept; - int stroke(size_t r, size_t g, size_t b, size_t a) noexcept; + int stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; int stroke(const size_t* dashPattern, size_t cnt) noexcept; int stroke(StrokeCap cap) noexcept; int stroke(StrokeJoin join) noexcept; //Fill - int fill(size_t r, size_t g, size_t b, size_t a) noexcept; + int fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; //Transform int rotate(float degree) noexcept override; @@ -158,11 +158,11 @@ public: //Getters size_t pathCommands(const PathCommand** cmds) const noexcept; size_t pathCoords(const Point** pts) const noexcept; - int fill(size_t* r, size_t* g, size_t* b, size_t* a) const noexcept; + int fill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; int bounds(float* x, float* y, float* w, float* h) const noexcept override; float strokeWidth() const noexcept; - int strokeColor(size_t* r, size_t* g, size_t* b, size_t* a) const noexcept; + int strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; size_t strokeDash(const size_t** dashPattern) const noexcept; StrokeCap strokeCap() const noexcept; StrokeJoin strokeJoin() const noexcept; diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 91c0013..38ac674 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -62,7 +62,7 @@ bool SwRenderer::render(const Shape& shape, void *data) SwShape* sdata = static_cast(data); if (!sdata) return false; - size_t r, g, b, a; + uint8_t r, g, b, a; shape.fill(&r, &g, &b, &a); if (a > 0) rasterShape(surface, *sdata, r, g, b, a); @@ -100,7 +100,7 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* //Shape if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) { - size_t alpha = 0; + uint8_t alpha = 0; shape.fill(nullptr, nullptr, nullptr, &alpha); if (alpha > 0) { @@ -116,7 +116,7 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* if (shape.strokeWidth() > 0.5) { shapeResetStroke(shape, *sdata); - size_t alpha = 0; + uint8_t alpha = 0; shape.strokeColor(nullptr, nullptr, nullptr, &alpha); if (alpha > 0) { if (!shapeGenStrokeRle(shape, *sdata, clip)) return sdata; diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index fe95eac..828e251 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -213,7 +213,7 @@ int Shape::appendRect(float x, float y, float w, float h, float cornerRadius) no } -int Shape::fill(size_t r, size_t g, size_t b, size_t a) noexcept +int Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept { auto impl = pImpl.get(); assert(impl); @@ -228,7 +228,7 @@ int Shape::fill(size_t r, size_t g, size_t b, size_t a) noexcept } -int Shape::fill(size_t* r, size_t* g, size_t* b, size_t* a) const noexcept +int Shape::fill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept { auto impl = pImpl.get(); assert(impl); @@ -301,7 +301,7 @@ float Shape::strokeWidth() const noexcept } -int Shape::stroke(size_t r, size_t g, size_t b, size_t a) noexcept +int Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept { auto impl = pImpl.get(); assert(impl); @@ -312,7 +312,7 @@ int Shape::stroke(size_t r, size_t g, size_t b, size_t a) noexcept } -int Shape::strokeColor(size_t* r, size_t* g, size_t* b, size_t* a) const noexcept +int Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept { auto impl = pImpl.get(); assert(impl); diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index 05be16d..e1a4e9f 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -31,7 +31,7 @@ struct ShapeFill struct ShapeStroke { float width = 0; - size_t color[4] = {0, 0, 0, 0}; + uint8_t color[4] = {0, 0, 0, 0}; size_t* dashPattern = nullptr; size_t dashCnt = 0; StrokeCap cap = StrokeCap::Square; @@ -188,7 +188,7 @@ struct Shape::Impl return 0; } - bool strokeColor(size_t r, size_t g, size_t b, size_t a) + bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!stroke) stroke = new ShapeStroke(); assert(stroke); -- 2.7.4 From 9aa2566b45f57c742f134a6e7f4597c7011406fc Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 3 Jun 2020 20:50:13 +0900 Subject: [PATCH 16/16] sw_engine: support stroke dash feature Change-Id: Ibed8bcb6a07952a059bb9a7355f7c43db97aa672 --- inc/tizenvg.h | 4 +- src/lib/sw_engine/tvgSwCommon.h | 40 +-- src/lib/sw_engine/tvgSwMath.cpp | 33 +-- src/lib/sw_engine/tvgSwRenderer.cpp | 37 ++- src/lib/sw_engine/tvgSwShape.cpp | 482 ++++++++++++++++++++++++++++++------ src/lib/sw_engine/tvgSwStroke.cpp | 33 ++- src/lib/tvgShape.cpp | 4 +- src/lib/tvgShapeImpl.h | 11 +- test/testStrokeLine.cpp | 49 ++++ 9 files changed, 533 insertions(+), 160 deletions(-) diff --git a/inc/tizenvg.h b/inc/tizenvg.h index 905c3f0..18db842 100644 --- a/inc/tizenvg.h +++ b/inc/tizenvg.h @@ -143,7 +143,7 @@ public: //Stroke int stroke(float width) noexcept; int stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; - int stroke(const size_t* dashPattern, size_t cnt) noexcept; + int stroke(const float* dashPattern, size_t cnt) noexcept; int stroke(StrokeCap cap) noexcept; int stroke(StrokeJoin join) noexcept; @@ -163,7 +163,7 @@ public: float strokeWidth() const noexcept; int strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; - size_t strokeDash(const size_t** dashPattern) const noexcept; + size_t strokeDash(const float** dashPattern) const noexcept; StrokeCap strokeCap() const noexcept; StrokeJoin strokeJoin() const noexcept; diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index cfb15bd..16d4c1e 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -125,7 +125,8 @@ struct SwStroke SwFixed angleOut; SwPoint center; SwFixed lineLength; - SwPoint subPathStart; + SwFixed subPathAngle; + SwPoint ptStartSubPath; SwFixed subPathLineLength; SwFixed width; @@ -136,11 +137,22 @@ struct SwStroke SwStrokeBorder borders[2]; bool firstPt; - bool subPathOpen; - bool subPathAngle; + bool openSubPath; bool handleWideStrokes; }; +struct SwDashStroke +{ + SwOutline* outline; + int32_t curLen; + int32_t curIdx; + Point ptStart; + Point ptCur; + float* pattern; + size_t cnt; + bool curOpGap; +}; + struct SwShape { SwOutline* outline; @@ -183,18 +195,16 @@ SwFixed mathLength(SwPoint& pt); bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); SwFixed mathMean(SwFixed angle1, SwFixed angle2); -void shapeReset(SwShape& sdata); -bool shapeGenOutline(const Shape& shape, SwShape& sdata); -bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip); -void shapeDelOutline(SwShape& sdata); -void shapeResetStroke(const Shape& shape, SwShape& sdata); -bool shapeGenStrokeOutline(const Shape& shape, SwShape& sdata); -bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip); -void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTransform& transform); -void shapeFree(SwShape* sdata); - -void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join); -bool strokeParseOutline(SwStroke& stroke, SwOutline& outline); +void shapeReset(SwShape& shape); +bool shapeGenOutline(SwShape& shape, const Shape& sdata); +bool shapeGenRle(SwShape& shape, const Shape& sdata, const SwSize& clip, const RenderTransform* transform); +void shapeDelOutline(SwShape& shape); +void shapeResetStroke(SwShape& shape, const Shape& sdata); +bool shapeGenStrokeRle(SwShape& shape, const Shape& sdata, const SwSize& clip); +void shapeFree(SwShape* shape); + +void strokeReset(SwStroke& stroke, const Shape& shape); +bool strokeParseOutline(SwStroke& stroke, const SwOutline& outline); SwOutline* strokeExportOutline(SwStroke& stroke); void strokeFree(SwStroke* stroke); diff --git a/src/lib/sw_engine/tvgSwMath.cpp b/src/lib/sw_engine/tvgSwMath.cpp index 80c7f8e..a0a0ea1 100644 --- a/src/lib/sw_engine/tvgSwMath.cpp +++ b/src/lib/sw_engine/tvgSwMath.cpp @@ -140,20 +140,21 @@ static void _polarize(SwPoint& pt) static void _rotate(SwPoint& pt, SwFixed theta) { - auto v = pt; + SwFixed x = pt.x; + SwFixed y = pt.y; //Rotate inside [-PI/4, PI/4] sector while (theta < -ANGLE_PI4) { - auto tmp = v.y; - v.y = -v.x; - v.x = tmp; + auto tmp = y; + y = -x; + x = tmp; theta += ANGLE_PI2; } while (theta > ANGLE_PI4) { - auto tmp = -v.y; - v.y = v.x; - v.x = tmp; + auto tmp = -y; + y = x; + x = tmp; theta -= ANGLE_PI2; } @@ -163,19 +164,19 @@ static void _rotate(SwPoint& pt, SwFixed theta) for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { if (theta < 0) { - auto tmp = v.x + ((v.y + j) >> i); - v.y = v.y - ((v.x + j) >> i); - v.x = tmp; + auto tmp = x + ((y + j) >> i); + y = y - ((x + j) >> i); + x = tmp; theta += *atan++; }else { - auto tmp = v.x - ((v.y + j) >> i); - v.y = v.y + ((v.x + j) >> i); - v.x = tmp; + auto tmp = x - ((y + j) >> i); + y = y + ((x + j) >> i); + x = tmp; theta -= *atan++; } } - pt = v; + pt = {x, y}; } @@ -305,9 +306,9 @@ void mathRotate(SwPoint& pt, SwFixed angle) auto v = pt; auto shift = _normalize(v); - auto theta = angle; - _rotate(v, theta); + auto theta = angle; + _rotate(v, theta); v.x = _downscale(v.x); v.y = _downscale(v.y); diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 38ac674..5461abe 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -82,16 +82,16 @@ bool SwRenderer::dispose(const Shape& shape, void *data) return true; } -void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* transform, RenderUpdateFlag flags) +void* SwRenderer::prepare(const Shape& sdata, void* data, const RenderTransform* transform, RenderUpdateFlag flags) { //prepare shape data - SwShape* sdata = static_cast(data); - if (!sdata) { - sdata = static_cast(calloc(1, sizeof(SwShape))); - assert(sdata); + auto shape = static_cast(data); + if (!shape) { + shape = static_cast(calloc(1, sizeof(SwShape))); + assert(shape); } - if (flags == RenderUpdateFlag::None) return sdata; + if (flags == RenderUpdateFlag::None) return shape; //TODO: Threading @@ -99,34 +99,29 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* //Shape if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) { - + shapeReset(*shape); uint8_t alpha = 0; - shape.fill(nullptr, nullptr, nullptr, &alpha); - + sdata.fill(nullptr, nullptr, nullptr, &alpha); if (alpha > 0) { - shapeReset(*sdata); - if (!shapeGenOutline(shape, *sdata)) return sdata; - if (transform) shapeTransformOutline(shape, *sdata, *transform); - if (!shapeGenRle(shape, *sdata, clip)) return sdata; + if (!shapeGenRle(*shape, sdata, clip, transform)) return shape; } } //Stroke if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { - - if (shape.strokeWidth() > 0.5) { - shapeResetStroke(shape, *sdata); + if (sdata.strokeWidth() > 0.5) { + shapeResetStroke(*shape, sdata); uint8_t alpha = 0; - shape.strokeColor(nullptr, nullptr, nullptr, &alpha); + sdata.strokeColor(nullptr, nullptr, nullptr, &alpha); if (alpha > 0) { - if (!shapeGenStrokeRle(shape, *sdata, clip)) return sdata; + if (!shapeGenStrokeRle(*shape, sdata, clip)) return shape; } } } - shapeDelOutline(*sdata); + shapeDelOutline(*shape); - return sdata; + return shape; } @@ -159,4 +154,4 @@ SwRenderer* SwRenderer::inst() return dynamic_cast(RenderInitializer::inst(renderInit)); } -#endif /* _TVG_SW_RENDERER_CPP_ */ \ No newline at end of file +#endif /* _TVG_SW_RENDERER_CPP_ */ diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index 30b79c8..dcb57c7 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -23,48 +23,164 @@ /* Internal Class Implementation */ /************************************************************************/ -static void _growOutlineContour(SwOutline& outline, uint32_t n) +struct Line { - if (n == 0) { - free(outline.cntrs); - outline.cntrs = nullptr; - outline.cntrsCnt = 0; - outline.reservedCntrsCnt = 0; - return; + Point pt1; + Point pt2; +}; + + +struct Bezier +{ + Point start; + Point ctrl1; + Point ctrl2; + Point end; +}; + + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +static void _lineSplitAt(const Line& cur, float at, Line& left, Line& right) +{ + auto len = _lineLength(cur.pt1, cur.pt2); + auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at; + auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at; + left.pt1 = cur.pt1; + left.pt2.x = left.pt1.x + dx; + left.pt2.y = left.pt1.y + dy; + right.pt1 = left.pt2; + right.pt2 = cur.pt2; +} + + +static void _bezSplit(const Bezier&cur, Bezier& left, Bezier& right) +{ + auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; + left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; + right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f; + left.start.x = cur.start.x; + right.end.x = cur.end.x; + left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; + right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; + left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; + + c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f; + left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f; + right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f; + left.start.y = cur.start.y; + right.end.y = cur.end.y; + left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; + right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; + left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; +} + + +static float _bezLength(const Bezier& cur) +{ + Bezier left, right; + auto len = _lineLength(cur.start, cur.ctrl1) + _lineLength(cur.ctrl1, cur.ctrl2) + _lineLength(cur.ctrl2, cur.end); + auto chord = _lineLength(cur.start, cur.end); + + if (fabs(len - chord) > FLT_EPSILON) { + _bezSplit(cur, left, right); + return _bezLength(left) + _bezLength(right); } - if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return; + return len; +} - //cout << "Grow Cntrs: " << outline.reservedCntrsCnt << " -> " << outline.cntrsCnt + n << endl;; - outline.reservedCntrsCnt = n; - outline.cntrs = static_cast(realloc(outline.cntrs, n * sizeof(uint32_t))); - assert(outline.cntrs); + +static void _bezSplitLeft(Bezier& cur, float at, Bezier& left) +{ + left.start = cur.start; + + left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x); + left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y); + + left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); // temporary holding spot + left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); // temporary holding spot + + cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x); + cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y); + + cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x); + cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y); + + left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x); + left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y); + + left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x); + left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y); } -static void _growOutlinePoint(SwOutline& outline, uint32_t n) +static float _bezAt(const Bezier& bz, float at) { - if (n == 0) { - free(outline.pts); - outline.pts = nullptr; - free(outline.types); - outline.types = nullptr; - outline.reservedPtsCnt = 0; - outline.ptsCnt = 0; - return; + auto len = _bezLength(bz); + auto biggest = 1.0f; + + if (at >= len) return 1.0f; + + at *= 0.5f; + + while (true) { + auto right = bz; + Bezier left; + _bezSplitLeft(right, at, left); + auto len2 = _bezLength(left); + + if (fabs(len2 - len) < FLT_EPSILON) break; + + if (len2 < len) { + at += (biggest - at) * 0.5f; + } else { + biggest = at; + at -= (at * 0.5f); + } } + return at; +} - if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; - //cout << "Grow Pts: " << outline.reservedPtsCnt << " -> " << outline.ptsCnt + n << endl; - outline.reservedPtsCnt = n; - outline.pts = static_cast(realloc(outline.pts, n * sizeof(SwPoint))); +static void _bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) +{ + right = cur; + auto t = _bezAt(right, at); + _bezSplitLeft(right, t, left); +} + + +static void _growOutlineContour(SwOutline& outline, uint32_t n) +{ + if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return; + outline.reservedCntrsCnt = outline.cntrsCnt + n; + outline.cntrs = static_cast(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint32_t))); + assert(outline.cntrs); +} + + +static void _growOutlinePoint(SwOutline& outline, uint32_t n) +{ + if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; + outline.reservedPtsCnt = outline.ptsCnt + n; + outline.pts = static_cast(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint))); assert(outline.pts); - outline.types = static_cast(realloc(outline.types, n * sizeof(uint8_t))); + outline.types = static_cast(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t))); assert(outline.types); } -static void _freeOutline(SwOutline* outline) +static void _delOutline(SwOutline* outline) { if (!outline) return; @@ -112,7 +228,6 @@ static void _outlineLineTo(SwOutline& outline, const Point* to) outline.pts[outline.ptsCnt] = TO_SWPOINT(to); outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; - ++outline.ptsCnt; } @@ -208,80 +323,271 @@ static bool _updateBBox(SwOutline* outline, SwBBox& bbox) } -static bool _checkValid(SwShape& sdata, const SwSize& clip) +static bool _checkValid(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip) { - assert(sdata.outline); + assert(outline); - if (sdata.outline->ptsCnt == 0 || sdata.outline->cntrsCnt <= 0) return false; + if (outline->ptsCnt == 0 || outline->cntrsCnt <= 0) return false; //Check boundary - if ((sdata.bbox.min.x > clip.w || sdata.bbox.min.y > clip.h) || - (sdata.bbox.min.x + sdata.bbox.max.x < 0) || - (sdata.bbox.min.y + sdata.bbox.max.y < 0)) return false; + if ((bbox.min.x > clip.w || bbox.min.y > clip.h) || + (bbox.min.x + bbox.max.x < 0) || + (bbox.min.y + bbox.max.y < 0)) return false; return true; } -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTransform& transform) +static void _transformOutline(SwOutline* outline, const RenderTransform* transform) { - auto outline = sdata.outline; assert(outline); + if (!transform) return; + for(uint32_t i = 0; i < outline->ptsCnt; ++i) { auto dx = static_cast(outline->pts[i].x >> 6); auto dy = static_cast(outline->pts[i].y >> 6); - auto tx = dx * transform.e11 + dy * transform.e12 + transform.e13; - auto ty = dx * transform.e21 + dy * transform.e22 + transform.e23; - auto pt = Point{tx + transform.e31, ty + transform.e32}; + auto tx = dx * transform->e11 + dy * transform->e12 + transform->e13; + auto ty = dx * transform->e21 + dy * transform->e22 + transform->e23; + auto pt = Point{tx + transform->e31, ty + transform->e32}; outline->pts[i] = TO_SWPOINT(&pt); } } -bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip) +static void _dashLineTo(SwDashStroke& dash, const Point* to) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Line cur = {dash.ptCur, *to}; + auto len = _lineLength(cur.pt1, cur.pt2); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur); + _outlineLineTo(*dash.outline, to); + } + } else { + while (len > dash.curLen) { + len -= dash.curLen; + Line left, right; + _lineSplitAt(cur, dash.curLen, left, right);; + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.pt1); + _outlineLineTo(*dash.outline, &left.pt2); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = cur.pt1; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.pt1); + _outlineLineTo(*dash.outline, &cur.pt2); + } + if (dash.curLen < 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Bezier cur = { dash.ptCur, *ctrl1, *ctrl2, *to}; + auto len = _bezLength(cur); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur); + _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to); + } + } else { + while (len > dash.curLen) { + Bezier left, right; + len -= dash.curLen; + _bezSplitAt(cur, dash.curLen, left, right); + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.start); + _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = right.start; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.start); + _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end); + } + if (dash.curLen < 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +SwOutline* _genDashOutline(const Shape& shape) { - /* OPTIMIZE ME: We may avoid this bounding box calculation in this stage - if this shape has stroke and stroke bbox can be used here... */ - if (!_updateBBox(sdata.outline, sdata.bbox)) goto end; - if (!_checkValid(sdata, clip)) goto end; + const PathCommand* cmds = nullptr; + auto cmdCnt = shape.pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = shape.pathCoords(&pts); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + + SwDashStroke dash; + dash.curIdx = 0; + dash.curLen = 0; + dash.ptStart = {0, 0}; + dash.ptCur = {0, 0}; + dash.curOpGap = false; + + const float* pattern; + dash.cnt = shape.strokeDash(&pattern); + assert(dash.cnt > 0 && pattern); + + //Is it safe to mutual exclusive? + dash.pattern = const_cast(pattern); + dash.outline = static_cast(calloc(1, sizeof(SwOutline))); + assert(dash.outline); + dash.outline->opened = true; + + //smart reservation + auto outlinePtsCnt = 0; + auto outlineCntrsCnt = 0; + + for (uint32_t i = 0; i < cmdCnt; ++i) { + switch(*(cmds + i)) { + case PathCommand::Close: { + ++outlinePtsCnt; + break; + } + case PathCommand::MoveTo: { + ++outlineCntrsCnt; + ++outlinePtsCnt; + break; + } + case PathCommand::LineTo: { + ++outlinePtsCnt; + break; + } + case PathCommand::CubicTo: { + outlinePtsCnt += 3; + break; + } + } + } - sdata.rle = rleRender(sdata.outline, sdata.bbox, clip); + ++outlinePtsCnt; //for close + ++outlineCntrsCnt; //for end + + //Reserve Approximitely 20x... + _growOutlinePoint(*dash.outline, outlinePtsCnt * 20); + _growOutlineContour(*dash.outline, outlineCntrsCnt * 20); + while (cmdCnt-- > 0) { + switch(*cmds) { + case PathCommand::Close: { + _dashLineTo(dash, &dash.ptStart); + break; + } + case PathCommand::MoveTo: { + //reset the dash + dash.curIdx = 0; + dash.curLen = *dash.pattern; + dash.curOpGap = false; + dash.ptStart = dash.ptCur = *pts; + ++pts; + break; + } + case PathCommand::LineTo: { + _dashLineTo(dash, pts); + ++pts; + break; + } + case PathCommand::CubicTo: { + _dashCubicTo(dash, pts, pts + 1, pts + 2); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*dash.outline); + + return dash.outline; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool shapeGenRle(SwShape& shape, const Shape& sdata, const SwSize& clip, const RenderTransform* transform) +{ + if (!shapeGenOutline(shape, sdata)) return false; + + _transformOutline(shape.outline, transform); + + if (!_updateBBox(shape.outline, shape.bbox)) goto end; + + if (!_checkValid(shape.outline, shape.bbox, clip)) goto end; + + shape.rle = rleRender(shape.outline, shape.bbox, clip); end: - if (sdata.rle) return true; + if (shape.rle) return true; return false; } -void shapeDelOutline(SwShape& sdata) +void shapeDelOutline(SwShape& shape) { - auto outline = sdata.outline; - _freeOutline(outline); - sdata.outline = nullptr; + auto outline = shape.outline; + _delOutline(outline); + shape.outline = nullptr; } -void shapeReset(SwShape& sdata) +void shapeReset(SwShape& shape) { - shapeDelOutline(sdata); - rleFree(sdata.rle); - sdata.rle = nullptr; - _initBBox(sdata.bbox); + shapeDelOutline(shape); + rleFree(shape.rle); + shape.rle = nullptr; + _initBBox(shape.bbox); } -bool shapeGenOutline(const Shape& shape, SwShape& sdata) +bool shapeGenOutline(SwShape& shape, const Shape& sdata) { const PathCommand* cmds = nullptr; - auto cmdCnt = shape.pathCommands(&cmds); + auto cmdCnt = sdata.pathCommands(&cmds); const Point* pts = nullptr; - auto ptsCnt = shape.pathCoords(&pts); + auto ptsCnt = sdata.pathCoords(&pts); //No actual shape data if (cmdCnt == 0 || ptsCnt == 0) return false; @@ -315,7 +621,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) ++outlinePtsCnt; //for close ++outlineCntrsCnt; //for end - auto outline = sdata.outline; + auto outline = shape.outline; if (!outline) outline = static_cast(calloc(1, sizeof(SwOutline))); assert(outline); outline->opened = true; @@ -354,7 +660,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) //FIXME: //outline->flags = SwOutline::FillRule::Winding; - sdata.outline = outline; + shape.outline = outline; return true; } @@ -376,38 +682,52 @@ void shapeFree(SwShape* sdata) } -void shapeResetStroke(const Shape& shape, SwShape& sdata) +void shapeResetStroke(SwShape& shape, const Shape& sdata) { - if (!sdata.stroke) sdata.stroke = static_cast(calloc(1, sizeof(SwStroke))); - auto stroke = sdata.stroke; + if (!shape.stroke) shape.stroke = static_cast(calloc(1, sizeof(SwStroke))); + auto stroke = shape.stroke; assert(stroke); - strokeReset(*stroke, shape.strokeWidth(), shape.strokeCap(), shape.strokeJoin()); - rleFree(sdata.strokeRle); - sdata.strokeRle = nullptr; + + strokeReset(*stroke, sdata); + + rleFree(shape.strokeRle); + shape.strokeRle = nullptr; } -bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip) +bool shapeGenStrokeRle(SwShape& shape, const Shape& sdata, const SwSize& clip) { - if (!sdata.outline) { - if (!shapeGenOutline(shape, sdata)) return false; - } + SwOutline* shapeOutline = nullptr; - if (!_checkValid(sdata, clip)) return false; + //Dash Style Stroke + if (sdata.strokeDash(nullptr) > 0) { + shapeOutline = _genDashOutline(sdata); + if (!shapeOutline) return false; + + //Normal Style stroke + } else { + if (!shape.outline) { + if (!shapeGenOutline(shape, sdata)) return false; + } + shapeOutline = shape.outline; + } - if (!strokeParseOutline(*sdata.stroke, *sdata.outline)) return false; + if (!strokeParseOutline(*shape.stroke, *shapeOutline)) return false; - auto outline = strokeExportOutline(*sdata.stroke); - if (!outline) return false; + auto strokeOutline = strokeExportOutline(*shape.stroke); + if (!strokeOutline) return false; SwBBox bbox; - _updateBBox(outline, bbox); + _updateBBox(strokeOutline, bbox); - sdata.strokeRle = rleRender(outline, bbox, clip); + if (!_checkValid(strokeOutline, bbox, clip)) return false; - _freeOutline(outline); + shape.strokeRle = rleRender(strokeOutline, bbox, clip); + + _delOutline(strokeOutline); return true; } + #endif /* _TVG_SW_SHAPE_H_ */ diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index 06ec989..fdc8cae 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -351,7 +351,7 @@ void _processCorner(SwStroke& stroke, SwFixed lineLength) } -void _subPathStart(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) +void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) { SwPoint delta = {stroke.width, 0}; mathRotate(delta, startAngle + ANGLE_PI2); @@ -390,7 +390,7 @@ static void _lineTo(SwStroke& stroke, const SwPoint& to) if (stroke.firstPt) { /* This is the first segment of a subpath. We need to add a point to each border at their respective starting point locations. */ - _subPathStart(stroke, angle, lineLength); + _firstSubPath(stroke, angle, lineLength); } else { //process the current corner stroke.angleOut = angle; @@ -455,7 +455,7 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl firstArc = false; //process corner if necessary if (stroke.firstPt) { - _subPathStart(stroke, angleIn, 0); + _firstSubPath(stroke, angleIn, 0); } else { stroke.angleOut = angleIn; _processCorner(stroke, 0); @@ -566,7 +566,6 @@ static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) SwPoint delta2 = {stroke.width, 0}; mathRotate(delta2, angle + rotate); - delta += stroke.center + delta2; _borderLineTo(border, delta, false); @@ -660,26 +659,26 @@ static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened) stroke.firstPt = true; stroke.center = to; - stroke.subPathOpen = opened; + stroke.openSubPath = opened; /* Determine if we need to check whether the border radius is greater than the radius of curvature of a curve, to handle this case specially. This is only required if bevel joins or butt caps may be created because round & miter joins and round & square caps cover the nagative sector created with wide strokes. */ - if ((stroke.join != StrokeJoin::Round) || (stroke.subPathOpen && stroke.cap == StrokeCap::Butt)) + if ((stroke.join != StrokeJoin::Round) || (stroke.openSubPath && stroke.cap == StrokeCap::Butt)) stroke.handleWideStrokes = true; else stroke.handleWideStrokes = false; - stroke.subPathStart = to; + stroke.ptStartSubPath = to; stroke.angleIn = 0; } static void _endSubPath(SwStroke& stroke) { - if (stroke.subPathOpen) { + if (stroke.openSubPath) { auto right = stroke.borders; assert(right); @@ -692,7 +691,7 @@ static void _endSubPath(SwStroke& stroke) _addReverseLeft(stroke, true); //now add the final cap - stroke.center = stroke.subPathStart; + stroke.center = stroke.ptStartSubPath; _addCap(stroke, stroke.subPathAngle + ANGLE_PI, 0); /* now end the right subpath accordingly. The left one is rewind @@ -701,8 +700,8 @@ static void _endSubPath(SwStroke& stroke) } else { //close the path if needed - if (stroke.center != stroke.subPathStart) - _lineTo(stroke, stroke.subPathStart); + if (stroke.center != stroke.ptStartSubPath) + _lineTo(stroke, stroke.ptStartSubPath); //process the corner stroke.angleOut = stroke.subPathAngle; @@ -792,7 +791,6 @@ static void _exportBorderOutline(SwStroke& stroke, SwOutline* outline, uint32_t ++cntrs; ++outline->cntrsCnt; } - ++src; ++tags; ++idx; @@ -820,13 +818,13 @@ void strokeFree(SwStroke* stroke) } -void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) +void strokeReset(SwStroke& stroke, const Shape& shape) { - stroke.width = TO_SWCOORD(width * 0.5); - stroke.cap = cap; + stroke.width = TO_SWCOORD(shape.strokeWidth() * 0.5); + stroke.cap = shape.strokeCap(); //Save line join: it can be temporarily changed when stroking curves... - stroke.joinSaved = stroke.join = join; + stroke.joinSaved = stroke.join = shape.strokeJoin(); stroke.borders[0].ptsCnt = 0; stroke.borders[0].start = -1; @@ -838,7 +836,7 @@ void strokeReset(SwStroke& stroke, float width, StrokeCap cap, StrokeJoin join) } -bool strokeParseOutline(SwStroke& stroke, SwOutline& outline) +bool strokeParseOutline(SwStroke& stroke, const SwOutline& outline) { uint32_t first = 0; @@ -926,5 +924,4 @@ SwOutline* strokeExportOutline(SwStroke& stroke) return outline; } - #endif /* _TVG_SW_STROKER_H_ */ diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index 828e251..67d5ab7 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -328,7 +328,7 @@ int Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noe } -int Shape::stroke(const size_t* dashPattern, size_t cnt) noexcept +int Shape::stroke(const float* dashPattern, size_t cnt) noexcept { if (cnt < 2 || !dashPattern) return -1; @@ -341,7 +341,7 @@ int Shape::stroke(const size_t* dashPattern, size_t cnt) noexcept } -size_t Shape::strokeDash(const size_t** dashPattern) const noexcept +size_t Shape::strokeDash(const float** dashPattern) const noexcept { auto impl = pImpl.get(); assert(impl); diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index e1a4e9f..09956be 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -32,7 +32,7 @@ struct ShapeStroke { float width = 0; uint8_t color[4] = {0, 0, 0, 0}; - size_t* dashPattern = nullptr; + float* dashPattern = nullptr; size_t dashCnt = 0; StrokeCap cap = StrokeCap::Square; StrokeJoin join = StrokeJoin::Bevel; @@ -203,7 +203,7 @@ struct Shape::Impl return 0; } - bool strokeDash(const size_t* pattern, size_t cnt) + bool strokeDash(const float* pattern, size_t cnt) { assert(pattern); @@ -215,12 +215,13 @@ struct Shape::Impl stroke->dashPattern = nullptr; } - if (!stroke->dashPattern) stroke->dashPattern = static_cast(malloc(sizeof(size_t) * cnt)); + if (!stroke->dashPattern) stroke->dashPattern = static_cast(malloc(sizeof(float) * cnt)); assert(stroke->dashPattern); - memcpy(stroke->dashPattern, pattern, cnt); - stroke->dashCnt = cnt; + for (size_t i = 0; i < cnt; ++i) + stroke->dashPattern[i] = pattern[i]; + stroke->dashCnt = cnt; flag |= RenderUpdateFlag::Stroke; return 0; diff --git a/test/testStrokeLine.cpp b/test/testStrokeLine.cpp index 8687b85..1288c07 100644 --- a/test/testStrokeLine.cpp +++ b/test/testStrokeLine.cpp @@ -17,6 +17,7 @@ void tvgtest() auto canvas = tvg::SwCanvas::gen(); canvas->target(buffer, WIDTH, WIDTH, HEIGHT); + //Test for Stroke Width for (int i = 0; i < 10; ++i) { auto shape = tvg::Shape::gen(); shape->moveTo(50, 50 + (25 * i)); @@ -27,6 +28,8 @@ void tvgtest() canvas->push(move(shape)); } + + //Test for StrokeJoin & StrokeCap auto shape1 = tvg::Shape::gen(); shape1->moveTo(20, 350); shape1->lineTo(250, 350); @@ -63,6 +66,52 @@ void tvgtest() shape3->stroke(tvg::StrokeCap::Butt); canvas->push(move(shape3)); + //Test for Stroke Dash + auto shape4 = tvg::Shape::gen(); + shape4->moveTo(20, 600); + shape4->lineTo(250, 600); + shape4->lineTo(220, 750); + shape4->lineTo(70, 720); + shape4->lineTo(70, 580); + shape4->stroke(255, 0, 0, 255); + shape4->stroke(5); + shape4->stroke(tvg::StrokeJoin::Round); + shape4->stroke(tvg::StrokeCap::Round); + + float dashPattern1[2] = {10, 10}; + shape4->stroke(dashPattern1, 2); + canvas->push(move(shape4)); + + auto shape5 = tvg::Shape::gen(); + shape5->moveTo(270, 600); + shape5->lineTo(500, 600); + shape5->lineTo(470, 750); + shape5->lineTo(320, 720); + shape5->lineTo(320, 580); + shape5->stroke(255, 255, 0, 255); + shape5->stroke(5); + shape5->stroke(tvg::StrokeJoin::Bevel); + shape5->stroke(tvg::StrokeCap::Butt); + + float dashPattern2[4] = {10, 10}; + shape5->stroke(dashPattern2, 4); + canvas->push(move(shape5)); + + auto shape6 = tvg::Shape::gen(); + shape6->moveTo(520, 600); + shape6->lineTo(750, 600); + shape6->lineTo(720, 750); + shape6->lineTo(570, 720); + shape6->lineTo(570, 580); + shape6->stroke(255, 255, 255, 255); + shape6->stroke(5); + shape6->stroke(tvg::StrokeJoin::Miter); + shape6->stroke(tvg::StrokeCap::Square); + + float dashPattern3[2] = {10, 10}; + shape6->stroke(dashPattern3, 2); + canvas->push(move(shape6)); + canvas->draw(); canvas->sync(); -- 2.7.4