diff --git a/thirdparty/README.md b/thirdparty/README.md index 575d4d6135c..057c4935285 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -944,7 +944,7 @@ Patches: ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.15.8 (bd8c2fca7663a22fba7a339937cb60f2f6247a2e, 2025) +- Version: 0.15.10 (bca94d244c67f573c6eddc27d783d9a6b1ef2f1b, 2025) - License: MIT Files extracted from upstream source: @@ -955,7 +955,6 @@ Files extracted from upstream source: Patches: - `0001-revert-tvglines-bezier-precision.patch` (GH-96658) -- `0002-gcc15-include-fix.patch` (GH-102022) ## tinyexr diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS index 3a4cc7a11cf..4f8fbb47db7 100644 --- a/thirdparty/thorvg/AUTHORS +++ b/thirdparty/thorvg/AUTHORS @@ -38,3 +38,4 @@ Elliott Sales de Andrade Kelly Loh DragoČ™ Tiselice Marcin Baszczewski +Fabian Blatz diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 62ca775dab5..b2b1cb6be57 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -15,5 +15,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.15.8" +#define THORVG_VERSION_STRING "0.15.10" #endif diff --git a/thirdparty/thorvg/patches/0002-gcc15-include-fix.patch b/thirdparty/thorvg/patches/0002-gcc15-include-fix.patch deleted file mode 100644 index 4071e1dd6e6..00000000000 --- a/thirdparty/thorvg/patches/0002-gcc15-include-fix.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h -index 8e3ab4e6ce..f515a03136 100644 ---- a/thirdparty/thorvg/inc/thorvg.h -+++ b/thirdparty/thorvg/inc/thorvg.h -@@ -1,6 +1,7 @@ - #ifndef _THORVG_H_ - #define _THORVG_H_ - -+#include - #include - #include - #include diff --git a/thirdparty/thorvg/src/common/tvgStr.cpp b/thirdparty/thorvg/src/common/tvgStr.cpp index 1ebdd41c5e9..957fe18d532 100644 --- a/thirdparty/thorvg/src/common/tvgStr.cpp +++ b/thirdparty/thorvg/src/common/tvgStr.cpp @@ -183,7 +183,7 @@ float strToFloat(const char *nPtr, char **endPtr) auto scale = 1.0f; while (exponentPart >= 8U) { - scale *= 1E8; + scale *= 1E8f; exponentPart -= 8U; } while (exponentPart > 0U) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 6ba433ba230..b5e90e7b404 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -200,7 +200,7 @@ static float _gradientToFloat(const SvgParser* svgParse, const char* str, bool& isPercentage = false; if (strstr(str, "%")) { - parsedValue = parsedValue / 100.0; + parsedValue = parsedValue / 100.0f; isPercentage = true; } else if (strstr(str, "cm")) parsedValue *= PX_PER_CM; @@ -225,7 +225,7 @@ static float _toOffset(const char* str) auto ptr = strstr(str, "%"); if (ptr) { - parsedValue = parsedValue / 100.0; + parsedValue = parsedValue / 100.0f; if (end != ptr || (end + 1) != strEnd) return 0; } else if (end != strEnd) return 0; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp index 57442139cdf..e37e750436f 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -402,10 +402,10 @@ static bool _processCommand(Array* cmds, Array* pts, char cm case 'q': case 'Q': { Point p[3]; - float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0 / 3.0); - float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0 / 3.0); - float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0 / 3.0); - float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0 / 3.0); + float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0f / 3.0f); + float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0f / 3.0f); + float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0f / 3.0f); + float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0f / 3.0f); cmds->push(PathCommand::CubicTo); p[0] = {ctrl_x0, ctrl_y0}; p[1] = {ctrl_x1, ctrl_y1}; @@ -428,10 +428,10 @@ static bool _processCommand(Array* cmds, Array* pts, char cm } else { ctrl = *cur; } - float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0 / 3.0); - float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0 / 3.0); - float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0 / 3.0); - float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0 / 3.0); + float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0f / 3.0f); + float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0f / 3.0f); + float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0f / 3.0f); + float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0f / 3.0f); cmds->push(PathCommand::CubicTo); p[0] = {ctrl_x0, ctrl_y0}; p[1] = {ctrl_x1, ctrl_y1}; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp index da1cdae9e0e..4e763f24524 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -26,7 +26,7 @@ #ifdef _WIN32 #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__ZEPHYR__) #include #else #include diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index 84fc4d9ea38..a45a996ad91 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -23,8 +23,8 @@ #ifndef _TVG_SW_COMMON_H_ #define _TVG_SW_COMMON_H_ -#include #include "tvgCommon.h" +#include "tvgMath.h" #include "tvgRender.h" #define SW_CURVE_TYPE_POINT 0 @@ -379,10 +379,13 @@ static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint static inline uint32_t opBlendExclusion(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { - //A + B - 2AB - auto c1 = std::min(255, C1(s) + C1(d) - std::min(255, (C1(s) * C1(d)) << 1)); - auto c2 = std::min(255, C2(s) + C2(d) - std::min(255, (C2(s) * C2(d)) << 1)); - auto c3 = std::min(255, C3(s) + C3(d) - std::min(255, (C3(s) * C3(d)) << 1)); + // (s + d) - (2 * s * d) + auto c1 = C1(s) + C1(d) - 2 * MULTIPLY(C1(s), C1(d)); + tvg::clamp(c1, 0, 255); + auto c2 = C2(s) + C2(d) - 2 * MULTIPLY(C2(s), C2(d)); + tvg::clamp(c2, 0, 255); + auto c3 = C3(s) + C3(d) - 2 * MULTIPLY(C3(s), C3(d)); + tvg::clamp(c3, 0, 255); return JOIN(255, c1, c2, c3); } @@ -444,10 +447,10 @@ static inline uint32_t opBlendLighten(uint32_t s, uint32_t d, TVG_UNUSED uint8_t static inline uint32_t opBlendColorDodge(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { // d / (1 - s) - auto is = 0xffffffff - s; - auto c1 = (C1(is) > 0) ? (C1(d) / C1(is)) : C1(d); - auto c2 = (C2(is) > 0) ? (C2(d) / C2(is)) : C2(d); - auto c3 = (C3(is) > 0) ? (C3(d) / C3(is)) : C3(d); + s = 0xffffffff - s; + auto c1 = (C1(s) == 0) ? C1(d) : std::min(C1(d) * 255 / C1(s), 255); + auto c2 = (C2(s) == 0) ? C2(d) : std::min(C2(d) * 255 / C2(s), 255); + auto c3 = (C3(s) == 0) ? C3(d) : std::min(C3(d) * 255 / C3(s), 255); return JOIN(255, c1, c2, c3); } @@ -455,14 +458,17 @@ static inline uint32_t opBlendColorBurn(uint32_t s, uint32_t d, TVG_UNUSED uint8 { // 1 - (1 - d) / s auto id = 0xffffffff - d; - auto c1 = 255 - ((C1(s) > 0) ? (C1(id) / C1(s)) : C1(id)); - auto c2 = 255 - ((C2(s) > 0) ? (C2(id) / C2(s)) : C2(id)); - auto c3 = 255 - ((C3(s) > 0) ? (C3(id) / C3(s)) : C3(id)); + auto c1 = (C1(s) == 0) ? C1(d) : 255 - std::min(C1(id) * 255 / C1(s), 255); + auto c2 = (C2(s) == 0) ? C2(d) : 255 - std::min(C2(id) * 255 / C2(s), 255); + auto c3 = (C3(s) == 0) ? C3(d) : 255 - std::min(C3(id) * 255 / C3(s), 255); + return JOIN(255, c1, c2, c3); } static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { + // if (s < sa), (2 * s * d) + // else (sa * da) - 2 * (da - s) * (sa - d) auto c1 = (C1(s) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d)))); auto c2 = (C2(s) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d)))); auto c3 = (C3(s) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d)))); @@ -472,9 +478,9 @@ static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8 static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { //(255 - 2 * s) * (d * d) + (2 * s * b) - auto c1 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + 2 * MULTIPLY(C1(s), C1(d))); - auto c2 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + 2 * MULTIPLY(C2(s), C2(d))); - auto c3 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + 2 * MULTIPLY(C3(s), C3(d))); + auto c1 = MULTIPLY(255 - std::min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + MULTIPLY(std::min(255, 2 * C1(s)), C1(d)); + auto c2 = MULTIPLY(255 - std::min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + MULTIPLY(std::min(255, 2 * C2(s)), C2(d)); + auto c3 = MULTIPLY(255 - std::min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + MULTIPLY(std::min(255, 2 * C3(s)), C3(d)); return JOIN(255, c1, c2, c3); } @@ -578,14 +584,16 @@ bool rasterConvertCS(RenderSurface* surface, ColorSpace to); uint32_t rasterUnpremultiply(uint32_t data); bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params); -bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect); +bool effectGaussianBlurRegion(RenderEffectGaussianBlur* effect); +void effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform); bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, bool direct); -bool effectDropShadowPrepare(RenderEffectDropShadow* effect); -bool effectFillPrepare(RenderEffectFill* effect); +bool effectDropShadowRegion(RenderEffectDropShadow* effect); +void effectDropShadowUpdate(RenderEffectDropShadow* effect, const Matrix& transform); +void effectFillUpdate(RenderEffectFill* effect); bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct); -bool effectTintPrepare(RenderEffectTint* effect); +void effectTintUpdate(RenderEffectTint* effect); bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct); -bool effectTritonePrepare(RenderEffectTritone* effect); +void effectTritoneUpdate(RenderEffectTritone* effect); bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct); #endif /* _TVG_SW_COMMON_H_ */ diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp index 3afbd660119..4be40c885dc 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -32,39 +32,27 @@ struct SwGaussianBlur static constexpr int MAX_LEVEL = 3; int level; int kernel[MAX_LEVEL]; + int extends; }; -static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direction) +static inline int _gaussianEdgeWrap(int end, int idx) { - //bbox region expansion for feathering - if (direction != 2) { - region.x = -extra; - region.w = extra * 2; - } - if (direction != 1) { - region.y = -extra; - region.h = extra * 2; - } + auto r = idx % (end + 1); + return (r < 0) ? (end + 1) + r : r; } -static int _gaussianEdgeWrap(int end, int idx) -{ - auto r = idx % end; - return (r < 0) ? end + r : r; -} - - -static int _gaussianEdgeExtend(int end, int idx) +static inline int _gaussianEdgeExtend(int end, int idx) { if (idx < 0) return 0; - else if (idx >= end) return end - 1; + else if (idx > end) return end; return idx; } -static int _gaussianRemap(int end, int idx, int border) +template +static inline int _gaussianRemap(int end, int idx) { if (border == 1) return _gaussianEdgeWrap(end, idx); return _gaussianEdgeExtend(end, idx); @@ -72,7 +60,8 @@ static int _gaussianRemap(int end, int idx, int border) //TODO: SIMD OPTIMIZATION? -static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped) +template +static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, bool flipped) { if (flipped) { src += (bbox.min.x * stride + bbox.min.y) << 2; @@ -83,6 +72,7 @@ static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t } auto iarr = 1.0f / (dimension + dimension + 1); + auto end = w - 1; #pragma omp parallel for for (int y = 0; y < h; ++y) { @@ -94,7 +84,7 @@ static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t //initial accumulation for (int x = l; x < r; ++x) { - auto id = (_gaussianRemap(w, x, border) + p) * 4; + auto id = (_gaussianRemap(end, x) + p) * 4; acc[0] += src[id++]; acc[1] += src[id++]; acc[2] += src[id++]; @@ -102,16 +92,17 @@ static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t } //perform filtering for (int x = 0; x < w; ++x, ++r, ++l) { - auto rid = (_gaussianRemap(w, r, border) + p) * 4; - auto lid = (_gaussianRemap(w, l, border) + p) * 4; + auto rid = (_gaussianRemap(end, r) + p) * 4; + auto lid = (_gaussianRemap(end, l) + p) * 4; acc[0] += src[rid++] - src[lid++]; acc[1] += src[rid++] - src[lid++]; acc[2] += src[rid++] - src[lid++]; acc[3] += src[rid] - src[lid]; - dst[i++] = static_cast(acc[0] * iarr + 0.5f); - dst[i++] = static_cast(acc[1] * iarr + 0.5f); - dst[i++] = static_cast(acc[2] * iarr + 0.5f); - dst[i++] = static_cast(acc[3] * iarr + 0.5f); + //ignored rounding for the performance. It should be originally: acc[idx] * iarr + 0.5f + dst[i++] = static_cast(acc[0] * iarr); + dst[i++] = static_cast(acc[1] * iarr); + dst[i++] = static_cast(acc[2] * iarr); + dst[i++] = static_cast(acc[3] * iarr); } } } @@ -142,27 +133,44 @@ static int _gaussianInit(SwGaussianBlur* data, float sigma, int quality) } -bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params) +bool effectGaussianBlurRegion(RenderEffectGaussianBlur* params) { - auto rd = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur)); + //bbox region expansion for feathering + auto& region = params->extend; + auto extra = static_cast(params->rd)->extends; - auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality); - - //invalid - if (extends == 0) { - free(rd); - return false; + if (params->direction != 2) { + region.x = -extra; + region.w = extra * 2; + } + if (params->direction != 1) { + region.y = -extra; + region.h = extra * 2; } - _gaussianExtendRegion(params->extend, extends, params->direction); - - params->rd = rd; - params->valid = true; - return true; } +void effectGaussianBlurUpdate(RenderEffectGaussianBlur* params, const Matrix& transform) +{ + if (!params->rd) params->rd = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur)); + auto rd = static_cast(params->rd); + + //compute box kernel sizes + auto scale = sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12); + rd->extends = _gaussianInit(rd, std::pow(params->sigma * scale, 2), params->quality); + + //invalid + if (rd->extends == 0) { + params->valid = false; + return; + } + + params->valid = true; +} + + bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params) { auto& buffer = surface->compositor->image; @@ -184,7 +192,7 @@ bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffec //horizontal if (params->direction != 2) { for (int i = 0; i < data->level; ++i) { - _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, w, h, bbox, data->kernel[i], params->border, false); + _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, w, h, bbox, data->kernel[i], false); std::swap(front, back); swapped = !swapped; } @@ -196,7 +204,7 @@ bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffec std::swap(front, back); for (int i = 0; i < data->level; ++i) { - _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, h, w, bbox, data->kernel[i], params->border, true); + _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, h, w, bbox, data->kernel[i], true); std::swap(front, back); swapped = !swapped; } @@ -231,6 +239,7 @@ static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, i dst += (bbox.min.y * stride + bbox.min.x); } auto iarr = 1.0f / (dimension + dimension + 1); + auto end = w - 1; #pragma omp parallel for for (int y = 0; y < h; ++y) { @@ -242,15 +251,16 @@ static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, i //initial accumulation for (int x = l; x < r; ++x) { - auto id = _gaussianEdgeExtend(w, x) + p; + auto id = _gaussianEdgeExtend(end, x) + p; acc += A(src[id]); } //perform filtering for (int x = 0; x < w; ++x, ++r, ++l) { - auto rid = _gaussianEdgeExtend(w, r) + p; - auto lid = _gaussianEdgeExtend(w, l) + p; + auto rid = _gaussianEdgeExtend(end, r) + p; + auto lid = _gaussianEdgeExtend(end, l) + p; acc += A(src[rid]) - A(src[lid]); - dst[i++] = ALPHA_BLEND(color, static_cast(acc * iarr + 0.5f)); + //ignored rounding for the performance. It should be originally: acc * iarr + dst[i++] = ALPHA_BLEND(color, static_cast(acc * iarr)); } } } @@ -281,9 +291,13 @@ static void _dropShadowShift(uint32_t* dst, uint32_t* src, int stride, SwBBox& r } -static void _dropShadowExtendRegion(RenderRegion& region, int extra, SwPoint& offset) +bool effectDropShadowRegion(RenderEffectDropShadow* params) { //bbox region expansion for feathering + auto& region = params->extend; + auto& offset = static_cast(params->rd)->offset; + auto extra = static_cast(params->rd)->extends; + region.x = -extra; region.w = extra * 2; region.y = -extra; @@ -293,20 +307,24 @@ static void _dropShadowExtendRegion(RenderRegion& region, int extra, SwPoint& of region.y = std::min(region.y + (int32_t)offset.y, region.y); region.w += abs(offset.x); region.h += abs(offset.y); + + return true; } -bool effectDropShadowPrepare(RenderEffectDropShadow* params) +void effectDropShadowUpdate(RenderEffectDropShadow* params, const Matrix& transform) { - auto rd = (SwDropShadow*)malloc(sizeof(SwDropShadow)); + if (!params->rd) params->rd = (SwDropShadow*)malloc(sizeof(SwDropShadow)); + auto rd = static_cast(params->rd); //compute box kernel sizes - auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality); + auto scale = sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12); + rd->extends = _gaussianInit(rd, std::pow(params->sigma * scale, 2), params->quality); //invalid - if (extends == 0 || params->color[3] == 0) { - free(rd); - return false; + if (rd->extends == 0 || params->color[3] == 0) { + params->valid = false; + return; } //offset @@ -317,13 +335,7 @@ bool effectDropShadowPrepare(RenderEffectDropShadow* params) rd->offset = {0, 0}; } - //bbox region expansion for feathering - _dropShadowExtendRegion(params->extend, extends, rd->offset); - - params->rd = rd; params->valid = true; - - return true; } @@ -405,10 +417,9 @@ bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffe /* Fill Implementation */ /************************************************************************/ -bool effectFillPrepare(RenderEffectFill* params) +void effectFillUpdate(RenderEffectFill* params) { params->valid = true; - return true; } @@ -456,10 +467,9 @@ bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct) /* Tint Implementation */ /************************************************************************/ -bool effectTintPrepare(RenderEffectTint* params) +void effectTintUpdate(RenderEffectTint* params) { params->valid = true; - return true; } @@ -529,10 +539,10 @@ static uint32_t _trintone(uint32_t s, uint32_t m, uint32_t h, int l) } } -bool effectTritonePrepare(RenderEffectTritone* params) + +void effectTritoneUpdate(RenderEffectTritone* params) { params->valid = true; - return true; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index a404c124ad7..cce97cffb3c 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -22,7 +22,7 @@ #ifdef _WIN32 #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__ZEPHYR__) #include #else #include diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h index 1162edc8381..8415e84aecb 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h @@ -75,197 +75,8 @@ static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, in static bool _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0) { + TVGERR("SW_ENGINE", "TODO: _rasterMaskedPolygonImageSegment()"); return false; - -#if 0 //Enable it when GRAYSCALE image is supported - auto maskOp = _getMaskOp(surface->compositor->method); - auto direct = _direct(surface->compositor->method); - float _dudx = dudx, _dvdx = dvdx; - float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya; - float _xa = xa, _xb = xb, _ua = ua, _va = va; - auto sbuf = image->buf8; - int32_t sw = static_cast(image->stride); - int32_t sh = image->h; - int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; - int32_t vv = 0, uu = 0; - int32_t minx = INT32_MAX, maxx = 0; - float dx, u, v, iptr; - SwSpan* span = nullptr; //used only when rle based. - - if (!_arrange(image, region, yStart, yEnd)) return false; - - //Loop through all lines in the segment - uint32_t spanIdx = 0; - - if (region) { - minx = region->min.x; - maxx = region->max.x; - } else { - span = image->rle->spans; - while (span->y < yStart) { - ++span; - ++spanIdx; - } - } - - y = yStart; - - while (y < yEnd) { - x1 = (int32_t)_xa; - x2 = (int32_t)_xb; - - if (!region) { - minx = INT32_MAX; - maxx = 0; - //one single row, could be consisted of multiple spans. - while (span->y == y && spanIdx < image->rle->size) { - if (minx > span->x) minx = span->x; - if (maxx < span->x + span->len) maxx = span->x + span->len; - ++span; - ++spanIdx; - } - } - if (x1 < minx) x1 = minx; - if (x2 > maxx) x2 = maxx; - - //Anti-Aliasing frames - ay = y - aaSpans->yStart; - if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1; - if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2; - - //Range allowed - if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) { - - //Perform subtexel pre-stepping on UV - dx = 1 - (_xa - x1); - u = _ua + dx * _dudx; - v = _va + dx * _dvdx; - - x = x1; - - auto cmp = &surface->compositor->image.buf8[y * surface->compositor->image.stride + x1]; - auto dst = &surface->buf8[y * surface->stride + x1]; - - if (opacity == 255) { - //Draw horizontal line - while (x++ < x2) { - uu = (int) u; - if (uu >= sw) continue; - vv = (int) v; - if (vv >= sh) continue; - - ar = (int)(255 * (1 - modff(u, &iptr))); - ab = (int)(255 * (1 - modff(v, &iptr))); - iru = uu + 1; - irv = vv + 1; - - px = *(sbuf + (vv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); - px = INTERPOLATE(px, px2, ar); - } - /* vertical interpolate */ - if (irv < sh) { - /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); - px2 = INTERPOLATE(px2, px3, ar); - } - px = INTERPOLATE(px, px2, ab); - } - if (direct) { - auto tmp = maskOp(px, *cmp, 0); //not use alpha - *dst = tmp + MULTIPLY(*dst, ~tmp); - ++dst; - } else { - *cmp = maskOp(px, *cmp, ~px); - } - ++cmp; - - //Step UV horizontally - u += _dudx; - v += _dvdx; - //range over? - if ((uint32_t)v >= image->h) break; - } - } else { - //Draw horizontal line - while (x++ < x2) { - uu = (int) u; - if (uu >= sw) continue; - vv = (int) v; - if (vv >= sh) continue; - - ar = (int)(255 * (1 - modff(u, &iptr))); - ab = (int)(255 * (1 - modff(v, &iptr))); - iru = uu + 1; - irv = vv + 1; - - px = *(sbuf + (vv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); - px = INTERPOLATE(px, px2, ar); - } - /* vertical interpolate */ - if (irv < sh) { - /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); - - /* horizontal interpolate */ - if (iru < sw) { - /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); - px2 = INTERPOLATE(px2, px3, ar); - } - px = INTERPOLATE(px, px2, ab); - } - - if (direct) { - auto tmp = maskOp(MULTIPLY(px, opacity), *cmp, 0); - *dst = tmp + MULTIPLY(*dst, ~tmp); - ++dst; - } else { - auto tmp = MULTIPLY(px, opacity); - *cmp = maskOp(tmp, *cmp, ~px); - } - ++cmp; - - //Step UV horizontally - u += _dudx; - v += _dvdx; - //range over? - if ((uint32_t)v >= image->h) break; - } - } - } - - //Step along both edges - _xa += _dxdya; - _xb += _dxdyb; - _ua += _dudya; - _va += _dvdya; - - if (!region && spanIdx >= image->rle->size) break; - - ++y; - } - xa = _xa; - xb = _xb; - ua = _ua; - va = _va; - - return true; -#endif } @@ -276,9 +87,8 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage float _xa = xa, _xb = xb, _ua = ua, _va = va; auto sbuf = image->buf32; auto dbuf = surface->buf32; - int32_t sw = static_cast(image->stride); - int32_t sh = image->h; - int32_t dw = surface->stride; + int32_t sw = static_cast(image->w); + int32_t sh = static_cast(image->h); int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; int32_t vv = 0, uu = 0; int32_t minx = INT32_MAX, maxx = 0; @@ -335,7 +145,7 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage u = _ua + dx * _dudx; v = _va + dx * _dvdx; - buf = dbuf + ((y * dw) + x1); + buf = dbuf + ((y * surface->stride) + x1); x = x1; @@ -343,32 +153,32 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage //Draw horizontal line while (x++ < x2) { uu = (int) u; - if (uu >= sw) continue; vv = (int) v; - if (vv >= sh) continue; + + if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue; ar = (int)(255 * (1 - modff(u, &iptr))); ab = (int)(255 * (1 - modff(v, &iptr))); iru = uu + 1; irv = vv + 1; - px = *(sbuf + (vv * sw) + uu); + px = *(sbuf + (vv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); + int px2 = *(sbuf + (vv * image->stride) + iru); px = INTERPOLATE(px, px2, ar); } /* vertical interpolate */ if (irv < sh) { /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); + int px2 = *(sbuf + (irv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); + int px3 = *(sbuf + (irv * image->stride) + iru); px2 = INTERPOLATE(px2, px3, ar); } px = INTERPOLATE(px, px2, ab); @@ -379,39 +189,37 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage //Step UV horizontally u += _dudx; v += _dvdx; - //range over? - if ((uint32_t)v >= image->h) break; } } else { //Draw horizontal line while (x++ < x2) { uu = (int) u; - if (uu >= sw) continue; vv = (int) v; - if (vv >= sh) continue; + + if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue; ar = (int)(255 * (1 - modff(u, &iptr))); ab = (int)(255 * (1 - modff(v, &iptr))); iru = uu + 1; irv = vv + 1; - px = *(sbuf + (vv * sw) + uu); + px = *(sbuf + (vv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); + int px2 = *(sbuf + (vv * image->stride) + iru); px = INTERPOLATE(px, px2, ar); } /* vertical interpolate */ if (irv < sh) { /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); + int px2 = *(sbuf + (irv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); + int px3 = *(sbuf + (irv * image->stride) + iru); px2 = INTERPOLATE(px2, px3, ar); } px = INTERPOLATE(px, px2, ab); @@ -423,8 +231,6 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage //Step UV horizontally u += _dudx; v += _dvdx; - //range over? - if ((uint32_t)v >= image->h) break; } } } @@ -453,9 +259,8 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, float _xa = xa, _xb = xb, _ua = ua, _va = va; auto sbuf = image->buf32; auto dbuf = surface->buf32; - int32_t sw = static_cast(image->stride); - int32_t sh = image->h; - int32_t dw = surface->stride; + int32_t sw = static_cast(image->w); + int32_t sh = static_cast(image->h); int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay; int32_t vv = 0, uu = 0; int32_t minx = INT32_MAX, maxx = 0; @@ -517,7 +322,7 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, u = _ua + dx * _dudx; v = _va + dx * _dvdx; - buf = dbuf + ((y * dw) + x1); + buf = dbuf + ((y * surface->stride) + x1); x = x1; @@ -527,32 +332,32 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, //Draw horizontal line while (x++ < x2) { uu = (int) u; - if (uu >= sw) continue; vv = (int) v; - if (vv >= sh) continue; + + if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue; ar = (int)(255.0f * (1.0f - modff(u, &iptr))); ab = (int)(255.0f * (1.0f - modff(v, &iptr))); iru = uu + 1; irv = vv + 1; - px = *(sbuf + (vv * sw) + uu); + px = *(sbuf + (vv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); + int px2 = *(sbuf + (vv * image->stride) + iru); px = INTERPOLATE(px, px2, ar); } /* vertical interpolate */ if (irv < sh) { /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); + int px2 = *(sbuf + (irv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); + int px3 = *(sbuf + (irv * image->stride) + iru); px2 = INTERPOLATE(px2, px3, ar); } px = INTERPOLATE(px, px2, ab); @@ -570,8 +375,6 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, //Step UV horizontally u += _dudx; v += _dvdx; - //range over? - if ((uint32_t)v >= image->h) break; } } else { //Draw horizontal line @@ -579,30 +382,30 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, uu = (int) u; vv = (int) v; + if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue; + ar = (int)(255.0f * (1.0f - modff(u, &iptr))); ab = (int)(255.0f * (1.0f - modff(v, &iptr))); iru = uu + 1; irv = vv + 1; - if (vv >= sh) continue; - px = *(sbuf + (vv * sw) + uu); /* horizontal interpolate */ if (iru < sw) { /* right pixel */ - int px2 = *(sbuf + (vv * sw) + iru); + int px2 = *(sbuf + (vv * image->stride) + iru); px = INTERPOLATE(px, px2, ar); } /* vertical interpolate */ if (irv < sh) { /* bottom pixel */ - int px2 = *(sbuf + (irv * sw) + uu); + int px2 = *(sbuf + (irv * image->stride) + uu); /* horizontal interpolate */ if (iru < sw) { /* bottom right pixel */ - int px3 = *(sbuf + (irv * sw) + iru); + int px3 = *(sbuf + (irv * image->stride) + iru); px2 = INTERPOLATE(px2, px3, ar); } px = INTERPOLATE(px, px2, ab); @@ -620,8 +423,6 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, //Step UV horizontally u += _dudx; v += _dvdx; - //range over? - if ((uint32_t)v >= image->h) break; } } } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 4a709869203..5b9719d2bc7 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -574,7 +574,7 @@ SwSurface* SwRenderer::request(int channelSize, bool square) cmp->compositor->image.data = (pixel_t*)malloc(channelSize * w * h); cmp->w = cmp->compositor->image.w = w; cmp->h = cmp->compositor->image.h = h; - cmp->compositor->image.stride = w; + cmp->stride = cmp->compositor->image.stride = w; cmp->compositor->image.direct = true; cmp->compositor->valid = true; cmp->channelSize = cmp->compositor->image.channelSize = channelSize; @@ -655,23 +655,31 @@ bool SwRenderer::endComposite(RenderCompositor* cmp) } -bool SwRenderer::prepare(RenderEffect* effect) +void SwRenderer::prepare(RenderEffect* effect, const Matrix& transform) { switch (effect->type) { - case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast(effect)); - case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast(effect)); - case SceneEffect::Fill: return effectFillPrepare(static_cast(effect)); - case SceneEffect::Tint: return effectTintPrepare(static_cast(effect)); - case SceneEffect::Tritone: return effectTritonePrepare(static_cast(effect)); + case SceneEffect::GaussianBlur: effectGaussianBlurUpdate(static_cast(effect), transform); break; + case SceneEffect::DropShadow: effectDropShadowUpdate(static_cast(effect), transform); break; + case SceneEffect::Fill: effectFillUpdate(static_cast(effect)); break; + case SceneEffect::Tint: effectTintUpdate(static_cast(effect)); break; + case SceneEffect::Tritone: effectTritoneUpdate(static_cast(effect)); break; + default: break; + } +} + + +bool SwRenderer::region(RenderEffect* effect) +{ + switch (effect->type) { + case SceneEffect::GaussianBlur: return effectGaussianBlurRegion(static_cast(effect)); + case SceneEffect::DropShadow: return effectDropShadowRegion(static_cast(effect)); default: return false; } } -bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) +bool SwRenderer::render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) { - if (!effect->valid) return false; - auto p = static_cast(cmp); if (p->image.channelSize != sizeof(uint32_t)) { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h index 02fbe3b6b8b..27868abf576 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h @@ -60,8 +60,9 @@ public: bool endComposite(RenderCompositor* cmp) override; void clearCompositors(); - bool prepare(RenderEffect* effect) override; - bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override; + void prepare(RenderEffect* effect, const Matrix& transform) override; + bool region(RenderEffect* effect) override; + bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override; static SwRenderer* gen(); static bool init(uint32_t threads); diff --git a/thirdparty/thorvg/src/renderer/tvgAnimation.cpp b/thirdparty/thorvg/src/renderer/tvgAnimation.cpp index 6c4711e8c1a..23973bbfc7a 100644 --- a/thirdparty/thorvg/src/renderer/tvgAnimation.cpp +++ b/thirdparty/thorvg/src/renderer/tvgAnimation.cpp @@ -95,7 +95,7 @@ float Animation::duration() const noexcept Result Animation::segment(float begin, float end) noexcept { - if (begin < 0.0 || end > 1.0 || begin > end) return Result::InvalidArguments; + if (begin < 0.0f || end > 1.0f || begin > end) return Result::InvalidArguments; auto loader = pImpl->picture->pImpl->loader; if (!loader) return Result::InsufficientCondition; diff --git a/thirdparty/thorvg/src/renderer/tvgFill.h b/thirdparty/thorvg/src/renderer/tvgFill.h index f249356aa2a..83f2a3a4e92 100644 --- a/thirdparty/thorvg/src/renderer/tvgFill.h +++ b/thirdparty/thorvg/src/renderer/tvgFill.h @@ -76,7 +76,7 @@ struct Fill::Impl ret->pImpl->cnt = cnt; ret->pImpl->spread = spread; ret->pImpl->colorStops = static_cast(malloc(sizeof(ColorStop) * cnt)); - memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt); + if (cnt > 0) memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt); if (transform) { ret->pImpl->transform = static_cast(malloc(sizeof(Matrix))); *ret->pImpl->transform = *transform; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index 6558bd3ed05..7d432cac765 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -352,7 +352,7 @@ struct RenderEffectTint : RenderEffect inst->white[0] = va_arg(args, int); inst->white[1] = va_arg(args, int); inst->white[2] = va_arg(args, int); - inst->intensity = (uint8_t)(va_arg(args, double) * 2.55f); + inst->intensity = (uint8_t)(va_arg(args, double) * 2.55); inst->type = SceneEffect::Tint; return inst; } @@ -413,8 +413,9 @@ public: virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0; virtual bool endComposite(RenderCompositor* cmp) = 0; - virtual bool prepare(RenderEffect* effect) = 0; - virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 0; + virtual void prepare(RenderEffect* effect, const Matrix& transform) = 0; + virtual bool region(RenderEffect* effect) = 0; + virtual bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 0; }; static inline bool MASK_REGION_MERGING(CompositeMethod method) diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h index bc789584ef5..e2eba0ce783 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.h +++ b/thirdparty/thorvg/src/renderer/tvgScene.h @@ -121,6 +121,12 @@ struct Scene::Impl paint->pImpl->update(renderer, transform, clips, opacity, flag, false); } + if (effects) { + for (auto e = effects->begin(); e < effects->end(); ++e) { + renderer->prepare(*e, transform); + } + } + return nullptr; } @@ -146,7 +152,7 @@ struct Scene::Impl //Notify the possiblity of the direct composition of the effect result to the origin surface. auto direct = (effects->count == 1) & (compFlag == CompositionFlag::PostProcessing); for (auto e = effects->begin(); e < effects->end(); ++e) { - renderer->effect(cmp, *e, direct); + if ((*e)->valid) renderer->render(cmp, *e, direct); } } renderer->endComposite(cmp); @@ -179,7 +185,7 @@ struct Scene::Impl if (effects) { for (auto e = effects->begin(); e < effects->end(); ++e) { auto effect = *e; - if (effect->valid || renderer->prepare(effect)) { + if (effect->valid && renderer->region(effect)) { ex = std::min(ex, effect->extend.x); ey = std::min(ey, effect->extend.y); ew = std::max(ew, effect->extend.w); diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index 5aeac089395..cd36e1626ee 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.15.8 +VERSION=0.15.10 # Uncomment and set a git hash to use specific commit instead of tag. #GIT_COMMIT=