From c51241f26b8f6ae726d0cf40cde9827e13116f0b Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Sat, 23 May 2020 20:06:40 +0900 Subject: [PATCH] sw_engine: implment basic stroke functions. Change-Id: Ib9203b4d133ce7ffd80b40d7ad0cac3519b5273d --- .gitignore | 1 + src/lib/sw_engine/meson.build | 7 +- src/lib/sw_engine/tvgSwCommon.h | 93 +++- src/lib/sw_engine/tvgSwRaster.cpp | 18 +- src/lib/sw_engine/tvgSwRenderer.cpp | 44 +- src/lib/sw_engine/tvgSwRle.cpp | 20 +- src/lib/sw_engine/tvgSwShape.cpp | 111 +++-- src/lib/sw_engine/tvgSwStroke.cpp | 658 ++++++++++++++++++++++++++++ test/makefile | 23 +- test/testStroke.cpp | 66 ++- 10 files changed, 938 insertions(+), 103 deletions(-) create mode 100644 src/lib/sw_engine/tvgSwStroke.cpp diff --git a/.gitignore b/.gitignore index 6210eb83..1353d089 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ testDirectUpdate testScene testTransform testSceneTransform +testStroke diff --git a/src/lib/sw_engine/meson.build b/src/lib/sw_engine/meson.build index a33059dc..b6664719 100644 --- a/src/lib/sw_engine/meson.build +++ b/src/lib/sw_engine/meson.build @@ -1,10 +1,11 @@ source_file = [ 'tvgSwCommon.h', 'tvgSwRenderer.h', - 'tvgSwRenderer.cpp', - 'tvgSwShape.cpp', - 'tvgSwRle.cpp', 'tvgSwRaster.cpp', + 'tvgSwRenderer.cpp', + 'tvgSwRle.cpp', + 'tvgSwShape.cpp', + 'tvgSwStroke.cpp', ] swraster_dep = declare_dependency( diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index 62a6b297..66d6fe02 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -23,15 +23,27 @@ using namespace tvg; constexpr auto SW_CURVE_TYPE_POINT = 0; 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; struct SwPoint { SwCoord x, y; + SwPoint& operator+=(const SwPoint& rhs) { + x += rhs.x; + y += rhs.y; + return *this; + } + SwPoint operator+(const SwPoint& rhs) const { return {x + rhs.x, y + rhs.y}; } @@ -56,14 +68,15 @@ struct SwSize struct SwOutline { - size_t* cntrs; //the contour end points - size_t cntrsCnt; //number of contours in glyph - size_t reservedCntrsCnt; - SwPoint* pts; //the outline's points - size_t ptsCnt; //number of points in the glyph - size_t reservedPtsCnt; - uint8_t* types; //curve type - uint8_t fillMode; //outline fill mode + uint32_t* cntrs; //the contour end points + uint32_t cntrsCnt; //number of contours in glyph + uint32_t reservedCntrsCnt; + SwPoint* pts; //the outline's points + uint32_t ptsCnt; //number of points in the glyph + uint32_t reservedPtsCnt; + uint8_t* types; //curve type + uint8_t fillMode; //outline fill mode + bool opened; //opened path? }; struct SwSpan @@ -76,8 +89,8 @@ struct SwSpan struct SwRleData { SwSpan *spans; - size_t alloc; - size_t size; + uint32_t alloc; + uint32_t size; }; struct SwBBox @@ -85,19 +98,77 @@ struct SwBBox SwPoint min, max; }; +struct SwStrokeBorder +{ + uint32_t ptsCnt; + uint32_t maxPts; + SwPoint* pts; + uint8_t* tags; + int32_t start; //index of current sub-path start point + bool movable; //true: for ends of lineto borders + bool valid; +}; + +struct SwStroke +{ + SwRleData* rle; + + SwFixed angleIn; + SwFixed angleOut; + SwPoint center; + SwFixed lineLength; + SwPoint subPathStart; + SwFixed subPathLineLength; + SwFixed width; + + StrokeCap cap; + StrokeJoin join; + StrokeJoin joinSaved; + + SwStrokeBorder borders[2]; + + bool firstPt; + bool subPathOpen; + bool subPathAngle; + bool handleWideStrokes; +}; + struct SwShape { SwOutline* outline; SwRleData* rle; + SwStroke* stroke; SwBBox bbox; }; +static inline SwPoint TO_SWPOINT(const Point* pt) +{ + return {SwCoord(pt->x * 64), SwCoord(pt->y * 64)}; +} + + +static inline SwCoord TO_SWCOORD(float val) +{ + return SwCoord(val * 64); +} + + void shapeReset(SwShape& sdata); bool shapeGenOutline(const Shape& shape, SwShape& sdata); -void shapeDelOutline(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 strokeFree(SwStroke* stroke); + SwRleData* rleRender(const SwShape& sdata, const SwSize& clip); +SwRleData* rleStrokeRender(const SwShape& sdata); bool rasterShape(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a); diff --git a/src/lib/sw_engine/tvgSwRaster.cpp b/src/lib/sw_engine/tvgSwRaster.cpp index 0b813fdf..ba7c6a35 100644 --- a/src/lib/sw_engine/tvgSwRaster.cpp +++ b/src/lib/sw_engine/tvgSwRaster.cpp @@ -24,52 +24,52 @@ /* Internal Class Implementation */ /************************************************************************/ -static inline size_t COLOR_ALPHA(size_t color) +static inline uint32_t COLOR_ALPHA(uint32_t color) { return (color >> 24) & 0xff; } -static inline size_t COLOR_ALPHA_BLEND(size_t color, size_t alpha) +static inline uint32_t COLOR_ALPHA_BLEND(uint32_t color, uint32_t alpha) { return (((((color >> 8) & 0x00ff00ff) * alpha) & 0xff00ff00) + ((((color & 0x00ff00ff) * alpha) >> 8) & 0x00ff00ff)); } -static inline size_t COLOR_ARGB_JOIN(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +static inline uint32_t COLOR_ARGB_JOIN(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return (a << 24 | r << 16 | g << 8 | b); } static void -_rasterTranslucent(uint32_t* dst, size_t len, size_t color, size_t cov) +_rasterTranslucent(uint32_t* dst, uint32_t len, uint32_t color, uint32_t cov) { //OPTIMIZE ME: SIMD if (cov < 255) color = COLOR_ALPHA_BLEND(color, cov); auto ialpha = 255 - COLOR_ALPHA(color); - for (size_t i = 0; i < len; ++i) { + for (uint32_t i = 0; i < len; ++i) { dst[i] = color + COLOR_ALPHA_BLEND(dst[i], ialpha); } } static void -_rasterSolid(uint32_t* dst, size_t len, size_t color, size_t cov) +_rasterSolid(uint32_t* dst, uint32_t len, uint32_t color, uint32_t cov) { //OPTIMIZE ME: SIMD //Fully Opaque if (cov == 255) { - for (size_t i = 0; i < len; ++i) { + for (uint32_t i = 0; i < len; ++i) { dst[i] = color; } } else { auto ialpha = 255 - cov; - for (size_t i = 0; i < len; ++i) { + for (uint32_t i = 0; i < len; ++i) { dst[i] = COLOR_ALPHA_BLEND(color, cov) + COLOR_ALPHA_BLEND(dst[i], ialpha); } } @@ -89,7 +89,7 @@ bool rasterShape(Surface& surface, SwShape& sdata, uint8_t r, uint8_t g, uint8_t auto stride = surface.stride; auto color = COLOR_ARGB_JOIN(r, g, b, a); - for (size_t i = 0; i < rle->size; ++i) { + for (uint32_t i = 0; i < rle->size; ++i) { assert(span); auto dst = &surface.buffer[span->y * stride + span->x]; diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 36e367b2..bf97c9b3 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -37,8 +37,8 @@ bool SwRenderer::clear() assert(surface.stride > 0 && surface.w > 0 && surface.h > 0); //OPTIMIZE ME: SIMD! - for (size_t i = 0; i < surface.h; i++) { - for (size_t j = 0; j < surface.w; j++) + for (uint32_t i = 0; i < surface.h; i++) { + for (uint32_t j = 0; j < surface.w; j++) surface.buffer[surface.stride * i + j] = 0xff000000; //Solid Black } @@ -57,16 +57,20 @@ bool SwRenderer::target(uint32_t* buffer, size_t stride, size_t w, size_t h) return true; } - bool SwRenderer::render(const Shape& shape, void *data) { SwShape* sdata = static_cast(data); if (!sdata) return false; - //invisible? size_t r, g, b, a; shape.fill(&r, &g, &b, &a); + size_t sa; + shape.strokeColor(nullptr, nullptr, nullptr, &sa); + + //invisible? + if (a == 0 && sa == 0) return false; + //TODO: Threading return rasterShape(surface, *sdata, r, g, b, a); } @@ -74,10 +78,9 @@ bool SwRenderer::render(const Shape& shape, void *data) bool SwRenderer::dispose(const Shape& shape, void *data) { - SwShape* sdata = static_cast(data); + auto sdata = static_cast(data); if (!sdata) return false; - shapeReset(*sdata); - free(sdata); + shapeFree(sdata); return true; } @@ -93,21 +96,33 @@ void* SwRenderer::prepare(const Shape& shape, void* data, const RenderTransform* if (flags == RenderUpdateFlag::None) return sdata; //invisible? - size_t alpha; - shape.fill(nullptr, nullptr, nullptr, &alpha); - if (alpha == 0) return sdata; + 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); - - SwSize clip = {static_cast(surface.w), static_cast(surface.h)}; if (!shapeGenRle(shape, *sdata, clip)) return sdata; - shapeDelOutline(*sdata); } + //Stroke + if (flags & RenderUpdateFlag::Stroke) { + shapeResetStroke(shape, *sdata); + if (shape.strokeWidth() > 0.01) { + if (!shapeGenStrokeRle(shape, *sdata, clip)) return sdata; + } + } + + shapeDelOutline(*sdata); + return sdata; } @@ -141,5 +156,4 @@ SwRenderer* SwRenderer::inst() return dynamic_cast(RenderInitializer::inst(renderInit)); } - -#endif /* _TVG_SW_RENDERER_CPP_ */ +#endif /* _TVG_SW_RENDERER_CPP_ */ \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp index ea2e4511..3a4b8eda 100644 --- a/src/lib/sw_engine/tvgSwRle.cpp +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -137,7 +137,7 @@ static inline SwCoord HYPOT(SwPoint pt) return ((pt.x > pt.y) ? (pt.x + (3 * pt.y >> 3)) : (pt.y + (3 * pt.x >> 3))); } -static void _genSpan(SwRleData* rle, SwSpan* spans, size_t count) +static void _genSpan(SwRleData* rle, SwSpan* spans, uint32_t count) { assert(rle && spans); @@ -598,15 +598,19 @@ static bool _decomposeOutline(RleWorker& rw) auto first = 0; //index of first point in contour - for (size_t n = 0; n < outline->cntrsCnt; ++n) { + for (uint32_t n = 0; n < outline->cntrsCnt; ++n) { auto last = outline->cntrs[n]; if (last < 0) goto invalid_outline; auto limit = outline->pts + last; assert(limit); + auto start = UPSCALE(outline->pts[first]); + auto pt = outline->pts + first; + assert(pt); auto types = outline->types + first; + assert(types); /* A contour cannot start with a cubic control point! */ if (types[0] == SW_CURVE_TYPE_CUBIC) goto invalid_outline; @@ -632,7 +636,7 @@ static bool _decomposeOutline(RleWorker& rw) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); continue; } - _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(outline->pts[first])); + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); goto close; } } @@ -665,6 +669,7 @@ static bool _genRle(RleWorker& rw) } + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -792,4 +797,13 @@ error: return nullptr; } + +SwRleData* rleStrokeRender(const SwShape& sdata) +{ + auto stroke = sdata.stroke; + assert(stroke); + + return nullptr; +} + #endif /* _TVG_SW_RLE_H_ */ diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index d7229dce..f1ab06e0 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -23,13 +23,7 @@ /* Internal Class Implementation */ /************************************************************************/ -static inline SwPoint TO_SWPOINT(const Point* pt) -{ - return {SwCoord(pt->x * 64), SwCoord(pt->y * 64)}; -} - - -static void _growOutlineContour(SwOutline& outline, size_t n) +static void _growOutlineContour(SwOutline& outline, uint32_t n) { if (n == 0) { free(outline.cntrs); @@ -42,12 +36,12 @@ static void _growOutlineContour(SwOutline& outline, size_t n) //cout << "Grow Cntrs: " << outline.reservedCntrsCnt << " -> " << outline.cntrsCnt + n << endl;; outline.reservedCntrsCnt = n; - outline.cntrs = static_cast(realloc(outline.cntrs, n * sizeof(size_t))); + outline.cntrs = static_cast(realloc(outline.cntrs, n * sizeof(uint32_t))); assert(outline.cntrs); } -static void _growOutlinePoint(SwOutline& outline, size_t n) +static void _growOutlinePoint(SwOutline& outline, uint32_t n) { if (n == 0) { free(outline.pts); @@ -132,9 +126,9 @@ static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* } -static bool _outlineClose(SwOutline& outline) +static void _outlineClose(SwOutline& outline) { - size_t i = 0; + uint32_t i = 0; if (outline.cntrsCnt > 0) { i = outline.cntrs[outline.cntrsCnt - 1] + 1; @@ -143,7 +137,10 @@ static bool _outlineClose(SwOutline& outline) } //Make sure there is at least one point in the current path - if (outline.ptsCnt == i) return false; + if (outline.ptsCnt == i) { + outline.opened = true; + return; + } //Close the path _growOutlinePoint(outline, 1); @@ -152,7 +149,7 @@ static bool _outlineClose(SwOutline& outline) outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; ++outline.ptsCnt; - return true; + outline.opened = false; } @@ -183,7 +180,7 @@ static bool _updateBBox(SwShape& sdata) ++pt; - for(size_t i = 1; i < outline->ptsCnt; ++i, ++pt) { + for(uint32_t i = 1; i < outline->ptsCnt; ++i, ++pt) { assert(pt); if (xMin > pt->x) xMin = pt->x; if (xMax < pt->x) xMax = pt->x; @@ -201,16 +198,31 @@ static bool _updateBBox(SwShape& sdata) } -void _deleteRle(SwShape& sdata) +static bool _checkValid(SwShape& sdata, const SwSize& clip) { - if (sdata.rle) { - if (sdata.rle->spans) free(sdata.rle->spans); - free(sdata.rle); - } + assert(sdata.outline); + + if (sdata.outline->ptsCnt == 0 || sdata.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; + + return true; +} + + +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 */ /************************************************************************/ @@ -220,7 +232,7 @@ void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTrans auto outline = sdata.outline; assert(outline); - for(size_t i = 0; i < outline->ptsCnt; ++i) { + 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; @@ -233,13 +245,8 @@ void shapeTransformOutline(const Shape& shape, SwShape& sdata, const RenderTrans bool shapeGenRle(const Shape& shape, SwShape& sdata, const SwSize& clip) { - if (sdata.outline->ptsCnt == 0 || sdata.outline->cntrsCnt <= 0) goto end; if (!_updateBBox(sdata)) goto end; - - //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)) goto end; + if (!_checkValid(sdata, clip)) goto end; sdata.rle = rleRender(sdata, clip); @@ -251,14 +258,13 @@ end: void shapeDelOutline(SwShape& sdata) { - if (!sdata.outline) return; + auto outline = sdata.outline; + if (!outline) return; - SwOutline* outline = sdata.outline; if (outline->cntrs) free(outline->cntrs); if (outline->pts) free(outline->pts); if (outline->types) free(outline->types); free(outline); - sdata.outline = nullptr; } @@ -286,7 +292,7 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) auto outlinePtsCnt = 0; auto outlineCntrsCnt = 0; - for (size_t i = 0; i < cmdCnt; ++i) { + for (uint32_t i = 0; i < cmdCnt; ++i) { switch(*(cmds + i)) { case PathCommand::Close: { ++outlinePtsCnt; @@ -311,14 +317,9 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) ++outlinePtsCnt; //for close ++outlineCntrsCnt; //for end - SwOutline* outline = sdata.outline; - - if (!outline) { - outline = static_cast(calloc(1, sizeof(SwOutline))); - assert(outline); - } else { - cout << "Outline was already allocated? How?" << endl; - } + auto outline = sdata.outline; + if (!outline) outline = static_cast(calloc(1, sizeof(SwOutline))); + assert(outline); _growOutlinePoint(*outline, outlinePtsCnt); _growOutlineContour(*outline, outlineCntrsCnt); @@ -360,4 +361,38 @@ bool shapeGenOutline(const Shape& shape, SwShape& sdata) } +void shapeFree(SwShape* sdata) +{ + assert(sdata); + + shapeDelOutline(*sdata); + _deleteRle(*sdata); + strokeFree(sdata->stroke); + free(sdata); +} + + +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()); +} + + +bool shapeGenStrokeRle(const Shape& shape, SwShape& sdata, const SwSize& clip) +{ + if (!sdata.outline) { + if (!shapeGenOutline(shape, sdata)) return false; + } + + if (!_checkValid(sdata, clip)) return false; + + if (!strokeParseOutline(*sdata.stroke, *sdata.outline)) return false; + + return true; +} + #endif /* _TVG_SW_SHAPE_H_ */ diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp new file mode 100644 index 00000000..2f810a1c --- /dev/null +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -0,0 +1,658 @@ +/* + * 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_STROKER_H_ +#define _TVG_SW_STROKER_H_ + +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* 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); + +//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(long) * 8 - 1)); +} + + +static inline SwFixed SIDE_TO_ROTATE(int32_t s) +{ + return (ANGLE_PI2 - (s) * ANGLE_PI); +} + + +static int64_t _multiply(int64_t a, int64_t b) +{ + int32_t s = 1; + + //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; +} + + +static int64_t _divide(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + //move sign + if (b < 0) { + b = -b; + s = -s; + } + int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; + return (s < 0 ? -q : q); +} + + +static SwFixed _angleDiff(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; +} + + +static void _trigDownscale(SwPoint& pt) +{ + //multiply a give value by the CORDIC shrink factor + + auto s = pt; + + //abs + if (pt.x < 0) pt.x = -pt.x; + if (pt.y < 0) pt.y = -pt.y; + + 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); + + if (s.x < 0) pt.x = -pt.x; + if (s.y < 0) pt.y = -pt.y; +} + + +static int32_t _trigPrenorm(SwPoint& pt) +{ + /* the highest bit in overflow-safe vector components + MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ + constexpr auto TRIG_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 <= 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); + } else { + shift -= TRIG_SAFE_MSB; + pt.x = v.x >> shift; + pt.y = v.y >> shift; + shift = -shift; + } + return shift; +} + + +static void _trigPseudoRotate(SwPoint& pt, SwFixed theta) +{ + 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; + } + + while (theta > ANGLE_PI4) { + auto temp = -v.y; + v.y = v.x; + v.x = temp; + 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 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++; + } + } + + pt = v; +} + + +static void _rotate(SwPoint& pt, SwFixed angle) +{ + if (angle == 0 || (pt.x == 0 && pt.y == 0)) return; + + auto v = pt; + auto shift = _trigPrenorm(v); + _trigPseudoRotate(v, angle); + _trigDownscale(v); + + 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); + } +} + + +static SwFixed _tan(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + _rotate(v, angle); + return _divide(v.y, v.x); +} + + +static SwFixed _cos(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + _rotate(v, angle); + return (v.x + 0x80L) >> 8; +} + + +static void _lineTo(SwStroke& stroke, const SwPoint& to) +{ + +} + + +static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + +} + + +static void _arcTo(SwStroke& stroke, int32_t side) +{ + +} + + +static void _growBorder(SwStrokeBorder* border, uint32_t newPts) +{ + 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 void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable) +{ + constexpr SwPoint EPSILON = 2; + + assert(border && border->start >= 0); + + 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; + + _growBorder(border, 1); + border->pts[border->ptsCnt] = to; + border->tags[border->ptsCnt] = SW_STROKE_TAG_ON; + border->ptsCnt += 1; + } + + border->movable = movable; +} + + +static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) +{ + if (stroke.cap == StrokeCap::Square) { + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {stroke.width, 0}; + _rotate(delta, angle); + + SwPoint delta2 = {stroke.width, 0}; + _rotate(delta2, angle + rotate); + + delta += stroke.center + delta2; + + _borderLineTo(border, delta, false); + + delta = {stroke.width, 0}; + _rotate(delta, angle); + + delta2 = {stroke.width, 0}; + _rotate(delta2, angle - rotate); + + delta += delta2 + stroke.center; + + _borderLineTo(border, delta, false); + + } else if (stroke.cap == StrokeCap::Round) { + + stroke.angleIn = angle; + stroke.angleOut = angle + ANGLE_PI; + _arcTo(stroke, side); + return; + + } else { //Butt + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {stroke.width, 0}; + _rotate(delta, angle + rotate); + + delta += stroke.center; + + _borderLineTo(border, delta, false); + + delta = {stroke.width, 0}; + _rotate(delta, angle - rotate); + + delta += stroke.center; + + _borderLineTo(border, delta, false); + } +} + + +static void _addReverseLeft(SwStroke& stroke, bool opened) +{ + auto right = stroke.borders + 0; + auto left = stroke.borders + 1; + assert(left->start >= 0); + + auto newPts = left->ptsCnt - left->start; + + if (newPts <= 0) return; + + _growBorder(right, newPts); + + auto dstPt = right->pts + right->ptsCnt; + auto dstTag = right->tags + right->ptsCnt; + auto srcPt = left->pts + left->ptsCnt - 1; + auto srcTag = left->tags + left->ptsCnt - 1; + + while (srcPt >= left->pts + left->start) { + *dstPt = *srcPt; + *dstTag = *srcTag; + + if (opened) { + dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + } 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); + } + } + + --srcPt; + --srcTag; + --dstPt; + --dstTag; + } + + left->ptsCnt = left->start; + right->ptsCnt += newPts; + right->movable = false; + left->movable = false; +} + + +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; + + /* We cannot process the first point because there is not enought + information regarding its corner/cap. Later, it will be processed + in the _strokeEndSubPath() */ + + stroke.firstPt = true; + stroke.center = to; + stroke.subPathOpen = 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)) + stroke.handleWideStrokes = true; + else + stroke.handleWideStrokes = false; + + stroke.subPathStart = to; + stroke.angleIn = 0; +} + + +static void _endSubPath(SwStroke& stroke) +{ + if (stroke.subPathOpen) { + auto right = stroke.borders; + assert(right); + + /* all right, this is an opened path, we need to add a cap between + right & left, add the reverse of left, then add a final cap + between left & right */ + _addCap(stroke, stroke.angleIn, 0); + + //add reversed points from 'left' to 'right' + _addReverseLeft(stroke, true); + + //now add the final cap + stroke.center = stroke.subPathStart; + _addCap(stroke, stroke.subPathAngle + ANGLE_PI, 0); + + /* now end the right subpath accordingly. The left one is rewind + and deosn't need further processing */ + _closeBorder(right, false); + } else { + + //close the path if needed + if (stroke.center != stroke.subPathStart) + _lineTo(stroke, stroke.subPathStart); + + //process the corner + stroke.angleOut = stroke.subPathAngle; + auto turn = _angleDiff(stroke.angleIn, stroke.angleOut); + + //No specific corner processing is required if the turn is 0 + if (turn != 0) { + + //when we turn to the right, the inside is 0 + auto 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 + } + + _closeBorder(stroke.borders + 0, false); + _closeBorder(stroke.borders + 1, true); + } +} + + +static void _deleteRle(SwStroke& stroke) +{ + if (!stroke.rle) return; + if (stroke.rle->spans) free(stroke.rle->spans); + free(stroke.rle); + stroke.rle = nullptr; +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void strokeFree(SwStroke* stroke) +{ + if (!stroke) return; + _deleteRle(*stroke); + free(stroke); +} + + +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; + + //Save line join: it can be temporarily changed when stroking curves... + stroke.joinSaved = stroke.join = join; + + stroke.borders[0].ptsCnt = 0; + stroke.borders[0].start = -1; + stroke.borders[0].valid = false; + + stroke.borders[1].ptsCnt = 0; + stroke.borders[1].start = -1; + stroke.borders[1].valid = false; +} + +bool strokeParseOutline(SwStroke& stroke, SwOutline& outline) +{ + uint32_t first = 0; + + for (uint32_t i = 0; i < outline.cntrsCnt; ++i) { + auto last = outline.cntrs[i]; //index of last point in contour + auto limit = outline.pts + last; + assert(limit); + + //Skip empty points + if (last <= first) { + first = last + 1; + continue; + } + + auto start = outline.pts[first]; + + auto pt = outline.pts + first; + assert(pt); + auto types = outline.types + first; + assert(types); + + auto type = types[0]; + + //A contour cannot start with a cubic control point + if (type == SW_CURVE_TYPE_CUBIC) return false; + + _beginSubPath(stroke, start, outline.opened); + + while (pt < limit) { + assert(++pt); + assert(++types); + + //emit a signel line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(stroke, *pt); + //types cubic + } else { + if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false; + + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(stroke, pt[-2], pt[-1], pt[0]); + continue; + } + _cubicTo(stroke, pt[-2], pt[-1], start); + goto close; + } + } + + close: + if (!stroke.firstPt) _endSubPath(stroke); + first = last + 1; + } + return true; +} + + +#endif /* _TVG_SW_STROKER_H_ */ diff --git a/test/makefile b/test/makefile index 50710e08..174b93f2 100644 --- a/test/makefile +++ b/test/makefile @@ -1,12 +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` diff --git a/test/testStroke.cpp b/test/testStroke.cpp index e2ae1bc2..1f865d68 100644 --- a/test/testStroke.cpp +++ b/test/testStroke.cpp @@ -1,4 +1,5 @@ #include +#include using namespace std; @@ -7,33 +8,72 @@ using namespace std; static uint32_t buffer[WIDTH * HEIGHT]; -int main(int argc, char **argv) + +void tvgtest() { //Initialize TizenVG Engine tvg::Engine::init(); - //Create a Canvas - auto canvas = tvg::SwCanvas::gen(buffer, WIDTH, HEIGHT); + //Initialize TizenVG Engine + tvg::Engine::init(); - //Prepare a Shape + //Create a Canvas + auto canvas = tvg::SwCanvas::gen(); + canvas->target(buffer, WIDTH, WIDTH, HEIGHT); + + //Prepare a Shape (Rectangle + Rectangle + Circle + Circle) auto shape1 = tvg::Shape::gen(); - shape1->rect(0, 0, 400, 400, 0.1); //x, y, w, h, cornerRadius - shape1->fill(0, 255, 0, 255); + shape1->appendRect(0, 0, 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 //Stroke Style - shape1->strokeColor(0, 0, 0, 255); //r, g, b, a - shape1->strokeWidth(1); //1px - shape1->strokeJoin(tvg::StrokeJoin::Miter); - shape1->strokeLineCap(tvg::StrokeLineCap::Butt); + 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); - uint32_t dash[] = {3, 1, 5, 1}; //dash pattern - shape1->strokeDash(dash, 4); +// uint32_t dash[] = {3, 1, 5, 1}; //dash pattern +// shape1->strokeDash(dash, 4); - //Draw the Shape onto the Canvas canvas->push(move(shape1)); + 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(); +}