summaryrefslogtreecommitdiff
path: root/thirdparty/thorvg/src
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/thorvg/src')
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h359
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp333
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp128
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp502
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp156
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp1497
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h179
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h64
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h126
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h602
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h163
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp685
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h81
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp1043
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp664
-rw-r--r--thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp932
-rw-r--r--thirdparty/thorvg/src/lib/tvgAccessor.cpp84
-rw-r--r--thirdparty/thorvg/src/lib/tvgArray.h109
-rw-r--r--thirdparty/thorvg/src/lib/tvgBezier.cpp150
-rw-r--r--thirdparty/thorvg/src/lib/tvgBezier.h48
-rw-r--r--thirdparty/thorvg/src/lib/tvgBinaryDesc.h96
-rw-r--r--thirdparty/thorvg/src/lib/tvgCanvas.cpp73
-rw-r--r--thirdparty/thorvg/src/lib/tvgCanvasImpl.h142
-rw-r--r--thirdparty/thorvg/src/lib/tvgCommon.h76
-rw-r--r--thirdparty/thorvg/src/lib/tvgFill.cpp115
-rw-r--r--thirdparty/thorvg/src/lib/tvgFill.h88
-rw-r--r--thirdparty/thorvg/src/lib/tvgGlCanvas.cpp87
-rw-r--r--thirdparty/thorvg/src/lib/tvgInitializer.cpp152
-rw-r--r--thirdparty/thorvg/src/lib/tvgIteratorAccessor.h42
-rw-r--r--thirdparty/thorvg/src/lib/tvgLinearGradient.cpp99
-rw-r--r--thirdparty/thorvg/src/lib/tvgLoadModule.h58
-rw-r--r--thirdparty/thorvg/src/lib/tvgLoader.cpp213
-rw-r--r--thirdparty/thorvg/src/lib/tvgLoader.h36
-rw-r--r--thirdparty/thorvg/src/lib/tvgLzw.cpp428
-rw-r--r--thirdparty/thorvg/src/lib/tvgLzw.h31
-rw-r--r--thirdparty/thorvg/src/lib/tvgMath.h157
-rw-r--r--thirdparty/thorvg/src/lib/tvgPaint.cpp407
-rw-r--r--thirdparty/thorvg/src/lib/tvgPaint.h200
-rw-r--r--thirdparty/thorvg/src/lib/tvgPicture.cpp121
-rw-r--r--thirdparty/thorvg/src/lib/tvgPictureImpl.h251
-rw-r--r--thirdparty/thorvg/src/lib/tvgRadialGradient.cpp97
-rw-r--r--thirdparty/thorvg/src/lib/tvgRender.cpp73
-rw-r--r--thirdparty/thorvg/src/lib/tvgRender.h113
-rw-r--r--thirdparty/thorvg/src/lib/tvgSaveModule.h41
-rw-r--r--thirdparty/thorvg/src/lib/tvgSaver.cpp140
-rw-r--r--thirdparty/thorvg/src/lib/tvgScene.cpp76
-rw-r--r--thirdparty/thorvg/src/lib/tvgSceneImpl.h217
-rw-r--r--thirdparty/thorvg/src/lib/tvgShape.cpp427
-rw-r--r--thirdparty/thorvg/src/lib/tvgShapeImpl.h392
-rw-r--r--thirdparty/thorvg/src/lib/tvgSwCanvas.cpp105
-rw-r--r--thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp192
-rw-r--r--thirdparty/thorvg/src/lib/tvgTaskScheduler.h88
-rw-r--r--thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp164
-rw-r--r--thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h52
-rw-r--r--thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp122
-rw-r--r--thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h46
-rw-r--r--thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp132
-rw-r--r--thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h51
-rw-r--r--thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp3028
-rw-r--r--thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h35
-rw-r--r--thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp2647
-rw-r--r--thirdparty/thorvg/src/loaders/png/tvgLodePng.h174
-rw-r--r--thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp189
-rw-r--r--thirdparty/thorvg/src/loaders/png/tvgPngLoader.h54
-rw-r--r--thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp87
-rw-r--r--thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h42
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp3007
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h59
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h412
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp563
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h30
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp652
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h30
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp272
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h33
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp527
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h57
-rw-r--r--thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp450
-rw-r--r--thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h54
-rw-r--r--thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp232
-rw-r--r--thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h61
-rw-r--r--thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp773
-rw-r--r--thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h77
83 files changed, 26850 insertions, 0 deletions
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h
new file mode 100644
index 0000000000..e0ffc1fb97
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SW_COMMON_H_
+#define _TVG_SW_COMMON_H_
+
+#include "tvgCommon.h"
+#include "tvgRender.h"
+
+#if 0
+#include <sys/time.h>
+static double timeStamp()
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (tv.tv_sec + tv.tv_usec / 1000000.0);
+}
+#endif
+
+#define SW_CURVE_TYPE_POINT 0
+#define SW_CURVE_TYPE_CUBIC 1
+#define SW_ANGLE_PI (180L << 16)
+#define SW_ANGLE_2PI (SW_ANGLE_PI << 1)
+#define SW_ANGLE_PI2 (SW_ANGLE_PI >> 1)
+#define SW_ANGLE_PI4 (SW_ANGLE_PI >> 2)
+
+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};
+ }
+
+ SwPoint operator-(const SwPoint& rhs) const
+ {
+ return {x - rhs.x, y - rhs.y};
+ }
+
+ bool operator==(const SwPoint& rhs) const
+ {
+ return (x == rhs.x && y == rhs.y);
+ }
+
+ bool operator!=(const SwPoint& rhs) const
+ {
+ return (x != rhs.x || y != rhs.y);
+ }
+
+ bool zero() const
+ {
+ if (x == 0 && y == 0) return true;
+ else return false;
+ }
+
+ bool small() const
+ {
+ //2 is epsilon...
+ if (abs(x) < 2 && abs(y) < 2) return true;
+ else return false;
+ }
+
+};
+
+struct SwSize
+{
+ SwCoord w, h;
+};
+
+struct SwOutline
+{
+ SwPoint* pts; //the outline's points
+ uint16_t ptsCnt; //number of points in the glyph
+ uint16_t reservedPtsCnt;
+ uint16_t* cntrs; //the contour end points
+ uint16_t cntrsCnt; //number of contours in glyph
+ uint16_t reservedCntrsCnt;
+ uint8_t* types; //curve type
+ bool* closed; //opened or closed path?
+ FillRule fillRule;
+};
+
+struct SwSpan
+{
+ uint16_t x, y;
+ uint16_t len;
+ uint8_t coverage;
+};
+
+struct SwRleData
+{
+ SwSpan *spans;
+ uint32_t alloc;
+ uint32_t size;
+};
+
+struct SwBBox
+{
+ SwPoint min, max;
+
+ void reset()
+ {
+ min.x = min.y = max.x = max.y = 0;
+ }
+};
+
+struct SwFill
+{
+ struct SwLinear {
+ float dx, dy;
+ float len;
+ float offset;
+ };
+
+ struct SwRadial {
+ float a11, a12, shiftX;
+ float a21, a22, shiftY;
+ float detSecDeriv;
+ float a;
+ };
+
+ union {
+ SwLinear linear;
+ SwRadial radial;
+ };
+
+ uint32_t* ctable;
+ FillSpread spread;
+
+ bool translucent;
+};
+
+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
+};
+
+struct SwStroke
+{
+ SwFixed angleIn;
+ SwFixed angleOut;
+ SwPoint center;
+ SwFixed lineLength;
+ SwFixed subPathAngle;
+ SwPoint ptStartSubPath;
+ SwFixed subPathLineLength;
+ SwFixed width;
+
+ StrokeCap cap;
+ StrokeJoin join;
+ StrokeJoin joinSaved;
+ SwFill* fill = nullptr;
+
+ SwStrokeBorder borders[2];
+
+ float sx, sy;
+
+ bool firstPt;
+ bool closedSubPath;
+ bool handleWideStrokes;
+};
+
+struct SwDashStroke
+{
+ SwOutline* outline;
+ float curLen;
+ int32_t curIdx;
+ Point ptStart;
+ Point ptCur;
+ float* pattern;
+ uint32_t cnt;
+ bool curOpGap;
+};
+
+struct SwShape
+{
+ SwOutline* outline = nullptr;
+ SwStroke* stroke = nullptr;
+ SwFill* fill = nullptr;
+ SwRleData* rle = nullptr;
+ SwRleData* strokeRle = nullptr;
+ SwBBox bbox; //Keep it boundary without stroke region. Using for optimal filling.
+
+ bool fastTrack = false; //Fast Track: axis-aligned rectangle without any clips?
+};
+
+struct SwImage
+{
+ SwOutline* outline = nullptr;
+ SwRleData* rle = nullptr;
+ uint32_t* data = nullptr;
+ uint32_t w, h, stride;
+ int32_t ox = 0; //offset x
+ int32_t oy = 0; //offset y
+ float scale;
+
+ bool direct = false; //draw image directly (with offset)
+ bool scaled = false; //draw scaled image
+};
+
+struct SwBlender
+{
+ uint32_t (*join)(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+};
+
+struct SwCompositor;
+
+struct SwSurface : Surface
+{
+ SwBlender blender; //mandatory
+ SwCompositor* compositor = nullptr; //compositor (optional)
+};
+
+struct SwCompositor : Compositor
+{
+ SwSurface* recoverSfc; //Recover surface when composition is started
+ SwCompositor* recoverCmp; //Recover compositor when composition is done
+ SwImage image;
+ SwBBox bbox;
+ bool valid;
+};
+
+struct SwMpool
+{
+ SwOutline* outline;
+ SwOutline* strokeOutline;
+ unsigned allocSize;
+};
+
+static inline SwCoord TO_SWCOORD(float val)
+{
+ return SwCoord(val * 64.0f);
+}
+
+static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a)
+{
+ return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) +
+ ((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff));
+}
+
+static inline uint32_t INTERPOLATE(uint32_t a, uint32_t c0, uint32_t c1)
+{
+ return (((((((c0 >> 8) & 0xff00ff) - ((c1 >> 8) & 0xff00ff)) * a) + (c1 & 0xff00ff00)) & 0xff00ff00) + ((((((c0 & 0xff00ff) - (c1 & 0xff00ff)) * a) >> 8) + (c1 & 0xff00ff)) & 0xff00ff));
+}
+
+static inline SwCoord HALF_STROKE(float width)
+{
+ return TO_SWCOORD(width * 0.5f);
+}
+
+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(const SwPoint& pt);
+bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
+SwFixed mathMean(SwFixed angle1, SwFixed angle2);
+SwPoint mathTransform(const Point* to, const Matrix* transform);
+bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
+bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee);
+
+void shapeReset(SwShape* shape);
+bool shapePrepare(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
+bool shapePrepared(const SwShape* shape);
+bool shapeGenRle(SwShape* shape, const Shape* sdata, bool antiAlias);
+void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid);
+void shapeResetStroke(SwShape* shape, const Shape* sdata, const Matrix* transform);
+bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
+void shapeFree(SwShape* shape);
+void shapeDelStroke(SwShape* shape);
+bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable);
+bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable);
+void shapeResetFill(SwShape* shape);
+void shapeResetStrokeFill(SwShape* shape);
+void shapeDelFill(SwShape* shape);
+void shapeDelStrokeFill(SwShape* shape);
+
+void strokeReset(SwStroke* stroke, const Shape* shape, const Matrix* transform);
+bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline);
+SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid);
+void strokeFree(SwStroke* stroke);
+
+bool imagePrepare(SwImage* image, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
+bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias);
+void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid);
+void imageReset(SwImage* image);
+void imageFree(SwImage* image);
+
+bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable);
+void fillReset(SwFill* fill);
+void fillFree(SwFill* fill);
+void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len);
+void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len);
+
+SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
+void rleFree(SwRleData* rle);
+void rleReset(SwRleData* rle);
+void rleClipPath(SwRleData *rle, const SwRleData *clip);
+void rleClipRect(SwRleData *rle, const SwBBox* clip);
+
+SwMpool* mpoolInit(uint32_t threads);
+bool mpoolTerm(SwMpool* mpool);
+bool mpoolClear(SwMpool* mpool);
+SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx);
+void mpoolRetOutline(SwMpool* mpool, unsigned idx);
+SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx);
+void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx);
+
+bool rasterCompositor(SwSurface* surface);
+bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id);
+bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity);
+bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id);
+bool rasterClear(SwSurface* surface);
+void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
+void rasterUnpremultiply(SwSurface* surface);
+
+#endif /* _TVG_SW_COMMON_H_ */
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp
new file mode 100644
index 0000000000..0bf051c17f
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgMath.h"
+#include "tvgSwCommon.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+#define GRADIENT_STOP_SIZE 1024
+#define FIXPT_BITS 8
+#define FIXPT_SIZE (1<<FIXPT_BITS)
+
+
+static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint32_t opacity)
+{
+ if (!fill->ctable) {
+ fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
+ if (!fill->ctable) return false;
+ }
+
+ const Fill::ColorStop* colors;
+ auto cnt = fdata->colorStops(&colors);
+ if (cnt == 0 || !colors) return false;
+
+ auto pColors = colors;
+
+ auto a = (pColors->a * opacity) / 255;
+ if (a < 255) fill->translucent = true;
+
+ auto r = pColors->r;
+ auto g = pColors->g;
+ auto b = pColors->b;
+ auto rgba = surface->blender.join(r, g, b, a);
+
+ auto inc = 1.0f / static_cast<float>(GRADIENT_STOP_SIZE);
+ auto pos = 1.5f * inc;
+ uint32_t i = 0;
+
+ fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a);
+
+ while (pos <= pColors->offset) {
+ fill->ctable[i] = fill->ctable[i - 1];
+ ++i;
+ pos += inc;
+ }
+
+ for (uint32_t j = 0; j < cnt - 1; ++j) {
+ auto curr = colors + j;
+ auto next = curr + 1;
+ auto delta = 1.0f / (next->offset - curr->offset);
+ auto a2 = (next->a * opacity) / 255;
+ if (!fill->translucent && a2 < 255) fill->translucent = true;
+
+ auto rgba2 = surface->blender.join(next->r, next->g, next->b, a2);
+
+ while (pos < next->offset && i < GRADIENT_STOP_SIZE) {
+ auto t = (pos - curr->offset) * delta;
+ auto dist = static_cast<int32_t>(255 * t);
+ auto dist2 = 255 - dist;
+
+ auto color = INTERPOLATE(dist2, rgba, rgba2);
+ fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
+
+ ++i;
+ pos += inc;
+ }
+ rgba = rgba2;
+ a = a2;
+ }
+ rgba = ALPHA_BLEND((rgba | 0xff000000), a);
+
+ for (; i < GRADIENT_STOP_SIZE; ++i)
+ fill->ctable[i] = rgba;
+
+ //Make sure the last color stop is represented at the end of the table
+ fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
+
+ return true;
+}
+
+
+bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform)
+{
+ float x1, x2, y1, y2;
+ if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false;
+
+ fill->linear.dx = x2 - x1;
+ fill->linear.dy = y2 - y1;
+ fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
+
+ if (fill->linear.len < FLT_EPSILON) return true;
+
+ fill->linear.dx /= fill->linear.len;
+ fill->linear.dy /= fill->linear.len;
+ fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;
+
+ auto gradTransform = linear->transform();
+ bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
+
+ if (isTransformation) {
+ if (transform) gradTransform = mathMultiply(transform, &gradTransform);
+ } else if (transform) {
+ gradTransform = *transform;
+ isTransformation = true;
+ }
+
+ if (isTransformation) {
+ Matrix invTransform;
+ if (!mathInverse(&gradTransform, &invTransform)) return false;
+
+ fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23;
+
+ auto dx = fill->linear.dx;
+ fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21;
+ fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22;
+
+ fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
+ if (fill->linear.len < FLT_EPSILON) return true;
+ }
+
+ return true;
+}
+
+
+bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform)
+{
+ float radius, cx, cy;
+ if (radial->radial(&cx, &cy, &radius) != Result::Success) return false;
+ if (radius < FLT_EPSILON) return true;
+
+ float invR = 1.0f / radius;
+ fill->radial.shiftX = -cx;
+ fill->radial.shiftY = -cy;
+ fill->radial.a = radius;
+
+ auto gradTransform = radial->transform();
+ bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
+
+ if (isTransformation) {
+ if (transform) gradTransform = mathMultiply(transform, &gradTransform);
+ } else if (transform) {
+ gradTransform = *transform;
+ isTransformation = true;
+ }
+
+ if (isTransformation) {
+ Matrix invTransform;
+ if (!mathInverse(&gradTransform, &invTransform)) return false;
+
+ fill->radial.a11 = invTransform.e11 * invR;
+ fill->radial.a12 = invTransform.e12 * invR;
+ fill->radial.shiftX += invTransform.e13;
+ fill->radial.a21 = invTransform.e21 * invR;
+ fill->radial.a22 = invTransform.e22 * invR;
+ fill->radial.shiftY += invTransform.e23;
+ fill->radial.detSecDeriv = 2.0f * fill->radial.a11 * fill->radial.a11 + 2 * fill->radial.a21 * fill->radial.a21;
+
+ fill->radial.a *= sqrt(pow(invTransform.e11, 2) + pow(invTransform.e21, 2));
+ } else {
+ fill->radial.a11 = fill->radial.a22 = invR;
+ fill->radial.a12 = fill->radial.a21 = 0.0f;
+ fill->radial.detSecDeriv = 2.0f * invR * invR;
+ }
+ fill->radial.shiftX *= invR;
+ fill->radial.shiftY *= invR;
+
+ return true;
+}
+
+
+static inline uint32_t _clamp(const SwFill* fill, int32_t pos)
+{
+ switch (fill->spread) {
+ case FillSpread::Pad: {
+ if (pos >= GRADIENT_STOP_SIZE) pos = GRADIENT_STOP_SIZE - 1;
+ else if (pos < 0) pos = 0;
+ break;
+ }
+ case FillSpread::Repeat: {
+ pos = pos % GRADIENT_STOP_SIZE;
+ if (pos < 0) pos = GRADIENT_STOP_SIZE + pos;
+ break;
+ }
+ case FillSpread::Reflect: {
+ auto limit = GRADIENT_STOP_SIZE * 2;
+ pos = pos % limit;
+ if (pos < 0) pos = limit + pos;
+ if (pos >= GRADIENT_STOP_SIZE) pos = (limit - pos - 1);
+ break;
+ }
+ }
+ return pos;
+}
+
+
+static inline uint32_t _fixedPixel(const SwFill* fill, int32_t pos)
+{
+ int32_t i = (pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS;
+ return fill->ctable[_clamp(fill, i)];
+}
+
+
+static inline uint32_t _pixel(const SwFill* fill, float pos)
+{
+ auto i = static_cast<int32_t>(pos * (GRADIENT_STOP_SIZE - 1) + 0.5f);
+ return fill->ctable[_clamp(fill, i)];
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len)
+{
+ auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
+ auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
+
+ // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
+ auto detSecondDerivative = fill->radial.detSecDeriv;
+ // detFirstDerivative = d(det)/dx
+ auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
+ auto det = rx * rx + ry * ry;
+
+ for (uint32_t i = 0 ; i < len ; ++i) {
+ *dst = _pixel(fill, sqrtf(det));
+ ++dst;
+ det += detFirstDerivative;
+ detFirstDerivative += detSecondDerivative;
+ }
+}
+
+
+void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len)
+{
+ //Rotation
+ float rx = x + 0.5f;
+ float ry = y + 0.5f;
+ float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
+ float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
+
+ if (mathZero(inc)) {
+ auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
+ rasterRGBA32(dst, color, 0, len);
+ return;
+ }
+
+ auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
+ auto vMin = -vMax;
+ auto v = t + (inc * len);
+
+ //we can use fixed point math
+ if (v < vMax && v > vMin) {
+ auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
+ auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
+ for (uint32_t j = 0; j < len; ++j) {
+ *dst = _fixedPixel(fill, t2);
+ ++dst;
+ t2 += inc2;
+ }
+ //we have to fallback to float math
+ } else {
+ uint32_t counter = 0;
+ while (counter++ < len) {
+ *dst = _pixel(fill, t / GRADIENT_STOP_SIZE);
+ ++dst;
+ t += inc;
+ }
+ }
+}
+
+
+bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable)
+{
+ if (!fill) return false;
+
+ fill->spread = fdata->spread();
+
+ if (ctable) {
+ if (!_updateColorTable(fill, fdata, surface, opacity)) return false;
+ }
+
+ if (fdata->identifier() == TVG_CLASS_ID_LINEAR) {
+ return _prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform);
+ } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
+ return _prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform);
+ }
+
+ //LOG: What type of gradient?!
+
+ return false;
+}
+
+
+void fillReset(SwFill* fill)
+{
+ if (fill->ctable) {
+ free(fill->ctable);
+ fill->ctable = nullptr;
+ }
+ fill->translucent = false;
+}
+
+
+void fillFree(SwFill* fill)
+{
+ if (!fill) return;
+
+ if (fill->ctable) free(fill->ctable);
+
+ free(fill);
+}
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp
new file mode 100644
index 0000000000..fe22fce017
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgMath.h"
+#include "tvgSwCommon.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static inline bool _onlyShifted(const Matrix* m)
+{
+ if (mathEqual(m->e11, 1.0f) && mathEqual(m->e22, 1.0f) && mathZero(m->e12) && mathZero(m->e21)) return true;
+ return false;
+}
+
+
+static bool _genOutline(SwImage* image, const Matrix* transform, SwMpool* mpool, unsigned tid)
+{
+ image->outline = mpoolReqOutline(mpool, tid);
+ auto outline = image->outline;
+
+ if (outline->reservedPtsCnt < 5) {
+ outline->reservedPtsCnt = 5;
+ outline->pts = static_cast<SwPoint*>(realloc(outline->pts, outline->reservedPtsCnt * sizeof(SwPoint)));
+ outline->types = static_cast<uint8_t*>(realloc(outline->types, outline->reservedPtsCnt * sizeof(uint8_t)));
+ }
+
+ if (outline->reservedCntrsCnt < 1) {
+ outline->reservedCntrsCnt = 1;
+ outline->cntrs = static_cast<uint16_t*>(realloc(outline->cntrs, outline->reservedCntrsCnt * sizeof(uint16_t)));
+ outline->closed = static_cast<bool*>(realloc(outline->closed, outline->reservedCntrsCnt * sizeof(bool)));
+ outline->closed[0] = true;
+ }
+
+ auto w = static_cast<float>(image->w);
+ auto h = static_cast<float>(image->h);
+
+ Point to[4] = {{0 ,0}, {w, 0}, {w, h}, {0, h}};
+ for (int i = 0; i < 4; i++) {
+ outline->pts[outline->ptsCnt] = mathTransform(&to[i], transform);
+ outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT;
+ ++outline->ptsCnt;
+ }
+
+ outline->pts[outline->ptsCnt] = outline->pts[0];
+ outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT;
+ ++outline->ptsCnt;
+
+ outline->cntrs[outline->cntrsCnt] = outline->ptsCnt - 1;
+ ++outline->cntrsCnt;
+
+ image->outline = outline;
+
+ return true;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+bool imagePrepare(SwImage* image, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
+{
+ image->direct = _onlyShifted(transform);
+
+ //Fast track: Non-transformed image but just shifted.
+ if (image->direct) {
+ image->ox = -static_cast<uint32_t>(round(transform->e13));
+ image->oy = -static_cast<uint32_t>(round(transform->e23));
+ //Figure out the scale factor by transform matrix
+ } else {
+ auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21));
+ auto scaleY = sqrtf((transform->e22 * transform->e22) + (transform->e12 * transform->e12));
+ image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX;
+
+ if (mathZero(transform->e12) && mathZero(transform->e21)) image->scaled = true;
+ else image->scaled = false;
+ }
+
+ if (!_genOutline(image, transform, mpool, tid)) return false;
+ return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, image->direct);
+}
+
+
+bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias)
+{
+ if ((image->rle = rleRender(image->rle, image->outline, renderRegion, antiAlias))) return true;
+
+ return false;
+}
+
+
+void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid)
+{
+ mpoolRetOutline(mpool, tid);
+ image->outline = nullptr;
+}
+
+
+void imageReset(SwImage* image)
+{
+ rleReset(image->rle);
+}
+
+
+void imageFree(SwImage* image)
+{
+ rleFree(image->rle);
+}
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp
new file mode 100644
index 0000000000..7a3529bd69
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <math.h>
+#include "tvgSwCommon.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+//clz: count leading zero’s
+#if defined(_MSC_VER) && !defined(__clang__)
+ #include <intrin.h>
+ static uint32_t __inline _clz(uint32_t value)
+ {
+ unsigned long leadingZero = 0;
+ if (_BitScanReverse(&leadingZero, value)) return 31 - leadingZero;
+ else return 32;
+ }
+#else
+ #define _clz(x) __builtin_clz((x))
+#endif
+
+
+constexpr SwFixed 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(SwFixed x)
+{
+ //multiply a give value by the CORDIC shrink factor
+ auto s = abs(x);
+ int64_t t = (s * static_cast<int64_t>(CORDIC_FACTOR)) + 0x100000000UL;
+ s = static_cast<SwFixed>(t >> 32);
+ if (x < 0) s = -s;
+ return s;
+}
+
+
+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)
+ int32_t shift = 31 - _clz(abs(v.x) | abs(v.y));
+
+ if (shift <= SAFE_MSB) {
+ shift = SAFE_MSB - shift;
+ pt.x = static_cast<SwCoord>((unsigned long)v.x << shift);
+ pt.y = static_cast<SwCoord>((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 = SW_ANGLE_PI2;
+ } else {
+ theta = v.y > 0 ? SW_ANGLE_PI : -SW_ANGLE_PI;
+ v.x = -v.x;
+ v.y = -v.y;
+ }
+ } else {
+ if (v.y < -v.x) {
+ theta = -SW_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;
+}
+
+
+static void _rotate(SwPoint& pt, SwFixed theta)
+{
+ SwFixed x = pt.x;
+ SwFixed y = pt.y;
+
+ //Rotate inside [-PI/4, PI/4] sector
+ while (theta < -SW_ANGLE_PI4) {
+ auto tmp = y;
+ y = -x;
+ x = tmp;
+ theta += SW_ANGLE_PI2;
+ }
+
+ while (theta > SW_ANGLE_PI4) {
+ auto tmp = -y;
+ y = x;
+ x = tmp;
+ theta -= SW_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 = x + ((y + j) >> i);
+ y = y - ((x + j) >> i);
+ x = tmp;
+ theta += *atan++;
+ } else {
+ auto tmp = x - ((y + j) >> i);
+ y = y + ((x + j) >> i);
+ x = tmp;
+ theta -= *atan++;
+ }
+ }
+
+ pt.x = static_cast<SwCoord>(x);
+ pt.y = static_cast<SwCoord>(y);
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+SwFixed mathMean(SwFixed angle1, SwFixed angle2)
+{
+ return angle1 + mathDiff(angle1, angle2) / 2;
+}
+
+
+bool mathSmallCubic(const 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 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true;
+ 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(v, theta);
+
+ v.x = _downscale(v.x);
+ v.y = _downscale(v.y);
+
+ if (shift > 0) {
+ auto half = static_cast<int32_t>(1L << (shift - 1));
+ pt.x = (v.x + half + SATURATE(v.x)) >> shift;
+ pt.y = (v.y + half + SATURATE(v.y)) >> shift;
+ } else {
+ shift = -shift;
+ pt.x = static_cast<SwCoord>((unsigned long)v.x << shift);
+ pt.y = static_cast<SwCoord>((unsigned long)v.y << shift);
+ }
+}
+
+SwFixed mathTan(SwFixed angle)
+{
+ SwPoint v = {CORDIC_FACTOR >> 8, 0};
+ _rotate(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(SW_ANGLE_PI2 - angle);
+}
+
+
+SwFixed mathCos(SwFixed angle)
+{
+ SwPoint v = {CORDIC_FACTOR >> 8, 0};
+ _rotate(v, angle);
+ return (v.x + 0x80L) >> 8;
+}
+
+
+SwFixed mathLength(const 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 + (static_cast<SwFixed>(1) << (shift -1))) >> shift;
+ return static_cast<SwFixed>((uint32_t)v.x << -shift);
+}
+
+
+void mathSplitCubic(SwPoint* 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 %= SW_ANGLE_2PI;
+ if (delta < 0) delta += SW_ANGLE_2PI;
+ if (delta > SW_ANGLE_PI) delta -= SW_ANGLE_2PI;
+
+ return delta;
+}
+
+
+SwPoint mathTransform(const Point* to, const Matrix* transform)
+{
+ if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)};
+
+ auto tx = to->x * transform->e11 + to->y * transform->e12 + transform->e13;
+ auto ty = to->x * transform->e21 + to->y * transform->e22 + transform->e23;
+
+ return {TO_SWCOORD(tx), TO_SWCOORD(ty)};
+}
+
+
+bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee)
+{
+ clipee.max.x = (clipee.max.x < clipper.max.x) ? clipee.max.x : clipper.max.x;
+ clipee.max.y = (clipee.max.y < clipper.max.y) ? clipee.max.y : clipper.max.y;
+ clipee.min.x = (clipee.min.x > clipper.min.x) ? clipee.min.x : clipper.min.x;
+ clipee.min.y = (clipee.min.y > clipper.min.y) ? clipee.min.y : clipper.min.y;
+
+ //Check valid region
+ if (clipee.max.x - clipee.min.x < 1 && clipee.max.y - clipee.min.y < 1) return false;
+
+ //Check boundary
+ if (clipee.min.x >= clipper.max.x || clipee.min.y >= clipper.max.y ||
+ clipee.max.x <= clipper.min.x || clipee.max.y <= clipper.min.y) return false;
+
+ return true;
+}
+
+
+bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack)
+{
+ if (!outline) return false;
+
+ auto pt = outline->pts;
+
+ if (outline->ptsCnt == 0 || outline->cntrsCnt <= 0) {
+ renderRegion.reset();
+ return false;
+ }
+
+ auto xMin = pt->x;
+ auto xMax = pt->x;
+ auto yMin = pt->y;
+ auto yMax = pt->y;
+
+ ++pt;
+
+ for (uint32_t i = 1; i < outline->ptsCnt; ++i, ++pt) {
+ if (xMin > pt->x) xMin = pt->x;
+ if (xMax < pt->x) xMax = pt->x;
+ if (yMin > pt->y) yMin = pt->y;
+ if (yMax < pt->y) yMax = pt->y;
+ }
+ //Since no antialiasing is applied in the Fast Track case,
+ //the rasterization region has to be rearranged.
+ //https://github.com/Samsung/thorvg/issues/916
+ if (fastTrack) {
+ renderRegion.min.x = static_cast<SwCoord>(round(xMin / 64.0f));
+ renderRegion.max.x = static_cast<SwCoord>(round(xMax / 64.0f));
+ renderRegion.min.y = static_cast<SwCoord>(round(yMin / 64.0f));
+ renderRegion.max.y = static_cast<SwCoord>(round(yMax / 64.0f));
+ } else {
+ renderRegion.min.x = xMin >> 6;
+ renderRegion.max.x = (xMax + 63) >> 6;
+ renderRegion.min.y = yMin >> 6;
+ renderRegion.max.y = (yMax + 63) >> 6;
+ }
+ return mathClipBBox(clipRegion, renderRegion);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp
new file mode 100644
index 0000000000..a44be85bbb
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgSwCommon.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx)
+{
+ return &mpool->outline[idx];
+}
+
+
+void mpoolRetOutline(SwMpool* mpool, unsigned idx)
+{
+ mpool->outline[idx].cntrsCnt = 0;
+ mpool->outline[idx].ptsCnt = 0;
+}
+
+
+SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx)
+{
+ return &mpool->strokeOutline[idx];
+}
+
+
+void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx)
+{
+ mpool->strokeOutline[idx].cntrsCnt = 0;
+ mpool->strokeOutline[idx].ptsCnt = 0;
+}
+
+
+SwMpool* mpoolInit(unsigned threads)
+{
+ if (threads == 0) threads = 1;
+
+ auto mpool = static_cast<SwMpool*>(calloc(sizeof(SwMpool), 1));
+ mpool->outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * threads));
+ if (!mpool->outline) goto err;
+
+ mpool->strokeOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * threads));
+ if (!mpool->strokeOutline) goto err;
+
+ mpool->allocSize = threads;
+
+ return mpool;
+
+err:
+ if (mpool->outline) {
+ free(mpool->outline);
+ mpool->outline = nullptr;
+ }
+
+ if (mpool->strokeOutline) {
+ free(mpool->strokeOutline);
+ mpool->strokeOutline = nullptr;
+ }
+ free(mpool);
+ return nullptr;
+}
+
+
+bool mpoolClear(SwMpool* mpool)
+{
+ SwOutline* p;
+
+ for (unsigned i = 0; i < mpool->allocSize; ++i) {
+
+ //Outline
+ p = &mpool->outline[i];
+
+ free(p->cntrs);
+ p->cntrs = nullptr;
+
+ free(p->pts);
+ p->pts = nullptr;
+
+ free(p->types);
+ p->types = nullptr;
+
+ free(p->closed);
+ p->closed = nullptr;
+
+ p->cntrsCnt = p->reservedCntrsCnt = 0;
+ p->ptsCnt = p->reservedPtsCnt = 0;
+
+ //StrokeOutline
+ p = &mpool->strokeOutline[i];
+
+ free(p->cntrs);
+ p->cntrs = nullptr;
+
+ free(p->pts);
+ p->pts = nullptr;
+
+ free(p->types);
+ p->types = nullptr;
+
+ free(p->closed);
+ p->closed = nullptr;
+
+ p->cntrsCnt = p->reservedCntrsCnt = 0;
+ p->ptsCnt = p->reservedPtsCnt = 0;
+ }
+
+ return true;
+}
+
+
+bool mpoolTerm(SwMpool* mpool)
+{
+ if (!mpool) return false;
+
+ mpoolClear(mpool);
+
+ if (mpool->outline) {
+ free(mpool->outline);
+ mpool->outline = nullptr;
+ }
+
+ if (mpool->strokeOutline) {
+ free(mpool->strokeOutline);
+ mpool->strokeOutline = nullptr;
+ }
+
+ free(mpool);
+
+ return true;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp
new file mode 100644
index 0000000000..deebed16ee
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp
@@ -0,0 +1,1497 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgMath.h"
+#include "tvgRender.h"
+#include "tvgSwCommon.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+constexpr auto DOWN_SCALE_TOLERANCE = 0.5f;
+
+
+static inline uint32_t _multiplyAlpha(uint32_t c, uint32_t a)
+{
+ return ((c * a + 0xff) >> 8);
+}
+
+
+static inline uint32_t _alpha(uint32_t c)
+{
+ return (c >> 24);
+}
+
+
+static inline uint32_t _ialpha(uint32_t c)
+{
+ return (~c >> 24);
+}
+
+
+static inline uint32_t _abgrJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ return (a << 24 | b << 16 | g << 8 | r);
+}
+
+
+static inline uint32_t _argbJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ return (a << 24 | r << 16 | g << 8 | b);
+}
+
+
+#include "tvgSwRasterTexmap.h"
+#include "tvgSwRasterC.h"
+#include "tvgSwRasterAvx.h"
+#include "tvgSwRasterNeon.h"
+
+
+static inline bool _compositing(const SwSurface* surface)
+{
+ if (!surface->compositor || surface->compositor->method == CompositeMethod::None) return false;
+ return true;
+}
+
+
+static inline uint32_t _halfScale(float scale)
+{
+ auto halfScale = static_cast<uint32_t>(0.5f / scale);
+ if (halfScale == 0) halfScale = 1;
+ return halfScale;
+}
+
+//Bilinear Interpolation
+static uint32_t _interpUpScaler(const uint32_t *img, uint32_t w, uint32_t h, float sx, float sy)
+{
+ auto rx = (uint32_t)(sx);
+ auto ry = (uint32_t)(sy);
+ auto rx2 = rx + 1;
+ if (rx2 >= w) rx2 = w - 1;
+ auto ry2 = ry + 1;
+ if (ry2 >= h) ry2 = h - 1;
+
+ auto dx = static_cast<uint32_t>((sx - rx) * 255.0f);
+ auto dy = static_cast<uint32_t>((sy - ry) * 255.0f);
+
+ auto c1 = img[rx + ry * w];
+ auto c2 = img[rx2 + ry * w];
+ auto c3 = img[rx2 + ry2 * w];
+ auto c4 = img[rx + ry2 * w];
+
+ return INTERPOLATE(dy, INTERPOLATE(dx, c3, c4), INTERPOLATE(dx, c2, c1));
+}
+
+
+//2n x 2n Mean Kernel
+static uint32_t _interpDownScaler(const uint32_t *img, uint32_t stride, uint32_t w, uint32_t h, uint32_t rx, uint32_t ry, uint32_t n)
+{
+ uint32_t c[4] = {0, 0, 0, 0};
+ auto n2 = n * n;
+ auto src = img + rx - n + (ry - n) * stride;
+
+ for (auto y = ry - n; y < ry + n; ++y) {
+ if (y >= h) continue;
+ auto p = src;
+ for (auto x = rx - n; x < rx + n; ++x, ++p) {
+ if (x >= w) continue;
+ c[0] += *p >> 24;
+ c[1] += (*p >> 16) & 0xff;
+ c[2] += (*p >> 8) & 0xff;
+ c[3] += *p & 0xff;
+ }
+ src += stride;
+ }
+ for (auto i = 0; i < 4; ++i) {
+ c[i] = (c[i] >> 2) / n2;
+ }
+ return (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
+}
+
+
+/************************************************************************/
+/* Rect */
+/************************************************************************/
+
+static bool _rasterMaskedRect(SwSurface* surface, const SwBBox& region, uint32_t color, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Masked Rect");
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride) + region.min.x; //compositor buffer
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+ auto cmp = &cbuffer[y * surface->stride];
+ for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp) {
+ auto tmp = ALPHA_BLEND(color, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterSolidRect(SwSurface* surface, const SwBBox& region, uint32_t color)
+{
+ auto buffer = surface->buffer + (region.min.y * surface->stride);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+
+ for (uint32_t y = 0; y < h; ++y) {
+ rasterRGBA32(buffer + y * surface->stride, color, region.min.x, w);
+ }
+ return true;
+}
+
+
+static bool _rasterRect(SwSurface* surface, const SwBBox& region, uint32_t color, uint8_t opacity)
+{
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterMaskedRect(surface, region, color, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterMaskedRect(surface, region, color, _ialpha);
+ }
+ } else {
+ if (opacity == 255) {
+ return _rasterSolidRect(surface, region, color);
+ } else {
+#if defined(THORVG_AVX_VECTOR_SUPPORT)
+ return avxRasterTranslucentRect(surface, region, color);
+#elif defined(THORVG_NEON_VECTOR_SUPPORT)
+ return neonRasterTranslucentRect(surface, region, color);
+#else
+ return cRasterTranslucentRect(surface, region, color);
+#endif
+ }
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* Rle */
+/************************************************************************/
+
+static bool _rasterMaskedRle(SwSurface* surface, SwRleData* rle, uint32_t color, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Masked Rle");
+
+ auto span = rle->spans;
+ uint32_t src;
+ auto cbuffer = surface->compositor->image.data;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &cbuffer[span->y * surface->compositor->image.stride + span->x];
+ if (span->coverage == 255) src = color;
+ else src = ALPHA_BLEND(color, span->coverage);
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp) {
+ auto tmp = ALPHA_BLEND(src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterSolidRle(SwSurface* surface, const SwRleData* rle, uint32_t color)
+{
+ auto span = rle->spans;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ if (span->coverage == 255) {
+ rasterRGBA32(surface->buffer + span->y * surface->stride, color, span->x, span->len);
+ } else {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto src = ALPHA_BLEND(color, span->coverage);
+ auto ialpha = 255 - span->coverage;
+ for (uint32_t x = 0; x < span->len; ++x, ++dst) {
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterRle(SwSurface* surface, SwRleData* rle, uint32_t color, uint8_t opacity)
+{
+ if (!rle) return false;
+
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterMaskedRle(surface, rle, color, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterMaskedRle(surface, rle, color, _ialpha);
+ }
+ } else {
+ if (opacity == 255) {
+ return _rasterSolidRle(surface, rle, color);
+ } else {
+#if defined(THORVG_AVX_VECTOR_SUPPORT)
+ return avxRasterTranslucentRle(surface, rle, color);
+#elif defined(THORVG_NEON_VECTOR_SUPPORT)
+ return neonRasterTranslucentRle(surface, rle, color);
+#else
+ return cRasterTranslucentRle(surface, rle, color);
+#endif
+ }
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* RLE Transformed RGBA Image */
+/************************************************************************/
+
+static bool _transformedRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* transform, uint32_t opacity)
+{
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterTexmapPolygon(surface, image, transform, nullptr, opacity, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterTexmapPolygon(surface, image, transform, nullptr, opacity, _ialpha);
+ }
+ } else {
+ return _rasterTexmapPolygon(surface, image, transform, nullptr, opacity, nullptr);
+ }
+ return false;
+}
+
+/************************************************************************/
+/* RLE Scaled RGBA Image */
+/************************************************************************/
+
+static bool _rasterScaledMaskedTranslucentRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t opacity, uint32_t halfScale, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Scaled Masked Translucent Rle Image");
+
+ auto span = image->rle->spans;
+
+ //Center (Down-Scaled)
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = (uint32_t)(span->y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &surface->compositor->image.data[span->y * surface->compositor->image.stride + span->x];
+ auto alpha = _multiplyAlpha(span->coverage, opacity);
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst, ++cmp) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), alpha);
+ auto tmp = ALPHA_BLEND(src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ //Center (Up-Scaled)
+ } else {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = span->y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &surface->compositor->image.data[span->y * surface->compositor->image.stride + span->x];
+ auto alpha = _multiplyAlpha(span->coverage, opacity);
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst, ++cmp) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), alpha);
+ auto tmp = ALPHA_BLEND(src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterScaledMaskedRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t halfScale, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Scaled Masked Rle Image");
+
+ auto span = image->rle->spans;
+
+ //Center (Down-Scaled)
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = (uint32_t)(span->y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &surface->compositor->image.data[span->y * surface->compositor->image.stride + span->x];
+ if (span->coverage == 255) {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst, ++cmp) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto tmp = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ } else {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst, ++cmp) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), span->coverage);
+ auto tmp = ALPHA_BLEND(src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ //Center (Up-Scaled)
+ } else {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = span->y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &surface->compositor->image.data[span->y * surface->compositor->image.stride + span->x];
+ if (span->coverage == 255) {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst, ++cmp) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto tmp = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ } else {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst, ++cmp) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), span->coverage);
+ auto tmp = ALPHA_BLEND(src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterScaledTranslucentRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t opacity, uint32_t halfScale)
+{
+ auto span = image->rle->spans;
+
+ //Center (Down-Scaled)
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = (uint32_t)(span->y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto alpha = _multiplyAlpha(span->coverage, opacity);
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), alpha);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ //Center (Up-Scaled)
+ } else {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = span->y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto alpha = _multiplyAlpha(span->coverage, opacity);
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), alpha);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterScaledRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t opacity, uint32_t halfScale)
+{
+ auto span = image->rle->spans;
+
+ //Center (Down-Scaled)
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = (uint32_t)(span->y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ if (span->coverage == 255) {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = _interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ } else {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), span->coverage);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ }
+ //Center (Up-Scaled)
+ } else {
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto sy = span->y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ if (span->coverage == 255) {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = _interpUpScaler(image->data, image->w, image->h, sx, sy);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ } else {
+ for (uint32_t x = static_cast<uint32_t>(span->x); x < static_cast<uint32_t>(span->x) + span->len; ++x, ++dst) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), span->coverage);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _scaledRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint32_t opacity)
+{
+ Matrix itransform;
+ if (transform && !mathInverse(transform, &itransform)) return false;
+
+ auto halfScale = _halfScale(image->scale);
+
+ if (_compositing(surface)) {
+ if (opacity == 255) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterScaledMaskedRleRGBAImage(surface, image, &itransform, region, halfScale, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterScaledMaskedRleRGBAImage(surface, image, &itransform, region, halfScale, _ialpha);
+ }
+ } else {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterScaledMaskedTranslucentRleRGBAImage(surface, image, &itransform, region, opacity, halfScale, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterScaledMaskedTranslucentRleRGBAImage(surface, image, &itransform, region, opacity, halfScale, _ialpha);
+ }
+ }
+ } else {
+ if (opacity == 255) return _rasterScaledRleRGBAImage(surface, image, &itransform, region, opacity, halfScale);
+ else return _rasterScaledTranslucentRleRGBAImage(surface, image, &itransform, region, opacity, halfScale);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* RLE Direct RGBA Image */
+/************************************************************************/
+
+static bool _rasterDirectMaskedTranslucentRleRGBAImage(SwSurface* surface, const SwImage* image, uint32_t opacity, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Direct Masked Rle Image");
+
+ auto span = image->rle->spans;
+ auto cbuffer = surface->compositor->image.data;
+
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &cbuffer[span->y * surface->compositor->image.stride + span->x];
+ auto img = image->data + (span->y + image->oy) * image->stride + (span->x + image->ox);
+ auto alpha = _multiplyAlpha(span->coverage, opacity);
+ if (alpha == 255) {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++img) {
+ auto tmp = ALPHA_BLEND(*img, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ } else {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++img) {
+ auto tmp = ALPHA_BLEND(*img, _multiplyAlpha(alpha, blendMethod(*cmp)));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterDirectMaskedRleRGBAImage(SwSurface* surface, const SwImage* image, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Direct Masked Rle Image");
+
+ auto span = image->rle->spans;
+ auto cbuffer = surface->compositor->image.data;
+
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &cbuffer[span->y * surface->compositor->image.stride + span->x];
+ auto img = image->data + (span->y + image->oy) * image->stride + (span->x + image->ox);
+ if (span->coverage == 255) {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++img) {
+ auto tmp = ALPHA_BLEND(*img, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ } else {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++img) {
+ auto tmp = ALPHA_BLEND(*img, _multiplyAlpha(span->coverage, blendMethod(*cmp)));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterDirectTranslucentRleRGBAImage(SwSurface* surface, const SwImage* image, uint32_t opacity)
+{
+ auto span = image->rle->spans;
+
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto img = image->data + (span->y + image->oy) * image->stride + (span->x + image->ox);
+ auto alpha = _multiplyAlpha(span->coverage, opacity);
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) {
+ auto src = ALPHA_BLEND(*img, alpha);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterDirectRleRGBAImage(SwSurface* surface, const SwImage* image)
+{
+ auto span = image->rle->spans;
+
+ for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto img = image->data + (span->y + image->oy) * image->stride + (span->x + image->ox);
+ if (span->coverage == 255) {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) {
+ *dst = *img + ALPHA_BLEND(*dst, _ialpha(*img));
+ }
+ } else {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) {
+ auto src = ALPHA_BLEND(*img, span->coverage);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _directRleRGBAImage(SwSurface* surface, const SwImage* image, uint32_t opacity)
+{
+ if (_compositing(surface)) {
+ if (opacity == 255) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterDirectMaskedRleRGBAImage(surface, image, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterDirectMaskedRleRGBAImage(surface, image, _ialpha);
+ }
+ } else {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterDirectMaskedTranslucentRleRGBAImage(surface, image, opacity, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterDirectMaskedTranslucentRleRGBAImage(surface, image, opacity, _ialpha);
+ }
+ }
+ } else {
+ if (opacity == 255) return _rasterDirectRleRGBAImage(surface, image);
+ else return _rasterDirectTranslucentRleRGBAImage(surface, image, opacity);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* Transformed RGBA Image */
+/************************************************************************/
+
+static bool _transformedRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint32_t opacity)
+{
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterTexmapPolygon(surface, image, transform, &region, opacity, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterTexmapPolygon(surface, image, transform, &region, opacity, _ialpha);
+ }
+ } else {
+ return _rasterTexmapPolygon(surface, image, transform, &region, opacity, nullptr);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/*Scaled RGBA Image */
+/************************************************************************/
+
+
+static bool _rasterScaledMaskedTranslucentRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t opacity, uint32_t halfScale, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Scaled Masked Image");
+
+ auto dbuffer = surface->buffer + (region.min.y * surface->stride + region.min.x);
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride + region.min.x);
+
+ // Down-Scaled
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto sy = (uint32_t)(y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = dbuffer;
+ auto cmp = cbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto alpha = _multiplyAlpha(opacity, blendMethod(*cmp));
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), alpha);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ dbuffer += surface->stride;
+ cbuffer += surface->compositor->image.stride;
+ }
+ // Up-Scaled
+ } else {
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto sy = y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = dbuffer;
+ auto cmp = cbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto alpha = _multiplyAlpha(opacity, blendMethod(*cmp));
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), alpha);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ dbuffer += surface->stride;
+ cbuffer += surface->compositor->image.stride;
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterScaledMaskedRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t halfScale, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Scaled Masked Image");
+
+ auto dbuffer = surface->buffer + (region.min.y * surface->stride + region.min.x);
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride + region.min.x);
+
+ // Down-Scaled
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto sy = (uint32_t)(y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = dbuffer;
+ auto cmp = cbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), blendMethod(*cmp));
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ dbuffer += surface->stride;
+ cbuffer += surface->compositor->image.stride;
+ }
+ // Up-Scaled
+ } else {
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto sy = y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = dbuffer;
+ auto cmp = cbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), blendMethod(*cmp));
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ dbuffer += surface->stride;
+ cbuffer += surface->compositor->image.stride;
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterScaledTranslucentRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t opacity, uint32_t halfScale)
+{
+ auto dbuffer = surface->buffer + (region.min.y * surface->stride + region.min.x);
+
+ // Down-Scaled
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) {
+ auto sy = (uint32_t)(y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = dbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale), opacity);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ // Up-Scaled
+ } else {
+ for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) {
+ auto sy = fabsf(y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = dbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = ALPHA_BLEND(_interpUpScaler(image->data, image->w, image->h, sx, sy), opacity);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterScaledRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint32_t halfScale)
+{
+ auto dbuffer = surface->buffer + (region.min.y * surface->stride + region.min.x);
+
+ // Down-Scaled
+ if (image->scale < DOWN_SCALE_TOLERANCE) {
+ for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) {
+ auto sy = (uint32_t)(y * itransform->e22 + itransform->e23);
+ if (sy >= image->h) continue;
+ auto dst = dbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
+ auto sx = (uint32_t)(x * itransform->e11 + itransform->e13);
+ if (sx >= image->w) continue;
+ auto src = _interpDownScaler(image->data, image->stride, image->w, image->h, sx, sy, halfScale);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ // Up-Scaled
+ } else {
+ for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) {
+ auto sy = y * itransform->e22 + itransform->e23;
+ if ((uint32_t)sy >= image->h) continue;
+ auto dst = dbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
+ auto sx = x * itransform->e11 + itransform->e13;
+ if ((uint32_t)sx >= image->w) continue;
+ auto src = _interpUpScaler(image->data, image->w, image->h, sx, sy);
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _scaledRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint32_t opacity)
+{
+ Matrix itransform;
+ if (transform && !mathInverse(transform, &itransform)) return false;
+
+ auto halfScale = _halfScale(image->scale);
+
+ if (_compositing(surface)) {
+ if (opacity == 255) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterScaledMaskedRGBAImage(surface, image, &itransform, region, halfScale, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterScaledMaskedRGBAImage(surface, image, &itransform, region, halfScale, _ialpha);
+ }
+ } else {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterScaledMaskedTranslucentRGBAImage(surface, image, &itransform, region, opacity, halfScale, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterScaledMaskedTranslucentRGBAImage(surface, image, &itransform, region, opacity, halfScale, _ialpha);
+ }
+ }
+ } else {
+ if (opacity == 255) return _rasterScaledRGBAImage(surface, image, &itransform, region, halfScale);
+ else return _rasterScaledTranslucentRGBAImage(surface, image, &itransform, region, opacity, halfScale);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* Direct RGBA Image */
+/************************************************************************/
+
+static bool _rasterDirectMaskedRGBAImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Direct Masked Image");
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h2 = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w2 = static_cast<uint32_t>(region.max.x - region.min.x);
+
+ auto sbuffer = image->data + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox);
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride) + region.min.x; //compositor buffer
+
+ for (uint32_t y = 0; y < h2; ++y) {
+ auto dst = buffer;
+ auto cmp = cbuffer;
+ auto src = sbuffer;
+ for (uint32_t x = 0; x < w2; ++x, ++dst, ++src, ++cmp) {
+ auto tmp = ALPHA_BLEND(*src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ buffer += surface->stride;
+ cbuffer += surface->compositor->image.stride;
+ sbuffer += image->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterDirectMaskedTranslucentRGBAImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint32_t opacity, uint32_t (*blendMethod)(uint32_t))
+{
+ TVGLOG("SW_ENGINE", "Direct Masked Translucent Image");
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h2 = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w2 = static_cast<uint32_t>(region.max.x - region.min.x);
+
+ auto sbuffer = image->data + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox);
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride) + region.min.x; //compositor buffer
+
+ for (uint32_t y = 0; y < h2; ++y) {
+ auto dst = buffer;
+ auto cmp = cbuffer;
+ auto src = sbuffer;
+ for (uint32_t x = 0; x < w2; ++x, ++dst, ++src, ++cmp) {
+ auto tmp = ALPHA_BLEND(*src, _multiplyAlpha(opacity, blendMethod(*cmp)));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ buffer += surface->stride;
+ cbuffer += surface->compositor->image.stride;
+ sbuffer += image->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterDirectTranslucentRGBAImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint32_t opacity)
+{
+ auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
+ auto sbuffer = image->data + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox);
+
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto dst = dbuffer;
+ auto src = sbuffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ auto tmp = ALPHA_BLEND(*src, opacity);
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ dbuffer += surface->stride;
+ sbuffer += image->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterDirectRGBAImage(SwSurface* surface, const SwImage* image, const SwBBox& region)
+{
+ auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
+ auto sbuffer = image->data + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox);
+
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto dst = dbuffer;
+ auto src = sbuffer;
+ for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
+ *dst = *src + ALPHA_BLEND(*dst, _ialpha(*src));
+ }
+ dbuffer += surface->stride;
+ sbuffer += image->stride;
+ }
+ return true;
+}
+
+
+//Blenders for the following scenarios: [Composition / Non-Composition] * [Opaque / Translucent]
+static bool _directRGBAImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint32_t opacity)
+{
+ if (_compositing(surface)) {
+ if (opacity == 255) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterDirectMaskedRGBAImage(surface, image, region, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterDirectMaskedRGBAImage(surface, image, region, _ialpha);
+ }
+ } else {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterDirectMaskedTranslucentRGBAImage(surface, image, region, opacity, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterDirectMaskedTranslucentRGBAImage(surface, image, region, opacity, _ialpha);
+ }
+ }
+ } else {
+ if (opacity == 255) return _rasterDirectRGBAImage(surface, image, region);
+ else return _rasterDirectTranslucentRGBAImage(surface, image, region, opacity);
+ }
+ return false;
+}
+
+
+//Blenders for the following scenarios: [RLE / Whole] * [Direct / Scaled / Transformed]
+static bool _rasterRGBAImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& region, uint32_t opacity)
+{
+ //RLE Image
+ if (image->rle) {
+ if (image->direct) return _directRleRGBAImage(surface, image, opacity);
+ else if (image->scaled) return _scaledRleRGBAImage(surface, image, transform, region, opacity);
+ else return _transformedRleRGBAImage(surface, image, transform, opacity);
+ //Whole Image
+ } else {
+ if (image->direct) return _directRGBAImage(surface, image, region, opacity);
+ else if (image->scaled) return _scaledRGBAImage(surface, image, transform, region, opacity);
+ else return _transformedRGBAImage(surface, image, transform, region, opacity);
+ }
+}
+
+
+/************************************************************************/
+/* Rect Linear Gradient */
+/************************************************************************/
+
+static bool _rasterLinearGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill, uint32_t (*blendMethod)(uint32_t))
+{
+ if (fill->linear.len < FLT_EPSILON) return false;
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride) + region.min.x;
+
+ auto sbuffer = static_cast<uint32_t*>(alloca(w * sizeof(uint32_t)));
+ if (!sbuffer) return false;
+
+ for (uint32_t y = 0; y < h; ++y) {
+ fillFetchLinear(fill, sbuffer, region.min.y + y, region.min.x, w);
+ auto dst = buffer;
+ auto cmp = cbuffer;
+ auto src = sbuffer;
+ for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp, ++src) {
+ auto tmp = ALPHA_BLEND(*src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ buffer += surface->stride;
+ cbuffer += surface->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterTranslucentLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
+{
+ if (fill->linear.len < FLT_EPSILON) return false;
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+
+ auto sbuffer = static_cast<uint32_t*>(alloca(w * sizeof(uint32_t)));
+ if (!sbuffer) return false;
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = buffer;
+ fillFetchLinear(fill, sbuffer, region.min.y + y, region.min.x, w);
+ for (uint32_t x = 0; x < w; ++x, ++dst) {
+ *dst = sbuffer[x] + ALPHA_BLEND(*dst, _ialpha(sbuffer[x]));
+ }
+ buffer += surface->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterSolidLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
+{
+ if (fill->linear.len < FLT_EPSILON) return false;
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+
+ for (uint32_t y = 0; y < h; ++y) {
+ fillFetchLinear(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, w);
+ }
+ return true;
+}
+
+
+static bool _rasterLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
+{
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterLinearGradientMaskedRect(surface, region, fill, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterLinearGradientMaskedRect(surface, region, fill, _ialpha);
+ }
+ } else {
+ if (fill->translucent) return _rasterTranslucentLinearGradientRect(surface, region, fill);
+ else _rasterSolidLinearGradientRect(surface, region, fill);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* Rle Linear Gradient */
+/************************************************************************/
+
+static bool _rasterLinearGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, uint32_t (*blendMethod)(uint32_t))
+{
+ if (fill->linear.len < FLT_EPSILON) return false;
+
+ auto span = rle->spans;
+ auto cbuffer = surface->compositor->image.data;
+ auto buffer = static_cast<uint32_t*>(alloca(surface->w * sizeof(uint32_t)));
+ if (!buffer) return false;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ fillFetchLinear(fill, buffer, span->y, span->x, span->len);
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &cbuffer[span->y * surface->compositor->image.stride + span->x];
+ auto src = buffer;
+ if (span->coverage == 255) {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) {
+ auto tmp = ALPHA_BLEND(*src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ } else {
+ auto ialpha = 255 - span->coverage;
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) {
+ auto tmp = ALPHA_BLEND(*src, blendMethod(*cmp));
+ tmp = ALPHA_BLEND(tmp, span->coverage) + ALPHA_BLEND(*dst, ialpha);
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterTranslucentLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill)
+{
+ if (fill->linear.len < FLT_EPSILON) return false;
+
+ auto span = rle->spans;
+ auto buffer = static_cast<uint32_t*>(alloca(surface->w * sizeof(uint32_t)));
+ if (!buffer) return false;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ fillFetchLinear(fill, buffer, span->y, span->x, span->len);
+ if (span->coverage == 255) {
+ for (uint32_t i = 0; i < span->len; ++i, ++dst) {
+ *dst = buffer[i] + ALPHA_BLEND(*dst, _ialpha(buffer[i]));
+ }
+ } else {
+ for (uint32_t i = 0; i < span->len; ++i, ++dst) {
+ auto tmp = ALPHA_BLEND(buffer[i], span->coverage);
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterSolidLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill)
+{
+ if (fill->linear.len < FLT_EPSILON) return false;
+
+ auto buf = static_cast<uint32_t*>(alloca(surface->w * sizeof(uint32_t)));
+ if (!buf) return false;
+
+ auto span = rle->spans;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ if (span->coverage == 255) {
+ fillFetchLinear(fill, surface->buffer + span->y * surface->stride + span->x, span->y, span->x, span->len);
+ } else {
+ fillFetchLinear(fill, buf, span->y, span->x, span->len);
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ for (uint32_t i = 0; i < span->len; ++i) {
+ dst[i] = INTERPOLATE(span->coverage, buf[i], dst[i]);
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill)
+{
+ if (!rle) return false;
+
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterLinearGradientMaskedRle(surface, rle, fill, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterLinearGradientMaskedRle(surface, rle, fill, _ialpha);
+ }
+ } else {
+ if (fill->translucent) return _rasterTranslucentLinearGradientRle(surface, rle, fill);
+ else return _rasterSolidLinearGradientRle(surface, rle, fill);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* Rect Radial Gradient */
+/************************************************************************/
+
+static bool _rasterRadialGradientMaskedRect(SwSurface* surface, const SwBBox& region, const SwFill* fill, uint32_t (*blendMethod)(uint32_t))
+{
+ if (fill->radial.a < FLT_EPSILON) return false;
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto cbuffer = surface->compositor->image.data + (region.min.y * surface->compositor->image.stride) + region.min.x;
+
+ auto sbuffer = static_cast<uint32_t*>(alloca(w * sizeof(uint32_t)));
+ if (!sbuffer) return false;
+
+ for (uint32_t y = 0; y < h; ++y) {
+ fillFetchRadial(fill, sbuffer, region.min.y + y, region.min.x, w);
+ auto dst = buffer;
+ auto cmp = cbuffer;
+ auto src = sbuffer;
+ for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp, ++src) {
+ auto tmp = ALPHA_BLEND(*src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ buffer += surface->stride;
+ cbuffer += surface->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterTranslucentRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
+{
+ if (fill->radial.a < FLT_EPSILON) return false;
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+
+ auto sbuffer = static_cast<uint32_t*>(alloca(w * sizeof(uint32_t)));
+ if (!sbuffer) return false;
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = buffer;
+ fillFetchRadial(fill, sbuffer, region.min.y + y, region.min.x, w);
+ for (uint32_t x = 0; x < w; ++x, ++dst) {
+ *dst = sbuffer[x] + ALPHA_BLEND(*dst, _ialpha(sbuffer[x]));
+ }
+ buffer += surface->stride;
+ }
+ return true;
+}
+
+
+static bool _rasterSolidRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
+{
+ if (fill->radial.a < FLT_EPSILON) return false;
+
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+ fillFetchRadial(fill, dst, region.min.y + y, region.min.x, w);
+ }
+ return true;
+}
+
+
+static bool _rasterRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
+{
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterRadialGradientMaskedRect(surface, region, fill, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterRadialGradientMaskedRect(surface, region, fill, _ialpha);
+ }
+ } else {
+ if (fill->translucent) return _rasterTranslucentRadialGradientRect(surface, region, fill);
+ else return _rasterSolidRadialGradientRect(surface, region, fill);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* RLE Radial Gradient */
+/************************************************************************/
+
+static bool _rasterRadialGradientMaskedRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill, uint32_t (*blendMethod)(uint32_t))
+{
+ if (fill->radial.a < FLT_EPSILON) return false;
+
+ auto span = rle->spans;
+ auto cbuffer = surface->compositor->image.data;
+ auto buffer = static_cast<uint32_t*>(alloca(surface->w * sizeof(uint32_t)));
+ if (!buffer) return false;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ fillFetchRadial(fill, buffer, span->y, span->x, span->len);
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto cmp = &cbuffer[span->y * surface->compositor->image.stride + span->x];
+ auto src = buffer;
+ if (span->coverage == 255) {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) {
+ auto tmp = ALPHA_BLEND(*src, blendMethod(*cmp));
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ } else {
+ for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) {
+ auto tmp = INTERPOLATE(span->coverage, ALPHA_BLEND(*src, blendMethod(*cmp)), *dst);
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterTranslucentRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill)
+{
+ if (fill->radial.a < FLT_EPSILON) return false;
+
+ auto span = rle->spans;
+ auto buffer = static_cast<uint32_t*>(alloca(surface->w * sizeof(uint32_t)));
+ if (!buffer) return false;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ fillFetchRadial(fill, buffer, span->y, span->x, span->len);
+ if (span->coverage == 255) {
+ for (uint32_t i = 0; i < span->len; ++i, ++dst) {
+ *dst = buffer[i] + ALPHA_BLEND(*dst, _ialpha(buffer[i]));
+ }
+ } else {
+ for (uint32_t i = 0; i < span->len; ++i, ++dst) {
+ auto tmp = ALPHA_BLEND(buffer[i], span->coverage);
+ *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp));
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterSolidRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill)
+{
+ if (fill->radial.a < FLT_EPSILON) return false;
+
+ auto buf = static_cast<uint32_t*>(alloca(surface->w * sizeof(uint32_t)));
+ if (!buf) return false;
+
+ auto span = rle->spans;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ if (span->coverage == 255) {
+ fillFetchRadial(fill, dst, span->y, span->x, span->len);
+ } else {
+ fillFetchRadial(fill, buf, span->y, span->x, span->len);
+ auto ialpha = 255 - span->coverage;
+ for (uint32_t i = 0; i < span->len; ++i, ++dst) {
+ *dst = ALPHA_BLEND(buf[i], span->coverage) + ALPHA_BLEND(*dst, ialpha);
+ }
+ }
+ }
+ return true;
+}
+
+
+static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill)
+{
+ if (!rle) return false;
+
+ if (_compositing(surface)) {
+ if (surface->compositor->method == CompositeMethod::AlphaMask) {
+ return _rasterRadialGradientMaskedRle(surface, rle, fill, _alpha);
+ } else if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
+ return _rasterRadialGradientMaskedRle(surface, rle, fill, _ialpha);
+ }
+ } else {
+ if (fill->translucent) _rasterTranslucentRadialGradientRle(surface, rle, fill);
+ else return _rasterSolidRadialGradientRle(surface, rle, fill);
+ }
+ return false;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
+{
+#if defined(THORVG_AVX_VECTOR_SUPPORT)
+ avxRasterRGBA32(dst, val, offset, len);
+#elif defined(THORVG_NEON_VECTOR_SUPPORT)
+ neonRasterRGBA32(dst, val, offset, len);
+#else
+ cRasterRGBA32(dst, val, offset, len);
+#endif
+}
+
+
+bool rasterCompositor(SwSurface* surface)
+{
+ if (surface->cs == SwCanvas::ABGR8888 || surface->cs == SwCanvas::ABGR8888_STRAIGHT) {
+ surface->blender.join = _abgrJoin;
+ } else if (surface->cs == SwCanvas::ARGB8888 || surface->cs == SwCanvas::ARGB8888_STRAIGHT) {
+ surface->blender.join = _argbJoin;
+ } else {
+ //What Color Space ???
+ return false;
+ }
+ return true;
+}
+
+
+bool rasterClear(SwSurface* surface)
+{
+ if (!surface || !surface->buffer || surface->stride <= 0 || surface->w <= 0 || surface->h <= 0) return false;
+
+ if (surface->w == surface->stride) {
+ rasterRGBA32(surface->buffer, 0x00000000, 0, surface->w * surface->h);
+ } else {
+ for (uint32_t i = 0; i < surface->h; i++) {
+ rasterRGBA32(surface->buffer + surface->stride * i, 0x00000000, 0, surface->w);
+ }
+ }
+ return true;
+}
+
+
+void rasterUnpremultiply(SwSurface* surface)
+{
+ //OPTIMIZE_ME: +SIMD
+ for (uint32_t y = 0; y < surface->h; y++) {
+ auto buffer = surface->buffer + surface->stride * y;
+ for (uint32_t x = 0; x < surface->w; ++x) {
+ uint8_t a = buffer[x] >> 24;
+ if (a == 255) {
+ continue;
+ } else if (a == 0) {
+ buffer[x] = 0x00ffffff;
+ } else {
+ uint16_t r = ((buffer[x] >> 8) & 0xff00) / a;
+ uint16_t g = ((buffer[x]) & 0xff00) / a;
+ uint16_t b = ((buffer[x] << 8) & 0xff00) / a;
+ if (r > 0xff) r = 0xff;
+ if (g > 0xff) g = 0xff;
+ if (b > 0xff) b = 0xff;
+ buffer[x] = (a << 24) | (r << 16) | (g << 8) | (b);
+ }
+ }
+ }
+}
+
+
+bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id)
+{
+ if (!shape->fill) return false;
+
+ if (shape->fastTrack) {
+ if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill);
+ else if (id == TVG_CLASS_ID_RADIAL)return _rasterRadialGradientRect(surface, shape->bbox, shape->fill);
+ } else {
+ if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->rle, shape->fill);
+ else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->rle, shape->fill);
+ }
+ return false;
+}
+
+
+bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id)
+{
+ if (!shape->stroke || !shape->stroke->fill || !shape->strokeRle) return false;
+
+ if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill);
+ else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill);
+
+ return false;
+}
+
+
+bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ if (a < 255) {
+ r = _multiplyAlpha(r, a);
+ g = _multiplyAlpha(g, a);
+ b = _multiplyAlpha(b, a);
+ }
+
+ auto color = surface->blender.join(r, g, b, a);
+
+ if (shape->fastTrack) return _rasterRect(surface, shape->bbox, color, a);
+ else return _rasterRle(surface, shape->rle, color, a);
+}
+
+
+bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ if (a < 255) {
+ r = _multiplyAlpha(r, a);
+ g = _multiplyAlpha(g, a);
+ b = _multiplyAlpha(b, a);
+ }
+
+ auto color = surface->blender.join(r, g, b, a);
+
+ return _rasterRle(surface, shape->strokeRle, color, a);
+}
+
+
+bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity)
+{
+ //Verify Boundary
+ if (bbox.max.x < 0 || bbox.max.y < 0 || bbox.min.x >= surface->w || bbox.min.y >= surface->h) return false;
+
+ //TOOD: switch (image->format)
+ //TODO: case: _rasterRGBImage()
+ //TODO: case: _rasterGrayscaleImage()
+ //TODO: case: _rasterAlphaImage()
+ return _rasterRGBAImage(surface, image, transform, bbox, opacity);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h
new file mode 100644
index 0000000000..1d6552f3e9
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifdef THORVG_AVX_VECTOR_SUPPORT
+
+#include <immintrin.h>
+
+#define N_32BITS_IN_128REG 4
+#define N_32BITS_IN_256REG 8
+
+static inline __m128i ALPHA_BLEND(__m128i c, __m128i a)
+{
+ //1. set the masks for the A/G and R/B channels
+ auto AG = _mm_set1_epi32(0xff00ff00);
+ auto RB = _mm_set1_epi32(0x00ff00ff);
+
+ //2. mask the alpha vector - originally quartet [a, a, a, a]
+ auto aAG = _mm_and_si128(a, AG);
+ auto aRB = _mm_and_si128(a, RB);
+
+ //3. calculate the alpha blending of the 2nd and 4th channel
+ //- mask the color vector
+ //- multiply it by the masked alpha vector
+ //- add the correction to compensate bit shifting used instead of dividing by 255
+ //- shift bits - corresponding to division by 256
+ auto even = _mm_and_si128(c, RB);
+ even = _mm_mullo_epi16(even, aRB);
+ even =_mm_add_epi16(even, RB);
+ even = _mm_srli_epi16(even, 8);
+
+ //4. calculate the alpha blending of the 1st and 3rd channel:
+ //- mask the color vector
+ //- multiply it by the corresponding masked alpha vector and store the high bits of the result
+ //- add the correction to compensate division by 256 instead of by 255 (next step)
+ //- remove the low 8 bits to mimic the division by 256
+ auto odd = _mm_and_si128(c, AG);
+ odd = _mm_mulhi_epu16(odd, aAG);
+ odd = _mm_add_epi16(odd, RB);
+ odd = _mm_and_si128(odd, AG);
+
+ //5. the final result
+ return _mm_or_si128(odd, even);
+}
+
+
+static void avxRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
+{
+ //1. calculate how many iterations we need to cover the length
+ uint32_t iterations = len / N_32BITS_IN_256REG;
+ uint32_t avxFilled = iterations * N_32BITS_IN_256REG;
+
+ //2. set the beginning of the array
+ dst += offset;
+
+ //3. fill the octets
+ for (uint32_t i = 0; i < iterations; ++i, dst += N_32BITS_IN_256REG) {
+ _mm256_storeu_si256((__m256i*)dst, _mm256_set1_epi32(val));
+ }
+
+ //4. fill leftovers (in the first step we have to set the pointer to the place where the avx job is done)
+ int32_t leftovers = len - avxFilled;
+ while (leftovers--) *dst++ = val;
+}
+
+
+static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color)
+{
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+
+ auto ialpha = 255 - static_cast<uint8_t>(_alpha(color));
+
+ auto avxColor = _mm_set1_epi32(color);
+ auto avxIalpha = _mm_set1_epi8(ialpha);
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+
+ //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
+ auto notAligned = ((uintptr_t)dst & 0xf) / 4;
+ if (notAligned) {
+ notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned);
+ for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ }
+ }
+
+ //2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once
+ uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG;
+ uint32_t avxFilled = iterations * N_32BITS_IN_128REG;
+ auto avxDst = (__m128i*)dst;
+ for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
+ *avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha));
+ }
+
+ //3. fill the remaining pixels
+ int32_t leftovers = w - notAligned - avxFilled;
+ dst += avxFilled;
+ while (leftovers--) {
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ dst++;
+ }
+ }
+ return true;
+}
+
+
+static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint32_t color)
+{
+ auto span = rle->spans;
+ uint32_t src;
+
+ for (uint32_t i = 0; i < rle->size; ++i) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+
+ if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
+ else src = color;
+
+ auto ialpha = 255 - static_cast<uint8_t>(_alpha(src));
+
+ //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
+ auto notAligned = ((uintptr_t)dst & 0xf) / 4;
+ if (notAligned) {
+ notAligned = (N_32BITS_IN_128REG - notAligned > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
+ for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ }
+ }
+
+ //2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
+ //In order to avoid unneccessary avx variables declarations a check is made whether there are any iterations at all
+ uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
+ uint32_t avxFilled = 0;
+ if (iterations > 0) {
+ auto avxSrc = _mm_set1_epi32(src);
+ auto avxIalpha = _mm_set1_epi8(ialpha);
+
+ avxFilled = iterations * N_32BITS_IN_128REG;
+ auto avxDst = (__m128i*)dst;
+ for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
+ *avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
+ }
+ }
+
+ //3. fill the remaining pixels
+ int32_t leftovers = span->len - notAligned - avxFilled;
+ dst += avxFilled;
+ while (leftovers--) {
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ dst++;
+ }
+
+ ++span;
+ }
+ return true;
+}
+
+
+#endif
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h
new file mode 100644
index 0000000000..6d60957eb9
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+
+static void inline cRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
+{
+ dst += offset;
+ while (len--) *dst++ = val;
+}
+
+
+static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint32_t color)
+{
+ auto span = rle->spans;
+ uint32_t src;
+
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+
+ if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
+ else src = color;
+
+ for (uint32_t x = 0; x < span->len; ++x, ++dst) {
+ *dst = src + ALPHA_BLEND(*dst, _ialpha(src));
+ }
+ }
+ return true;
+}
+
+
+static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color)
+{
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto ialpha = _ialpha(color);
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+ for (uint32_t x = 0; x < w; ++x, ++dst) {
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ }
+ }
+ return true;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h
new file mode 100644
index 0000000000..c74a6b309c
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifdef THORVG_NEON_VECTOR_SUPPORT
+
+#include <arm_neon.h>
+
+static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a)
+{
+ uint16x8_t t = vmull_u8(c, a);
+ return vshrn_n_u16(t, 8);
+}
+
+
+static void neonRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
+{
+ uint32_t iterations = len / 4;
+ uint32_t neonFilled = iterations * 4;
+
+ dst += offset;
+ uint32x4_t vectorVal = {val, val, val, val};
+
+ for (uint32_t i = 0; i < iterations; ++i) {
+ vst1q_u32(dst, vectorVal);
+ dst += 4;
+ }
+
+ int32_t leftovers = len - neonFilled;
+ while (leftovers--) *dst++ = val;
+}
+
+
+static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint32_t color)
+{
+ auto span = rle->spans;
+ uint32_t src;
+ uint8x8_t *vDst = nullptr;
+ uint16_t align;
+
+ for (uint32_t i = 0; i < rle->size; ++i) {
+ if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
+ else src = color;
+
+ auto dst = &surface->buffer[span->y * surface->stride + span->x];
+ auto ialpha = 255 - _alpha(src);
+
+ if ((((uint32_t) dst) & 0x7) != 0) {
+ //fill not aligned byte
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ vDst = (uint8x8_t*)(dst + 1);
+ align = 1;
+ } else {
+ vDst = (uint8x8_t*) dst;
+ align = 0;
+ }
+
+ uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src);
+ uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha);
+
+ for (uint32_t x = 0; x < (span->len - align) / 2; ++x)
+ vDst[x] = vadd_u8(vSrc, ALPHA_BLEND(vDst[x], vIalpha));
+
+ auto leftovers = (span->len - align) % 2;
+ if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha);
+
+ ++span;
+ }
+ return true;
+}
+
+
+static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color)
+{
+ auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x;
+ auto h = static_cast<uint32_t>(region.max.y - region.min.y);
+ auto w = static_cast<uint32_t>(region.max.x - region.min.x);
+ auto ialpha = 255 - _alpha(color);
+
+ auto vColor = vdup_n_u32(color);
+ auto vIalpha = vdup_n_u8((uint8_t) ialpha);
+
+ uint8x8_t* vDst = nullptr;
+ uint32_t align;
+
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+
+ if ((((uint32_t) dst) & 0x7) != 0) {
+ //fill not aligned byte
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ vDst = (uint8x8_t*) (dst + 1);
+ align = 1;
+ } else {
+ vDst = (uint8x8_t*) dst;
+ align = 0;
+ }
+
+ for (uint32_t x = 0; x < (w - align) / 2; ++x)
+ vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha));
+
+ auto leftovers = (w - align) % 2;
+ if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
+ }
+ return true;
+}
+
+#endif
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h
new file mode 100644
index 0000000000..2cf9fb4e93
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmap.h
@@ -0,0 +1,602 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+struct Vertex
+{
+ Point pt;
+ Point uv;
+};
+
+struct Polygon
+{
+ Vertex vertex[3];
+};
+
+struct AALine
+{
+ int32_t x[2];
+ int32_t coverage[2];
+ int32_t length[2];
+};
+
+struct AASpans
+{
+ AALine *lines;
+ int32_t yStart;
+ int32_t yEnd;
+};
+
+static inline void _swap(float& a, float& b, float& tmp)
+{
+ tmp = a;
+ a = b;
+ b = tmp;
+}
+
+//Careful! Shared resource, No support threading
+static float dudx, dvdx;
+static float dxdya, dxdyb, dudya, dvdya;
+static float xa, xb, ua, va;
+
+
+//Y Range exception handling
+static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, int& yEnd)
+{
+ int32_t regionTop, regionBottom;
+
+ if (region) {
+ regionTop = region->min.y;
+ regionBottom = region->max.y;
+ } else {
+ regionTop = image->rle->spans->y;
+ regionBottom = image->rle->spans[image->rle->size - 1].y;
+ }
+
+ if (yStart >= regionBottom) return false;
+
+ if (yStart < regionTop) yStart = regionTop;
+ if (yEnd > regionBottom) yEnd = regionBottom;
+
+ return true;
+}
+
+
+static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, uint32_t opacity, uint32_t (*blendMethod)(uint32_t), AASpans* aaSpans)
+{
+#define TEXMAP_TRANSLUCENT
+#define TEXMAP_MASKING
+ #include "tvgSwRasterTexmapInternal.h"
+#undef TEXMAP_MASKING
+#undef TEXMAP_TRANSLUCENT
+}
+
+
+static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, uint32_t (*blendMethod)(uint32_t), AASpans* aaSpans)
+{
+#define TEXMAP_MASKING
+ #include "tvgSwRasterTexmapInternal.h"
+#undef TEXMAP_MASKING
+}
+
+
+static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, uint32_t opacity, AASpans* aaSpans)
+{
+#define TEXMAP_TRANSLUCENT
+ #include "tvgSwRasterTexmapInternal.h"
+#undef TEXMAP_TRANSLUCENT
+}
+
+
+static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans)
+{
+ #include "tvgSwRasterTexmapInternal.h"
+}
+
+
+/* This mapping algorithm is based on Mikael Kalms's. */
+static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const SwBBox* region, uint32_t opacity, Polygon& polygon, uint32_t (*blendMethod)(uint32_t), AASpans* aaSpans)
+{
+ float x[3] = {polygon.vertex[0].pt.x, polygon.vertex[1].pt.x, polygon.vertex[2].pt.x};
+ float y[3] = {polygon.vertex[0].pt.y, polygon.vertex[1].pt.y, polygon.vertex[2].pt.y};
+ float u[3] = {polygon.vertex[0].uv.x, polygon.vertex[1].uv.x, polygon.vertex[2].uv.x};
+ float v[3] = {polygon.vertex[0].uv.y, polygon.vertex[1].uv.y, polygon.vertex[2].uv.y};
+
+ float off_y;
+ float dxdy[3] = {0.0f, 0.0f, 0.0f};
+ float tmp;
+
+ auto upper = false;
+
+ //Sort the vertices in ascending Y order
+ if (y[0] > y[1]) {
+ _swap(x[0], x[1], tmp);
+ _swap(y[0], y[1], tmp);
+ _swap(u[0], u[1], tmp);
+ _swap(v[0], v[1], tmp);
+ }
+ if (y[0] > y[2]) {
+ _swap(x[0], x[2], tmp);
+ _swap(y[0], y[2], tmp);
+ _swap(u[0], u[2], tmp);
+ _swap(v[0], v[2], tmp);
+ }
+ if (y[1] > y[2]) {
+ _swap(x[1], x[2], tmp);
+ _swap(y[1], y[2], tmp);
+ _swap(u[1], u[2], tmp);
+ _swap(v[1], v[2], tmp);
+ }
+
+ //Y indexes
+ int yi[3] = {(int)y[0], (int)y[1], (int)y[2]};
+
+ //Skip drawing if it's too thin to cover any pixels at all.
+ if ((yi[0] == yi[1] && yi[0] == yi[2]) || ((int) x[0] == (int) x[1] && (int) x[0] == (int) x[2])) return;
+
+ //Calculate horizontal and vertical increments for UV axes (these calcs are certainly not optimal, although they're stable (handles any dy being 0)
+ auto denom = ((x[2] - x[0]) * (y[1] - y[0]) - (x[1] - x[0]) * (y[2] - y[0]));
+
+ //Skip poly if it's an infinitely thin line
+ if (mathZero(denom)) return;
+
+ denom = 1 / denom; //Reciprocal for speeding up
+ dudx = ((u[2] - u[0]) * (y[1] - y[0]) - (u[1] - u[0]) * (y[2] - y[0])) * denom;
+ dvdx = ((v[2] - v[0]) * (y[1] - y[0]) - (v[1] - v[0]) * (y[2] - y[0])) * denom;
+ auto dudy = ((u[1] - u[0]) * (x[2] - x[0]) - (u[2] - u[0]) * (x[1] - x[0])) * denom;
+ auto dvdy = ((v[1] - v[0]) * (x[2] - x[0]) - (v[2] - v[0]) * (x[1] - x[0])) * denom;
+
+ //Calculate X-slopes along the edges
+ if (y[1] > y[0]) dxdy[0] = (x[1] - x[0]) / (y[1] - y[0]);
+ if (y[2] > y[0]) dxdy[1] = (x[2] - x[0]) / (y[2] - y[0]);
+ if (y[2] > y[1]) dxdy[2] = (x[2] - x[1]) / (y[2] - y[1]);
+
+ //Determine which side of the polygon the longer edge is on
+ auto side = (dxdy[1] > dxdy[0]) ? true : false;
+
+ if (mathEqual(y[0], y[1])) side = x[0] > x[1];
+ if (mathEqual(y[1], y[2])) side = x[2] > x[1];
+
+ auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image?
+
+ //Longer edge is on the left side
+ if (!side) {
+ //Calculate slopes along left edge
+ dxdya = dxdy[1];
+ dudya = dxdya * dudx + dudy;
+ dvdya = dxdya * dvdx + dvdy;
+
+ //Perform subpixel pre-stepping along left edge
+ auto dy = 1.0f - (y[0] - yi[0]);
+ xa = x[0] + dy * dxdya;
+ ua = u[0] + dy * dudya;
+ va = v[0] + dy * dvdya;
+
+ //Draw upper segment if possibly visible
+ if (yi[0] < yi[1]) {
+ off_y = y[0] < regionTop ? (regionTop - y[0]) : 0;
+ xa += (off_y * dxdya);
+ ua += (off_y * dudya);
+ va += (off_y * dvdya);
+
+ // Set right edge X-slope and perform subpixel pre-stepping
+ dxdyb = dxdy[0];
+ xb = x[0] + dy * dxdyb + (off_y * dxdyb);
+
+ if (blendMethod) {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], blendMethod, aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, blendMethod, aaSpans);
+ } else {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, aaSpans);
+ }
+
+ upper = true;
+ }
+ //Draw lower segment if possibly visible
+ if (yi[1] < yi[2]) {
+ off_y = y[1] < regionTop ? (regionTop - y[1]) : 0;
+ if (!upper) {
+ xa += (off_y * dxdya);
+ ua += (off_y * dudya);
+ va += (off_y * dvdya);
+ }
+ // Set right edge X-slope and perform subpixel pre-stepping
+ dxdyb = dxdy[2];
+ xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb);
+ if (blendMethod) {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], blendMethod, aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, blendMethod, aaSpans);
+ } else {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, aaSpans);
+ }
+ }
+ //Longer edge is on the right side
+ } else {
+ //Set right edge X-slope and perform subpixel pre-stepping
+ dxdyb = dxdy[1];
+ auto dy = 1.0f - (y[0] - yi[0]);
+ xb = x[0] + dy * dxdyb;
+
+ //Draw upper segment if possibly visible
+ if (yi[0] < yi[1]) {
+ off_y = y[0] < regionTop ? (regionTop - y[0]) : 0;
+ xb += (off_y *dxdyb);
+
+ // Set slopes along left edge and perform subpixel pre-stepping
+ dxdya = dxdy[0];
+ dudya = dxdya * dudx + dudy;
+ dvdya = dxdya * dvdx + dvdy;
+
+ xa = x[0] + dy * dxdya + (off_y * dxdya);
+ ua = u[0] + dy * dudya + (off_y * dudya);
+ va = v[0] + dy * dvdya + (off_y * dvdya);
+
+ if (blendMethod) {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], blendMethod, aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, blendMethod, aaSpans);
+ } else {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, aaSpans);
+ }
+
+ upper = true;
+ }
+ //Draw lower segment if possibly visible
+ if (yi[1] < yi[2]) {
+ off_y = y[1] < regionTop ? (regionTop - y[1]) : 0;
+ if (!upper) xb += (off_y *dxdyb);
+
+ // Set slopes along left edge and perform subpixel pre-stepping
+ dxdya = dxdy[2];
+ dudya = dxdya * dudx + dudy;
+ dvdya = dxdya * dvdx + dvdy;
+ dy = 1 - (y[1] - yi[1]);
+ xa = x[1] + dy * dxdya + (off_y * dxdya);
+ ua = u[1] + dy * dudya + (off_y * dudya);
+ va = v[1] + dy * dvdya + (off_y * dvdya);
+
+ if (blendMethod) {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], blendMethod, aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, blendMethod, aaSpans);
+ } else {
+ if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans);
+ else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, aaSpans);
+ }
+ }
+ }
+}
+
+
+static AASpans* _AASpans(const Vertex* vertices, const SwImage* image, const SwBBox* region)
+{
+ //Initialize Y range
+ float ys = FLT_MAX, ye = -1.0f;
+
+ for (int i = 0; i < 4; i++) {
+ if (vertices[i].pt.y < ys) ys = vertices[i].pt.y;
+ if (vertices[i].pt.y > ye) ye = vertices[i].pt.y;
+ }
+
+ auto yStart = static_cast<int32_t>(ys);
+ auto yEnd = static_cast<int32_t>(ye);
+
+ if (!_arrange(image, region, yStart, yEnd)) return nullptr;
+
+ auto aaSpans = static_cast<AASpans*>(malloc(sizeof(AASpans)));
+ aaSpans->yStart = yStart;
+ aaSpans->yEnd = yEnd;
+
+ //Initialize X range
+ auto height = yEnd - yStart;
+
+ aaSpans->lines = static_cast<AALine*>(calloc(height, sizeof(AALine)));
+
+ for (int32_t i = 0; i < height; i++) {
+ aaSpans->lines[i].x[0] = INT32_MAX;
+ aaSpans->lines[i].x[1] = INT32_MIN;
+ }
+ return aaSpans;
+}
+
+
+static void _calcIrregularCoverage(AALine* lines, int32_t eidx, int32_t y, int32_t diagonal, int32_t edgeDist, bool reverse)
+{
+ if (eidx == 1) reverse = !reverse;
+ int32_t coverage = (255 / (diagonal + 2));
+ int32_t tmp;
+ for (int32_t ry = 0; ry < (diagonal + 2); ry++) {
+ tmp = y - ry - edgeDist;
+ if (tmp < 0) return;
+ lines[tmp].length[eidx] = 1;
+ if (reverse) lines[tmp].coverage[eidx] = 255 - (coverage * ry);
+ else lines[tmp].coverage[eidx] = (coverage * ry);
+ }
+}
+
+
+static void _calcVertCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t rewind, bool reverse)
+{
+ if (eidx == 1) reverse = !reverse;
+ int32_t coverage = (255 / (rewind + 1));
+ int32_t tmp;
+ for (int ry = 1; ry < (rewind + 1); ry++) {
+ tmp = y - ry;
+ if (tmp < 0) return;
+ lines[tmp].length[eidx] = 1;
+ if (reverse) lines[tmp].coverage[eidx] = (255 - (coverage * ry));
+ else lines[tmp].coverage[eidx] = (coverage * ry);
+ }
+}
+
+
+static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x, int32_t x2)
+{
+ if (lines[y].length[eidx] < abs(x - x2)) {
+ lines[y].length[eidx] = abs(x - x2);
+ lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
+ }
+}
+
+
+/*
+ * This Anti-Aliasing mechanism is originated from Hermet Park's idea.
+ * To understand this AA logic, you can refer this page:
+ * www.hermet.pe.kr/122 (hermetpark@gmail.com)
+*/
+static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
+{
+//Previous edge direction:
+#define DirOutHor 0x0011
+#define DirOutVer 0x0001
+#define DirInHor 0x0010
+#define DirInVer 0x0000
+#define DirNone 0x1000
+
+#define PUSH_VERTEX() \
+ do { \
+ pEdge.x = lines[y].x[eidx]; \
+ pEdge.y = y; \
+ ptx[0] = tx[0]; \
+ ptx[1] = tx[1]; \
+ } while (0)
+
+ int32_t y = 0;
+ SwPoint pEdge = {-1, -1}; //previous edge point
+ SwPoint edgeDiff = {0, 0}; //temporary used for point distance
+
+ /* store bigger to tx[0] between prev and current edge's x positions. */
+ int32_t tx[2] = {0, 0};
+ /* back up prev tx values */
+ int32_t ptx[2] = {0, 0};
+ int32_t diagonal = 0; //straight diagonal pixels count
+
+ auto yStart = aaSpans->yStart;
+ auto yEnd = aaSpans->yEnd;
+ auto lines = aaSpans->lines;
+
+ int32_t prevDir = DirNone;
+ int32_t curDir = DirNone;
+
+ yEnd -= yStart;
+
+ //Start Edge
+ if (y < yEnd) {
+ pEdge.x = lines[y].x[eidx];
+ pEdge.y = y;
+ }
+
+ //Calculates AA Edges
+ for (y++; y < yEnd; y++) {
+ //Ready tx
+ if (eidx == 0) {
+ tx[0] = pEdge.x;
+ tx[1] = lines[y].x[0];
+ } else {
+ tx[0] = lines[y].x[1];
+ tx[1] = pEdge.x;
+ }
+ edgeDiff.x = (tx[0] - tx[1]);
+ edgeDiff.y = (y - pEdge.y);
+
+ //Confirm current edge direction
+ if (edgeDiff.x > 0) {
+ if (edgeDiff.y == 1) curDir = DirOutHor;
+ else curDir = DirOutVer;
+ } else if (edgeDiff.x < 0) {
+ if (edgeDiff.y == 1) curDir = DirInHor;
+ else curDir = DirInVer;
+ } else curDir = DirNone;
+
+ //straight diagonal increase
+ if ((curDir == prevDir) && (y < yEnd)) {
+ if ((abs(edgeDiff.x) == 1) && (edgeDiff.y == 1)) {
+ ++diagonal;
+ PUSH_VERTEX();
+ continue;
+ }
+ }
+
+ switch (curDir) {
+ case DirOutHor: {
+ _calcHorizCoverage(lines, eidx, y, tx[0], tx[1]);
+ if (diagonal > 0) {
+ _calcIrregularCoverage(lines, eidx, y, diagonal, 0, true);
+ diagonal = 0;
+ }
+ /* Increment direction is changed: Outside Vertical -> Outside Horizontal */
+ if (prevDir == DirOutVer) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
+
+ //Trick, but fine-tunning!
+ if (y == 1) _calcHorizCoverage(lines, eidx, pEdge.y, tx[0], tx[1]);
+ PUSH_VERTEX();
+ }
+ break;
+ case DirOutVer: {
+ _calcVertCoverage(lines, eidx, y, edgeDiff.y, true);
+ if (diagonal > 0) {
+ _calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, false);
+ diagonal = 0;
+ }
+ /* Increment direction is changed: Outside Horizontal -> Outside Vertical */
+ if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
+ PUSH_VERTEX();
+ }
+ break;
+ case DirInHor: {
+ _calcHorizCoverage(lines, eidx, (y - 1), tx[0], tx[1]);
+ if (diagonal > 0) {
+ _calcIrregularCoverage(lines, eidx, y, diagonal, 0, false);
+ diagonal = 0;
+ }
+ /* Increment direction is changed: Outside Horizontal -> Inside Horizontal */
+ if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
+ PUSH_VERTEX();
+ }
+ break;
+ case DirInVer: {
+ _calcVertCoverage(lines, eidx, y, edgeDiff.y, false);
+ if (prevDir == DirOutHor) edgeDiff.y -= 1; //Weird, fine tuning?????????????????????
+ if (diagonal > 0) {
+ _calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, true);
+ diagonal = 0;
+ }
+ /* Increment direction is changed: Outside Horizontal -> Inside Vertical */
+ if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
+ PUSH_VERTEX();
+ }
+ break;
+ }
+ if (curDir != DirNone) prevDir = curDir;
+ }
+
+ //leftovers...?
+ if ((edgeDiff.y == 1) && (edgeDiff.x != 0)) {
+ if (y >= yEnd) y = (yEnd - 1);
+ _calcHorizCoverage(lines, eidx, y - 1, ptx[0], ptx[1]);
+ _calcHorizCoverage(lines, eidx, y, tx[0], tx[1]);
+ } else {
+ ++y;
+ if (y > yEnd) y = yEnd;
+ _calcVertCoverage(lines, eidx, y, (edgeDiff.y + 1), (prevDir & 0x00000001));
+ }
+}
+
+
+static bool _apply(SwSurface* surface, AASpans* aaSpans)
+{
+ auto y = aaSpans->yStart;
+ uint32_t pixel;
+ uint32_t* dst;
+ int32_t pos;
+
+ //left side
+ _calcAAEdge(aaSpans, 0);
+ //right side
+ _calcAAEdge(aaSpans, 1);
+
+ while (y < aaSpans->yEnd) {
+ auto line = &aaSpans->lines[y - aaSpans->yStart];
+ auto width = line->x[1] - line->x[0];
+ if (width > 0) {
+ auto offset = y * surface->stride;
+
+ //Left edge
+ dst = surface->buffer + (offset + line->x[0]);
+ if (line->x[0] > 1) pixel = *(dst - 1);
+ else pixel = *dst;
+
+ pos = 1;
+ while (pos <= line->length[0]) {
+ *dst = INTERPOLATE((line->coverage[0] * pos), *dst, pixel);
+ ++dst;
+ ++pos;
+ }
+
+ //Right edge
+ dst = surface->buffer + (offset + line->x[1] - 1);
+ if (line->x[1] < (int32_t)(surface->w - 1)) pixel = *(dst + 1);
+ else pixel = *dst;
+
+ pos = width;
+ while ((int32_t)(width - line->length[1]) < pos) {
+ *dst = INTERPOLATE(255 - (line->coverage[1] * (line->length[1] - (width - pos))), *dst, pixel);
+ --dst;
+ --pos;
+ }
+ }
+ y++;
+ }
+
+ free(aaSpans->lines);
+ free(aaSpans);
+
+ return true;
+}
+
+
+/*
+ 2 triangles constructs 1 mesh.
+ below figure illustrates vert[4] index info.
+ If you need better quality, please divide a mesh by more number of triangles.
+
+ 0 -- 1
+ | / |
+ | / |
+ 3 -- 2
+*/
+static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint32_t opacity, uint32_t (*blendMethod)(uint32_t))
+{
+ //Exceptions: No dedicated drawing area?
+ if (!region && image->rle->size == 0) return false;
+
+ /* Prepare vertices.
+ shift XY coordinates to match the sub-pixeling technique. */
+ Vertex vertices[4];
+ vertices[0] = {{0.0f, 0.0f}, {0.0f, 0.0f}};
+ vertices[1] = {{float(image->w), 0.0f}, {float(image->w), 0.0f}};
+ vertices[2] = {{float(image->w), float(image->h)}, {float(image->w), float(image->h)}};
+ vertices[3] = {{0.0f, float(image->h)}, {0.0f, float(image->h)}};
+
+ for (int i = 0; i < 4; i++) mathMultiply(&vertices[i].pt, transform);
+
+ auto aaSpans = _AASpans(vertices, image, region);
+ if (!aaSpans) return true;
+
+ Polygon polygon;
+
+ //Draw the first polygon
+ polygon.vertex[0] = vertices[0];
+ polygon.vertex[1] = vertices[1];
+ polygon.vertex[2] = vertices[3];
+
+ _rasterPolygonImage(surface, image, region, opacity, polygon, blendMethod, aaSpans);
+
+ //Draw the second polygon
+ polygon.vertex[0] = vertices[1];
+ polygon.vertex[1] = vertices[2];
+ polygon.vertex[2] = vertices[3];
+
+ _rasterPolygonImage(surface, image, region, opacity, polygon, blendMethod, aaSpans);
+
+ return _apply(surface, aaSpans);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h
new file mode 100644
index 0000000000..4e8d342137
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+{
+ 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->data;
+ auto dbuf = surface->buffer;
+ int32_t sw = static_cast<int32_t>(image->stride);
+ int32_t sh = image->h;
+ int32_t dw = surface->stride;
+ int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
+ int32_t vv = 0, uu = 0;
+ int32_t minx, maxx;
+ float dx, u, v, iptr;
+ uint32_t* buf;
+ SwSpan* span = nullptr; //used only when rle based.
+
+#ifdef TEXMAP_MASKING
+ uint32_t* cmp;
+#endif
+
+ if (!_arrange(image, region, yStart, yEnd)) return;
+
+ //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 = _xa;
+ x2 = _xb;
+
+ if (!region) {
+ minx = INT32_MAX;
+ maxx = INT32_MIN;
+ //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 exception
+ if ((x2 - x1) < 1 || (x1 >= maxx) || (x2 <= minx)) goto next;
+
+ //Perform subtexel pre-stepping on UV
+ dx = 1 - (_xa - x1);
+ u = _ua + dx * _dudx;
+ v = _va + dx * _dvdx;
+
+ buf = dbuf + ((y * dw) + x1);
+
+ x = x1;
+
+#ifdef TEXMAP_MASKING
+ cmp = &surface->compositor->image.data[y * surface->compositor->image.stride + x1];
+#endif
+ //Draw horizontal line
+ while (x++ < x2) {
+ uu = (int) u;
+ vv = (int) v;
+
+ 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(ar, px, px2);
+ }
+ /* 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(ar, px2, px3);
+ }
+ px = INTERPOLATE(ab, px, px2);
+ }
+#if defined(TEXMAP_MASKING) && defined(TEXMAP_TRANSLUCENT)
+ auto src = ALPHA_BLEND(px, _multiplyAlpha(opacity, blendMethod(*cmp)));
+#elif defined(TEXMAP_MASKING)
+ auto src = ALPHA_BLEND(px, blendMethod(*cmp));
+#elif defined(TEXMAP_TRANSLUCENT)
+ auto src = ALPHA_BLEND(px, opacity);
+#else
+ auto src = px;
+#endif
+ *buf = src + ALPHA_BLEND(*buf, _ialpha(src));
+ ++buf;
+#ifdef TEXMAP_MASKING
+ ++cmp;
+#endif
+ //Step UV horizontally
+ u += _dudx;
+ v += _dvdx;
+ //range over?
+ if ((uint32_t)v >= image->h) break;
+ }
+next:
+ //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;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp
new file mode 100644
index 0000000000..78537e7726
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp
@@ -0,0 +1,685 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <math.h>
+#include "tvgSwCommon.h"
+#include "tvgTaskScheduler.h"
+#include "tvgSwRenderer.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+static int32_t initEngineCnt = false;
+static int32_t rendererCnt = 0;
+static SwMpool* globalMpool = nullptr;
+static uint32_t threadsCnt = 0;
+
+struct SwTask : Task
+{
+ Matrix* transform = nullptr;
+ SwSurface* surface = nullptr;
+ SwMpool* mpool = nullptr;
+ RenderUpdateFlag flags = RenderUpdateFlag::None;
+ Array<RenderData> clips;
+ uint32_t opacity;
+ SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region
+ bool pushed = false; //Pushed into task list?
+ bool disposed = false; //Disposed task?
+
+ RenderRegion bounds() const
+ {
+ RenderRegion region;
+
+ //Range over?
+ region.x = bbox.min.x > 0 ? bbox.min.x : 0;
+ region.y = bbox.min.y > 0 ? bbox.min.y : 0;
+ region.w = bbox.max.x - region.x;
+ region.h = bbox.max.y - region.y;
+ if (region.w < 0) region.w = 0;
+ if (region.h < 0) region.h = 0;
+
+ return region;
+ }
+
+ virtual bool dispose() = 0;
+
+ virtual ~SwTask()
+ {
+ free(transform);
+ }
+};
+
+
+struct SwShapeTask : SwTask
+{
+ SwShape shape;
+ const Shape* sdata = nullptr;
+ bool cmpStroking = false;
+
+ void run(unsigned tid) override
+ {
+ if (opacity == 0) return; //Invisible
+
+ uint8_t strokeAlpha = 0;
+ auto visibleStroke = false;
+ bool visibleFill = false;
+ auto clipRegion = bbox;
+
+ if (HALF_STROKE(sdata->strokeWidth()) > 0) {
+ sdata->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha);
+ visibleStroke = sdata->strokeFill() || (static_cast<uint32_t>(strokeAlpha * opacity / 255) > 0);
+ }
+
+ //This checks also for the case, if the invisible shape turned to visible by alpha.
+ auto prepareShape = false;
+ if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true;
+
+ //Shape
+ if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
+ uint8_t alpha = 0;
+ sdata->fillColor(nullptr, nullptr, nullptr, &alpha);
+ alpha = static_cast<uint8_t>(static_cast<uint32_t>(alpha) * opacity / 255);
+ visibleFill = (alpha > 0 || sdata->fill());
+ if (visibleFill || visibleStroke) {
+ shapeReset(&shape);
+ if (!shapePrepare(&shape, sdata, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) goto err;
+ }
+ }
+
+ //Decide Stroking Composition
+ if (visibleStroke && visibleFill && opacity < 255) cmpStroking = true;
+ else cmpStroking = false;
+
+ //Fill
+ if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
+ if (visibleFill) {
+ /* We assume that if stroke width is bigger than 2,
+ shape outline below stroke could be full covered by stroke drawing.
+ Thus it turns off antialising in that condition.
+ Also, it shouldn't be dash style. */
+ auto antiAlias = (strokeAlpha == 255 && sdata->strokeWidth() > 2 && sdata->strokeDash(nullptr) == 0) ? false : true;
+
+ if (!shapeGenRle(&shape, sdata, antiAlias)) goto err;
+ }
+ if (auto fill = sdata->fill()) {
+ auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false;
+ if (ctable) shapeResetFill(&shape);
+ if (!shapeGenFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err;
+ } else {
+ shapeDelFill(&shape);
+ }
+ }
+
+ //Stroke
+ if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
+ if (visibleStroke) {
+ shapeResetStroke(&shape, sdata, transform);
+ if (!shapeGenStrokeRle(&shape, sdata, transform, clipRegion, bbox, mpool, tid)) goto err;
+
+ if (auto fill = sdata->strokeFill()) {
+ auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
+ if (ctable) shapeResetStrokeFill(&shape);
+ if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err;
+ } else {
+ shapeDelStrokeFill(&shape);
+ }
+ } else {
+ shapeDelStroke(&shape);
+ }
+ }
+
+ //Clip Path
+ for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
+ auto clipper = &static_cast<SwShapeTask*>(*clip)->shape;
+ //Clip shape rle
+ if (shape.rle) {
+ if (clipper->fastTrack) rleClipRect(shape.rle, &clipper->bbox);
+ else if (clipper->rle) rleClipPath(shape.rle, clipper->rle);
+ else goto err;
+ }
+ //Clip stroke rle
+ if (shape.strokeRle) {
+ if (clipper->fastTrack) rleClipRect(shape.strokeRle, &clipper->bbox);
+ else if (clipper->rle) rleClipPath(shape.strokeRle, clipper->rle);
+ else goto err;
+ }
+ }
+ goto end;
+
+ err:
+ shapeReset(&shape);
+ end:
+ shapeDelOutline(&shape, mpool, tid);
+ }
+
+ bool dispose() override
+ {
+ shapeFree(&shape);
+ return true;
+ }
+};
+
+
+struct SwImageTask : SwTask
+{
+ SwImage image;
+
+ void run(unsigned tid) override
+ {
+ auto clipRegion = bbox;
+
+ //Invisible shape turned to visible by alpha.
+ if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) {
+ imageReset(&image);
+ if (!image.data || image.w == 0 || image.h == 0) goto end;
+
+ if (!imagePrepare(&image, transform, clipRegion, bbox, mpool, tid)) goto end;
+
+ if (clips.count > 0) {
+ if (!imageGenRle(&image, bbox, false)) goto end;
+ if (image.rle) {
+ for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
+ auto clipper = &static_cast<SwShapeTask*>(*clip)->shape;
+ if (clipper->fastTrack) rleClipRect(image.rle, &clipper->bbox);
+ else if (clipper->rle) rleClipPath(image.rle, clipper->rle);
+ else goto err;
+ }
+ }
+ }
+ }
+ goto end;
+
+ err:
+ rleReset(image.rle);
+ end:
+ imageDelOutline(&image, mpool, tid);
+ }
+
+ bool dispose() override
+ {
+ imageFree(&image);
+ return true;
+ }
+};
+
+
+static void _termEngine()
+{
+ if (rendererCnt > 0) return;
+
+ mpoolTerm(globalMpool);
+ globalMpool = nullptr;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+SwRenderer::~SwRenderer()
+{
+ clearCompositors();
+
+ if (surface) delete(surface);
+
+ if (!sharedMpool) mpoolTerm(mpool);
+
+ --rendererCnt;
+
+ if (rendererCnt == 0 && initEngineCnt == 0) _termEngine();
+}
+
+
+bool SwRenderer::clear()
+{
+ for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) {
+ if ((*task)->disposed) {
+ delete(*task);
+ } else {
+ (*task)->done();
+ (*task)->pushed = false;
+ }
+ }
+ tasks.clear();
+
+ if (!sharedMpool) mpoolClear(mpool);
+
+ if (surface) {
+ vport.x = vport.y = 0;
+ vport.w = surface->w;
+ vport.h = surface->h;
+ }
+
+ return true;
+}
+
+
+bool SwRenderer::sync()
+{
+ return true;
+}
+
+
+RenderRegion SwRenderer::viewport()
+{
+ return vport;
+}
+
+
+bool SwRenderer::viewport(const RenderRegion& vp)
+{
+ vport = vp;
+ return true;
+}
+
+
+bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs)
+{
+ if (!buffer || stride == 0 || w == 0 || h == 0 || w > stride) return false;
+
+ if (!surface) surface = new SwSurface;
+
+ surface->buffer = buffer;
+ surface->stride = stride;
+ surface->w = w;
+ surface->h = h;
+ surface->cs = cs;
+
+ vport.x = vport.y = 0;
+ vport.w = surface->w;
+ vport.h = surface->h;
+
+ return rasterCompositor(surface);
+}
+
+
+bool SwRenderer::preRender()
+{
+ return rasterClear(surface);
+}
+
+void SwRenderer::clearCompositors()
+{
+ //Free Composite Caches
+ for (auto comp = compositors.data; comp < (compositors.data + compositors.count); ++comp) {
+ free((*comp)->compositor->image.data);
+ delete((*comp)->compositor);
+ delete(*comp);
+ }
+ compositors.reset();
+}
+
+
+bool SwRenderer::postRender()
+{
+ //Unmultiply alpha if needed
+ if (surface->cs == SwCanvas::ABGR8888_STRAIGHT || surface->cs == SwCanvas::ARGB8888_STRAIGHT) {
+ rasterUnpremultiply(surface);
+ }
+
+ for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) {
+ (*task)->pushed = false;
+ }
+ tasks.clear();
+
+ clearCompositors();
+ return true;
+}
+
+
+bool SwRenderer::renderImage(RenderData data)
+{
+ auto task = static_cast<SwImageTask*>(data);
+ task->done();
+
+ if (task->opacity == 0) return true;
+
+ return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity);
+}
+
+
+bool SwRenderer::renderShape(RenderData data)
+{
+ auto task = static_cast<SwShapeTask*>(data);
+ if (!task) return false;
+
+ task->done();
+
+ if (task->opacity == 0) return true;
+
+ uint32_t opacity;
+ Compositor* cmp = nullptr;
+
+ //Do Stroking Composition
+ if (task->cmpStroking) {
+ opacity = 255;
+ cmp = target(task->bounds());
+ beginComposite(cmp, CompositeMethod::None, task->opacity);
+ //No Stroking Composition
+ } else {
+ opacity = task->opacity;
+ }
+
+ //Main raster stage
+ uint8_t r, g, b, a;
+
+ if (auto fill = task->sdata->fill()) {
+ rasterGradientShape(surface, &task->shape, fill->identifier());
+ } else {
+ task->sdata->fillColor(&r, &g, &b, &a);
+ a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
+ if (a > 0) rasterShape(surface, &task->shape, r, g, b, a);
+ }
+
+ if (auto strokeFill = task->sdata->strokeFill()) {
+ rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
+ } else {
+ if (task->sdata->strokeColor(&r, &g, &b, &a) == Result::Success) {
+ a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
+ if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
+ }
+ }
+
+ if (task->cmpStroking) endComposite(cmp);
+
+ return true;
+}
+
+
+RenderRegion SwRenderer::region(RenderData data)
+{
+ return static_cast<SwTask*>(data)->bounds();
+}
+
+
+bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity)
+{
+ if (!cmp) return false;
+ auto p = static_cast<SwCompositor*>(cmp);
+
+ p->method = method;
+ p->opacity = opacity;
+
+ //Current Context?
+ if (p->method != CompositeMethod::None) {
+ surface = p->recoverSfc;
+ surface->compositor = p;
+ }
+
+ return true;
+}
+
+
+bool SwRenderer::mempool(bool shared)
+{
+ if (shared == sharedMpool) return true;
+
+ if (shared) {
+ if (!sharedMpool) {
+ if (!mpoolTerm(mpool)) return false;
+ mpool = globalMpool;
+ }
+ } else {
+ if (sharedMpool) mpool = mpoolInit(threadsCnt);
+ }
+
+ sharedMpool = shared;
+
+ if (mpool) return true;
+ return false;
+}
+
+
+Compositor* SwRenderer::target(const RenderRegion& region)
+{
+ auto x = region.x;
+ auto y = region.y;
+ auto w = region.w;
+ auto h = region.h;
+ auto sw = static_cast<int32_t>(surface->w);
+ auto sh = static_cast<int32_t>(surface->h);
+
+ //Out of boundary
+ if (x > sw || y > sh) return nullptr;
+
+ SwSurface* cmp = nullptr;
+
+ //Use cached data
+ for (auto p = compositors.data; p < (compositors.data + compositors.count); ++p) {
+ if ((*p)->compositor->valid) {
+ cmp = *p;
+ break;
+ }
+ }
+
+ //New Composition
+ if (!cmp) {
+ cmp = new SwSurface;
+ if (!cmp) goto err;
+
+ //Inherits attributes from main surface
+ *cmp = *surface;
+
+ cmp->compositor = new SwCompositor;
+ if (!cmp->compositor) goto err;
+
+ //SwImage, Optimize Me: Surface size from MainSurface(WxH) to Parameter W x H
+ cmp->compositor->image.data = (uint32_t*) malloc(sizeof(uint32_t) * surface->stride * surface->h);
+ if (!cmp->compositor->image.data) goto err;
+ compositors.push(cmp);
+ }
+
+ //Boundary Check
+ if (x + w > sw) w = (sw - x);
+ if (y + h > sh) h = (sh - y);
+
+ TVGLOG("SW_ENGINE", "Using intermediate composition [Region: %d %d %d %d]", x, y, w, h);
+
+ cmp->compositor->recoverSfc = surface;
+ cmp->compositor->recoverCmp = surface->compositor;
+ cmp->compositor->valid = false;
+ cmp->compositor->bbox.min.x = x;
+ cmp->compositor->bbox.min.y = y;
+ cmp->compositor->bbox.max.x = x + w;
+ cmp->compositor->bbox.max.y = y + h;
+ cmp->compositor->image.stride = surface->stride;
+ cmp->compositor->image.w = surface->w;
+ cmp->compositor->image.h = surface->h;
+ cmp->compositor->image.direct = true;
+
+ //We know partial clear region
+ cmp->buffer = cmp->compositor->image.data + (cmp->stride * y + x);
+ cmp->w = w;
+ cmp->h = h;
+
+ rasterClear(cmp);
+
+ //Recover context
+ cmp->buffer = cmp->compositor->image.data;
+ cmp->w = cmp->compositor->image.w;
+ cmp->h = cmp->compositor->image.h;
+
+ //Switch render target
+ surface = cmp;
+
+ return cmp->compositor;
+
+err:
+ if (cmp) {
+ if (cmp->compositor) delete(cmp->compositor);
+ delete(cmp);
+ }
+
+ return nullptr;
+}
+
+
+bool SwRenderer::endComposite(Compositor* cmp)
+{
+ if (!cmp) return false;
+
+ auto p = static_cast<SwCompositor*>(cmp);
+ p->valid = true;
+
+ //Recover Context
+ surface = p->recoverSfc;
+ surface->compositor = p->recoverCmp;
+
+ //Default is alpha blending
+ if (p->method == CompositeMethod::None) {
+ return rasterImage(surface, &p->image, nullptr, p->bbox, p->opacity);
+ }
+
+ return true;
+}
+
+
+bool SwRenderer::dispose(RenderData data)
+{
+ auto task = static_cast<SwTask*>(data);
+ if (!task) return true;
+ task->done();
+ task->dispose();
+
+ if (task->pushed) task->disposed = true;
+ else delete(task);
+
+ return true;
+}
+
+
+void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array<RenderData>& clips, RenderUpdateFlag flags)
+{
+ if (!surface) return task;
+ if (flags == RenderUpdateFlag::None) return task;
+
+ //Finish previous task if it has duplicated request.
+ task->done();
+
+ if (clips.count > 0) {
+ //Guarantee composition targets get ready.
+ for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
+ static_cast<SwShapeTask*>(*clip)->done();
+ }
+ task->clips = clips;
+ }
+
+ if (transform) {
+ if (!task->transform) task->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
+ *task->transform = transform->m;
+ } else {
+ if (task->transform) free(task->transform);
+ task->transform = nullptr;
+ }
+
+ task->opacity = opacity;
+ task->surface = surface;
+ task->mpool = mpool;
+ task->flags = flags;
+ task->bbox.min.x = max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
+ task->bbox.min.y = max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
+ task->bbox.max.x = min(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
+ task->bbox.max.y = min(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
+
+ if (!task->pushed) {
+ task->pushed = true;
+ tasks.push(task);
+ }
+
+ TaskScheduler::request(task);
+
+ return task;
+}
+
+
+RenderData SwRenderer::prepare(Surface* image, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags)
+{
+ //prepare task
+ auto task = static_cast<SwImageTask*>(data);
+ if (!task) {
+ task = new SwImageTask;
+ if (flags & RenderUpdateFlag::Image) {
+ task->image.data = image->buffer;
+ task->image.w = image->w;
+ task->image.h = image->h;
+ task->image.stride = image->stride;
+ }
+ }
+ return prepareCommon(task, transform, opacity, clips, flags);
+}
+
+
+RenderData SwRenderer::prepare(const Shape& sdata, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags)
+{
+ //prepare task
+ auto task = static_cast<SwShapeTask*>(data);
+ if (!task) {
+ task = new SwShapeTask;
+ task->sdata = &sdata;
+ }
+ return prepareCommon(task, transform, opacity, clips, flags);
+}
+
+
+SwRenderer::SwRenderer():mpool(globalMpool)
+{
+}
+
+
+bool SwRenderer::init(uint32_t threads)
+{
+ if ((initEngineCnt++) > 0) return true;
+
+ threadsCnt = threads;
+
+ //Share the memory pool among the renderer
+ globalMpool = mpoolInit(threads);
+ if (!globalMpool) {
+ --initEngineCnt;
+ return false;
+ }
+
+ return true;
+}
+
+
+int32_t SwRenderer::init()
+{
+ return initEngineCnt;
+}
+
+
+bool SwRenderer::term()
+{
+ if ((--initEngineCnt) > 0) return true;
+
+ initEngineCnt = 0;
+
+ _termEngine();
+
+ return true;
+}
+
+SwRenderer* SwRenderer::gen()
+{
+ ++rendererCnt;
+ return new SwRenderer();
+}
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h
new file mode 100644
index 0000000000..3f883ac409
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SW_RENDERER_H_
+#define _TVG_SW_RENDERER_H_
+
+#include "tvgRender.h"
+
+struct SwSurface;
+struct SwTask;
+struct SwCompositor;
+struct SwMpool;
+
+namespace tvg
+{
+
+class SwRenderer : public RenderMethod
+{
+public:
+ RenderData prepare(const Shape& shape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) override;
+ RenderData prepare(Surface* image, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) override;
+ bool preRender() override;
+ bool renderShape(RenderData data) override;
+ bool renderImage(RenderData data) override;
+ bool postRender() override;
+ bool dispose(RenderData data) override;
+ RenderRegion region(RenderData data) override;
+ RenderRegion viewport() override;
+ bool viewport(const RenderRegion& vp) override;
+
+ bool clear() override;
+ bool sync() override;
+ bool target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs);
+ bool mempool(bool shared);
+
+ Compositor* target(const RenderRegion& region) override;
+ bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) override;
+ bool endComposite(Compositor* cmp) override;
+ void clearCompositors();
+
+ static SwRenderer* gen();
+ static bool init(uint32_t threads);
+ static int32_t init();
+ static bool term();
+
+private:
+ SwSurface* surface = nullptr; //active surface
+ Array<SwTask*> tasks; //async task list
+ Array<SwSurface*> compositors; //render targets cache list
+ SwMpool* mpool; //private memory pool
+ RenderRegion vport; //viewport
+
+ bool sharedMpool = true; //memory-pool behavior policy
+
+ SwRenderer();
+ ~SwRenderer();
+
+ RenderData prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array<RenderData>& clips, RenderUpdateFlag flags);
+};
+
+}
+
+#endif /* _TVG_SW_RENDERER_H_ */
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp
new file mode 100644
index 0000000000..b41e48b7ca
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp
@@ -0,0 +1,1043 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * The FreeType Project LICENSE
+ * ----------------------------
+
+ * 2006-Jan-27
+
+ * Copyright 1996-2002, 2006 by
+ * David Turner, Robert Wilhelm, and Werner Lemberg
+
+
+
+ * Introduction
+ * ============
+
+ * The FreeType Project is distributed in several archive packages;
+ * some of them may contain, in addition to the FreeType font engine,
+ * various tools and contributions which rely on, or relate to, the
+ * FreeType Project.
+
+ * This license applies to all files found in such packages, and
+ * which do not fall under their own explicit license. The license
+ * affects thus the FreeType font engine, the test programs,
+ * documentation and makefiles, at the very least.
+
+ * This license was inspired by the BSD, Artistic, and IJG
+ * (Independent JPEG Group) licenses, which all encourage inclusion
+ * and use of free software in commercial and freeware products
+ * alike. As a consequence, its main points are that:
+
+ * o We don't promise that this software works. However, we will be
+ * interested in any kind of bug reports. (`as is' distribution)
+
+ * o You can use this software for whatever you want, in parts or
+ * full form, without having to pay us. (`royalty-free' usage)
+
+ * o You may not pretend that you wrote this software. If you use
+ * it, or only parts of it, in a program, you must acknowledge
+ * somewhere in your documentation that you have used the
+ * FreeType code. (`credits')
+
+ * We specifically permit and encourage the inclusion of this
+ * software, with or without modifications, in commercial products.
+ * We disclaim all warranties covering The FreeType Project and
+ * assume no liability related to The FreeType Project.
+
+
+ * Finally, many people asked us for a preferred form for a
+ * credit/disclaimer to use in compliance with this license. We thus
+ * encourage you to use the following text:
+
+ * """
+ * Portions of this software are copyright � <year> The FreeType
+ * Project (www.freetype.org). All rights reserved.
+ * """
+
+ * Please replace <year> with the value from the FreeType version you
+ * actually use.
+
+* Legal Terms
+* ===========
+
+* 0. Definitions
+* --------------
+
+* Throughout this license, the terms `package', `FreeType Project',
+* and `FreeType archive' refer to the set of files originally
+* distributed by the authors (David Turner, Robert Wilhelm, and
+* Werner Lemberg) as the `FreeType Project', be they named as alpha,
+* beta or final release.
+
+* `You' refers to the licensee, or person using the project, where
+* `using' is a generic term including compiling the project's source
+* code as well as linking it to form a `program' or `executable'.
+* This program is referred to as `a program using the FreeType
+* engine'.
+
+* This license applies to all files distributed in the original
+* FreeType Project, including all source code, binaries and
+* documentation, unless otherwise stated in the file in its
+* original, unmodified form as distributed in the original archive.
+* If you are unsure whether or not a particular file is covered by
+* this license, you must contact us to verify this.
+
+* The FreeType Project is copyright (C) 1996-2000 by David Turner,
+* Robert Wilhelm, and Werner Lemberg. All rights reserved except as
+* specified below.
+
+* 1. No Warranty
+* --------------
+
+* THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
+* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+* PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
+* USE, OF THE FREETYPE PROJECT.
+
+* 2. Redistribution
+* -----------------
+
+* This license grants a worldwide, royalty-free, perpetual and
+* irrevocable right and license to use, execute, perform, compile,
+* display, copy, create derivative works of, distribute and
+* sublicense the FreeType Project (in both source and object code
+* forms) and derivative works thereof for any purpose; and to
+* authorize others to exercise some or all of the rights granted
+* herein, subject to the following conditions:
+
+* o Redistribution of source code must retain this license file
+* (`FTL.TXT') unaltered; any additions, deletions or changes to
+* the original files must be clearly indicated in accompanying
+* documentation. The copyright notices of the unaltered,
+* original files must be preserved in all copies of source
+* files.
+
+* o Redistribution in binary form must provide a disclaimer that
+* states that the software is based in part of the work of the
+* FreeType Team, in the distribution documentation. We also
+* encourage you to put an URL to the FreeType web page in your
+* documentation, though this isn't mandatory.
+
+* These conditions apply to any software derived from or based on
+* the FreeType Project, not just the unmodified files. If you use
+* our work, you must acknowledge us. However, no fee need be paid
+* to us.
+
+* 3. Advertising
+* --------------
+
+* Neither the FreeType authors and contributors nor you shall use
+* the name of the other for commercial, advertising, or promotional
+* purposes without specific prior written permission.
+
+* We suggest, but do not require, that you use one or more of the
+* following phrases to refer to this software in your documentation
+* or advertising materials: `FreeType Project', `FreeType Engine',
+* `FreeType library', or `FreeType Distribution'.
+
+* As you have not signed this license, you are not required to
+* accept it. However, as the FreeType Project is copyrighted
+* material, only this license, or another one contracted with the
+* authors, grants you the right to use, distribute, and modify it.
+* Therefore, by using, distributing, or modifying the FreeType
+* Project, you indicate that you understand and accept all the terms
+* of this license.
+
+* 4. Contacts
+* -----------
+
+* There are two mailing lists related to FreeType:
+
+* o freetype@nongnu.org
+
+* Discusses general use and applications of FreeType, as well as
+* future and wanted additions to the library and distribution.
+* If you are looking for support, start in this list if you
+* haven't found anything to help you in the documentation.
+
+* o freetype-devel@nongnu.org
+
+* Discusses bugs, as well as engine internals, design issues,
+* specific licenses, porting, etc.
+
+* Our home page can be found at
+
+* http://www.freetype.org
+*/
+
+#include <setjmp.h>
+#include <limits.h>
+#include <memory.h>
+#include "tvgSwCommon.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+constexpr auto MAX_SPANS = 256;
+constexpr auto PIXEL_BITS = 8; //must be at least 6 bits!
+constexpr auto ONE_PIXEL = (1L << PIXEL_BITS);
+
+using Area = long;
+
+struct Band
+{
+ SwCoord min, max;
+};
+
+struct Cell
+{
+ SwCoord x;
+ SwCoord cover;
+ Area area;
+ Cell *next;
+};
+
+struct RleWorker
+{
+ SwRleData* rle;
+
+ SwPoint cellPos;
+ SwPoint cellMin;
+ SwPoint cellMax;
+ SwCoord cellXCnt;
+ SwCoord cellYCnt;
+
+ Area area;
+ SwCoord cover;
+
+ Cell* cells;
+ ptrdiff_t maxCells;
+ ptrdiff_t cellsCnt;
+
+ SwPoint pos;
+
+ SwPoint bezStack[32 * 3 + 1];
+ int levStack[32];
+
+ SwOutline* outline;
+
+ SwSpan spans[MAX_SPANS];
+ int spansCnt;
+ int ySpan;
+
+ int bandSize;
+ int bandShoot;
+
+ jmp_buf jmpBuf;
+
+ void* buffer;
+ long bufferSize;
+
+ Cell** yCells;
+ SwCoord yCnt;
+
+ bool invalid;
+ bool antiAlias;
+};
+
+
+static inline SwPoint UPSCALE(const SwPoint& pt)
+{
+ return {SwCoord(((unsigned long) pt.x) << (PIXEL_BITS - 6)), SwCoord(((unsigned long) pt.y) << (PIXEL_BITS - 6))};
+}
+
+
+static inline SwPoint TRUNC(const SwPoint& pt)
+{
+ return {pt.x >> PIXEL_BITS, pt.y >> PIXEL_BITS};
+}
+
+
+static inline SwCoord TRUNC(const SwCoord x)
+{
+ return x >> PIXEL_BITS;
+}
+
+
+static inline SwPoint SUBPIXELS(const SwPoint& pt)
+{
+ return {SwCoord(((unsigned long) pt.x) << PIXEL_BITS), SwCoord(((unsigned long) pt.y) << PIXEL_BITS)};
+}
+
+
+static inline SwCoord SUBPIXELS(const SwCoord x)
+{
+ return SwCoord(((unsigned long) x) << PIXEL_BITS);
+}
+
+/*
+ * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min'
+ * algorithm. We use alpha = 1, beta = 3/8, giving us results with a
+ * largest error less than 7% compared to the exact value.
+ */
+static inline SwCoord HYPOT(SwPoint pt)
+{
+ if (pt.x < 0) pt.x = -pt.x;
+ if (pt.y < 0) pt.y = -pt.y;
+ return ((pt.x > pt.y) ? (pt.x + (3 * pt.y >> 3)) : (pt.y + (3 * pt.x >> 3)));
+}
+
+static void _genSpan(SwRleData* rle, const SwSpan* spans, uint32_t count)
+{
+ auto newSize = rle->size + count;
+
+ /* allocate enough memory for new spans */
+ /* alloc is required to prevent free and reallocation */
+ /* when the rle needs to be regenerated because of attribute change. */
+ if (rle->alloc < newSize) {
+ rle->alloc = (newSize * 2);
+ //OPTIMIZE: use mempool!
+ rle->spans = static_cast<SwSpan*>(realloc(rle->spans, rle->alloc * sizeof(SwSpan)));
+ }
+
+ //copy the new spans to the allocated memory
+ SwSpan* lastSpan = rle->spans + rle->size;
+ memcpy(lastSpan, spans, count * sizeof(SwSpan));
+
+ rle->size = newSize;
+}
+
+
+static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord acount)
+{
+ x += rw.cellMin.x;
+ y += rw.cellMin.y;
+
+ //Clip Y range
+ if (y < rw.cellMin.y || y >= rw.cellMax.y) return;
+
+ /* compute the coverage line's coverage, depending on the outline fill rule */
+ /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */
+ auto coverage = static_cast<int>(area >> (PIXEL_BITS * 2 + 1 - 8)); //range 0 - 255
+
+ if (coverage < 0) coverage = -coverage;
+
+ if (rw.outline->fillRule == FillRule::EvenOdd) {
+ coverage &= 511;
+ if (coverage > 255) coverage = 511 - coverage;
+ } else {
+ //normal non-zero winding rule
+ if (coverage > 255) coverage = 255;
+ }
+
+ //span has ushort coordinates. check limit overflow
+ if (x >= SHRT_MAX) {
+ TVGERR("SW_ENGINE", "X-coordiante overflow!");
+ x = SHRT_MAX;
+ }
+ if (y >= SHRT_MAX) {
+ TVGERR("SW_ENGINE", "Y Coordiante overflow!");
+ y = SHRT_MAX;
+ }
+
+ if (coverage > 0) {
+ if (!rw.antiAlias) coverage = 255;
+ auto count = rw.spansCnt;
+ auto span = rw.spans + count - 1;
+
+ //see whether we can add this span to the current list
+ if ((count > 0) && (rw.ySpan == y) &&
+ (span->x + span->len == x) && (span->coverage == coverage)) {
+
+ //Clip x range
+ SwCoord xOver = 0;
+ if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x);
+ if (x < rw.cellMin.x) xOver -= (rw.cellMin.x - x);
+
+ //span->len += (acount + xOver) - 1;
+ span->len += (acount + xOver);
+ return;
+ }
+
+ if (count >= MAX_SPANS) {
+ _genSpan(rw.rle, rw.spans, count);
+ rw.spansCnt = 0;
+ rw.ySpan = 0;
+ span = rw.spans;
+ } else {
+ ++span;
+ }
+
+ //Clip x range
+ SwCoord xOver = 0;
+ if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x);
+ if (x < rw.cellMin.x) {
+ xOver -= (rw.cellMin.x - x);
+ x = rw.cellMin.x;
+ }
+
+ //Nothing to draw
+ if (acount + xOver <= 0) return;
+
+ //add a span to the current list
+ span->x = x;
+ span->y = y;
+ span->len = (acount + xOver);
+ span->coverage = coverage;
+ ++rw.spansCnt;
+ rw.ySpan = y;
+ }
+}
+
+
+static void _sweep(RleWorker& rw)
+{
+ if (rw.cellsCnt == 0) return;
+
+ rw.spansCnt = 0;
+ rw.ySpan = 0;
+
+ for (int y = 0; y < rw.yCnt; ++y) {
+ auto cover = 0;
+ auto x = 0;
+ auto cell = rw.yCells[y];
+
+ while (cell) {
+ if (cell->x > x && cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), cell->x - x);
+ cover += cell->cover;
+ auto area = cover * (ONE_PIXEL * 2) - cell->area;
+ if (area != 0 && cell->x >= 0) _horizLine(rw, cell->x, y, area, 1);
+ x = cell->x + 1;
+ cell = cell->next;
+ }
+
+ if (cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), rw.cellXCnt - x);
+ }
+
+ if (rw.spansCnt > 0) _genSpan(rw.rle, rw.spans, rw.spansCnt);
+}
+
+
+static Cell* _findCell(RleWorker& rw)
+{
+ auto x = rw.cellPos.x;
+ if (x > rw.cellXCnt) x = rw.cellXCnt;
+
+ auto pcell = &rw.yCells[rw.cellPos.y];
+
+ while(true) {
+ Cell* cell = *pcell;
+ if (!cell || cell->x > x) break;
+ if (cell->x == x) return cell;
+ pcell = &cell->next;
+ }
+
+ if (rw.cellsCnt >= rw.maxCells) longjmp(rw.jmpBuf, 1);
+
+ auto cell = rw.cells + rw.cellsCnt++;
+ cell->x = x;
+ cell->area = 0;
+ cell->cover = 0;
+ cell->next = *pcell;
+ *pcell = cell;
+
+ return cell;
+}
+
+
+static void _recordCell(RleWorker& rw)
+{
+ if (rw.area | rw.cover) {
+ auto cell = _findCell(rw);
+ cell->area += rw.area;
+ cell->cover += rw.cover;
+ }
+}
+
+
+static void _setCell(RleWorker& rw, SwPoint pos)
+{
+ /* Move the cell pointer to a new position. We set the `invalid' */
+ /* flag to indicate that the cell isn't part of those we're interested */
+ /* in during the render phase. This means that: */
+ /* */
+ /* . the new vertical position must be within min_ey..max_ey-1. */
+ /* . the new horizontal position must be strictly less than max_ex */
+ /* */
+ /* Note that if a cell is to the left of the clipping region, it is */
+ /* actually set to the (min_ex-1) horizontal position. */
+
+ /* All cells that are on the left of the clipping region go to the
+ min_ex - 1 horizontal position. */
+ pos.x -= rw.cellMin.x;
+ pos.y -= rw.cellMin.y;
+
+ if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x;
+
+ //Are we moving to a different cell?
+ if (pos != rw.cellPos) {
+ //Record the current one if it is valid
+ if (!rw.invalid) _recordCell(rw);
+ }
+
+ rw.area = 0;
+ rw.cover = 0;
+ rw.cellPos = pos;
+ rw.invalid = ((unsigned)pos.y >= (unsigned)rw.cellYCnt || pos.x >= rw.cellXCnt);
+}
+
+
+static void _startCell(RleWorker& rw, SwPoint pos)
+{
+ if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x;
+ if (pos.x < rw.cellMin.x) pos.x = rw.cellMin.x;
+
+ rw.area = 0;
+ rw.cover = 0;
+ rw.cellPos = pos - rw.cellMin;
+ rw.invalid = false;
+
+ _setCell(rw, pos);
+}
+
+
+static void _moveTo(RleWorker& rw, const SwPoint& to)
+{
+ //record current cell, if any */
+ if (!rw.invalid) _recordCell(rw);
+
+ //start to a new position
+ _startCell(rw, TRUNC(to));
+
+ rw.pos = to;
+}
+
+
+static void _lineTo(RleWorker& rw, const SwPoint& to)
+{
+#define SW_UDIV(a, b) \
+ static_cast<SwCoord>(((unsigned long)(a) * (unsigned long)(b)) >> \
+ (sizeof(long) * CHAR_BIT - PIXEL_BITS))
+
+ auto e1 = TRUNC(rw.pos);
+ auto e2 = TRUNC(to);
+
+ //vertical clipping
+ if ((e1.y >= rw.cellMax.y && e2.y >= rw.cellMax.y) || (e1.y < rw.cellMin.y && e2.y < rw.cellMin.y)) {
+ rw.pos = to;
+ return;
+ }
+
+ auto diff = to - rw.pos;
+ auto f1 = rw.pos - SUBPIXELS(e1);
+ SwPoint f2;
+
+ //inside one cell
+ if (e1 == e2) {
+ ;
+ //any horizontal line
+ } else if (diff.y == 0) {
+ e1.x = e2.x;
+ _setCell(rw, e1);
+ } else if (diff.x == 0) {
+ //vertical line up
+ if (diff.y > 0) {
+ do {
+ f2.y = ONE_PIXEL;
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * f1.x * 2;
+ f1.y = 0;
+ ++e1.y;
+ _setCell(rw, e1);
+ } while(e1.y != e2.y);
+ //vertical line down
+ } else {
+ do {
+ f2.y = 0;
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * f1.x * 2;
+ f1.y = ONE_PIXEL;
+ --e1.y;
+ _setCell(rw, e1);
+ } while(e1.y != e2.y);
+ }
+ //any other line
+ } else {
+ Area prod = diff.x * f1.y - diff.y * f1.x;
+
+ /* These macros speed up repetitive divisions by replacing them
+ with multiplications and right shifts. */
+ auto dx_r = static_cast<long>(ULONG_MAX >> PIXEL_BITS) / (diff.x);
+ auto dy_r = static_cast<long>(ULONG_MAX >> PIXEL_BITS) / (diff.y);
+
+ /* The fundamental value `prod' determines which side and the */
+ /* exact coordinate where the line exits current cell. It is */
+ /* also easily updated when moving from one cell to the next. */
+ do {
+ auto px = diff.x * ONE_PIXEL;
+ auto py = diff.y * ONE_PIXEL;
+
+ //left
+ if (prod <= 0 && prod - px > 0) {
+ f2 = {0, SW_UDIV(-prod, -dx_r)};
+ prod -= py;
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * (f1.x + f2.x);
+ f1 = {ONE_PIXEL, f2.y};
+ --e1.x;
+ //up
+ } else if (prod - px <= 0 && prod - px + py > 0) {
+ prod -= px;
+ f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL};
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * (f1.x + f2.x);
+ f1 = {f2.x, 0};
+ ++e1.y;
+ //right
+ } else if (prod - px + py <= 0 && prod + py >= 0) {
+ prod += py;
+ f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)};
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * (f1.x + f2.x);
+ f1 = {0, f2.y};
+ ++e1.x;
+ //down
+ } else {
+ f2 = {SW_UDIV(prod, -dy_r), 0};
+ prod += px;
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * (f1.x + f2.x);
+ f1 = {f2.x, ONE_PIXEL};
+ --e1.y;
+ }
+
+ _setCell(rw, e1);
+
+ } while(e1 != e2);
+ }
+
+ f2 = {to.x - SUBPIXELS(e2.x), to.y - SUBPIXELS(e2.y)};
+ rw.cover += (f2.y - f1.y);
+ rw.area += (f2.y - f1.y) * (f1.x + f2.x);
+ rw.pos = to;
+}
+
+
+static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
+{
+ auto arc = rw.bezStack;
+ arc[0] = to;
+ arc[1] = ctrl2;
+ arc[2] = ctrl1;
+ arc[3] = rw.pos;
+
+ //Short-cut the arc that crosses the current band
+ auto min = arc[0].y;
+ auto max = arc[0].y;
+
+ SwCoord y;
+ for (auto i = 1; i < 4; ++i) {
+ y = arc[i].y;
+ if (y < min) min = y;
+ if (y > max) max = y;
+ }
+
+ if (TRUNC(min) >= rw.cellMax.y || TRUNC(max) < rw.cellMin.y) goto draw;
+
+ /* Decide whether to split or draw. See `Rapid Termination */
+ /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */
+ /* F. Hain, at */
+ /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */
+ while (true) {
+ {
+ //diff is the P0 - P3 chord vector
+ auto diff = arc[3] - arc[0];
+ auto L = HYPOT(diff);
+
+ //avoid possible arithmetic overflow below by splitting
+ if (L > SHRT_MAX) goto split;
+
+ //max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1)
+ auto sLimit = L * (ONE_PIXEL / 6);
+
+ auto diff1 = arc[1] - arc[0];
+ auto s = diff.y * diff1.x - diff.x * diff1.y;
+ if (s < 0) s = -s;
+ if (s > sLimit) goto split;
+
+ //s is L * the perpendicular distance from P2 to the line P0 - P3
+ auto diff2 = arc[2] - arc[0];
+ s = diff.y * diff2.x - diff.x * diff2.y;
+ if (s < 0) s = -s;
+ if (s > sLimit) goto split;
+
+ /* Split super curvy segments where the off points are so far
+ from the chord that the angles P0-P1-P3 or P0-P2-P3 become
+ acute as detected by appropriate dot products */
+ if (diff1.x * (diff1.x - diff.x) + diff1.y * (diff1.y - diff.y) > 0 ||
+ diff2.x * (diff2.x - diff.x) + diff2.y * (diff2.y - diff.y) > 0)
+ goto split;
+
+ //no reason to split
+ goto draw;
+ }
+ split:
+ mathSplitCubic(arc);
+ arc += 3;
+ continue;
+
+ draw:
+ _lineTo(rw, arc[0]);
+ if (arc == rw.bezStack) return;
+ arc -= 3;
+ }
+}
+
+
+static bool _decomposeOutline(RleWorker& rw)
+{
+ auto outline = rw.outline;
+ auto first = 0; //index of first point in contour
+
+ for (uint32_t n = 0; n < outline->cntrsCnt; ++n) {
+ auto last = outline->cntrs[n];
+ auto limit = outline->pts + last;
+ auto start = UPSCALE(outline->pts[first]);
+ auto pt = outline->pts + first;
+ auto types = outline->types + first;
+
+ /* A contour cannot start with a cubic control point! */
+ if (types[0] == SW_CURVE_TYPE_CUBIC) goto invalid_outline;
+
+ _moveTo(rw, UPSCALE(outline->pts[first]));
+
+ while (pt < limit) {
+ ++pt;
+ ++types;
+
+ //emit a single line_to
+ if (types[0] == SW_CURVE_TYPE_POINT) {
+ _lineTo(rw, UPSCALE(*pt));
+ //types cubic
+ } else {
+ if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC)
+ goto invalid_outline;
+
+ pt += 2;
+ types += 2;
+
+ if (pt <= limit) {
+ _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0]));
+ continue;
+ }
+ _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start);
+ goto close;
+ }
+ }
+ _lineTo(rw, start);
+ close:
+ first = last + 1;
+ }
+
+ return true;
+
+invalid_outline:
+ TVGERR("SW_ENGINE", "Invalid Outline!");
+ return false;
+}
+
+
+static int _genRle(RleWorker& rw)
+{
+ if (setjmp(rw.jmpBuf) == 0) {
+ auto ret = _decomposeOutline(rw);
+ if (!rw.invalid) _recordCell(rw);
+ if (ret) return 0; //success
+ else return 1; //fail
+ }
+ return -1; //lack of cell memory
+}
+
+
+SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt)
+{
+ auto out = outSpans;
+ auto spans = targetRle->spans;
+ auto end = targetRle->spans + targetRle->size;
+ auto clipSpans = clip->spans;
+ auto clipEnd = clip->spans + clip->size;
+
+ while (spanCnt > 0 && spans < end) {
+ if (clipSpans == clipEnd) {
+ spans = end;
+ break;
+ }
+ if (clipSpans->y > spans->y) {
+ ++spans;
+ continue;
+ }
+ if (spans->y != clipSpans->y) {
+ ++clipSpans;
+ continue;
+ }
+ auto sx1 = spans->x;
+ auto sx2 = sx1 + spans->len;
+ auto cx1 = clipSpans->x;
+ auto cx2 = cx1 + clipSpans->len;
+
+ if (cx1 < sx1 && cx2 < sx1) {
+ ++clipSpans;
+ continue;
+ }
+ else if (sx1 < cx1 && sx2 < cx1) {
+ ++spans;
+ continue;
+ }
+ auto x = sx1 > cx1 ? sx1 : cx1;
+ auto len = (sx2 < cx2 ? sx2 : cx2) - x;
+ if (len) {
+ auto spansCorverage = spans->coverage;
+ auto clipSpansCoverage = clipSpans->coverage;
+ out->x = sx1 > cx1 ? sx1 : cx1;
+ out->len = (sx2 < cx2 ? sx2 : cx2) - out->x;
+ out->y = spans->y;
+ out->coverage = (uint8_t)(((spansCorverage * clipSpansCoverage) + 0xff) >> 8);
+ ++out;
+ --spanCnt;
+ }
+ if (sx2 < cx2) ++spans;
+ else ++clipSpans;
+ }
+ return out;
+}
+
+
+SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt)
+{
+ auto out = outSpans;
+ auto spans = targetRle->spans;
+ auto end = targetRle->spans + targetRle->size;
+ auto minx = static_cast<int16_t>(bbox->min.x);
+ auto miny = static_cast<int16_t>(bbox->min.y);
+ auto maxx = minx + static_cast<int16_t>(bbox->max.x - bbox->min.x) - 1;
+ auto maxy = miny + static_cast<int16_t>(bbox->max.y - bbox->min.y) - 1;
+
+ while (spanCnt && spans < end) {
+ if (spans->y > maxy) {
+ spans = end;
+ break;
+ }
+ if (spans->y < miny || spans->x > maxx || spans->x + spans->len <= minx) {
+ ++spans;
+ continue;
+ }
+ if (spans->x < minx) {
+ out->len = (spans->len - (minx - spans->x)) < (maxx - minx + 1) ? (spans->len - (minx - spans->x)) : (maxx - minx + 1);
+ out->x = minx;
+ }
+ else {
+ out->x = spans->x;
+ out->len = spans->len < (maxx - spans->x + 1) ? spans->len : (maxx - spans->x + 1);
+ }
+ if (out->len != 0) {
+ out->y = spans->y;
+ out->coverage = spans->coverage;
+ ++out;
+ }
+ ++spans;
+ --spanCnt;
+ }
+ return out;
+}
+
+
+void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size)
+{
+ free(rle->spans);
+ rle->spans = clippedSpans;
+ rle->size = rle->alloc = size;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias)
+{
+ constexpr auto RENDER_POOL_SIZE = 16384L;
+ constexpr auto BAND_SIZE = 40;
+
+ //TODO: We can preserve several static workers in advance
+ RleWorker rw;
+ Cell buffer[RENDER_POOL_SIZE / sizeof(Cell)];
+
+ //Init Cells
+ rw.buffer = buffer;
+ rw.bufferSize = sizeof(buffer);
+ rw.yCells = reinterpret_cast<Cell**>(buffer);
+ rw.cells = nullptr;
+ rw.maxCells = 0;
+ rw.cellsCnt = 0;
+ rw.area = 0;
+ rw.cover = 0;
+ rw.invalid = true;
+ rw.cellMin = renderRegion.min;
+ rw.cellMax = renderRegion.max;
+ rw.cellXCnt = rw.cellMax.x - rw.cellMin.x;
+ rw.cellYCnt = rw.cellMax.y - rw.cellMin.y;
+ rw.ySpan = 0;
+ rw.outline = const_cast<SwOutline*>(outline);
+ rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64
+ rw.bandShoot = 0;
+ rw.antiAlias = antiAlias;
+
+ if (!rle) rw.rle = reinterpret_cast<SwRleData*>(calloc(1, sizeof(SwRleData)));
+ else rw.rle = rle;
+
+ //Generate RLE
+ Band bands[BAND_SIZE];
+ Band* band;
+
+ /* set up vertical bands */
+ auto bandCnt = static_cast<int>((rw.cellMax.y - rw.cellMin.y) / rw.bandSize);
+ if (bandCnt == 0) bandCnt = 1;
+ else if (bandCnt >= BAND_SIZE) bandCnt = (BAND_SIZE - 1);
+
+ auto min = rw.cellMin.y;
+ auto yMax = rw.cellMax.y;
+ SwCoord max;
+ int ret;
+
+ for (int n = 0; n < bandCnt; ++n, min = max) {
+ max = min + rw.bandSize;
+ if (n == bandCnt -1 || max > yMax) max = yMax;
+
+ bands[0].min = min;
+ bands[0].max = max;
+ band = bands;
+
+ while (band >= bands) {
+ rw.yCells = static_cast<Cell**>(rw.buffer);
+ rw.yCnt = band->max - band->min;
+
+ int cellStart = sizeof(Cell*) * (int)rw.yCnt;
+ int cellMod = cellStart % sizeof(Cell);
+
+ if (cellMod > 0) cellStart += sizeof(Cell) - cellMod;
+
+ auto cellEnd = rw.bufferSize;
+ cellEnd -= cellEnd % sizeof(Cell);
+
+ auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + cellEnd);
+ rw.cells = reinterpret_cast<Cell*>((char*)rw.buffer + cellStart);
+
+ if (rw.cells >= cellsMax) goto reduce_bands;
+
+ rw.maxCells = cellsMax - rw.cells;
+ if (rw.maxCells < 2) goto reduce_bands;
+
+ for (int y = 0; y < rw.yCnt; ++y)
+ rw.yCells[y] = nullptr;
+
+ rw.cellsCnt = 0;
+ rw.invalid = true;
+ rw.cellMin.y = band->min;
+ rw.cellMax.y = band->max;
+ rw.cellYCnt = band->max - band->min;
+
+ ret = _genRle(rw);
+ if (ret == 0) {
+ _sweep(rw);
+ --band;
+ continue;
+ } else if (ret == 1) {
+ goto error;
+ }
+
+ reduce_bands:
+ /* render pool overflow: we will reduce the render band by half */
+ auto bottom = band->min;
+ auto top = band->max;
+ auto middle = bottom + ((top - bottom) >> 1);
+
+ /* This is too complex for a single scanline; there must
+ be some problems */
+ if (middle == bottom) goto error;
+
+ if (bottom - top >= rw.bandSize) ++rw.bandShoot;
+
+ band[1].min = bottom;
+ band[1].max = middle;
+ band[0].min = middle;
+ band[0].max = top;
+ ++band;
+ }
+ }
+
+ if (rw.bandShoot > 8 && rw.bandSize > 16)
+ rw.bandSize = (rw.bandSize >> 1);
+
+ return rw.rle;
+
+error:
+ free(rw.rle);
+ rw.rle = nullptr;
+ return nullptr;
+}
+
+
+void rleReset(SwRleData* rle)
+{
+ if (!rle) return;
+ rle->size = 0;
+}
+
+
+void rleFree(SwRleData* rle)
+{
+ if (!rle) return;
+ if (rle->spans) free(rle->spans);
+ free(rle);
+}
+
+
+void rleClipPath(SwRleData *rle, const SwRleData *clip)
+{
+ if (rle->size == 0 || clip->size == 0) return;
+ auto spanCnt = rle->size > clip->size ? rle->size : clip->size;
+ auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (spanCnt)));
+ if (!spans) return;
+ auto spansEnd = _intersectSpansRegion(clip, rle, spans, spanCnt);
+
+ _replaceClipSpan(rle, spans, spansEnd - spans);
+
+ TVGLOG("SW_ENGINE", "Using ClipPath!");
+}
+
+
+void rleClipRect(SwRleData *rle, const SwBBox* clip)
+{
+ if (rle->size == 0) return;
+ auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (rle->size)));
+ if (!spans) return;
+ auto spansEnd = _intersectSpansRect(clip, rle, spans, rle->size);
+
+ _replaceClipSpan(rle, spans, spansEnd - spans);
+
+ TVGLOG("SW_ENGINE", "Using ClipRect!");
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp
new file mode 100644
index 0000000000..7b49c232b1
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp
@@ -0,0 +1,664 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgSwCommon.h"
+#include "tvgBezier.h"
+#include <float.h>
+#include <math.h>
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct Line
+{
+ Point pt1;
+ Point pt2;
+};
+
+
+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 bool _growOutlineContour(SwOutline& outline, uint32_t n)
+{
+ if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return false;
+ outline.reservedCntrsCnt = outline.cntrsCnt + n;
+ outline.cntrs = static_cast<uint16_t*>(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint16_t)));
+ return true;
+}
+
+
+static void _reserveOutlineClose(SwOutline& outline)
+{
+ //Dash outlines are always opened.
+ //Only normal outlines use this information, it sholud be same to their contour counts.
+ if (outline.closed) free(outline.closed);
+ outline.closed = static_cast<bool*>(calloc(outline.reservedCntrsCnt, sizeof(bool)));
+}
+
+
+static void _resetOutlineClose(SwOutline& outline)
+{
+ memset(outline.closed, 0x0, outline.reservedCntrsCnt * sizeof(bool));
+}
+
+
+static void _growOutlinePoint(SwOutline& outline, uint32_t n)
+{
+ if (outline.reservedPtsCnt >= outline.ptsCnt + n) return;
+ outline.reservedPtsCnt = outline.ptsCnt + n;
+ outline.pts = static_cast<SwPoint*>(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint)));
+ outline.types = static_cast<uint8_t*>(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t)));
+}
+
+
+static void _outlineEnd(SwOutline& outline)
+{
+ if (outline.ptsCnt == 0) return;
+
+ _growOutlineContour(outline, 1);
+ outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1;
+ ++outline.cntrsCnt;
+}
+
+
+static void _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform)
+{
+ _growOutlinePoint(outline, 1);
+
+ outline.pts[outline.ptsCnt] = mathTransform(to, transform);
+ outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
+
+ if (outline.ptsCnt > 0) {
+ _growOutlineContour(outline, 1);
+ outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1;
+ ++outline.cntrsCnt;
+ }
+
+ ++outline.ptsCnt;
+}
+
+
+static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform)
+{
+ _growOutlinePoint(outline, 1);
+
+ outline.pts[outline.ptsCnt] = mathTransform(to, transform);
+ outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
+ ++outline.ptsCnt;
+}
+
+
+static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
+{
+ _growOutlinePoint(outline, 3);
+
+ outline.pts[outline.ptsCnt] = mathTransform(ctrl1, transform);
+ outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC;
+ ++outline.ptsCnt;
+
+ outline.pts[outline.ptsCnt] = mathTransform(ctrl2, transform);
+ outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC;
+ ++outline.ptsCnt;
+
+ outline.pts[outline.ptsCnt] = mathTransform(to, transform);
+ outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
+ ++outline.ptsCnt;
+}
+
+
+static void _outlineClose(SwOutline& outline)
+{
+ uint32_t i = 0;
+
+ if (outline.cntrsCnt > 0) {
+ i = outline.cntrs[outline.cntrsCnt - 1] + 1;
+ } else {
+ i = 0; //First Path
+ }
+
+ //Make sure there is at least one point in the current path
+ if (outline.ptsCnt == i) return;
+
+ //Close the path
+ _growOutlinePoint(outline, 1);
+
+ outline.pts[outline.ptsCnt] = outline.pts[i];
+ outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
+ ++outline.ptsCnt;
+ outline.closed[outline.cntrsCnt] = true;
+}
+
+
+static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform)
+{
+ _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, transform);
+ _outlineLineTo(*dash.outline, to, transform);
+ }
+ } 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, transform);
+ _outlineLineTo(*dash.outline, &left.pt2, transform);
+ }
+ 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, transform);
+ _outlineLineTo(*dash.outline, &cur.pt2, transform);
+ }
+ if (dash.curLen < 1 && TO_SWCOORD(len) > 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, const Matrix* transform)
+{
+ _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, transform);
+ _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform);
+ }
+ } else {
+ while (len > dash.curLen) {
+ Bezier left, right;
+ len -= dash.curLen;
+ bezSplitAt(cur, dash.curLen, left, right);
+ if (!dash.curOpGap) {
+ // leftovers from a previous command don't require moveTo
+ if (dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) {
+ _outlineMoveTo(*dash.outline, &left.start, transform);
+ }
+ _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform);
+ }
+ dash.curIdx = (dash.curIdx + 1) % dash.cnt;
+ 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, transform);
+ _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform);
+ }
+ if (dash.curLen < 1 && TO_SWCOORD(len) > 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 SwOutline* _genDashOutline(const Shape* sdata, const Matrix* transform)
+{
+ const PathCommand* cmds = nullptr;
+ auto cmdCnt = sdata->pathCommands(&cmds);
+
+ const Point* pts = nullptr;
+ auto ptsCnt = sdata->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 = sdata->strokeDash(&pattern);
+ if (dash.cnt == 0) return nullptr;
+
+ //OPTMIZE ME: Use mempool???
+ dash.pattern = const_cast<float*>(pattern);
+ dash.outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline)));
+
+ //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;
+ }
+ }
+ }
+
+ ++outlinePtsCnt; //for close
+ ++outlineCntrsCnt; //for end
+
+ //No idea exact count.... 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, transform);
+ 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, transform);
+ ++pts;
+ break;
+ }
+ case PathCommand::CubicTo: {
+ _dashCubicTo(dash, pts, pts + 1, pts + 2, transform);
+ pts += 3;
+ break;
+ }
+ }
+ ++cmds;
+ }
+
+ _outlineEnd(*dash.outline);
+
+ return dash.outline;
+}
+
+
+static bool _axisAlignedRect(const SwOutline* outline)
+{
+ //Fast Track: axis-aligned rectangle?
+ if (outline->ptsCnt != 5) return false;
+
+ auto pt1 = outline->pts + 0;
+ auto pt2 = outline->pts + 1;
+ auto pt3 = outline->pts + 2;
+ auto pt4 = outline->pts + 3;
+
+ auto a = SwPoint{pt1->x, pt3->y};
+ auto b = SwPoint{pt3->x, pt1->y};
+
+ if ((*pt2 == a && *pt4 == b) || (*pt2 == b && *pt4 == a)) return true;
+
+ return false;
+}
+
+
+
+static bool _genOutline(SwShape* shape, const Shape* sdata, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite)
+{
+ const PathCommand* cmds = nullptr;
+ auto cmdCnt = sdata->pathCommands(&cmds);
+
+ const Point* pts = nullptr;
+ auto ptsCnt = sdata->pathCoords(&pts);
+
+ //No actual shape data
+ if (cmdCnt == 0 || ptsCnt == 0) return false;
+
+ //smart reservation
+ auto outlinePtsCnt = 0;
+ auto outlineCntrsCnt = 0;
+ auto closeCnt = 0;
+
+ for (uint32_t i = 0; i < cmdCnt; ++i) {
+ switch (*(cmds + i)) {
+ case PathCommand::Close: {
+ ++outlinePtsCnt;
+ ++closeCnt;
+ break;
+ }
+ case PathCommand::MoveTo: {
+ ++outlineCntrsCnt;
+ ++outlinePtsCnt;
+ break;
+ }
+ case PathCommand::LineTo: {
+ ++outlinePtsCnt;
+ break;
+ }
+ case PathCommand::CubicTo: {
+ outlinePtsCnt += 3;
+ break;
+ }
+ }
+ }
+
+ if (static_cast<uint32_t>(outlinePtsCnt - closeCnt) > ptsCnt) {
+ TVGERR("SW_ENGINE", "Wrong a pair of the commands & points - required(%d), current(%d)", outlinePtsCnt - closeCnt, ptsCnt);
+ return false;
+ }
+
+ ++outlinePtsCnt; //for close
+ ++outlineCntrsCnt; //for end
+
+ shape->outline = mpoolReqOutline(mpool, tid);
+ auto outline = shape->outline;
+
+ _growOutlinePoint(*outline, outlinePtsCnt);
+
+ if (_growOutlineContour(*outline, outlineCntrsCnt)) {
+ _reserveOutlineClose(*outline);
+ } else {
+ _resetOutlineClose(*outline);
+ }
+
+ //Generate Outlines
+ while (cmdCnt-- > 0) {
+ switch (*cmds) {
+ case PathCommand::Close: {
+ _outlineClose(*outline);
+ break;
+ }
+ case PathCommand::MoveTo: {
+ _outlineMoveTo(*outline, pts, transform);
+ ++pts;
+ break;
+ }
+ case PathCommand::LineTo: {
+ _outlineLineTo(*outline, pts, transform);
+ ++pts;
+ break;
+ }
+ case PathCommand::CubicTo: {
+ _outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform);
+ pts += 3;
+ break;
+ }
+ }
+ ++cmds;
+ }
+
+ _outlineEnd(*outline);
+
+ outline->fillRule = sdata->fillRule();
+ shape->outline = outline;
+
+ shape->fastTrack = (!hasComposite && _axisAlignedRect(shape->outline));
+ return true;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+bool shapePrepare(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
+{
+ if (!_genOutline(shape, sdata, transform, mpool, tid, hasComposite)) return false;
+ if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false;
+
+ //Keep it for Rasterization Region
+ shape->bbox = renderRegion;
+
+ //Check valid region
+ if (renderRegion.max.x - renderRegion.min.x < 1 && renderRegion.max.y - renderRegion.min.y < 1) return false;
+
+ //Check boundary
+ if (renderRegion.min.x >= clipRegion.max.x || renderRegion.min.y >= clipRegion.max.y ||
+ renderRegion.max.x <= clipRegion.min.x || renderRegion.max.y <= clipRegion.min.y) return false;
+
+ return true;
+}
+
+
+bool shapePrepared(const SwShape* shape)
+{
+ return shape->rle ? true : false;
+}
+
+
+bool shapeGenRle(SwShape* shape, TVG_UNUSED const Shape* sdata, bool antiAlias)
+{
+ //FIXME: Should we draw it?
+ //Case: Stroke Line
+ //if (shape.outline->opened) return true;
+
+ //Case A: Fast Track Rectangle Drawing
+ if (shape->fastTrack) return true;
+
+ //Case B: Normal Shape RLE Drawing
+ if ((shape->rle = rleRender(shape->rle, shape->outline, shape->bbox, antiAlias))) return true;
+
+ return false;
+}
+
+
+void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid)
+{
+ mpoolRetOutline(mpool, tid);
+ shape->outline = nullptr;
+}
+
+
+void shapeReset(SwShape* shape)
+{
+ rleReset(shape->rle);
+ rleReset(shape->strokeRle);
+ shape->fastTrack = false;
+ shape->bbox.reset();
+}
+
+
+void shapeFree(SwShape* shape)
+{
+ rleFree(shape->rle);
+ shapeDelFill(shape);
+
+ if (shape->stroke) {
+ rleFree(shape->strokeRle);
+ strokeFree(shape->stroke);
+ }
+}
+
+
+void shapeDelStroke(SwShape* shape)
+{
+ if (!shape->stroke) return;
+ rleFree(shape->strokeRle);
+ shape->strokeRle = nullptr;
+ strokeFree(shape->stroke);
+ shape->stroke = nullptr;
+}
+
+
+void shapeResetStroke(SwShape* shape, const Shape* sdata, const Matrix* transform)
+{
+ if (!shape->stroke) shape->stroke = static_cast<SwStroke*>(calloc(1, sizeof(SwStroke)));
+ auto stroke = shape->stroke;
+ if (!stroke) return;
+
+ strokeReset(stroke, sdata, transform);
+ rleReset(shape->strokeRle);
+}
+
+
+bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
+{
+ SwOutline* shapeOutline = nullptr;
+ SwOutline* strokeOutline = nullptr;
+ bool freeOutline = false;
+ bool ret = true;
+
+ //Dash Style Stroke
+ if (sdata->strokeDash(nullptr) > 0) {
+ shapeOutline = _genDashOutline(sdata, transform);
+ if (!shapeOutline) return false;
+ freeOutline = true;
+ //Normal Style stroke
+ } else {
+ if (!shape->outline) {
+ if (!_genOutline(shape, sdata, transform, mpool, tid, false)) return false;
+ }
+ shapeOutline = shape->outline;
+ }
+
+ if (!strokeParseOutline(shape->stroke, *shapeOutline)) {
+ ret = false;
+ goto fail;
+ }
+
+ strokeOutline = strokeExportOutline(shape->stroke, mpool, tid);
+
+ if (!mathUpdateOutlineBBox(strokeOutline, clipRegion, renderRegion, false)) {
+ ret = false;
+ goto fail;
+ }
+
+ shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderRegion, true);
+
+fail:
+ if (freeOutline) {
+ if (shapeOutline->cntrs) free(shapeOutline->cntrs);
+ if (shapeOutline->pts) free(shapeOutline->pts);
+ if (shapeOutline->types) free(shapeOutline->types);
+ if (shapeOutline->closed) free(shapeOutline->closed);
+ free(shapeOutline);
+ }
+ mpoolRetStrokeOutline(mpool, tid);
+
+ return ret;
+}
+
+
+bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable)
+{
+ return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable);
+}
+
+
+bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable)
+{
+ return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable);
+}
+
+
+void shapeResetFill(SwShape* shape)
+{
+ if (!shape->fill) {
+ shape->fill = static_cast<SwFill*>(calloc(1, sizeof(SwFill)));
+ if (!shape->fill) return;
+ }
+ fillReset(shape->fill);
+}
+
+
+void shapeResetStrokeFill(SwShape* shape)
+{
+ if (!shape->stroke->fill) {
+ shape->stroke->fill = static_cast<SwFill*>(calloc(1, sizeof(SwFill)));
+ if (!shape->stroke->fill) return;
+ }
+ fillReset(shape->stroke->fill);
+}
+
+
+void shapeDelFill(SwShape* shape)
+{
+ if (!shape->fill) return;
+ fillFree(shape->fill);
+ shape->fill = nullptr;
+}
+
+
+void shapeDelStrokeFill(SwShape* shape)
+{
+ if (!shape->stroke->fill) return;
+ fillFree(shape->stroke->fill);
+ shape->stroke->fill = nullptr;
+}
diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp
new file mode 100644
index 0000000000..c0cfc1be26
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp
@@ -0,0 +1,932 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <string.h>
+#include <math.h>
+#include "tvgSwCommon.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+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;
+
+static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
+{
+ return (SW_ANGLE_PI2 - static_cast<SwFixed>(s) * SW_ANGLE_PI);
+}
+
+
+static inline void SCALE(const SwStroke& stroke, SwPoint& pt)
+{
+ pt.x = static_cast<SwCoord>(pt.x * stroke.sx);
+ pt.y = static_cast<SwCoord>(pt.y * stroke.sy);
+}
+
+
+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;
+ //OPTIMIZE: use mempool!
+ border->pts = static_cast<SwPoint*>(realloc(border->pts, maxCur * sizeof(SwPoint)));
+ border->tags = static_cast<uint8_t*>(realloc(border->tags, maxCur * sizeof(uint8_t)));
+ border->maxPts = maxCur;
+}
+
+
+static void _borderClose(SwStrokeBorder* border, bool reverse)
+{
+ auto start = border->start;
+ auto 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 void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
+{
+ _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_POINT;
+
+ border->ptsCnt += 3;
+
+ border->movable = false;
+}
+
+
+static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke)
+{
+ constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
+ SwPoint a = {static_cast<SwCoord>(radius), 0};
+ mathRotate(a, angleStart);
+ SCALE(stroke, a);
+ a += center;
+
+ auto total = angleDiff;
+ auto angle = angleStart;
+ auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_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 = {static_cast<SwCoord>(radius), 0};
+ mathRotate(b, next);
+ SCALE(stroke, b);
+ b += center;
+
+ //compute first and second control points
+ auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
+
+ SwPoint a2 = {static_cast<SwCoord>(length), 0};
+ mathRotate(a2, angle + rotate);
+ SCALE(stroke, a2);
+ a2 += a;
+
+ SwPoint b2 = {static_cast<SwCoord>(length), 0};
+ mathRotate(b2, next - rotate);
+ SCALE(stroke, b2);
+ b2 += b;
+
+ //add cubic arc
+ _borderCubicTo(border, a2, b2, b);
+
+ //process the rest of the arc?
+ a = b;
+ total -= step;
+ angle = next;
+ }
+}
+
+
+static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable)
+{
+ if (border->movable) {
+ //move last point
+ border->pts[border->ptsCnt - 1] = to;
+ } else {
+
+ //don't add zero-length line_to
+ 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_POINT;
+ border->ptsCnt += 1;
+ }
+
+ border->movable = movable;
+}
+
+
+static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
+{
+ //close current open path if any?
+ if (border->start >= 0) _borderClose(border, false);
+
+ border->start = border->ptsCnt;
+ border->movable = false;
+
+ _borderLineTo(border, to, false);
+}
+
+
+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 == SW_ANGLE_PI) total = -rotate * 2;
+
+ _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke);
+ border->movable = false;
+}
+
+
+static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
+{
+ constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
+
+ auto border = stroke.borders + side;
+
+ if (stroke.join == StrokeJoin::Round) {
+ _arcTo(stroke, side);
+ } else {
+ //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 == SW_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 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, stroke.angleOut + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ border->movable = false;
+ _borderLineTo(border, delta, false);
+ //this is a miter (intersection)
+ } else {
+ auto length = mathDivide(stroke.width, thcos);
+ SwPoint delta = {static_cast<SwCoord>(length), 0};
+ mathRotate(delta, phi);
+ SCALE(stroke, delta);
+ 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 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, stroke.angleOut + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ _borderLineTo(border, delta, false);
+ }
+ }
+ }
+}
+
+
+static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
+{
+ auto border = stroke.borders + side;
+ auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
+ SwPoint delta;
+ bool intersect = false;
+
+ /* 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) {
+ //compute minimum required length of lines
+ SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
+ if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
+ }
+
+ auto rotate = SIDE_TO_ROTATE(side);
+
+ if (!intersect) {
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, stroke.angleOut + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ border->movable = false;
+ } else {
+ //compute median angle
+ auto phi = stroke.angleIn + theta;
+ auto thcos = mathCos(theta);
+ delta = {static_cast<SwCoord>(mathDivide(stroke.width, thcos)), 0};
+ mathRotate(delta, phi + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+ }
+
+ _borderLineTo(border, delta, false);
+}
+
+
+void _processCorner(SwStroke& stroke, SwFixed lineLength)
+{
+ auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
+
+ //no specific corner processing is required if the turn is 0
+ if (turn == 0) return;
+
+ //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;
+
+ //process the inside
+ _inside(stroke, inside, lineLength);
+
+ //process the outside
+ _outside(stroke, 1 - inside, lineLength);
+}
+
+
+void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
+{
+ SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, startAngle + SW_ANGLE_PI2);
+ SCALE(stroke, delta);
+
+ 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 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle + SW_ANGLE_PI2);
+ SCALE(stroke, delta);
+
+ //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. */
+ _firstSubPath(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 limit = bezStack + 32;
+ auto arc = bezStack;
+ auto firstArc = true;
+ 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) {
+ _firstSubPath(stroke, angleIn, 0);
+ } else {
+ stroke.angleOut = angleIn;
+ _processCorner(stroke, 0);
+ }
+ } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) {
+ //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;
+
+ _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]);
+ }
+
+ auto border = stroke.borders;
+ int32_t side = 0;
+
+ while (side <= 1)
+ {
+ auto rotate = SIDE_TO_ROTATE(side);
+
+ //compute control points
+ SwPoint _ctrl1 = {static_cast<SwCoord>(length1), 0};
+ mathRotate(_ctrl1, phi1 + rotate);
+ SCALE(stroke, _ctrl1);
+ _ctrl1 += arc[2];
+
+ SwPoint _ctrl2 = {static_cast<SwCoord>(length2), 0};
+ mathRotate(_ctrl2, phi2 + rotate);
+ SCALE(stroke, _ctrl2);
+ _ctrl2 += arc[1];
+
+ //compute end point
+ SwPoint _end = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(_end, angleOut + rotate);
+ SCALE(stroke, _end);
+ _end += arc[0];
+
+ if (stroke.handleWideStrokes) {
+
+ /* 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);
+
+ //is the direction of the border arc opposite to that of the original arc?
+ if (abs(mathDiff(alpha0, alpha1)) > SW_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 = {static_cast<SwCoord>(alen), 0};
+ mathRotate(delta, beta);
+ delta += _start;
+
+ //circumnavigate the negative sector backwards
+ border->movable = false;
+ _borderLineTo(border, delta, false);
+ _borderLineTo(border, _end, false);
+ _borderCubicTo(border, _ctrl2, _ctrl1, _start);
+
+ //and then move to the endpoint
+ _borderLineTo(border, _end, false);
+
+ ++side;
+ ++border;
+ continue;
+ }
+
+ //else fall through
+ }
+ _borderCubicTo(border, _ctrl1, _ctrl2, _end);
+ ++side;
+ ++border;
+ }
+ arc -= 3;
+ stroke.angleIn = angleOut;
+ }
+ stroke.center = to;
+}
+
+
+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 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle);
+ SCALE(stroke, delta);
+
+ SwPoint delta2 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta2, angle + rotate);
+ SCALE(stroke, delta2);
+ delta += stroke.center + delta2;
+
+ _borderLineTo(border, delta, false);
+
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle);
+ SCALE(stroke, delta);
+
+ delta2 = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta2, angle - rotate);
+ SCALE(stroke, delta2);
+ delta += delta2 + stroke.center;
+
+ _borderLineTo(border, delta, false);
+
+ } else if (stroke.cap == StrokeCap::Round) {
+
+ stroke.angleIn = angle;
+ stroke.angleOut = angle + SW_ANGLE_PI;
+ _arcTo(stroke, side);
+ return;
+
+ } else { //Butt
+ auto rotate = SIDE_TO_ROTATE(side);
+ auto border = stroke.borders + side;
+
+ SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle + rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+
+ _borderLineTo(border, delta, false);
+
+ delta = {static_cast<SwCoord>(stroke.width), 0};
+ mathRotate(delta, angle - rotate);
+ SCALE(stroke, delta);
+ delta += stroke.center;
+
+ _borderLineTo(border, delta, false);
+ }
+}
+
+
+static void _addReverseLeft(SwStroke& stroke, bool opened)
+{
+ auto right = stroke.borders + 0;
+ auto left = stroke.borders + 1;
+ 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 || ttag == 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 _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed)
+{
+ /* We cannot process the first point because there is not enough
+ information regarding its corner/cap. Later, it will be processed
+ in the _endSubPath() */
+
+ stroke.firstPt = true;
+ stroke.center = to;
+ stroke.closedSubPath = closed;
+
+ /* 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.closedSubPath && stroke.cap == StrokeCap::Butt))
+ stroke.handleWideStrokes = true;
+ else
+ stroke.handleWideStrokes = false;
+
+ stroke.ptStartSubPath = to;
+ stroke.angleIn = 0;
+}
+
+
+static void _endSubPath(SwStroke& stroke)
+{
+ if (stroke.closedSubPath) {
+ //close the path if needed
+ if (stroke.center != stroke.ptStartSubPath)
+ _lineTo(stroke, stroke.ptStartSubPath);
+
+ //process the corner
+ stroke.angleOut = stroke.subPathAngle;
+ auto turn = mathDiff(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
+ int32_t inside = 0;
+
+ //otherwise, the inside is 1
+ if (turn < 0) inside = 1;
+
+ _inside(stroke, inside, stroke.subPathLineLength); //inside
+ _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside
+ }
+
+ _borderClose(stroke.borders + 0, false);
+ _borderClose(stroke.borders + 1, true);
+ } else {
+ auto right = stroke.borders;
+
+ /* 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.ptStartSubPath;
+ _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0);
+
+ /* now end the right subpath accordingly. The left one is rewind
+ and deosn't need further processing */
+ _borderClose(right, false);
+ }
+}
+
+
+static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt)
+{
+ 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;
+
+ ptsCnt = _ptsCnt;
+ cntrsCnt = _cntrsCnt;
+
+ return;
+
+fail:
+ ptsCnt = 0;
+ cntrsCnt = 0;
+}
+
+
+static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
+{
+ auto border = stroke.borders + side;
+
+ if (border->ptsCnt == 0) return; //invalid border
+
+ 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 {
+ //LOG: What type of stroke outline??
+ }
+
+ if (*src & SW_STROKE_TAG_END) {
+ *cntrs = idx;
+ ++cntrs;
+ ++outline->cntrsCnt;
+ }
+ ++src;
+ ++tags;
+ ++idx;
+ --cnt;
+ }
+ outline->ptsCnt = outline->ptsCnt + border->ptsCnt;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+void strokeFree(SwStroke* stroke)
+{
+ if (!stroke) return;
+
+ //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);
+
+ fillFree(stroke->fill);
+ stroke->fill = nullptr;
+
+ free(stroke);
+}
+
+
+void strokeReset(SwStroke* stroke, const Shape* sdata, const Matrix* transform)
+{
+ if (transform) {
+ stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f));
+ stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f));
+ } else {
+ stroke->sx = stroke->sy = 1.0f;
+ }
+
+ stroke->width = HALF_STROKE(sdata->strokeWidth());
+ stroke->cap = sdata->strokeCap();
+
+ //Save line join: it can be temporarily changed when stroking curves...
+ stroke->joinSaved = stroke->join = sdata->strokeJoin();
+
+ stroke->borders[0].ptsCnt = 0;
+ stroke->borders[0].start = -1;
+ stroke->borders[1].ptsCnt = 0;
+ stroke->borders[1].start = -1;
+}
+
+
+bool strokeParseOutline(SwStroke* stroke, const 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;
+
+ //Skip empty points
+ if (last <= first) {
+ first = last + 1;
+ continue;
+ }
+
+ auto start = outline.pts[first];
+ auto pt = outline.pts + first;
+ auto types = outline.types + first;
+ auto type = types[0];
+
+ //A contour cannot start with a cubic control point
+ if (type == SW_CURVE_TYPE_CUBIC) return false;
+
+ auto closed = outline.closed ? outline.closed[i]: false;
+
+ _beginSubPath(*stroke, start, closed);
+
+ while (pt < limit) {
+ ++pt;
+ ++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;
+}
+
+
+SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
+{
+ 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 = mpoolReqStrokeOutline(mpool, tid);
+ if (outline->reservedPtsCnt < ptsCnt) {
+ outline->pts = static_cast<SwPoint*>(realloc(outline->pts, sizeof(SwPoint) * ptsCnt));
+ outline->types = static_cast<uint8_t*>(realloc(outline->types, sizeof(uint8_t) * ptsCnt));
+ outline->reservedPtsCnt = ptsCnt;
+ }
+ if (outline->reservedCntrsCnt < cntrsCnt) {
+ outline->cntrs = static_cast<uint16_t*>(realloc(outline->cntrs, sizeof(uint16_t) * cntrsCnt));
+ outline->reservedCntrsCnt = cntrsCnt;
+ }
+
+ _exportBorderOutline(*stroke, outline, 0); //left
+ _exportBorderOutline(*stroke, outline, 1); //right
+
+ return outline;
+}
diff --git a/thirdparty/thorvg/src/lib/tvgAccessor.cpp b/thirdparty/thorvg/src/lib/tvgAccessor.cpp
new file mode 100644
index 0000000000..085c8a3cbc
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgAccessor.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "tvgIteratorAccessor.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static bool accessChildren(Iterator* it, bool(*func)(const Paint* paint), IteratorAccessor& itrAccessor)
+{
+ while (auto child = it->next()) {
+ //Access the child
+ if (!func(child)) return false;
+
+ //Access the children of the child
+ if (auto it2 = itrAccessor.iterator(child)) {
+ if (!accessChildren(it2, func, itrAccessor)) {
+ delete(it2);
+ return false;
+ }
+ delete(it2);
+ }
+ }
+ return true;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+unique_ptr<Picture> Accessor::access(unique_ptr<Picture> picture, bool(*func)(const Paint* paint)) noexcept
+{
+ auto p = picture.get();
+ if (!p || !func) return picture;
+
+ //Use the Preorder Tree-Search
+
+ //Root
+ if (!func(p)) return picture;
+
+ //Children
+ IteratorAccessor itrAccessor;
+ if (auto it = itrAccessor.iterator(p)) {
+ accessChildren(it, func, itrAccessor);
+ delete(it);
+ }
+ return picture;
+}
+
+
+Accessor::~Accessor()
+{
+
+}
+
+
+Accessor::Accessor()
+{
+
+}
+
+
+unique_ptr<Accessor> Accessor::gen() noexcept
+{
+ return unique_ptr<Accessor>(new Accessor);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgArray.h b/thirdparty/thorvg/src/lib/tvgArray.h
new file mode 100644
index 0000000000..d04e68e73c
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgArray.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_ARRAY_H_
+#define _TVG_ARRAY_H_
+
+#include <memory.h>
+
+namespace tvg
+{
+
+template<class T>
+struct Array
+{
+ T* data = nullptr;
+ uint32_t count = 0;
+ uint32_t reserved = 0;
+
+ void push(T element)
+ {
+ if (count + 1 > reserved) {
+ reserved = (count + 1) * 2;
+ auto p = data;
+ data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
+ if (!data) {
+ data = p;
+ return;
+ }
+ }
+ data[count++] = element;
+ }
+
+ bool reserve(uint32_t size)
+ {
+ if (size > reserved) {
+ reserved = size;
+ auto p = data;
+ data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
+ if (!data) {
+ data = p;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool grow(uint32_t size)
+ {
+ return reserve(count + size);
+ }
+
+ T* ptr()
+ {
+ return data + count;
+ }
+
+ void pop()
+ {
+ if (count > 0) --count;
+ }
+
+ void reset()
+ {
+ if (data) {
+ free(data);
+ data = nullptr;
+ }
+ count = reserved = 0;
+ }
+
+ void clear()
+ {
+ count = 0;
+ }
+
+ void operator=(const Array& rhs)
+ {
+ reserve(rhs.count);
+ if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * reserved);
+ count = rhs.count;
+ }
+
+ ~Array()
+ {
+ if (data) free(data);
+ }
+};
+
+}
+
+#endif //_TVG_ARRAY_H_
diff --git a/thirdparty/thorvg/src/lib/tvgBezier.cpp b/thirdparty/thorvg/src/lib/tvgBezier.cpp
new file mode 100644
index 0000000000..2290afa728
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgBezier.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <float.h>
+#include <math.h>
+#include "tvgBezier.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+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);
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+namespace tvg
+{
+
+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;
+}
+
+
+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 (fabsf(len - chord) > BEZIER_EPSILON) {
+ bezSplit(cur, left, right);
+ return bezLength(left) + bezLength(right);
+ }
+ return len;
+}
+
+
+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);
+}
+
+
+float bezAt(const Bezier& bz, float at)
+{
+ auto len = bezLength(bz);
+ auto biggest = 1.0f;
+ auto smallest = 0.0f;
+ auto t = 0.5f;
+
+ //just in case to prevent an infinite loop
+ if (at <= 0) return 0.0f;
+
+ if (at >= len) return 1.0f;
+
+ while (true) {
+ auto right = bz;
+ Bezier left;
+ bezSplitLeft(right, t, left);
+ len = bezLength(left);
+
+ if (fabsf(len - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
+ break;
+ }
+
+ if (len < at) {
+ smallest = t;
+ t = (t + biggest) * 0.5f;
+ } else {
+ biggest = t;
+ t = (smallest + t) * 0.5f;
+ }
+ }
+ return t;
+}
+
+
+void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right)
+{
+ right = cur;
+ auto t = bezAt(right, at);
+ bezSplitLeft(right, t, left);
+}
+
+}
diff --git a/thirdparty/thorvg/src/lib/tvgBezier.h b/thirdparty/thorvg/src/lib/tvgBezier.h
new file mode 100644
index 0000000000..866e39148e
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgBezier.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_BEZIER_H_
+#define _TVG_BEZIER_H_
+
+#include "tvgCommon.h"
+
+namespace tvg
+{
+
+#define BEZIER_EPSILON 1e-4f
+
+struct Bezier
+{
+ Point start;
+ Point ctrl1;
+ Point ctrl2;
+ Point end;
+};
+
+void bezSplit(const Bezier&cur, Bezier& left, Bezier& right);
+float bezLength(const Bezier& cur);
+void bezSplitLeft(Bezier& cur, float at, Bezier& left);
+float bezAt(const Bezier& bz, float at);
+void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right);
+
+}
+
+#endif //_TVG_BEZIER_H_
diff --git a/thirdparty/thorvg/src/lib/tvgBinaryDesc.h b/thirdparty/thorvg/src/lib/tvgBinaryDesc.h
new file mode 100644
index 0000000000..30ba2d5a29
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgBinaryDesc.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_BINARY_DESC_H_
+#define _TVG_BINARY_DESC_H_
+
+/* TODO: Need to consider whether uin8_t is enough size for extension...
+ Rather than optimal data, we can use enough size and data compress? */
+
+/* Data types, do not change data types once Tvg Format is officially released,
+ That would occur the abi break. */
+
+using TvgBinByte = uint8_t;
+using TvgBinCounter = uint32_t;
+using TvgBinTag = TvgBinByte;
+using TvgBinFlag = TvgBinByte;
+
+
+//Header
+#define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE
+#define TVG_HEADER_SIGNATURE "ThorVG"
+#define TVG_HEADER_SIGNATURE_LENGTH 6
+#define TVG_HEADER_VERSION "000400" //Major 00, Minor 04, Micro 00
+#define TVG_HEADER_VERSION_LENGTH 6
+#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions
+#define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS
+//Compress Size
+#define TVG_HEADER_UNCOMPRESSED_SIZE 4 //SIZE (TvgBinCounter)
+#define TVG_HEADER_COMPRESSED_SIZE 4 //SIZE (TvgBinCounter)
+#define TVG_HEADER_COMPRESSED_SIZE_BITS 4 //SIZE (TvgBinCounter)
+//Reserved Flag
+#define TVG_HEAD_FLAG_COMPRESSED 0x01
+
+//Paint Type
+#define TVG_TAG_CLASS_PICTURE (TvgBinTag)0xfc
+#define TVG_TAG_CLASS_SHAPE (TvgBinTag)0xfd
+#define TVG_TAG_CLASS_SCENE (TvgBinTag)0xfe
+
+
+//Paint
+#define TVG_TAG_PAINT_OPACITY (TvgBinTag)0x10
+#define TVG_TAG_PAINT_TRANSFORM (TvgBinTag)0x11
+#define TVG_TAG_PAINT_CMP_TARGET (TvgBinTag)0x01
+#define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20
+
+
+//Scene
+#define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30
+
+
+//Shape
+#define TVG_TAG_SHAPE_PATH (TvgBinTag)0x40
+#define TVG_TAG_SHAPE_STROKE (TvgBinTag)0x41
+#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42
+#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43
+#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44
+#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50
+#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51
+
+//Stroke
+#define TVG_TAG_SHAPE_STROKE_WIDTH (TvgBinTag)0x52
+#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53
+#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54
+#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55
+
+
+//Fill
+#define TVG_TAG_FILL_LINEAR_GRADIENT (TvgBinTag)0x60
+#define TVG_TAG_FILL_RADIAL_GRADIENT (TvgBinTag)0x61
+#define TVG_TAG_FILL_COLORSTOPS (TvgBinTag)0x62
+#define TVG_TAG_FILL_FILLSPREAD (TvgBinTag)0x63
+#define TVG_TAG_FILL_TRANSFORM (TvgBinTag)0x64
+
+
+//Picture
+#define TVG_TAG_PICTURE_RAW_IMAGE (TvgBinTag)0x70
+
+#endif //_TVG_BINARY_DESC_H_
diff --git a/thirdparty/thorvg/src/lib/tvgCanvas.cpp b/thirdparty/thorvg/src/lib/tvgCanvas.cpp
new file mode 100644
index 0000000000..bb42441c62
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgCanvas.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgCanvasImpl.h"
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Canvas::Canvas(RenderMethod *pRenderer):pImpl(new Impl(pRenderer))
+{
+}
+
+
+Canvas::~Canvas()
+{
+ delete(pImpl);
+}
+
+
+Result Canvas::reserve(uint32_t n) noexcept
+{
+ if (!pImpl->paints.reserve(n)) return Result::FailedAllocation;
+ return Result::Success;
+}
+
+
+Result Canvas::push(unique_ptr<Paint> paint) noexcept
+{
+ return pImpl->push(move(paint));
+}
+
+
+Result Canvas::clear(bool free) noexcept
+{
+ return pImpl->clear(free);
+}
+
+
+Result Canvas::draw() noexcept
+{
+ return pImpl->draw();
+}
+
+
+Result Canvas::update(Paint* paint) noexcept
+{
+ return pImpl->update(paint, false);
+}
+
+
+Result Canvas::sync() noexcept
+{
+ return pImpl->sync();
+}
diff --git a/thirdparty/thorvg/src/lib/tvgCanvasImpl.h b/thirdparty/thorvg/src/lib/tvgCanvasImpl.h
new file mode 100644
index 0000000000..848c03aaed
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgCanvasImpl.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_CANVAS_IMPL_H_
+#define _TVG_CANVAS_IMPL_H_
+
+#include "tvgPaint.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct Canvas::Impl
+{
+ Array<Paint*> paints;
+ RenderMethod* renderer;
+ bool refresh = false; //if all paints should be updated by force.
+ bool drawing = false; //on drawing condition?
+
+ Impl(RenderMethod* pRenderer):renderer(pRenderer)
+ {
+ }
+
+ ~Impl()
+ {
+ clear(true);
+ delete(renderer);
+ }
+
+ Result push(unique_ptr<Paint> paint)
+ {
+ //You can not push paints during rendering.
+ if (drawing) return Result::InsufficientCondition;
+
+ auto p = paint.release();
+ if (!p) return Result::MemoryCorruption;
+ paints.push(p);
+
+ return update(p, true);
+ }
+
+ Result clear(bool free)
+ {
+ //Clear render target before drawing
+ if (!renderer || !renderer->clear()) return Result::InsufficientCondition;
+
+ //Free paints
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ (*paint)->pImpl->dispose(*renderer);
+ if (free) delete(*paint);
+ }
+
+ paints.clear();
+
+ drawing = false;
+
+ return Result::Success;
+ }
+
+ void needRefresh()
+ {
+ refresh = true;
+ }
+
+ Result update(Paint* paint, bool force)
+ {
+ if (paints.count == 0 || drawing || !renderer) return Result::InsufficientCondition;
+
+ Array<RenderData> clips;
+ auto flag = RenderUpdateFlag::None;
+ if (refresh || force) flag = RenderUpdateFlag::All;
+
+ //Update single paint node
+ if (paint) {
+ //Optimize Me: Can we skip the searching?
+ for (auto paint2 = paints.data; paint2 < (paints.data + paints.count); ++paint2) {
+ if ((*paint2) == paint) {
+ paint->pImpl->update(*renderer, nullptr, 255, clips, flag);
+ return Result::Success;
+ }
+ }
+ return Result::InvalidArguments;
+ //Update all retained paint nodes
+ } else {
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ (*paint)->pImpl->update(*renderer, nullptr, 255, clips, flag);
+ }
+ }
+
+ refresh = false;
+
+ return Result::Success;
+ }
+
+ Result draw()
+ {
+ if (drawing || paints.count == 0 || !renderer || !renderer->preRender()) return Result::InsufficientCondition;
+
+ bool rendered = false;
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ if ((*paint)->pImpl->render(*renderer)) rendered = true;
+ }
+
+ if (!rendered || !renderer->postRender()) return Result::InsufficientCondition;
+
+ drawing = true;
+
+ return Result::Success;
+ }
+
+ Result sync()
+ {
+ if (!drawing) return Result::InsufficientCondition;
+
+ if (renderer->sync()) {
+ drawing = false;
+ return Result::Success;
+ }
+
+ return Result::InsufficientCondition;
+ }
+};
+
+#endif /* _TVG_CANVAS_IMPL_H_ */
diff --git a/thirdparty/thorvg/src/lib/tvgCommon.h b/thirdparty/thorvg/src/lib/tvgCommon.h
new file mode 100644
index 0000000000..b1c586188c
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgCommon.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_COMMON_H_
+#define _TVG_COMMON_H_
+
+#include "config.h"
+#include "thorvg.h"
+
+using namespace std;
+using namespace tvg;
+
+//for MSVC Compat
+#ifdef _MSC_VER
+ #define TVG_UNUSED
+ #define strncasecmp _strnicmp
+ #define strcasecmp _stricmp
+#else
+ #define TVG_UNUSED __attribute__ ((__unused__))
+#endif
+
+// Portable 'fallthrough' attribute
+#if __has_cpp_attribute(fallthrough)
+ #ifdef _MSC_VER
+ #define TVG_FALLTHROUGH [[fallthrough]];
+ #else
+ #define TVG_FALLTHROUGH __attribute__ ((fallthrough));
+ #endif
+#else
+ #define TVG_FALLTHROUGH
+#endif
+
+#if defined(_MSC_VER) && defined(__clang__)
+ #define strncpy strncpy_s
+ #define strdup _strdup
+#endif
+
+//TVG class identifier values
+#define TVG_CLASS_ID_UNDEFINED 0
+#define TVG_CLASS_ID_SHAPE 1
+#define TVG_CLASS_ID_SCENE 2
+#define TVG_CLASS_ID_PICTURE 3
+#define TVG_CLASS_ID_LINEAR 4
+#define TVG_CLASS_ID_RADIAL 5
+
+enum class FileType { Tvg = 0, Svg, Raw, Png, Jpg, Unknown };
+
+#ifdef THORVG_LOG_ENABLED
+ #define TVGLOG(tag, fmt, ...) fprintf(stdout, tag ": " fmt "\n", ##__VA_ARGS__) //Log Message for notifying user some useful info
+ #define TVGERR(tag, fmt, ...) fprintf(stderr, tag ": " fmt "\n", ##__VA_ARGS__) //Error Message for us to fix it
+#else
+ #define TVGERR(...)
+ #define TVGLOG(...)
+#endif
+
+uint16_t THORVG_VERSION_NUMBER();
+
+#endif //_TVG_COMMON_H_
diff --git a/thirdparty/thorvg/src/lib/tvgFill.cpp b/thirdparty/thorvg/src/lib/tvgFill.cpp
new file mode 100644
index 0000000000..4bfb93c102
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgFill.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgFill.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Fill::Fill():pImpl(new Impl())
+{
+}
+
+
+Fill::~Fill()
+{
+ delete(pImpl);
+}
+
+
+Result Fill::colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept
+{
+ if ((!colorStops && cnt > 0) || (colorStops && cnt == 0)) return Result::InvalidArguments;
+
+ if (cnt == 0) {
+ if (pImpl->colorStops) {
+ free(pImpl->colorStops);
+ pImpl->colorStops = nullptr;
+ pImpl->cnt = 0;
+ }
+ return Result::Success;
+ }
+
+ if (pImpl->cnt != cnt) {
+ pImpl->colorStops = static_cast<ColorStop*>(realloc(pImpl->colorStops, cnt * sizeof(ColorStop)));
+ }
+
+ pImpl->cnt = cnt;
+ memcpy(pImpl->colorStops, colorStops, cnt * sizeof(ColorStop));
+
+ return Result::Success;
+}
+
+
+uint32_t Fill::colorStops(const ColorStop** colorStops) const noexcept
+{
+ if (colorStops) *colorStops = pImpl->colorStops;
+
+ return pImpl->cnt;
+}
+
+
+Result Fill::spread(FillSpread s) noexcept
+{
+ pImpl->spread = s;
+
+ return Result::Success;
+}
+
+
+FillSpread Fill::spread() const noexcept
+{
+ return pImpl->spread;
+}
+
+
+Result Fill::transform(const Matrix& m) noexcept
+{
+ if (!pImpl->transform) {
+ pImpl->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
+ }
+ *pImpl->transform = m;
+ return Result::Success;
+}
+
+
+Matrix Fill::transform() const noexcept
+{
+ if (pImpl->transform) return *pImpl->transform;
+ return {1, 0, 0, 0, 1, 0, 0, 0, 1};
+}
+
+
+Fill* Fill::duplicate() const noexcept
+{
+ return pImpl->duplicate();
+}
+
+uint32_t Fill::identifier() const noexcept
+{
+ return pImpl->id;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgFill.h b/thirdparty/thorvg/src/lib/tvgFill.h
new file mode 100644
index 0000000000..912091f8cb
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgFill.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_FILL_H_
+#define _TVG_FILL_H_
+
+#include <cstdlib>
+#include <cstring>
+#include "tvgCommon.h"
+
+template<typename T>
+struct DuplicateMethod
+{
+ virtual ~DuplicateMethod() {}
+ virtual T* duplicate() = 0;
+};
+
+template<class T>
+struct FillDup : DuplicateMethod<Fill>
+{
+ T* inst = nullptr;
+
+ FillDup(T* _inst) : inst(_inst) {}
+ ~FillDup() {}
+
+ Fill* duplicate() override
+ {
+ return inst->duplicate();
+ }
+};
+
+struct Fill::Impl
+{
+ ColorStop* colorStops = nullptr;
+ Matrix* transform = nullptr;
+ uint32_t cnt = 0;
+ FillSpread spread;
+ DuplicateMethod<Fill>* dup = nullptr;
+ uint32_t id;
+
+ ~Impl()
+ {
+ if (dup) delete(dup);
+ free(colorStops);
+ free(transform);
+ }
+
+ void method(DuplicateMethod<Fill>* dup)
+ {
+ this->dup = dup;
+ }
+
+ Fill* duplicate()
+ {
+ auto ret = dup->duplicate();
+ if (!ret) return nullptr;
+
+ ret->pImpl->cnt = cnt;
+ ret->pImpl->spread = spread;
+ ret->pImpl->colorStops = static_cast<ColorStop*>(malloc(sizeof(ColorStop) * cnt));
+ memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt);
+ if (transform) {
+ ret->pImpl->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
+ *ret->pImpl->transform = *transform;
+ }
+ return ret;
+ }
+};
+
+#endif //_TVG_FILL_H_
diff --git a/thirdparty/thorvg/src/lib/tvgGlCanvas.cpp b/thirdparty/thorvg/src/lib/tvgGlCanvas.cpp
new file mode 100644
index 0000000000..3a88b6d799
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgGlCanvas.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgCanvasImpl.h"
+
+#ifdef THORVG_GL_RASTER_SUPPORT
+ #include "tvgGlRenderer.h"
+#else
+ class GlRenderer : public RenderMethod
+ {
+ //Non Supported. Dummy Class */
+ };
+#endif
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct GlCanvas::Impl
+{
+};
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+#ifdef THORVG_GL_RASTER_SUPPORT
+GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(new Impl)
+#else
+GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(new Impl)
+#endif
+{
+}
+
+
+
+GlCanvas::~GlCanvas()
+{
+ delete(pImpl);
+}
+
+
+Result GlCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept
+{
+#ifdef THORVG_GL_RASTER_SUPPORT
+ //We know renderer type, avoid dynamic_cast for performance.
+ auto renderer = static_cast<GlRenderer*>(Canvas::pImpl->renderer);
+ if (!renderer) return Result::MemoryCorruption;
+
+ if (!renderer->target(buffer, stride, w, h)) return Result::Unknown;
+
+ //Paints must be updated again with this new target.
+ Canvas::pImpl->needRefresh();
+
+ return Result::Success;
+#endif
+ return Result::NonSupport;
+}
+
+
+unique_ptr<GlCanvas> GlCanvas::gen() noexcept
+{
+#ifdef THORVG_GL_RASTER_SUPPORT
+ if (GlRenderer::init() <= 0) return nullptr;
+ return unique_ptr<GlCanvas>(new GlCanvas);
+#endif
+ return nullptr;
+}
diff --git a/thirdparty/thorvg/src/lib/tvgInitializer.cpp b/thirdparty/thorvg/src/lib/tvgInitializer.cpp
new file mode 100644
index 0000000000..83ec50be95
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgInitializer.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgCommon.h"
+#include "tvgTaskScheduler.h"
+#include "tvgLoader.h"
+
+#ifdef _WIN32
+ #include <cstring>
+#endif
+
+#ifdef THORVG_SW_RASTER_SUPPORT
+ #include "tvgSwRenderer.h"
+#endif
+
+#ifdef THORVG_GL_RASTER_SUPPORT
+ #include "tvgGlRenderer.h"
+#endif
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static int _initCnt = 0;
+static uint16_t _version = 0;
+
+
+static bool _buildVersionInfo()
+{
+ auto SRC = THORVG_VERSION_STRING; //ex) 0.3.99
+ auto p = SRC;
+ const char* x;
+
+ char major[3];
+ x = strchr(p, '.');
+ if (!x) return false;
+ memcpy(major, p, x - p);
+ major[x - p] = '\0';
+ p = x + 1;
+
+ char minor[3];
+ x = strchr(p, '.');
+ if (!x) return false;
+ memcpy(minor, p, x - p);
+ minor[x - p] = '\0';
+ p = x + 1;
+
+ char micro[3];
+ x = SRC + strlen(THORVG_VERSION_STRING);
+ memcpy(micro, p, x - p);
+ micro[x - p] = '\0';
+
+ char sum[7];
+ snprintf(sum, sizeof(sum), "%s%s%s", major, minor, micro);
+
+ _version = atoi(sum);
+
+ return true;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept
+{
+ auto nonSupport = true;
+
+ if (static_cast<uint32_t>(engine) & static_cast<uint32_t>(CanvasEngine::Sw)) {
+ #ifdef THORVG_SW_RASTER_SUPPORT
+ if (!SwRenderer::init(threads)) return Result::FailedAllocation;
+ nonSupport = false;
+ #endif
+ } else if (static_cast<uint32_t>(engine) & static_cast<uint32_t>(CanvasEngine::Gl)) {
+ #ifdef THORVG_GL_RASTER_SUPPORT
+ if (!GlRenderer::init(threads)) return Result::FailedAllocation;
+ nonSupport = false;
+ #endif
+ } else {
+ return Result::InvalidArguments;
+ }
+
+ if (nonSupport) return Result::NonSupport;
+
+ if (_initCnt++ > 0) return Result::Success;
+
+ if (!_buildVersionInfo()) return Result::Unknown;
+
+ if (!LoaderMgr::init()) return Result::Unknown;
+
+ TaskScheduler::init(threads);
+
+ return Result::Success;
+}
+
+
+Result Initializer::term(CanvasEngine engine) noexcept
+{
+ if (_initCnt == 0) return Result::InsufficientCondition;
+
+ auto nonSupport = true;
+
+ if (static_cast<uint32_t>(engine) & static_cast<uint32_t>(CanvasEngine::Sw)) {
+ #ifdef THORVG_SW_RASTER_SUPPORT
+ if (!SwRenderer::term()) return Result::InsufficientCondition;
+ nonSupport = false;
+ #endif
+ } else if (static_cast<uint32_t>(engine) & static_cast<uint32_t>(CanvasEngine::Gl)) {
+ #ifdef THORVG_GL_RASTER_SUPPORT
+ if (!GlRenderer::term()) return Result::InsufficientCondition;
+ nonSupport = false;
+ #endif
+ } else {
+ return Result::InvalidArguments;
+ }
+
+ if (nonSupport) return Result::NonSupport;
+
+ if (--_initCnt > 0) return Result::Success;
+
+ TaskScheduler::term();
+
+ if (!LoaderMgr::term()) return Result::Unknown;
+
+ return Result::Success;
+}
+
+
+uint16_t THORVG_VERSION_NUMBER()
+{
+ return _version;
+}
diff --git a/thirdparty/thorvg/src/lib/tvgIteratorAccessor.h b/thirdparty/thorvg/src/lib/tvgIteratorAccessor.h
new file mode 100644
index 0000000000..10b55a536d
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgIteratorAccessor.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_ITERATOR_ACCESSOR_H_
+#define _TVG_ITERATOR_ACCESSOR_H_
+
+#include "tvgPaint.h"
+
+namespace tvg
+{
+
+class IteratorAccessor
+{
+public:
+ //Utility Method: Iterator Accessor
+ Iterator* iterator(const Paint* paint)
+ {
+ return paint->pImpl->iterator();
+ }
+};
+
+}
+
+#endif //_TVG_ITERATOR_ACCESSOR_H_
diff --git a/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp b/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp
new file mode 100644
index 0000000000..6ec7ddab20
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <float.h>
+#include <math.h>
+#include "tvgFill.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct LinearGradient::Impl
+{
+ float x1 = 0;
+ float y1 = 0;
+ float x2 = 0;
+ float y2 = 0;
+
+ Fill* duplicate()
+ {
+ auto ret = LinearGradient::gen();
+ if (!ret) return nullptr;
+
+ ret->pImpl->x1 = x1;
+ ret->pImpl->y1 = y1;
+ ret->pImpl->x2 = x2;
+ ret->pImpl->y2 = y2;
+
+ return ret.release();
+ }
+};
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+LinearGradient::LinearGradient():pImpl(new Impl())
+{
+ Fill::pImpl->id = TVG_CLASS_ID_LINEAR;
+ Fill::pImpl->method(new FillDup<LinearGradient::Impl>(pImpl));
+}
+
+
+LinearGradient::~LinearGradient()
+{
+ delete(pImpl);
+}
+
+
+Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept
+{
+ pImpl->x1 = x1;
+ pImpl->y1 = y1;
+ pImpl->x2 = x2;
+ pImpl->y2 = y2;
+
+ return Result::Success;
+}
+
+
+Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept
+{
+ if (x1) *x1 = pImpl->x1;
+ if (x2) *x2 = pImpl->x2;
+ if (y1) *y1 = pImpl->y1;
+ if (y2) *y2 = pImpl->y2;
+
+ return Result::Success;
+}
+
+
+unique_ptr<LinearGradient> LinearGradient::gen() noexcept
+{
+ return unique_ptr<LinearGradient>(new LinearGradient);
+}
+
+
+uint32_t LinearGradient::identifier() noexcept
+{
+ return TVG_CLASS_ID_LINEAR;
+}
diff --git a/thirdparty/thorvg/src/lib/tvgLoadModule.h b/thirdparty/thorvg/src/lib/tvgLoadModule.h
new file mode 100644
index 0000000000..0c34ecbf6a
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgLoadModule.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_LOAD_MODULE_H_
+#define _TVG_LOAD_MODULE_H_
+
+#include "tvgRender.h"
+
+namespace tvg
+{
+
+class LoadModule
+{
+public:
+ //default view box, if any.
+ float vx = 0;
+ float vy = 0;
+ float vw = 0;
+ float vh = 0;
+ float w = 0, h = 0; //default image size
+ bool preserveAspect = true; //keep aspect ratio by default.
+
+ virtual ~LoadModule() {}
+
+ virtual bool open(const string& path) { return false; }
+ virtual bool open(const char* data, uint32_t size, bool copy) { return false; }
+ virtual bool open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) { return false; }
+
+ //Override this if the vector-format has own resizing policy.
+ virtual bool resize(Paint* paint, float w, float h) { return false; }
+
+ virtual bool read() = 0;
+ virtual bool close() = 0;
+ virtual unique_ptr<Surface> bitmap() { return nullptr; }
+ virtual unique_ptr<Paint> paint() { return nullptr; }
+};
+
+}
+
+#endif //_TVG_LOAD_MODULE_H_
diff --git a/thirdparty/thorvg/src/lib/tvgLoader.cpp b/thirdparty/thorvg/src/lib/tvgLoader.cpp
new file mode 100644
index 0000000000..fb93e0faec
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgLoader.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgLoader.h"
+
+#ifdef THORVG_SVG_LOADER_SUPPORT
+ #include "tvgSvgLoader.h"
+#endif
+
+#ifdef THORVG_PNG_LOADER_SUPPORT
+ #include "tvgPngLoader.h"
+#endif
+
+#ifdef THORVG_TVG_LOADER_SUPPORT
+ #include "tvgTvgLoader.h"
+#endif
+
+#ifdef THORVG_JPG_LOADER_SUPPORT
+ #include "tvgJpgLoader.h"
+#endif
+
+#include "tvgRawLoader.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static LoadModule* _find(FileType type)
+{
+ switch(type) {
+ case FileType::Tvg: {
+#ifdef THORVG_TVG_LOADER_SUPPORT
+ return new TvgLoader;
+#endif
+ break;
+ }
+ case FileType::Svg: {
+#ifdef THORVG_SVG_LOADER_SUPPORT
+ return new SvgLoader;
+#endif
+ break;
+ }
+ case FileType::Raw: {
+ return new RawLoader;
+ break;
+ }
+ case FileType::Png: {
+#ifdef THORVG_PNG_LOADER_SUPPORT
+ return new PngLoader;
+#endif
+ break;
+ }
+ case FileType::Jpg: {
+#ifdef THORVG_JPG_LOADER_SUPPORT
+ return new JpgLoader;
+#endif
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+#ifdef THORVG_LOG_ENABLED
+ const char *format;
+ switch(type) {
+ case FileType::Tvg: {
+ format = "TVG";
+ break;
+ }
+ case FileType::Svg: {
+ format = "SVG";
+ break;
+ }
+ case FileType::Raw: {
+ format = "RAW";
+ break;
+ }
+ case FileType::Png: {
+ format = "PNG";
+ break;
+ }
+ case FileType::Jpg: {
+ format = "JPG";
+ break;
+ }
+ default: {
+ format = "???";
+ break;
+ }
+ }
+ TVGLOG("LOADER", "%s format is not supported", format);
+#endif
+ return nullptr;
+}
+
+
+static LoadModule* _findByPath(const string& path)
+{
+ auto ext = path.substr(path.find_last_of(".") + 1);
+ if (!ext.compare("tvg")) return _find(FileType::Tvg);
+ if (!ext.compare("svg")) return _find(FileType::Svg);
+ if (!ext.compare("png")) return _find(FileType::Png);
+ if (!ext.compare("jpg")) return _find(FileType::Jpg);
+ return nullptr;
+}
+
+
+static LoadModule* _findByType(const string& mimeType)
+{
+ if (mimeType.empty()) return nullptr;
+
+ auto type = FileType::Unknown;
+
+ if (mimeType == "tvg") type = FileType::Tvg;
+ else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg;
+ else if (mimeType == "raw") type = FileType::Raw;
+ else if (mimeType == "png") type = FileType::Png;
+ else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg;
+ else {
+ TVGLOG("LOADER", "Given mimetype is unknown = \"%s\".", mimeType.c_str());
+ return nullptr;
+ }
+
+ return _find(type);
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+bool LoaderMgr::init()
+{
+ //TODO:
+
+ return true;
+}
+
+
+bool LoaderMgr::term()
+{
+ //TODO:
+
+ return true;
+}
+
+
+shared_ptr<LoadModule> LoaderMgr::loader(const string& path, bool* invalid)
+{
+ *invalid = false;
+
+ if (auto loader = _findByPath(path)) {
+ if (loader->open(path)) return shared_ptr<LoadModule>(loader);
+ else delete(loader);
+ *invalid = true;
+ }
+ return nullptr;
+}
+
+
+shared_ptr<LoadModule> LoaderMgr::loader(const char* data, uint32_t size, const string& mimeType, bool copy)
+{
+ //Try first with the given MimeType
+ if (auto loader = _findByType(mimeType)) {
+ if (loader->open(data, size, copy)) {
+ return shared_ptr<LoadModule>(loader);
+ } else {
+ TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported. Will try again with other types.", mimeType.c_str());
+ delete(loader);
+ }
+ }
+
+ //Abnormal MimeType. Try with the candidates in the order
+ for (int i = 0; i < static_cast<int>(FileType::Unknown); i++) {
+ auto loader = _find(static_cast<FileType>(i));
+ if (loader) {
+ if (loader->open(data, size, copy)) return shared_ptr<LoadModule>(loader);
+ else delete(loader);
+ }
+ }
+ return nullptr;
+}
+
+
+shared_ptr<LoadModule> LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool copy)
+{
+ //function is dedicated for raw images only
+ auto loader = new RawLoader;
+ if (loader->open(data, w, h, copy)) return shared_ptr<LoadModule>(loader);
+ else delete(loader);
+
+ return nullptr;
+}
diff --git a/thirdparty/thorvg/src/lib/tvgLoader.h b/thirdparty/thorvg/src/lib/tvgLoader.h
new file mode 100644
index 0000000000..8ba3c139fa
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgLoader.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_LOADER_H_
+#define _TVG_LOADER_H_
+
+#include "tvgLoadModule.h"
+
+struct LoaderMgr
+{
+ static bool init();
+ static bool term();
+ static shared_ptr<LoadModule> loader(const string& path, bool* invalid);
+ static shared_ptr<LoadModule> loader(const char* data, uint32_t size, const string& mimeType, bool copy);
+ static shared_ptr<LoadModule> loader(const uint32_t* data, uint32_t w, uint32_t h, bool copy);
+};
+
+#endif //_TVG_LOADER_H_
diff --git a/thirdparty/thorvg/src/lib/tvgLzw.cpp b/thirdparty/thorvg/src/lib/tvgLzw.cpp
new file mode 100644
index 0000000000..0049c89962
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgLzw.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Lempel–Ziv–Welch (LZW) encoder/decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com)
+
+ * This is the compression scheme used by the GIF image format and the Unix 'compress' tool.
+ * Main differences from this implementation is that End Of Input (EOI) and Clear Codes (CC)
+ * are not stored in the output and the max code length in bits is 12, vs 16 in compress.
+ *
+ * EOI is simply detected by the end of the data stream, while CC happens if the
+ * dictionary gets filled. Data is written/read from bit streams, which handle
+ * byte-alignment for us in a transparent way.
+
+ * The decoder relies on the hardcoded data layout produced by the encoder, since
+ * no additional reconstruction data is added to the output, so they must match.
+ * The nice thing about LZW is that we can reconstruct the dictionary directly from
+ * the stream of codes generated by the encoder, so this avoids storing additional
+ * headers in the bit stream.
+
+ * The output code length is variable. It starts with the minimum number of bits
+ * required to store the base byte-sized dictionary and automatically increases
+ * as the dictionary gets larger (it starts at 9-bits and grows to 10-bits when
+ * code 512 is added, then 11-bits when 1024 is added, and so on). If the dictionary
+ * is filled (4096 items for a 12-bits dictionary), the whole thing is cleared and
+ * the process starts over. This is the main reason why the encoder and the decoder
+ * must match perfectly, since the lengths of the codes will not be specified with
+ * the data itself.
+
+ * USEFUL LINKS:
+ * https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
+ * http://rosettacode.org/wiki/LZW_compression
+ * http://www.cs.duke.edu/csed/curious/compression/lzw.html
+ * http://www.cs.cf.ac.uk/Dave/Multimedia/node214.html
+ * http://marknelson.us/1989/10/01/lzw-data-compression/
+ */
+#include "config.h"
+
+#if defined(THORVG_TVG_SAVER_SUPPORT) || defined(THORVG_TVG_LOADER_SUPPORT)
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+#include <string>
+#include <memory.h>
+#include "tvgLzw.h"
+
+namespace {
+//LZW Dictionary helper:
+constexpr int Nil = -1;
+constexpr int MaxDictBits = 12;
+constexpr int StartBits = 9;
+constexpr int FirstCode = (1 << (StartBits - 1)); // 256
+constexpr int MaxDictEntries = (1 << MaxDictBits); // 4096
+
+
+//Round up to the next power-of-two number, e.g. 37 => 64
+static int nextPowerOfTwo(int num)
+{
+ --num;
+ for (size_t i = 1; i < sizeof(num) * 8; i <<= 1) {
+ num = num | num >> i;
+ }
+ return ++num;
+}
+
+
+struct BitStreamWriter
+{
+ uint8_t* stream; //Growable buffer to store our bits. Heap allocated & owned by the class instance.
+ int bytesAllocated; //Current size of heap-allocated stream buffer *in bytes*.
+ int granularity; //Amount bytesAllocated multiplies by when auto-resizing in appendBit().
+ int currBytePos; //Current byte being written to, from 0 to bytesAllocated-1.
+ int nextBitPos; //Bit position within the current byte to access next. 0 to 7.
+ int numBitsWritten; //Number of bits in use from the stream buffer, not including byte-rounding padding.
+
+ void internalInit()
+ {
+ stream = nullptr;
+ bytesAllocated = 0;
+ granularity = 2;
+ currBytePos = 0;
+ nextBitPos = 0;
+ numBitsWritten = 0;
+ }
+
+ uint8_t* allocBytes(const int bytesWanted, uint8_t * oldPtr, const int oldSize)
+ {
+ auto newMemory = static_cast<uint8_t *>(malloc(bytesWanted));
+ memset(newMemory, 0, bytesWanted);
+
+ if (oldPtr) {
+ memcpy(newMemory, oldPtr, oldSize);
+ free(oldPtr);
+ }
+ return newMemory;
+ }
+
+ BitStreamWriter()
+ {
+ /* 8192 bits for a start (1024 bytes). It will resize if needed.
+ Default granularity is 2. */
+ internalInit();
+ allocate(8192);
+ }
+
+ BitStreamWriter(const int initialSizeInBits, const int growthGranularity = 2)
+ {
+ internalInit();
+ setGranularity(growthGranularity);
+ allocate(initialSizeInBits);
+ }
+
+ ~BitStreamWriter()
+ {
+ free(stream);
+ }
+
+ void allocate(int bitsWanted)
+ {
+ //Require at least a byte.
+ if (bitsWanted <= 0) bitsWanted = 8;
+
+ //Round upwards if needed:
+ if ((bitsWanted % 8) != 0) bitsWanted = nextPowerOfTwo(bitsWanted);
+
+ //We might already have the required count.
+ const int sizeInBytes = bitsWanted / 8;
+ if (sizeInBytes <= bytesAllocated) return;
+
+ stream = allocBytes(sizeInBytes, stream, bytesAllocated);
+ bytesAllocated = sizeInBytes;
+ }
+
+ void appendBit(const int bit)
+ {
+ const uint32_t mask = uint32_t(1) << nextBitPos;
+ stream[currBytePos] = (stream[currBytePos] & ~mask) | (-bit & mask);
+ ++numBitsWritten;
+
+ if (++nextBitPos == 8) {
+ nextBitPos = 0;
+ if (++currBytePos == bytesAllocated) allocate(bytesAllocated * granularity * 8);
+ }
+ }
+
+ void appendBitsU64(const uint64_t num, const int bitCount)
+ {
+ for (int b = 0; b < bitCount; ++b) {
+ const uint64_t mask = uint64_t(1) << b;
+ const int bit = !!(num & mask);
+ appendBit(bit);
+ }
+ }
+
+ uint8_t* release()
+ {
+ auto oldPtr = stream;
+ internalInit();
+ return oldPtr;
+ }
+
+ void setGranularity(const int growthGranularity)
+ {
+ granularity = (growthGranularity >= 2) ? growthGranularity : 2;
+ }
+
+ int getByteCount() const
+ {
+ int usedBytes = numBitsWritten / 8;
+ int leftovers = numBitsWritten % 8;
+ if (leftovers != 0) ++usedBytes;
+ return usedBytes;
+ }
+};
+
+
+struct BitStreamReader
+{
+ const uint8_t* stream; // Pointer to the external bit stream. Not owned by the reader.
+ const int sizeInBytes; // Size of the stream *in bytes*. Might include padding.
+ const int sizeInBits; // Size of the stream *in bits*, padding *not* include.
+ int currBytePos = 0; // Current byte being read in the stream.
+ int nextBitPos = 0; // Bit position within the current byte to access next. 0 to 7.
+ int numBitsRead = 0; // Total bits read from the stream so far. Never includes byte-rounding padding.
+
+ BitStreamReader(const uint8_t* bitStream, const int byteCount, const int bitCount) : stream(bitStream), sizeInBytes(byteCount), sizeInBits(bitCount)
+ {
+ }
+
+ bool readNextBit(int& bitOut)
+ {
+ if (numBitsRead >= sizeInBits) return false; //We are done.
+
+ const uint32_t mask = uint32_t(1) << nextBitPos;
+ bitOut = !!(stream[currBytePos] & mask);
+ ++numBitsRead;
+
+ if (++nextBitPos == 8) {
+ nextBitPos = 0;
+ ++currBytePos;
+ }
+ return true;
+ }
+
+ uint64_t readBitsU64(const int bitCount)
+ {
+ uint64_t num = 0;
+ for (int b = 0; b < bitCount; ++b) {
+ int bit;
+ if (!readNextBit(bit)) break;
+ /* Based on a "Stanford bit-hack":
+ http://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching */
+ const uint64_t mask = uint64_t(1) << b;
+ num = (num & ~mask) | (-bit & mask);
+ }
+ return num;
+ }
+
+ bool isEndOfStream() const
+ {
+ return numBitsRead >= sizeInBits;
+ }
+};
+
+
+struct Dictionary
+{
+ struct Entry
+ {
+ int code;
+ int value;
+ };
+
+ //Dictionary entries 0-255 are always reserved to the byte/ASCII range.
+ int size;
+ Entry entries[MaxDictEntries];
+
+ Dictionary()
+ {
+ /* First 256 dictionary entries are reserved to the byte/ASCII range.
+ Additional entries follow for the character sequences found in the input.
+ Up to 4096 - 256 (MaxDictEntries - FirstCode). */
+ size = FirstCode;
+
+ for (int i = 0; i < size; ++i) {
+ entries[i].code = Nil;
+ entries[i].value = i;
+ }
+ }
+
+ int findIndex(const int code, const int value) const
+ {
+ if (code == Nil) return value;
+
+ //Linear search for now.
+ //TODO: Worth optimizing with a proper hash-table?
+ for (int i = 0; i < size; ++i) {
+ if (entries[i].code == code && entries[i].value == value) return i;
+ }
+ return Nil;
+ }
+
+ bool add(const int code, const int value)
+ {
+ if (size == MaxDictEntries) return false;
+ entries[size].code = code;
+ entries[size].value = value;
+ ++size;
+ return true;
+ }
+
+ bool flush(int & codeBitsWidth)
+ {
+ if (size == (1 << codeBitsWidth)) {
+ ++codeBitsWidth;
+ if (codeBitsWidth > MaxDictBits) {
+ //Clear the dictionary (except the first 256 byte entries).
+ codeBitsWidth = StartBits;
+ size = FirstCode;
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+
+static bool outputByte(int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar)
+{
+ if (bytesDecodedSoFar >= outputSizeBytes) return false;
+ *output++ = static_cast<uint8_t>(code);
+ ++bytesDecodedSoFar;
+ return true;
+}
+
+
+static bool outputSequence(const Dictionary& dict, int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar, int& firstByte)
+{
+ /* A sequence is stored backwards, so we have to write
+ it to a temp then output the buffer in reverse. */
+ int i = 0;
+ uint8_t sequence[MaxDictEntries];
+
+ do {
+ sequence[i++] = dict.entries[code].value;
+ code = dict.entries[code].code;
+ } while (code >= 0);
+
+ firstByte = sequence[--i];
+
+ for (; i >= 0; --i) {
+ if (!outputByte(sequence[i], output, outputSizeBytes, bytesDecodedSoFar)) return false;
+ }
+ return true;
+}
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+namespace tvg {
+
+uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes)
+{
+ int code = Nil;
+ int prevCode = Nil;
+ int firstByte = 0;
+ int bytesDecoded = 0;
+ int codeBitsWidth = StartBits;
+ auto uncompressed = (uint8_t*) malloc(sizeof(uint8_t) * uncompressedSizeBytes);
+ auto ptr = uncompressed;
+
+ /* We'll reconstruct the dictionary based on the bit stream codes.
+ Unlike Huffman encoding, we don't store the dictionary as a prefix to the data. */
+ Dictionary dictionary;
+ BitStreamReader bitStream(compressed, compressedSizeBytes, compressedSizeBits);
+
+ /* We check to avoid an overflow of the user buffer.
+ If the buffer is smaller than the decompressed size, we break the loop and return the current decompression count. */
+ while (!bitStream.isEndOfStream()) {
+ code = static_cast<int>(bitStream.readBitsU64(codeBitsWidth));
+
+ if (prevCode == Nil) {
+ if (!outputByte(code, ptr, uncompressedSizeBytes, bytesDecoded)) break;
+ firstByte = code;
+ prevCode = code;
+ continue;
+ }
+ if (code >= dictionary.size) {
+ if (!outputSequence(dictionary, prevCode, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break;
+ if (!outputByte(firstByte, ptr, uncompressedSizeBytes, bytesDecoded)) break;
+ } else if (!outputSequence(dictionary, code, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break;
+
+ dictionary.add(prevCode, firstByte);
+ if (dictionary.flush(codeBitsWidth)) prevCode = Nil;
+ else prevCode = code;
+ }
+
+ return uncompressed;
+}
+
+
+uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits)
+{
+ //LZW encoding context:
+ int code = Nil;
+ int codeBitsWidth = StartBits;
+ Dictionary dictionary;
+
+ //Output bit stream we write to. This will allocate memory as needed to accommodate the encoded data.
+ BitStreamWriter bitStream;
+
+ for (; uncompressedSizeBytes > 0; --uncompressedSizeBytes, ++uncompressed) {
+ const int value = *uncompressed;
+ const int index = dictionary.findIndex(code, value);
+
+ if (index != Nil) {
+ code = index;
+ continue;
+ }
+
+ //Write the dictionary code using the minimum bit-with:
+ bitStream.appendBitsU64(code, codeBitsWidth);
+
+ //Flush it when full so we can restart the sequences.
+ if (!dictionary.flush(codeBitsWidth)) {
+ //There's still space for this sequence.
+ dictionary.add(code, value);
+ }
+ code = value;
+ }
+
+ //Residual code at the end:
+ if (code != Nil) bitStream.appendBitsU64(code, codeBitsWidth);
+
+ //Pass ownership of the compressed data buffer to the user pointer:
+ *compressedSizeBytes = bitStream.getByteCount();
+ *compressedSizeBits = bitStream.numBitsWritten;
+
+ return bitStream.release();
+}
+
+}
+
+#endif
diff --git a/thirdparty/thorvg/src/lib/tvgLzw.h b/thirdparty/thorvg/src/lib/tvgLzw.h
new file mode 100644
index 0000000000..3fdb439a02
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgLzw.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_LZW_H_
+#define _TVG_LZW_H_
+
+namespace tvg
+{
+ uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits);
+ uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes);
+}
+
+#endif //_TVG_LZW_H \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgMath.h b/thirdparty/thorvg/src/lib/tvgMath.h
new file mode 100644
index 0000000000..9e5c915fc3
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgMath.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_MATH_H_
+#define _TVG_MATH_H_
+
+ #define _USE_MATH_DEFINES
+
+#include <float.h>
+#include <math.h>
+#include "tvgCommon.h"
+
+
+static inline bool mathZero(float a)
+{
+ return (fabsf(a) < FLT_EPSILON) ? true : false;
+}
+
+
+static inline bool mathEqual(float a, float b)
+{
+ return (fabsf(a - b) < FLT_EPSILON);
+}
+
+
+static inline bool mathRightAngle(const Matrix* m)
+{
+ auto radian = fabsf(atan2(m->e21, m->e11));
+ if (radian < FLT_EPSILON || mathEqual(radian, float(M_PI_2)) || mathEqual(radian, float(M_PI))) return true;
+ return false;
+}
+
+
+static inline bool mathIdentity(const Matrix* m)
+{
+ if (!mathEqual(m->e11, 1.0f) || !mathZero(m->e12) || !mathZero(m->e13) ||
+ !mathZero(m->e21) || !mathEqual(m->e22, 1.0f) || !mathZero(m->e23) ||
+ !mathZero(m->e31) || !mathZero(m->e32) || !mathEqual(m->e33, 1.0f)) {
+ return false;
+ }
+ return true;
+}
+
+
+static inline bool mathInverse(const Matrix* m, Matrix* out)
+{
+ auto det = m->e11 * (m->e22 * m->e33 - m->e32 * m->e23) -
+ m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) +
+ m->e13 * (m->e21 * m->e32 - m->e22 * m->e31);
+
+ if (mathZero(det)) return false;
+
+ auto invDet = 1 / det;
+
+ out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet;
+ out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet;
+ out->e13 = (m->e12 * m->e23 - m->e13 * m->e22) * invDet;
+ out->e21 = (m->e23 * m->e31 - m->e21 * m->e33) * invDet;
+ out->e22 = (m->e11 * m->e33 - m->e13 * m->e31) * invDet;
+ out->e23 = (m->e21 * m->e13 - m->e11 * m->e23) * invDet;
+ out->e31 = (m->e21 * m->e32 - m->e31 * m->e22) * invDet;
+ out->e32 = (m->e31 * m->e12 - m->e11 * m->e32) * invDet;
+ out->e33 = (m->e11 * m->e22 - m->e21 * m->e12) * invDet;
+
+ return true;
+}
+
+
+static inline void mathIdentity(Matrix* m)
+{
+ m->e11 = 1.0f;
+ m->e12 = 0.0f;
+ m->e13 = 0.0f;
+ m->e21 = 0.0f;
+ m->e22 = 1.0f;
+ m->e23 = 0.0f;
+ m->e31 = 0.0f;
+ m->e32 = 0.0f;
+ m->e33 = 1.0f;
+}
+
+
+static inline void mathScale(Matrix* m, float scale)
+{
+ m->e11 = scale;
+ m->e22 = scale;
+}
+
+
+static inline void mathTranslate(Matrix* m, float x, float y)
+{
+ m->e13 = x;
+ m->e23 = y;
+}
+
+
+static inline void mathRotate(Matrix* m, float degree)
+{
+ auto radian = degree / 180.0f * M_PI;
+ auto cosVal = cosf(radian);
+ auto sinVal = sinf(radian);
+
+ m->e12 = m->e11 * -sinVal;
+ m->e11 *= cosVal;
+ m->e21 = m->e22 * sinVal;
+ m->e22 *= cosVal;
+}
+
+
+static inline void mathMultiply(Point* pt, const Matrix* transform)
+{
+ auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13;
+ auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23;
+ pt->x = tx;
+ pt->y = ty;
+}
+
+
+static inline Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs)
+{
+ Matrix m;
+
+ m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31;
+ m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32;
+ m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33;
+
+ m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31;
+ m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32;
+ m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33;
+
+ m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31;
+ m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32;
+ m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33;
+
+ return m;
+}
+
+
+#endif //_TVG_MATH_H_ \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgPaint.cpp b/thirdparty/thorvg/src/lib/tvgPaint.cpp
new file mode 100644
index 0000000000..30e82fbc60
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgPaint.cpp
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgMath.h"
+#include "tvgPaint.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+static bool _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport)
+{
+ /* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
+ auto shape = static_cast<Shape*>(cmpTarget);
+
+ //Rectangle Candidates?
+ const Point* pts;
+ if (shape->pathCoords(&pts) != 4) return false;
+
+ if (rTransform) rTransform->update();
+
+ //No rotational.
+ if (pTransform && !mathRightAngle(&pTransform->m)) return false;
+ if (rTransform && !mathRightAngle(&rTransform->m)) return false;
+
+ //Perpendicular Rectangle?
+ auto pt1 = pts + 0;
+ auto pt2 = pts + 1;
+ auto pt3 = pts + 2;
+ auto pt4 = pts + 3;
+
+ if ((mathEqual(pt1->x, pt2->x) && mathEqual(pt2->y, pt3->y) && mathEqual(pt3->x, pt4->x) && mathEqual(pt1->y, pt4->y)) ||
+ (mathEqual(pt2->x, pt3->x) && mathEqual(pt1->y, pt2->y) && mathEqual(pt1->x, pt4->x) && mathEqual(pt3->y, pt4->y))) {
+
+ auto v1 = *pt1;
+ auto v2 = *pt3;
+
+ if (rTransform) {
+ mathMultiply(&v1, &rTransform->m);
+ mathMultiply(&v2, &rTransform->m);
+ }
+
+ if (pTransform) {
+ mathMultiply(&v1, &pTransform->m);
+ mathMultiply(&v2, &pTransform->m);
+ }
+
+ //sorting
+ if (v1.x > v2.x) {
+ auto tmp = v2.x;
+ v2.x = v1.x;
+ v1.x = tmp;
+ }
+
+ if (v1.y > v2.y) {
+ auto tmp = v2.y;
+ v2.y = v1.y;
+ v1.y = tmp;
+ }
+
+ viewport.x = static_cast<int32_t>(v1.x);
+ viewport.y = static_cast<int32_t>(v1.y);
+ viewport.w = static_cast<int32_t>(v2.x - v1.x + 0.5f);
+ viewport.h = static_cast<int32_t>(v2.y - v1.y + 0.5f);
+
+ if (viewport.w < 0) viewport.w = 0;
+ if (viewport.h < 0) viewport.h = 0;
+
+ return true;
+ }
+
+ return false;
+}
+
+
+Paint* Paint::Impl::duplicate()
+{
+ auto ret = smethod->duplicate();
+
+ //duplicate Transform
+ if (rTransform) {
+ ret->pImpl->rTransform = new RenderTransform();
+ *ret->pImpl->rTransform = *rTransform;
+ ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
+ }
+
+ ret->pImpl->opacity = opacity;
+
+ if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
+
+ return ret;
+}
+
+
+bool Paint::Impl::rotate(float degree)
+{
+ if (rTransform) {
+ if (mathEqual(degree, rTransform->degree)) return true;
+ } else {
+ if (mathZero(degree)) return true;
+ rTransform = new RenderTransform();
+ }
+ rTransform->degree = degree;
+ if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
+
+ return true;
+}
+
+
+bool Paint::Impl::scale(float factor)
+{
+ if (rTransform) {
+ if (mathEqual(factor, rTransform->scale)) return true;
+ } else {
+ if (mathZero(factor)) return true;
+ rTransform = new RenderTransform();
+ }
+ rTransform->scale = factor;
+ if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
+
+ return true;
+}
+
+
+bool Paint::Impl::translate(float x, float y)
+{
+ if (rTransform) {
+ if (mathEqual(x, rTransform->x) && mathEqual(y, rTransform->y)) return true;
+ } else {
+ if (mathZero(x) && mathZero(y)) return true;
+ rTransform = new RenderTransform();
+ }
+ rTransform->x = x;
+ rTransform->y = y;
+ if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
+
+ return true;
+}
+
+
+bool Paint::Impl::render(RenderMethod& renderer)
+{
+ Compositor* cmp = nullptr;
+
+ //OPTIMIZE_ME: Can we replace the simple AlphaMasking with ClipPath?
+
+ /* Note: only ClipPath is processed in update() step.
+ Create a composition image. */
+ if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
+ auto region = smethod->bounds(renderer);
+ if (region.w == 0 || region.h == 0) return true;
+ cmp = renderer.target(region);
+ renderer.beginComposite(cmp, CompositeMethod::None, 255);
+ compData->target->pImpl->render(renderer);
+ }
+
+ if (cmp) renderer.beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
+
+ auto ret = smethod->render(renderer);
+
+ if (cmp) renderer.endComposite(cmp);
+
+ return ret;
+}
+
+
+void* Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, uint32_t pFlag)
+{
+ if (renderFlag & RenderUpdateFlag::Transform) {
+ if (!rTransform) return nullptr;
+ if (!rTransform->update()) {
+ delete(rTransform);
+ rTransform = nullptr;
+ }
+ }
+
+ /* 1. Composition Pre Processing */
+ void *tdata = nullptr;
+ RenderRegion viewport;
+ bool compFastTrack = false;
+
+ if (compData) {
+ auto target = compData->target;
+ auto method = compData->method;
+ target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset
+
+ /* If transform has no rotation factors && ClipPath / AlphaMasking is a simple rectangle,
+ we can avoid regular ClipPath / AlphaMasking sequence but use viewport for performance */
+ auto tryFastTrack = false;
+ if (method == CompositeMethod::ClipPath) tryFastTrack = true;
+ else if (method == CompositeMethod::AlphaMask && target->identifier() == TVG_CLASS_ID_SHAPE) {
+ auto shape = static_cast<Shape*>(target);
+ uint8_t a;
+ shape->fillColor(nullptr, nullptr, nullptr, &a);
+ if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true;
+ }
+ if (tryFastTrack) {
+ RenderRegion viewport2;
+ if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2))) {
+ viewport = renderer.viewport();
+ viewport2.intersect(viewport);
+ renderer.viewport(viewport2);
+ target->pImpl->ctxFlag |= ContextFlag::FastTrack;
+ }
+ }
+ if (!compFastTrack) {
+ tdata = target->pImpl->update(renderer, pTransform, 255, clips, pFlag);
+ if (method == CompositeMethod::ClipPath) clips.push(tdata);
+ }
+ }
+
+ /* 2. Main Update */
+ void *edata = nullptr;
+ auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
+ renderFlag = RenderUpdateFlag::None;
+ opacity = (opacity * this->opacity) / 255;
+
+ if (rTransform && pTransform) {
+ RenderTransform outTransform(pTransform, rTransform);
+ edata = smethod->update(renderer, &outTransform, opacity, clips, newFlag);
+ } else {
+ auto outTransform = pTransform ? pTransform : rTransform;
+ edata = smethod->update(renderer, outTransform, opacity, clips, newFlag);
+ }
+
+ /* 3. Composition Post Processing */
+ if (compFastTrack) renderer.viewport(viewport);
+ else if (tdata && compData->method == CompositeMethod::ClipPath) clips.pop();
+
+ return edata;
+}
+
+
+bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed)
+{
+ Matrix* m = nullptr;
+
+ //Case: No transformed, quick return!
+ if (!transformed || !(m = this->transform())) return smethod->bounds(x, y, w, h);
+
+ //Case: Transformed
+ auto tx = 0.0f;
+ auto ty = 0.0f;
+ auto tw = 0.0f;
+ auto th = 0.0f;
+
+ auto ret = smethod->bounds(&tx, &ty, &tw, &th);
+
+ //Get vertices
+ Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}};
+
+ //New bounding box
+ auto x1 = FLT_MAX;
+ auto y1 = FLT_MAX;
+ auto x2 = -FLT_MAX;
+ auto y2 = -FLT_MAX;
+
+ //Compute the AABB after transformation
+ for (int i = 0; i < 4; i++) {
+ mathMultiply(&pt[i], m);
+
+ if (pt[i].x < x1) x1 = pt[i].x;
+ if (pt[i].x > x2) x2 = pt[i].x;
+ if (pt[i].y < y1) y1 = pt[i].y;
+ if (pt[i].y > y2) y2 = pt[i].y;
+ }
+
+ if (x) *x = x1;
+ if (y) *y = y1;
+ if (w) *w = x2 - x1;
+ if (h) *h = y2 - y1;
+
+ return ret;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Paint :: Paint() : pImpl(new Impl())
+{
+}
+
+
+Paint :: ~Paint()
+{
+ delete(pImpl);
+}
+
+
+Result Paint::rotate(float degree) noexcept
+{
+ if (pImpl->rotate(degree)) return Result::Success;
+ return Result::FailedAllocation;
+}
+
+
+Result Paint::scale(float factor) noexcept
+{
+ if (pImpl->scale(factor)) return Result::Success;
+ return Result::FailedAllocation;
+}
+
+
+Result Paint::translate(float x, float y) noexcept
+{
+ if (pImpl->translate(x, y)) return Result::Success;
+ return Result::FailedAllocation;
+}
+
+
+Result Paint::transform(const Matrix& m) noexcept
+{
+ if (pImpl->transform(m)) return Result::Success;
+ return Result::FailedAllocation;
+}
+
+
+Matrix Paint::transform() noexcept
+{
+ auto pTransform = pImpl->transform();
+ if (pTransform) return *pTransform;
+ return {1, 0, 0, 0, 1, 0, 0, 0, 1};
+}
+
+
+TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept
+{
+ return this->bounds(x, y, w, h, false);
+}
+
+
+Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept
+{
+ if (pImpl->bounds(x, y, w, h, transform)) return Result::Success;
+ return Result::InsufficientCondition;
+}
+
+
+Paint* Paint::duplicate() const noexcept
+{
+ return pImpl->duplicate();
+}
+
+
+Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
+{
+ auto p = target.release();
+ if (pImpl->composite(this, p, method)) return Result::Success;
+ if (p) delete(p);
+ return Result::InvalidArguments;
+}
+
+
+CompositeMethod Paint::composite(const Paint** target) const noexcept
+{
+ if (pImpl->compData) {
+ if (target) *target = pImpl->compData->target;
+ return pImpl->compData->method;
+ } else {
+ if (target) *target = nullptr;
+ return CompositeMethod::None;
+ }
+}
+
+
+Result Paint::opacity(uint8_t o) noexcept
+{
+ if (pImpl->opacity == o) return Result::Success;
+
+ pImpl->opacity = o;
+ pImpl->renderFlag |= RenderUpdateFlag::Color;
+
+ return Result::Success;
+}
+
+
+uint8_t Paint::opacity() const noexcept
+{
+ return pImpl->opacity;
+}
+
+
+uint32_t Paint::identifier() const noexcept
+{
+ return pImpl->id;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgPaint.h b/thirdparty/thorvg/src/lib/tvgPaint.h
new file mode 100644
index 0000000000..4003bdbeac
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgPaint.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_PAINT_H_
+#define _TVG_PAINT_H_
+
+#include "tvgRender.h"
+
+
+namespace tvg
+{
+ enum ContextFlag {Invalid = 0, FastTrack = 1};
+
+ struct Iterator
+ {
+ virtual ~Iterator() {}
+ virtual const Paint* next() = 0;
+ virtual uint32_t count() = 0;
+ virtual void begin() = 0;
+ };
+
+ struct StrategyMethod
+ {
+ virtual ~StrategyMethod() {}
+
+ virtual bool dispose(RenderMethod& renderer) = 0;
+ virtual void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag) = 0; //Return engine data if it has.
+ virtual bool render(RenderMethod& renderer) = 0;
+ virtual bool bounds(float* x, float* y, float* w, float* h) = 0;
+ virtual RenderRegion bounds(RenderMethod& renderer) const = 0;
+ virtual Paint* duplicate() = 0;
+ virtual Iterator* iterator() = 0;
+ };
+
+ struct Composite
+ {
+ Paint* target;
+ Paint* source;
+ CompositeMethod method;
+ };
+
+ struct Paint::Impl
+ {
+ StrategyMethod* smethod = nullptr;
+ RenderTransform* rTransform = nullptr;
+ Composite* compData = nullptr;
+ uint32_t renderFlag = RenderUpdateFlag::None;
+ uint32_t ctxFlag = ContextFlag::Invalid;
+ uint32_t id;
+ uint8_t opacity = 255;
+
+ ~Impl()
+ {
+ if (compData) {
+ delete(compData->target);
+ free(compData);
+ }
+ if (smethod) delete(smethod);
+ if (rTransform) delete(rTransform);
+ }
+
+ void method(StrategyMethod* method)
+ {
+ smethod = method;
+ }
+
+ bool transform(const Matrix& m)
+ {
+ if (!rTransform) {
+ rTransform = new RenderTransform();
+ if (!rTransform) return false;
+ }
+ rTransform->override(m);
+ renderFlag |= RenderUpdateFlag::Transform;
+
+ return true;
+ }
+
+ Matrix* transform()
+ {
+ if (rTransform) {
+ rTransform->update();
+ return &rTransform->m;
+ }
+ return nullptr;
+ }
+
+ RenderRegion bounds(RenderMethod& renderer) const
+ {
+ return smethod->bounds(renderer);
+ }
+
+ bool dispose(RenderMethod& renderer)
+ {
+ if (compData) compData->target->pImpl->dispose(renderer);
+ return smethod->dispose(renderer);
+ }
+
+ Iterator* iterator()
+ {
+ return smethod->iterator();
+ }
+
+ bool composite(Paint* source, Paint* target, CompositeMethod method)
+ {
+ //Invalid case
+ if ((!target && method != CompositeMethod::None) || (target && method == CompositeMethod::None)) return false;
+
+ if (compData) {
+ delete(compData->target);
+ //Reset scenario
+ if (!target && method == CompositeMethod::None) {
+ free(compData);
+ compData = nullptr;
+ return true;
+ }
+ } else {
+ if (!target && method == CompositeMethod::None) return true;
+ compData = static_cast<Composite*>(calloc(1, sizeof(Composite)));
+ }
+ compData->target = target;
+ compData->source = source;
+ compData->method = method;
+ return true;
+ }
+
+ bool rotate(float degree);
+ bool scale(float factor);
+ bool translate(float x, float y);
+ bool bounds(float* x, float* y, float* w, float* h, bool transformed);
+ void* update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, uint32_t pFlag);
+ bool render(RenderMethod& renderer);
+ Paint* duplicate();
+ };
+
+
+ template<class T>
+ struct PaintMethod : StrategyMethod
+ {
+ T* inst = nullptr;
+
+ PaintMethod(T* _inst) : inst(_inst) {}
+ ~PaintMethod() {}
+
+ bool bounds(float* x, float* y, float* w, float* h) override
+ {
+ return inst->bounds(x, y, w, h);
+ }
+
+ RenderRegion bounds(RenderMethod& renderer) const override
+ {
+ return inst->bounds(renderer);
+ }
+
+ bool dispose(RenderMethod& renderer) override
+ {
+ return inst->dispose(renderer);
+ }
+
+ void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag renderFlag) override
+ {
+ return inst->update(renderer, transform, opacity, clips, renderFlag);
+ }
+
+ bool render(RenderMethod& renderer) override
+ {
+ return inst->render(renderer);
+ }
+
+ Paint* duplicate() override
+ {
+ return inst->duplicate();
+ }
+
+ Iterator* iterator() override
+ {
+ return inst->iterator();
+ }
+ };
+}
+
+#endif //_TVG_PAINT_H_
diff --git a/thirdparty/thorvg/src/lib/tvgPicture.cpp b/thirdparty/thorvg/src/lib/tvgPicture.cpp
new file mode 100644
index 0000000000..1d9776363c
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgPicture.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "tvgPictureImpl.h"
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Picture::Picture() : pImpl(new Impl)
+{
+ Paint::pImpl->id = TVG_CLASS_ID_PICTURE;
+ Paint::pImpl->method(new PaintMethod<Picture::Impl>(pImpl));
+}
+
+
+Picture::~Picture()
+{
+ delete(pImpl);
+}
+
+
+unique_ptr<Picture> Picture::gen() noexcept
+{
+ return unique_ptr<Picture>(new Picture);
+}
+
+
+uint32_t Picture::identifier() noexcept
+{
+ return TVG_CLASS_ID_PICTURE;
+}
+
+
+Result Picture::load(const std::string& path) noexcept
+{
+ if (path.empty()) return Result::InvalidArguments;
+
+ return pImpl->load(path);
+}
+
+
+Result Picture::load(const char* data, uint32_t size, const string& mimeType, bool copy) noexcept
+{
+ if (!data || size <= 0) return Result::InvalidArguments;
+
+ return pImpl->load(data, size, mimeType, copy);
+}
+
+
+TVG_DEPRECATED Result Picture::load(const char* data, uint32_t size, bool copy) noexcept
+{
+ return load(data, size, "", copy);
+}
+
+
+Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept
+{
+ if (!data || w <= 0 || h <= 0) return Result::InvalidArguments;
+
+ return pImpl->load(data, w, h, copy);
+}
+
+
+Result Picture::viewbox(float* x, float* y, float* w, float* h) const noexcept
+{
+ if (pImpl->viewbox(x, y, w, h)) return Result::Success;
+ return Result::InsufficientCondition;
+}
+
+
+Result Picture::size(float w, float h) noexcept
+{
+ if (pImpl->size(w, h)) return Result::Success;
+ return Result::InsufficientCondition;
+}
+
+
+Result Picture::size(float* w, float* h) const noexcept
+{
+ if (!pImpl->loader) return Result::InsufficientCondition;
+ if (w) *w = pImpl->w;
+ if (h) *h = pImpl->h;
+ return Result::Success;
+}
+
+
+const uint32_t* Picture::data(uint32_t* w, uint32_t* h) const noexcept
+{
+ //Try it, If not loaded yet.
+ pImpl->reload();
+
+ if (pImpl->loader) {
+ if (w) *w = static_cast<uint32_t>(pImpl->loader->w);
+ if (h) *h = static_cast<uint32_t>(pImpl->loader->h);
+ } else {
+ if (w) *w = 0;
+ if (h) *h = 0;
+ }
+ if (pImpl->surface) return pImpl->surface->buffer;
+ else return nullptr;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgPictureImpl.h b/thirdparty/thorvg/src/lib/tvgPictureImpl.h
new file mode 100644
index 0000000000..00e8aa6dd9
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgPictureImpl.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_PICTURE_IMPL_H_
+#define _TVG_PICTURE_IMPL_H_
+
+#include <string>
+#include "tvgPaint.h"
+#include "tvgLoader.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct PictureIterator : Iterator
+{
+ Paint* paint = nullptr;
+ Paint* ptr = nullptr;
+
+ PictureIterator(Paint* p) : paint(p) {}
+
+ const Paint* next() override
+ {
+ if (!ptr) ptr = paint;
+ else ptr = nullptr;
+ return ptr;
+ }
+
+ uint32_t count() override
+ {
+ if (paint) return 1;
+ else return 0;
+ }
+
+ void begin() override
+ {
+ ptr = nullptr;
+ }
+};
+
+
+struct Picture::Impl
+{
+ shared_ptr<LoadModule> loader = nullptr;
+
+ Paint* paint = nullptr; //vector picture uses
+ Surface* surface = nullptr; //bitmap picture uses
+ void* rdata = nullptr; //engine data
+ float w = 0, h = 0;
+ bool resizing = false;
+
+ ~Impl()
+ {
+ if (paint) delete(paint);
+ free(surface);
+ }
+
+ bool dispose(RenderMethod& renderer)
+ {
+ bool ret = true;
+ if (paint) {
+ ret = paint->pImpl->dispose(renderer);
+ } else if (surface) {
+ ret = renderer.dispose(rdata);
+ rdata = nullptr;
+ }
+ return ret;
+ }
+
+ uint32_t reload()
+ {
+ if (loader) {
+ if (!paint) {
+ if (auto p = loader->paint()) {
+ paint = p.release();
+ loader->close();
+ if (w != loader->w || h != loader->h) {
+ loader->resize(paint, w, h);
+ resizing = false;
+ }
+ if (paint) return RenderUpdateFlag::None;
+ }
+ }
+ free(surface);
+ if ((surface = loader->bitmap().release())) {
+ loader->close();
+ return RenderUpdateFlag::Image;
+ }
+ }
+ return RenderUpdateFlag::None;
+ }
+
+ RenderTransform resizeTransform(const RenderTransform* pTransform)
+ {
+ //Overriding Transformation by the desired image size
+ auto sx = w / loader->w;
+ auto sy = h / loader->h;
+ auto scale = sx < sy ? sx : sy;
+
+ RenderTransform tmp;
+ tmp.m = {scale, 0, 0, 0, scale, 0, 0, 0, 1};
+
+ if (!pTransform) return tmp;
+ else return RenderTransform(pTransform, &tmp);
+ }
+
+ void* update(RenderMethod &renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag)
+ {
+ auto flag = reload();
+
+ if (surface) {
+ auto transform = resizeTransform(pTransform);
+ rdata = renderer.prepare(surface, rdata, &transform, opacity, clips, static_cast<RenderUpdateFlag>(pFlag | flag));
+ } else if (paint) {
+ if (resizing) {
+ loader->resize(paint, w, h);
+ resizing = false;
+ }
+ rdata = paint->pImpl->update(renderer, pTransform, opacity, clips, static_cast<RenderUpdateFlag>(pFlag | flag));
+ }
+ return rdata;
+ }
+
+ bool render(RenderMethod &renderer)
+ {
+ if (surface) return renderer.renderImage(rdata);
+ else if (paint) return paint->pImpl->render(renderer);
+ return false;
+ }
+
+ bool viewbox(float* x, float* y, float* w, float* h) const
+ {
+ if (!loader) return false;
+ if (x) *x = loader->vx;
+ if (y) *y = loader->vy;
+ if (w) *w = loader->vw;
+ if (h) *h = loader->vh;
+ return true;
+ }
+
+ bool size(float w, float h)
+ {
+ this->w = w;
+ this->h = h;
+ resizing = true;
+ return true;
+ }
+
+ bool bounds(float* x, float* y, float* w, float* h)
+ {
+ if (x) *x = 0;
+ if (y) *y = 0;
+ if (w) *w = this->w;
+ if (h) *h = this->h;
+
+ return true;
+ }
+
+ RenderRegion bounds(RenderMethod& renderer)
+ {
+ if (rdata) return renderer.region(rdata);
+ if (paint) return paint->pImpl->bounds(renderer);
+ return {0, 0, 0, 0};
+ }
+
+ Result load(const string& path)
+ {
+ if (paint || surface) return Result::InsufficientCondition;
+ if (loader) loader->close();
+ bool invalid; //Invalid Path
+ loader = LoaderMgr::loader(path, &invalid);
+ if (!loader) {
+ if (invalid) return Result::InvalidArguments;
+ return Result::NonSupport;
+ }
+ if (!loader->read()) return Result::Unknown;
+ w = loader->w;
+ h = loader->h;
+ return Result::Success;
+ }
+
+ Result load(const char* data, uint32_t size, const string& mimeType, bool copy)
+ {
+ if (paint || surface) return Result::InsufficientCondition;
+ if (loader) loader->close();
+ loader = LoaderMgr::loader(data, size, mimeType, copy);
+ if (!loader) return Result::NonSupport;
+ if (!loader->read()) return Result::Unknown;
+ w = loader->w;
+ h = loader->h;
+ return Result::Success;
+ }
+
+ Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy)
+ {
+ if (paint || surface) return Result::InsufficientCondition;
+ if (loader) loader->close();
+ loader = LoaderMgr::loader(data, w, h, copy);
+ if (!loader) return Result::NonSupport;
+ this->w = loader->w;
+ this->h = loader->h;
+ return Result::Success;
+ }
+
+ Paint* duplicate()
+ {
+ reload();
+
+ auto ret = Picture::gen();
+
+ auto dup = ret.get()->pImpl;
+ if (paint) dup->paint = paint->duplicate();
+
+ dup->loader = loader;
+ if (surface) {
+ dup->surface = static_cast<Surface*>(malloc(sizeof(Surface)));
+ *dup->surface = *surface;
+ }
+ dup->w = w;
+ dup->h = h;
+ dup->resizing = resizing;
+
+ return ret.release();
+ }
+
+ Iterator* iterator()
+ {
+ reload();
+ return new PictureIterator(paint);
+ }
+};
+
+#endif //_TVG_PICTURE_IMPL_H_
diff --git a/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp b/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp
new file mode 100644
index 0000000000..42a83461e3
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <float.h>
+#include "tvgFill.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct RadialGradient::Impl
+{
+ float cx = 0;
+ float cy = 0;
+ float radius = 0;
+
+ Fill* duplicate()
+ {
+ auto ret = RadialGradient::gen();
+ if (!ret) return nullptr;
+
+ ret->pImpl->cx = cx;
+ ret->pImpl->cy = cy;
+ ret->pImpl->radius = radius;
+
+ return ret.release();
+ }
+};
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+RadialGradient::RadialGradient():pImpl(new Impl())
+{
+ Fill::pImpl->id = TVG_CLASS_ID_RADIAL;
+ Fill::pImpl->method(new FillDup<RadialGradient::Impl>(pImpl));
+}
+
+
+RadialGradient::~RadialGradient()
+{
+ delete(pImpl);
+}
+
+
+Result RadialGradient::radial(float cx, float cy, float radius) noexcept
+{
+ if (radius < 0) return Result::InvalidArguments;
+
+ pImpl->cx = cx;
+ pImpl->cy = cy;
+ pImpl->radius = radius;
+
+ return Result::Success;
+}
+
+
+Result RadialGradient::radial(float* cx, float* cy, float* radius) const noexcept
+{
+ if (cx) *cx = pImpl->cx;
+ if (cy) *cy = pImpl->cy;
+ if (radius) *radius = pImpl->radius;
+
+ return Result::Success;
+}
+
+
+unique_ptr<RadialGradient> RadialGradient::gen() noexcept
+{
+ return unique_ptr<RadialGradient>(new RadialGradient);
+}
+
+
+uint32_t RadialGradient::identifier() noexcept
+{
+ return TVG_CLASS_ID_RADIAL;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgRender.cpp b/thirdparty/thorvg/src/lib/tvgRender.cpp
new file mode 100644
index 0000000000..a48075dd04
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgRender.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgMath.h"
+#include "tvgRender.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+void RenderTransform::override(const Matrix& m)
+{
+ this->m = m;
+
+ if (m.e11 == 0.0f && m.e12 == 0.0f && m.e13 == 0.0f &&
+ m.e21 == 0.0f && m.e22 == 0.0f && m.e23 == 0.0f &&
+ m.e31 == 0.0f && m.e32 == 0.0f && m.e33 == 0.0f) {
+ overriding = false;
+ } else overriding = true;
+}
+
+
+bool RenderTransform::update()
+{
+ if (overriding) return true;
+
+ //Init Status
+ if (mathZero(x) && mathZero(y) && mathZero(degree) && mathEqual(scale, 1)) return false;
+
+ mathIdentity(&m);
+
+ mathScale(&m, scale);
+
+ if (!mathZero(degree)) mathRotate(&m, degree);
+
+ mathTranslate(&m, x, y);
+
+ return true;
+}
+
+
+RenderTransform::RenderTransform()
+{
+}
+
+
+RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs)
+{
+ m = mathMultiply(&lhs->m, &rhs->m);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgRender.h b/thirdparty/thorvg/src/lib/tvgRender.h
new file mode 100644
index 0000000000..f927947aa6
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgRender.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_RENDER_H_
+#define _TVG_RENDER_H_
+
+#include "tvgCommon.h"
+#include "tvgArray.h"
+
+namespace tvg
+{
+
+enum RenderUpdateFlag {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, All = 255};
+
+struct Surface
+{
+ //TODO: Union for multiple types
+ uint32_t* buffer;
+ uint32_t stride;
+ uint32_t w, h;
+ uint32_t cs;
+};
+
+using RenderData = void*;
+
+struct Compositor
+{
+ CompositeMethod method;
+ uint32_t opacity;
+};
+
+struct RenderRegion
+{
+ int32_t x, y, w, h;
+
+ void intersect(const RenderRegion& rhs)
+ {
+ auto x1 = x + w;
+ auto y1 = y + h;
+ auto x2 = rhs.x + rhs.w;
+ auto y2 = rhs.y + rhs.h;
+
+ x = (x > rhs.x) ? x : rhs.x;
+ y = (y > rhs.y) ? y : rhs.y;
+ w = ((x1 < x2) ? x1 : x2) - x;
+ h = ((y1 < y2) ? y1 : y2) - y;
+
+ if (w < 0) w = 0;
+ if (h < 0) h = 0;
+ }
+};
+
+struct RenderTransform
+{
+ Matrix m; //3x3 Matrix Elements
+ float x = 0.0f;
+ float y = 0.0f;
+ float degree = 0.0f; //rotation degree
+ float scale = 1.0f; //scale factor
+ bool overriding = false; //user transform?
+
+ bool update();
+ void override(const Matrix& m);
+
+ RenderTransform();
+ RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs);
+};
+
+
+class RenderMethod
+{
+public:
+ virtual ~RenderMethod() {}
+ virtual RenderData prepare(const Shape& shape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) = 0;
+ virtual RenderData prepare(Surface* image, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) = 0;
+ virtual bool preRender() = 0;
+ virtual bool renderShape(RenderData data) = 0;
+ virtual bool renderImage(RenderData data) = 0;
+ virtual bool postRender() = 0;
+ virtual bool dispose(RenderData data) = 0;
+ virtual RenderRegion region(RenderData data) = 0;
+ virtual RenderRegion viewport() = 0;
+ virtual bool viewport(const RenderRegion& vp) = 0;
+
+ virtual bool clear() = 0;
+ virtual bool sync() = 0;
+
+ virtual Compositor* target(const RenderRegion& region) = 0;
+ virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) = 0;
+ virtual bool endComposite(Compositor* cmp) = 0;
+};
+
+}
+
+#endif //_TVG_RENDER_H_
diff --git a/thirdparty/thorvg/src/lib/tvgSaveModule.h b/thirdparty/thorvg/src/lib/tvgSaveModule.h
new file mode 100644
index 0000000000..2a0f427f11
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgSaveModule.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SAVE_MODULE_H_
+#define _TVG_SAVE_MODULE_H_
+
+#include "tvgIteratorAccessor.h"
+
+namespace tvg
+{
+
+class SaveModule : public IteratorAccessor
+{
+public:
+ virtual ~SaveModule() {}
+
+ virtual bool save(Paint* paint, const string& path, bool compress) = 0;
+ virtual bool close() = 0;
+};
+
+}
+
+#endif //_TVG_SAVE_MODULE_H_
diff --git a/thirdparty/thorvg/src/lib/tvgSaver.cpp b/thirdparty/thorvg/src/lib/tvgSaver.cpp
new file mode 100644
index 0000000000..1a3e8614a6
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgSaver.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgCommon.h"
+#include "tvgSaveModule.h"
+
+#ifdef THORVG_TVG_SAVER_SUPPORT
+ #include "tvgTvgSaver.h"
+#endif
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct Saver::Impl
+{
+ SaveModule* saveModule = nullptr;
+ ~Impl()
+ {
+ if (saveModule) delete(saveModule);
+ }
+};
+
+
+static SaveModule* _find(FileType type)
+{
+ switch(type) {
+ case FileType::Tvg: {
+#ifdef THORVG_TVG_SAVER_SUPPORT
+ return new TvgSaver;
+#endif
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+#ifdef THORVG_LOG_ENABLED
+ const char *format;
+ switch(type) {
+ case FileType::Tvg: {
+ format = "TVG";
+ break;
+ }
+ default: {
+ format = "???";
+ break;
+ }
+ }
+ TVGLOG("SAVER", "%s format is not supported", format);
+#endif
+ return nullptr;
+}
+
+
+static SaveModule* _find(const string& path)
+{
+ auto ext = path.substr(path.find_last_of(".") + 1);
+ if (!ext.compare("tvg")) {
+ return _find(FileType::Tvg);
+ }
+ return nullptr;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Saver::Saver() : pImpl(new Impl())
+{
+}
+
+
+Saver::~Saver()
+{
+ delete(pImpl);
+}
+
+
+Result Saver::save(std::unique_ptr<Paint> paint, const string& path, bool compress) noexcept
+{
+ auto p = paint.release();
+ if (!p) return Result::MemoryCorruption;
+
+ //Already on saving an other resource.
+ if (pImpl->saveModule) {
+ delete(p);
+ return Result::InsufficientCondition;
+ }
+
+ if (auto saveModule = _find(path)) {
+ if (saveModule->save(p, path, compress)) {
+ pImpl->saveModule = saveModule;
+ return Result::Success;
+ } else {
+ delete(p);
+ delete(saveModule);
+ return Result::Unknown;
+ }
+ }
+ delete(p);
+ return Result::NonSupport;
+}
+
+
+Result Saver::sync() noexcept
+{
+ if (!pImpl->saveModule) return Result::InsufficientCondition;
+ pImpl->saveModule->close();
+ delete(pImpl->saveModule);
+ pImpl->saveModule = nullptr;
+
+ return Result::Success;
+}
+
+
+unique_ptr<Saver> Saver::gen() noexcept
+{
+ return unique_ptr<Saver>(new Saver);
+}
diff --git a/thirdparty/thorvg/src/lib/tvgScene.cpp b/thirdparty/thorvg/src/lib/tvgScene.cpp
new file mode 100644
index 0000000000..4b2f77dde9
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgScene.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgSceneImpl.h"
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Scene::Scene() : pImpl(new Impl())
+{
+ Paint::pImpl->id = TVG_CLASS_ID_SCENE;
+ Paint::pImpl->method(new PaintMethod<Scene::Impl>(pImpl));
+}
+
+
+Scene::~Scene()
+{
+ delete(pImpl);
+}
+
+
+unique_ptr<Scene> Scene::gen() noexcept
+{
+ return unique_ptr<Scene>(new Scene);
+}
+
+
+uint32_t Scene::identifier() noexcept
+{
+ return TVG_CLASS_ID_SCENE;
+}
+
+
+Result Scene::push(unique_ptr<Paint> paint) noexcept
+{
+ auto p = paint.release();
+ if (!p) return Result::MemoryCorruption;
+ pImpl->paints.push(p);
+
+ return Result::Success;
+}
+
+
+Result Scene::reserve(uint32_t size) noexcept
+{
+ if (!pImpl->paints.reserve(size)) return Result::FailedAllocation;
+
+ return Result::Success;
+}
+
+
+Result Scene::clear(bool free) noexcept
+{
+ pImpl->clear(free);
+
+ return Result::Success;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgSceneImpl.h b/thirdparty/thorvg/src/lib/tvgSceneImpl.h
new file mode 100644
index 0000000000..de94a66e2f
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgSceneImpl.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SCENE_IMPL_H_
+#define _TVG_SCENE_IMPL_H_
+
+#include <float.h>
+#include "tvgPaint.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct SceneIterator : Iterator
+{
+ Array<Paint*>* paints;
+ uint32_t idx = 0;
+
+ SceneIterator(Array<Paint*>* p) : paints(p)
+ {
+ }
+
+ const Paint* next() override
+ {
+ if (idx >= paints->count) return nullptr;
+ return paints->data[idx++];
+ }
+
+ uint32_t count() override
+ {
+ return paints->count;
+ }
+
+ void begin() override
+ {
+ idx = 0;
+ }
+};
+
+struct Scene::Impl
+{
+ Array<Paint*> paints;
+ uint8_t opacity; //for composition
+ RenderMethod* renderer = nullptr; //keep it for explicit clear
+
+ ~Impl()
+ {
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ delete(*paint);
+ }
+ }
+
+ bool dispose(RenderMethod& renderer)
+ {
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ (*paint)->pImpl->dispose(renderer);
+ }
+
+ this->renderer = nullptr;
+
+ return true;
+ }
+
+ bool needComposition(uint32_t opacity)
+ {
+ //Half translucent requires intermediate composition.
+ if (opacity == 255 || opacity == 0) return false;
+
+ //If scene has several children or only scene, it may require composition.
+ if (paints.count > 1) return true;
+ if (paints.count == 1 && (*paints.data)->identifier() == TVG_CLASS_ID_SCENE) return true;
+ return false;
+ }
+
+ void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flag)
+ {
+ /* Overriding opacity value. If this scene is half-translucent,
+ It must do intermeidate composition with that opacity value. */
+ this->opacity = static_cast<uint8_t>(opacity);
+ if (needComposition(opacity)) opacity = 255;
+
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ (*paint)->pImpl->update(renderer, transform, opacity, clips, static_cast<uint32_t>(flag));
+ }
+
+ /* FXIME: it requires to return list of children engine data
+ This is necessary for scene composition */
+
+ this->renderer = &renderer;
+
+ return nullptr;
+ }
+
+ bool render(RenderMethod& renderer)
+ {
+ Compositor* cmp = nullptr;
+
+ if (needComposition(opacity)) {
+ cmp = renderer.target(bounds(renderer));
+ renderer.beginComposite(cmp, CompositeMethod::None, opacity);
+ }
+
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ if (!(*paint)->pImpl->render(renderer)) return false;
+ }
+
+ if (cmp) renderer.endComposite(cmp);
+
+ return true;
+ }
+
+ RenderRegion bounds(RenderMethod& renderer) const
+ {
+ if (paints.count == 0) return {0, 0, 0, 0};
+
+ int32_t x1 = INT32_MAX;
+ int32_t y1 = INT32_MAX;
+ int32_t x2 = 0;
+ int32_t y2 = 0;
+
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ auto region = (*paint)->pImpl->bounds(renderer);
+
+ //Merge regions
+ if (region.x < x1) x1 = region.x;
+ if (x2 < region.x + region.w) x2 = (region.x + region.w);
+ if (region.y < y1) y1 = region.y;
+ if (y2 < region.y + region.h) y2 = (region.y + region.h);
+ }
+
+ return {x1, y1, (x2 - x1), (y2 - y1)};
+ }
+
+ bool bounds(float* px, float* py, float* pw, float* ph)
+ {
+ if (paints.count == 0) return false;
+
+ auto x1 = FLT_MAX;
+ auto y1 = FLT_MAX;
+ auto x2 = -FLT_MAX;
+ auto y2 = -FLT_MAX;
+
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ auto x = FLT_MAX;
+ auto y = FLT_MAX;
+ auto w = 0.0f;
+ auto h = 0.0f;
+
+ if ((*paint)->bounds(&x, &y, &w, &h, true) != tvg::Result::Success) continue;
+
+ //Merge regions
+ if (x < x1) x1 = x;
+ if (x2 < x + w) x2 = (x + w);
+ if (y < y1) y1 = y;
+ if (y2 < y + h) y2 = (y + h);
+ }
+
+ if (px) *px = x1;
+ if (py) *py = y1;
+ if (pw) *pw = (x2 - x1);
+ if (ph) *ph = (y2 - y1);
+
+ return true;
+ }
+
+ Paint* duplicate()
+ {
+ auto ret = Scene::gen();
+
+ auto dup = ret.get()->pImpl;
+
+ dup->paints.reserve(paints.count);
+
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ dup->paints.push((*paint)->duplicate());
+ }
+
+ return ret.release();
+ }
+
+ void clear(bool free)
+ {
+ auto dispose = renderer ? true : false;
+
+ for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
+ if (dispose) (*paint)->pImpl->dispose(*renderer);
+ if (free) delete(*paint);
+ }
+ paints.clear();
+ renderer = nullptr;
+ }
+
+ Iterator* iterator()
+ {
+ return new SceneIterator(&paints);
+ }
+};
+
+#endif //_TVG_SCENE_IMPL_H_
diff --git a/thirdparty/thorvg/src/lib/tvgShape.cpp b/thirdparty/thorvg/src/lib/tvgShape.cpp
new file mode 100644
index 0000000000..8db5635932
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgShape.cpp
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "tvgMath.h"
+#include "tvgShapeImpl.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+constexpr auto PATH_KAPPA = 0.552284f;
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+Shape :: Shape() : pImpl(new Impl(this))
+{
+ Paint::pImpl->id = TVG_CLASS_ID_SHAPE;
+ Paint::pImpl->method(new PaintMethod<Shape::Impl>(pImpl));
+}
+
+
+Shape :: ~Shape()
+{
+ delete(pImpl);
+}
+
+
+unique_ptr<Shape> Shape::gen() noexcept
+{
+ return unique_ptr<Shape>(new Shape);
+}
+
+
+uint32_t Shape::identifier() noexcept
+{
+ return TVG_CLASS_ID_SHAPE;
+}
+
+
+Result Shape::reset() noexcept
+{
+ pImpl->path.reset();
+ pImpl->flag = RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept
+{
+ if (!cmds) return 0;
+
+ *cmds = pImpl->path.cmds;
+
+ return pImpl->path.cmdCnt;
+}
+
+
+uint32_t Shape::pathCoords(const Point** pts) const noexcept
+{
+ if (!pts) return 0;
+
+ *pts = pImpl->path.pts;
+
+ return pImpl->path.ptsCnt;
+}
+
+
+Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept
+{
+ if (cmdCnt == 0 || ptsCnt == 0 || !cmds || !pts) return Result::InvalidArguments;
+
+ pImpl->path.grow(cmdCnt, ptsCnt);
+ pImpl->path.append(cmds, cmdCnt, pts, ptsCnt);
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::moveTo(float x, float y) noexcept
+{
+ pImpl->path.moveTo(x, y);
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::lineTo(float x, float y) noexcept
+{
+ pImpl->path.lineTo(x, y);
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept
+{
+ pImpl->path.cubicTo(cx1, cy1, cx2, cy2, x, y);
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::close() noexcept
+{
+ pImpl->path.close();
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept
+{
+ auto rxKappa = rx * PATH_KAPPA;
+ auto ryKappa = ry * PATH_KAPPA;
+
+ pImpl->path.grow(6, 13);
+ pImpl->path.moveTo(cx, cy - ry);
+ pImpl->path.cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy);
+ pImpl->path.cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry);
+ pImpl->path.cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy);
+ pImpl->path.cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry);
+ pImpl->path.close();
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept
+{
+ //just circle
+ if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius);
+
+ startAngle = (startAngle * M_PI) / 180.0f;
+ sweep = sweep * M_PI / 180.0f;
+
+ auto nCurves = ceil(fabsf(sweep / float(M_PI_2)));
+ auto sweepSign = (sweep < 0 ? -1 : 1);
+ auto fract = fmodf(sweep, float(M_PI_2));
+ fract = (mathZero(fract)) ? float(M_PI_2) * sweepSign : fract;
+
+ //Start from here
+ Point start = {radius * cosf(startAngle), radius * sinf(startAngle)};
+
+ if (pie) {
+ pImpl->path.moveTo(cx, cy);
+ pImpl->path.lineTo(start.x + cx, start.y + cy);
+ } else {
+ pImpl->path.moveTo(start.x + cx, start.y + cy);
+ }
+
+ for (int i = 0; i < nCurves; ++i) {
+ auto endAngle = startAngle + ((i != nCurves - 1) ? float(M_PI_2) * sweepSign : fract);
+ Point end = {radius * cosf(endAngle), radius * sinf(endAngle)};
+
+ //variables needed to calculate bezier control points
+
+ //get bezier control points using article:
+ //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479)
+ auto ax = start.x;
+ auto ay = start.y;
+ auto bx = end.x;
+ auto by = end.y;
+ auto q1 = ax * ax + ay * ay;
+ auto q2 = ax * bx + ay * by + q1;
+ auto k2 = (4.0f/3.0f) * ((sqrtf(2 * q1 * q2) - q2) / (ax * by - ay * bx));
+
+ start = end; //Next start point is the current end point
+
+ end.x += cx;
+ end.y += cy;
+
+ Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy};
+ Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy};
+
+ pImpl->path.cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y);
+
+ startAngle = endAngle;
+ }
+
+ if (pie) pImpl->path.close();
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept
+{
+ auto halfW = w * 0.5f;
+ auto halfH = h * 0.5f;
+
+ //clamping cornerRadius by minimum size
+ if (rx > halfW) rx = halfW;
+ if (ry > halfH) ry = halfH;
+
+ //rectangle
+ if (rx == 0 && ry == 0) {
+ pImpl->path.grow(5, 4);
+ pImpl->path.moveTo(x, y);
+ pImpl->path.lineTo(x + w, y);
+ pImpl->path.lineTo(x + w, y + h);
+ pImpl->path.lineTo(x, y + h);
+ pImpl->path.close();
+ //circle
+ } else if (mathEqual(rx, halfW) && mathEqual(ry, halfH)) {
+ return appendCircle(x + (w * 0.5f), y + (h * 0.5f), rx, ry);
+ } else {
+ auto hrx = rx * 0.5f;
+ auto hry = ry * 0.5f;
+ pImpl->path.grow(10, 17);
+ pImpl->path.moveTo(x + rx, y);
+ pImpl->path.lineTo(x + w - rx, y);
+ pImpl->path.cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry);
+ pImpl->path.lineTo(x + w, y + h - ry);
+ pImpl->path.cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h);
+ pImpl->path.lineTo(x + rx, y + h);
+ pImpl->path.cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry);
+ pImpl->path.lineTo(x, y + ry);
+ pImpl->path.cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y);
+ pImpl->path.close();
+ }
+
+ pImpl->flag |= RenderUpdateFlag::Path;
+
+ return Result::Success;
+}
+
+
+Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
+{
+ pImpl->color[0] = r;
+ pImpl->color[1] = g;
+ pImpl->color[2] = b;
+ pImpl->color[3] = a;
+ pImpl->flag |= RenderUpdateFlag::Color;
+
+ if (pImpl->fill) {
+ delete(pImpl->fill);
+ pImpl->fill = nullptr;
+ pImpl->flag |= RenderUpdateFlag::Gradient;
+ }
+
+ return Result::Success;
+}
+
+
+Result Shape::fill(unique_ptr<Fill> f) noexcept
+{
+ auto p = f.release();
+ if (!p) return Result::MemoryCorruption;
+
+ if (pImpl->fill && pImpl->fill != p) delete(pImpl->fill);
+ pImpl->fill = p;
+ pImpl->flag |= RenderUpdateFlag::Gradient;
+
+ return Result::Success;
+}
+
+
+Result Shape::fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept
+{
+ if (r) *r = pImpl->color[0];
+ if (g) *g = pImpl->color[1];
+ if (b) *b = pImpl->color[2];
+ if (a) *a = pImpl->color[3];
+
+ return Result::Success;
+}
+
+const Fill* Shape::fill() const noexcept
+{
+ return pImpl->fill;
+}
+
+
+Result Shape::stroke(float width) noexcept
+{
+ if (!pImpl->strokeWidth(width)) return Result::FailedAllocation;
+
+ return Result::Success;
+}
+
+
+float Shape::strokeWidth() const noexcept
+{
+ if (!pImpl->stroke) return 0;
+ return pImpl->stroke->width;
+}
+
+
+Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
+{
+ if (!pImpl->strokeColor(r, g, b, a)) return Result::FailedAllocation;
+
+ return Result::Success;
+}
+
+
+Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept
+{
+ if (!pImpl->stroke) return Result::InsufficientCondition;
+
+ if (r) *r = pImpl->stroke->color[0];
+ if (g) *g = pImpl->stroke->color[1];
+ if (b) *b = pImpl->stroke->color[2];
+ if (a) *a = pImpl->stroke->color[3];
+
+ return Result::Success;
+}
+
+
+Result Shape::stroke(unique_ptr<Fill> f) noexcept
+{
+ return pImpl->strokeFill(move(f));
+}
+
+
+const Fill* Shape::strokeFill() const noexcept
+{
+ if (!pImpl->stroke) return nullptr;
+
+ return pImpl->stroke->fill;
+}
+
+
+Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept
+{
+ if ((cnt == 1) || (!dashPattern && cnt > 0) || (dashPattern && cnt == 0)) {
+ return Result::InvalidArguments;
+ }
+
+ for (uint32_t i = 0; i < cnt; i++)
+ if (dashPattern[i] < FLT_EPSILON) return Result::InvalidArguments;
+
+ if (!pImpl->strokeDash(dashPattern, cnt)) return Result::FailedAllocation;
+
+ return Result::Success;
+}
+
+
+uint32_t Shape::strokeDash(const float** dashPattern) const noexcept
+{
+ if (!pImpl->stroke) return 0;
+
+ if (dashPattern) *dashPattern = pImpl->stroke->dashPattern;
+
+ return pImpl->stroke->dashCnt;
+}
+
+
+Result Shape::stroke(StrokeCap cap) noexcept
+{
+ if (!pImpl->strokeCap(cap)) return Result::FailedAllocation;
+
+ return Result::Success;
+}
+
+
+Result Shape::stroke(StrokeJoin join) noexcept
+{
+ if (!pImpl->strokeJoin(join)) return Result::FailedAllocation;
+
+ return Result::Success;
+}
+
+
+StrokeCap Shape::strokeCap() const noexcept
+{
+ if (!pImpl->stroke) return StrokeCap::Square;
+
+ return pImpl->stroke->cap;
+}
+
+
+StrokeJoin Shape::strokeJoin() const noexcept
+{
+ if (!pImpl->stroke) return StrokeJoin::Bevel;
+
+ return pImpl->stroke->join;
+}
+
+
+Result Shape::fill(FillRule r) noexcept
+{
+ pImpl->rule = r;
+
+ return Result::Success;
+}
+
+
+FillRule Shape::fillRule() const noexcept
+{
+ return pImpl->rule;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgShapeImpl.h b/thirdparty/thorvg/src/lib/tvgShapeImpl.h
new file mode 100644
index 0000000000..dcf4e6e954
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgShapeImpl.h
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SHAPE_IMPL_H_
+#define _TVG_SHAPE_IMPL_H_
+
+#include <memory.h>
+#include "tvgPaint.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct ShapeStroke
+{
+ float width;
+ uint8_t color[4];
+ Fill *fill;
+ float* dashPattern;
+ uint32_t dashCnt;
+ StrokeCap cap;
+ StrokeJoin join;
+
+ void copy(const ShapeStroke* src)
+ {
+ width = src->width;
+ dashCnt = src->dashCnt;
+ cap = src->cap;
+ join = src->join;
+
+ memcpy(color, src->color, sizeof(color));
+ if (dashCnt > 0) {
+ dashPattern = static_cast<float*>(malloc(sizeof(float) * dashCnt));
+ memcpy(dashPattern, src->dashPattern, sizeof(float) * dashCnt);
+ }
+ if (src->fill) fill = src->fill->duplicate();
+ }
+
+ void clear()
+ {
+ if (dashPattern) free(dashPattern);
+ if (fill) delete(fill);
+ }
+};
+
+
+struct ShapePath
+{
+ PathCommand* cmds = nullptr;
+ uint32_t cmdCnt = 0;
+ uint32_t reservedCmdCnt = 0;
+
+ Point *pts = nullptr;
+ uint32_t ptsCnt = 0;
+ uint32_t reservedPtsCnt = 0;
+
+ ~ShapePath()
+ {
+ if (cmds) free(cmds);
+ if (pts) free(pts);
+ }
+
+ ShapePath()
+ {
+ }
+
+ void duplicate(const ShapePath* src)
+ {
+ if (src->cmdCnt == 0 || src->ptsCnt == 0) return;
+
+ cmdCnt = src->cmdCnt;
+ reservedCmdCnt = src->reservedCmdCnt;
+ ptsCnt = src->ptsCnt;
+ reservedPtsCnt = src->reservedPtsCnt;
+
+ cmds = static_cast<PathCommand*>(malloc(sizeof(PathCommand) * reservedCmdCnt));
+ if (!cmds) return;
+ memcpy(cmds, src->cmds, sizeof(PathCommand) * cmdCnt);
+
+ pts = static_cast<Point*>(malloc(sizeof(Point) * reservedPtsCnt));
+ if (!pts) {
+ free(cmds);
+ return;
+ }
+ memcpy(pts, src->pts, sizeof(Point) * ptsCnt);
+ }
+
+ void reserveCmd(uint32_t cmdCnt)
+ {
+ if (cmdCnt <= reservedCmdCnt) return;
+ reservedCmdCnt = cmdCnt;
+ cmds = static_cast<PathCommand*>(realloc(cmds, sizeof(PathCommand) * reservedCmdCnt));
+ }
+
+ void reservePts(uint32_t ptsCnt)
+ {
+ if (ptsCnt <= reservedPtsCnt) return;
+ reservedPtsCnt = ptsCnt;
+ pts = static_cast<Point*>(realloc(pts, sizeof(Point) * reservedPtsCnt));
+ }
+
+ void grow(uint32_t cmdCnt, uint32_t ptsCnt)
+ {
+ reserveCmd(this->cmdCnt + cmdCnt);
+ reservePts(this->ptsCnt + ptsCnt);
+ }
+
+ void reset()
+ {
+ cmdCnt = 0;
+ ptsCnt = 0;
+ }
+
+ void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt)
+ {
+ memcpy(this->cmds + this->cmdCnt, cmds, sizeof(PathCommand) * cmdCnt);
+ memcpy(this->pts + this->ptsCnt, pts, sizeof(Point) * ptsCnt);
+ this->cmdCnt += cmdCnt;
+ this->ptsCnt += ptsCnt;
+ }
+
+ void moveTo(float x, float y)
+ {
+ if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2);
+ if (ptsCnt + 2 > reservedPtsCnt) reservePts((ptsCnt + 2) * 2);
+
+ cmds[cmdCnt++] = PathCommand::MoveTo;
+ pts[ptsCnt++] = {x, y};
+ }
+
+ void lineTo(float x, float y)
+ {
+ if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2);
+ if (ptsCnt + 2 > reservedPtsCnt) reservePts((ptsCnt + 2) * 2);
+
+ cmds[cmdCnt++] = PathCommand::LineTo;
+ pts[ptsCnt++] = {x, y};
+ }
+
+ void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
+ {
+ if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2);
+ if (ptsCnt + 3 > reservedPtsCnt) reservePts((ptsCnt + 3) * 2);
+
+ cmds[cmdCnt++] = PathCommand::CubicTo;
+ pts[ptsCnt++] = {cx1, cy1};
+ pts[ptsCnt++] = {cx2, cy2};
+ pts[ptsCnt++] = {x, y};
+ }
+
+ void close()
+ {
+ if (cmdCnt > 0 && cmds[cmdCnt - 1] == PathCommand::Close) return;
+
+ if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2);
+ cmds[cmdCnt++] = PathCommand::Close;
+ }
+
+ bool bounds(float* x, float* y, float* w, float* h) const
+ {
+ if (ptsCnt == 0) return false;
+
+ Point min = { pts[0].x, pts[0].y };
+ Point max = { pts[0].x, pts[0].y };
+
+ for (uint32_t i = 1; i < ptsCnt; ++i) {
+ if (pts[i].x < min.x) min.x = pts[i].x;
+ if (pts[i].y < min.y) min.y = pts[i].y;
+ if (pts[i].x > max.x) max.x = pts[i].x;
+ if (pts[i].y > max.y) max.y = pts[i].y;
+ }
+
+ if (x) *x = min.x;
+ if (y) *y = min.y;
+ if (w) *w = max.x - min.x;
+ if (h) *h = max.y - min.y;
+
+ return true;
+ }
+};
+
+
+struct Shape::Impl
+{
+ ShapePath path;
+ Fill *fill = nullptr;
+ ShapeStroke *stroke = nullptr;
+ uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a
+ FillRule rule = FillRule::Winding;
+ RenderData rdata = nullptr; //engine data
+ Shape *shape = nullptr;
+ uint32_t flag = RenderUpdateFlag::None;
+
+ Impl(Shape* s) : shape(s)
+ {
+ }
+
+ ~Impl()
+ {
+ if (fill) delete(fill);
+ if (stroke) {
+ stroke->clear();
+ free (stroke);
+ }
+ }
+
+ bool dispose(RenderMethod& renderer)
+ {
+ auto ret = renderer.dispose(rdata);
+ rdata = nullptr;
+ return ret;
+ }
+
+ bool render(RenderMethod& renderer)
+ {
+ return renderer.renderShape(rdata);
+ }
+
+ void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag)
+ {
+ this->rdata = renderer.prepare(*shape, this->rdata, transform, opacity, clips, static_cast<RenderUpdateFlag>(pFlag | flag));
+ flag = RenderUpdateFlag::None;
+ return this->rdata;
+ }
+
+ RenderRegion bounds(RenderMethod& renderer)
+ {
+ return renderer.region(rdata);
+ }
+
+ bool bounds(float* x, float* y, float* w, float* h)
+ {
+ auto ret = path.bounds(x, y, w, h);
+
+ //Stroke feathering
+ if (stroke) {
+ if (x) *x -= stroke->width * 0.5f;
+ if (y) *y -= stroke->width * 0.5f;
+ if (w) *w += stroke->width;
+ if (h) *h += stroke->width;
+ }
+ return ret;
+ }
+
+ bool strokeWidth(float width)
+ {
+ //TODO: Size Exception?
+
+ if (!stroke) stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ stroke->width = width;
+ flag |= RenderUpdateFlag::Stroke;
+
+ return true;
+ }
+
+ bool strokeCap(StrokeCap cap)
+ {
+ if (!stroke) stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ stroke->cap = cap;
+ flag |= RenderUpdateFlag::Stroke;
+
+ return true;
+ }
+
+ bool strokeJoin(StrokeJoin join)
+ {
+ if (!stroke) stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ stroke->join = join;
+ flag |= RenderUpdateFlag::Stroke;
+
+ return true;
+ }
+
+ bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+ {
+ if (!stroke) stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ if (stroke->fill) {
+ delete(stroke->fill);
+ stroke->fill = nullptr;
+ flag |= RenderUpdateFlag::GradientStroke;
+ }
+
+ stroke->color[0] = r;
+ stroke->color[1] = g;
+ stroke->color[2] = b;
+ stroke->color[3] = a;
+
+ flag |= RenderUpdateFlag::Stroke;
+
+ return true;
+ }
+
+ Result strokeFill(unique_ptr<Fill> f)
+ {
+ auto p = f.release();
+ if (!p) return Result::MemoryCorruption;
+
+ if (!stroke) stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ if (stroke->fill && stroke->fill != p) delete(stroke->fill);
+ stroke->fill = p;
+
+ flag |= RenderUpdateFlag::Stroke;
+ flag |= RenderUpdateFlag::GradientStroke;
+
+ return Result::Success;
+ }
+
+ bool strokeDash(const float* pattern, uint32_t cnt)
+ {
+ //Reset dash
+ if (!pattern && cnt == 0) {
+ free(stroke->dashPattern);
+ stroke->dashPattern = nullptr;
+ } else {
+ if (!stroke) stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ if (stroke->dashCnt != cnt) {
+ free(stroke->dashPattern);
+ stroke->dashPattern = nullptr;
+ }
+ if (!stroke->dashPattern) {
+ stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * cnt));
+ if (!stroke->dashPattern) return false;
+ }
+ for (uint32_t i = 0; i < cnt; ++i) {
+ stroke->dashPattern[i] = pattern[i];
+ }
+ }
+ stroke->dashCnt = cnt;
+ flag |= RenderUpdateFlag::Stroke;
+
+ return true;
+ }
+
+ Paint* duplicate()
+ {
+ auto ret = Shape::gen();
+
+ auto dup = ret.get()->pImpl;
+ dup->rule = rule;
+
+ //Color
+ memcpy(dup->color, color, sizeof(color));
+ dup->flag = RenderUpdateFlag::Color;
+
+ //Path
+ dup->path.duplicate(&path);
+ dup->flag |= RenderUpdateFlag::Path;
+
+ //Stroke
+ if (stroke) {
+ dup->stroke = static_cast<ShapeStroke*>(calloc(sizeof(ShapeStroke), 1));
+ dup->stroke->copy(stroke);
+ dup->flag |= RenderUpdateFlag::Stroke;
+
+ if (stroke->fill)
+ dup->flag |= RenderUpdateFlag::GradientStroke;
+ }
+
+ //Fill
+ if (fill) {
+ dup->fill = fill->duplicate();
+ dup->flag |= RenderUpdateFlag::Gradient;
+ }
+
+ return ret.release();
+ }
+
+ Iterator* iterator()
+ {
+ return nullptr;
+ }
+};
+
+#endif //_TVG_SHAPE_IMPL_H_
diff --git a/thirdparty/thorvg/src/lib/tvgSwCanvas.cpp b/thirdparty/thorvg/src/lib/tvgSwCanvas.cpp
new file mode 100644
index 0000000000..f7a03b819c
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgSwCanvas.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgCanvasImpl.h"
+
+#ifdef THORVG_SW_RASTER_SUPPORT
+ #include "tvgSwRenderer.h"
+#else
+ class SwRenderer : public RenderMethod
+ {
+ //Non Supported. Dummy Class */
+ };
+#endif
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct SwCanvas::Impl
+{
+};
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+#ifdef THORVG_SW_RASTER_SUPPORT
+SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(new Impl)
+#else
+SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(new Impl)
+#endif
+{
+}
+
+
+SwCanvas::~SwCanvas()
+{
+ delete(pImpl);
+}
+
+
+Result SwCanvas::mempool(MempoolPolicy policy) noexcept
+{
+#ifdef THORVG_SW_RASTER_SUPPORT
+ //We know renderer type, avoid dynamic_cast for performance.
+ auto renderer = static_cast<SwRenderer*>(Canvas::pImpl->renderer);
+ if (!renderer) return Result::MemoryCorruption;
+
+ //It can't change the policy during the running.
+ if (Canvas::pImpl->paints.count > 0) return Result::InsufficientCondition;
+
+ if (policy == MempoolPolicy::Individual) renderer->mempool(false);
+ else renderer->mempool(true);
+
+ return Result::Success;
+#endif
+ return Result::NonSupport;
+}
+
+
+Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept
+{
+#ifdef THORVG_SW_RASTER_SUPPORT
+ //We know renderer type, avoid dynamic_cast for performance.
+ auto renderer = static_cast<SwRenderer*>(Canvas::pImpl->renderer);
+ if (!renderer) return Result::MemoryCorruption;
+
+ if (!renderer->target(buffer, stride, w, h, cs)) return Result::InvalidArguments;
+
+ //Paints must be updated again with this new target.
+ Canvas::pImpl->needRefresh();
+
+ return Result::Success;
+#endif
+ return Result::NonSupport;
+}
+
+
+unique_ptr<SwCanvas> SwCanvas::gen() noexcept
+{
+#ifdef THORVG_SW_RASTER_SUPPORT
+ if (SwRenderer::init() <= 0) return nullptr;
+ return unique_ptr<SwCanvas>(new SwCanvas);
+#endif
+ return nullptr;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp b/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp
new file mode 100644
index 0000000000..780127b87b
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <deque>
+#include <thread>
+#include <vector>
+#include <atomic>
+#include <condition_variable>
+#include "tvgTaskScheduler.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+namespace tvg {
+
+struct TaskQueue {
+ deque<Task*> taskDeque;
+ mutex mtx;
+ condition_variable ready;
+ bool done = false;
+
+ bool tryPop(Task** task)
+ {
+ unique_lock<mutex> lock{mtx, try_to_lock};
+ if (!lock || taskDeque.empty()) return false;
+ *task = taskDeque.front();
+ taskDeque.pop_front();
+
+ return true;
+ }
+
+ bool tryPush(Task* task)
+ {
+ {
+ unique_lock<mutex> lock{mtx, try_to_lock};
+ if (!lock) return false;
+ taskDeque.push_back(task);
+ }
+
+ ready.notify_one();
+
+ return true;
+ }
+
+ void complete()
+ {
+ {
+ unique_lock<mutex> lock{mtx};
+ done = true;
+ }
+ ready.notify_all();
+ }
+
+ bool pop(Task** task)
+ {
+ unique_lock<mutex> lock{mtx};
+
+ while (taskDeque.empty() && !done) {
+ ready.wait(lock);
+ }
+
+ if (taskDeque.empty()) return false;
+
+ *task = taskDeque.front();
+ taskDeque.pop_front();
+
+ return true;
+ }
+
+ void push(Task* task)
+ {
+ {
+ unique_lock<mutex> lock{mtx};
+ taskDeque.push_back(task);
+ }
+
+ ready.notify_one();
+ }
+
+};
+
+
+class TaskSchedulerImpl
+{
+public:
+ unsigned threadCnt;
+ vector<thread> threads;
+ vector<TaskQueue> taskQueues;
+ atomic<unsigned> idx{0};
+
+ TaskSchedulerImpl(unsigned threadCnt) : threadCnt(threadCnt), taskQueues(threadCnt)
+ {
+ for (unsigned i = 0; i < threadCnt; ++i) {
+ threads.emplace_back([&, i] { run(i); });
+ }
+ }
+
+ ~TaskSchedulerImpl()
+ {
+ for (auto& queue : taskQueues) queue.complete();
+ for (auto& thread : threads) thread.join();
+ }
+
+ void run(unsigned i)
+ {
+ Task* task;
+
+ //Thread Loop
+ while (true) {
+ auto success = false;
+ for (unsigned x = 0; x < threadCnt * 2; ++x) {
+ if (taskQueues[(i + x) % threadCnt].tryPop(&task)) {
+ success = true;
+ break;
+ }
+ }
+
+ if (!success && !taskQueues[i].pop(&task)) break;
+ (*task)(i);
+ }
+ }
+
+ void request(Task* task)
+ {
+ //Async
+ if (threadCnt > 0) {
+ task->prepare();
+ auto i = idx++;
+ for (unsigned n = 0; n < threadCnt; ++n) {
+ if (taskQueues[(i + n) % threadCnt].tryPush(task)) return;
+ }
+ taskQueues[i % threadCnt].push(task);
+ //Sync
+ } else {
+ task->run(0);
+ }
+ }
+};
+
+}
+
+static TaskSchedulerImpl* inst = nullptr;
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+void TaskScheduler::init(unsigned threads)
+{
+ if (inst) return;
+ inst = new TaskSchedulerImpl(threads);
+}
+
+
+void TaskScheduler::term()
+{
+ if (!inst) return;
+ delete(inst);
+ inst = nullptr;
+}
+
+
+void TaskScheduler::request(Task* task)
+{
+ if (inst) inst->request(task);
+}
+
+
+unsigned TaskScheduler::threads()
+{
+ if (inst) return inst->threadCnt;
+ return 0;
+}
diff --git a/thirdparty/thorvg/src/lib/tvgTaskScheduler.h b/thirdparty/thorvg/src/lib/tvgTaskScheduler.h
new file mode 100644
index 0000000000..f30bdf9a8f
--- /dev/null
+++ b/thirdparty/thorvg/src/lib/tvgTaskScheduler.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_TASK_SCHEDULER_H_
+#define _TVG_TASK_SCHEDULER_H_
+
+#include <mutex>
+#include <condition_variable>
+#include "tvgCommon.h"
+
+namespace tvg
+{
+
+struct Task;
+
+struct TaskScheduler
+{
+ static unsigned threads();
+ static void init(unsigned threads);
+ static void term();
+ static void request(Task* task);
+};
+
+struct Task
+{
+private:
+ mutex mtx;
+ condition_variable cv;
+ bool ready{true};
+ bool pending{false};
+
+public:
+ virtual ~Task() = default;
+
+ void done()
+ {
+ if (!pending) return;
+
+ unique_lock<mutex> lock(mtx);
+ while (!ready) cv.wait(lock);
+ pending = false;
+ }
+
+protected:
+ virtual void run(unsigned tid) = 0;
+
+private:
+ void operator()(unsigned tid)
+ {
+ run(tid);
+
+ lock_guard<mutex> lock(mtx);
+ ready = true;
+ cv.notify_one();
+ }
+
+ void prepare()
+ {
+ ready = false;
+ pending = true;
+ }
+
+ friend class TaskSchedulerImpl;
+};
+
+
+
+}
+
+#endif //_TVG_TASK_SCHEDULER_H_
diff --git a/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp
new file mode 100644
index 0000000000..6f9416b69c
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <memory.h>
+#include <turbojpeg.h>
+#include "tvgLoader.h"
+#include "tvgJpgLoader.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+void JpgLoader::clear()
+{
+ if (freeData) free(data);
+ data = nullptr;
+ size = 0;
+ freeData = false;
+}
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+JpgLoader::JpgLoader()
+{
+ jpegDecompressor = tjInitDecompress();
+}
+
+
+JpgLoader::~JpgLoader()
+{
+ if (freeData) free(data);
+ tjDestroy(jpegDecompressor);
+
+ //This image is shared with raster engine.
+ tjFree(image);
+}
+
+
+bool JpgLoader::open(const string& path)
+{
+ clear();
+
+ auto jpegFile = fopen(path.c_str(), "rb");
+ if (!jpegFile) return false;
+
+ auto ret = false;
+
+ //determine size
+ if (fseek(jpegFile, 0, SEEK_END) < 0) goto finalize;
+ if (((size = ftell(jpegFile)) < 1)) goto finalize;
+ if (fseek(jpegFile, 0, SEEK_SET)) goto finalize;
+
+ data = (unsigned char *) malloc(size);
+ if (!data) goto finalize;
+
+ freeData = true;
+
+ if (fread(data, size, 1, jpegFile) < 1) goto failure;
+
+ int width, height, subSample, colorSpace;
+ if (tjDecompressHeader3(jpegDecompressor, data, size, &width, &height, &subSample, &colorSpace) < 0) {
+ TVGERR("JPG LOADER", "%s", tjGetErrorStr());
+ goto failure;
+ }
+
+ w = static_cast<float>(width);
+ h = static_cast<float>(height);
+ ret = true;
+
+ goto finalize;
+
+failure:
+ clear();
+
+finalize:
+ fclose(jpegFile);
+ return ret;
+}
+
+
+bool JpgLoader::open(const char* data, uint32_t size, bool copy)
+{
+ clear();
+
+ int width, height, subSample, colorSpace;
+ if (tjDecompressHeader3(jpegDecompressor, (unsigned char *) data, size, &width, &height, &subSample, &colorSpace) < 0) return false;
+
+ if (copy) {
+ this->data = (unsigned char *) malloc(size);
+ if (!this->data) return false;
+ memcpy((unsigned char *)this->data, data, size);
+ freeData = true;
+ } else {
+ this->data = (unsigned char *) data;
+ freeData = false;
+ }
+
+ w = static_cast<float>(width);
+ h = static_cast<float>(height);
+ this->size = size;
+
+ return true;
+}
+
+
+bool JpgLoader::read()
+{
+ if (image) tjFree(image);
+ image = (unsigned char *)tjAlloc(static_cast<int>(w) * static_cast<int>(h) * tjPixelSize[TJPF_BGRX]);
+ if (!image) return false;
+
+ //decompress jpg image
+ if (tjDecompress2(jpegDecompressor, data, size, image, static_cast<int>(w), 0, static_cast<int>(h), TJPF_BGRX, 0) < 0) {
+ TVGERR("JPG LOADER", "%s", tjGetErrorStr());
+ tjFree(image);
+ image = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+
+bool JpgLoader::close()
+{
+ clear();
+ return true;
+}
+
+
+unique_ptr<Surface> JpgLoader::bitmap()
+{
+ if (!image) return nullptr;
+
+ auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
+ surface->buffer = (uint32_t*)(image);
+ surface->stride = w;
+ surface->w = w;
+ surface->h = h;
+ surface->cs = SwCanvas::ARGB8888;
+
+ return unique_ptr<Surface>(surface);
+}
diff --git a/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h
new file mode 100644
index 0000000000..7d35e57d72
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_JPG_LOADER_H_
+#define _TVG_JPG_LOADER_H_
+
+using tjhandle = void*;
+
+//TODO: Use Task?
+class JpgLoader : public LoadModule
+{
+public:
+ JpgLoader();
+ ~JpgLoader();
+
+ using LoadModule::open;
+ bool open(const string& path) override;
+ bool open(const char* data, uint32_t size, bool copy) override;
+ bool read() override;
+ bool close() override;
+
+ unique_ptr<Surface> bitmap() override;
+
+private:
+ void clear();
+
+ tjhandle jpegDecompressor;
+ unsigned char* data = nullptr;
+ unsigned char *image = nullptr;
+ unsigned long size = 0;
+ bool freeData = false;
+};
+
+#endif //_TVG_JPG_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp
new file mode 100644
index 0000000000..c3d281482a
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "tvgLoader.h"
+#include "tvgPngLoader.h"
+
+static inline uint32_t PREMULTIPLY(uint32_t c)
+{
+ auto a = (c >> 24);
+ return (c & 0xff000000) + ((((c >> 8) & 0xff) * a) & 0xff00) + ((((c & 0x00ff00ff) * a) >> 8) & 0x00ff00ff);
+}
+
+
+static void _premultiply(uint32_t* data, uint32_t w, uint32_t h)
+{
+ auto buffer = data;
+ for (uint32_t y = 0; y < h; ++y, buffer += w) {
+ auto src = buffer;
+ for (uint32_t x = 0; x < w; ++x, ++src) {
+ *src = PREMULTIPLY(*src);
+ }
+ }
+}
+
+
+PngLoader::PngLoader()
+{
+ image = static_cast<png_imagep>(calloc(1, sizeof(png_image)));
+ image->version = PNG_IMAGE_VERSION;
+ image->opaque = NULL;
+}
+
+PngLoader::~PngLoader()
+{
+ if (content) {
+ free((void*)content);
+ content = nullptr;
+ }
+ free(image);
+}
+
+bool PngLoader::open(const string& path)
+{
+ image->opaque = NULL;
+
+ if (!png_image_begin_read_from_file(image, path.c_str())) return false;
+
+ w = (float)image->width;
+ h = (float)image->height;
+
+ return true;
+}
+
+bool PngLoader::open(const char* data, uint32_t size, bool copy)
+{
+ image->opaque = NULL;
+
+ if (!png_image_begin_read_from_memory(image, data, size)) return false;
+
+ w = (float)image->width;
+ h = (float)image->height;
+
+ return true;
+}
+
+
+bool PngLoader::read()
+{
+ png_bytep buffer;
+ image->format = PNG_FORMAT_BGRA;
+ buffer = static_cast<png_bytep>(malloc(PNG_IMAGE_SIZE((*image))));
+ if (!buffer) {
+ //out of memory, only time when libpng doesnt free its data
+ png_image_free(image);
+ return false;
+ }
+ if (!png_image_finish_read(image, NULL, buffer, 0, NULL)) return false;
+ content = reinterpret_cast<uint32_t*>(buffer);
+
+ _premultiply(reinterpret_cast<uint32_t*>(buffer), image->width, image->height);
+
+ return true;
+}
+
+bool PngLoader::close()
+{
+ png_image_free(image);
+ return true;
+}
+
+unique_ptr<Surface> PngLoader::bitmap()
+{
+ if (!content) return nullptr;
+
+ auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
+ surface->buffer = (uint32_t*)(content);
+ surface->stride = w;
+ surface->w = w;
+ surface->h = h;
+ surface->cs = SwCanvas::ARGB8888;
+
+ return unique_ptr<Surface>(surface);
+}
diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h
new file mode 100644
index 0000000000..b42537c73f
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_PNG_LOADER_H_
+#define _TVG_PNG_LOADER_H_
+
+#include <png.h>
+
+class PngLoader : public LoadModule
+{
+public:
+ PngLoader();
+ ~PngLoader();
+
+ using LoadModule::open;
+ bool open(const string& path) override;
+ bool open(const char* data, uint32_t size, bool copy) override;
+ bool read() override;
+ bool close() override;
+
+ unique_ptr<Surface> bitmap() override;
+
+private:
+ png_imagep image = nullptr;
+ const uint32_t* content = nullptr;
+};
+
+#endif //_TVG_PNG_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp
new file mode 100644
index 0000000000..8846613c6b
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <memory.h>
+#include "tvgLoader.h"
+#include "tvgJpgLoader.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+void JpgLoader::clear()
+{
+ jpgdDelete(decoder);
+ if (freeData) free(data);
+ decoder = nullptr;
+ data = nullptr;
+ freeData = false;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+JpgLoader::~JpgLoader()
+{
+ jpgdDelete(decoder);
+ if (freeData) free(data);
+}
+
+
+bool JpgLoader::open(const string& path)
+{
+ clear();
+
+ int width, height;
+ decoder = jpgdHeader(path.c_str(), &width, &height);
+ if (!decoder) return false;
+
+ w = static_cast<float>(width);
+ h = static_cast<float>(height);
+
+ return true;
+}
+
+
+bool JpgLoader::open(const char* data, uint32_t size, bool copy)
+{
+ clear();
+
+ if (copy) {
+ this->data = (char *) malloc(size);
+ if (!this->data) return false;
+ memcpy((char *)this->data, data, size);
+ freeData = true;
+ } else {
+ this->data = (char *) data;
+ freeData = false;
+ }
+
+ int width, height;
+ decoder = jpgdHeader(this->data, size, &width, &height);
+ if (!decoder) return false;
+
+ w = static_cast<float>(width);
+ h = static_cast<float>(height);
+
+ return true;
+}
+
+
+
+bool JpgLoader::read()
+{
+ if (!decoder || w <= 0 || h <= 0) return false;
+
+ TaskScheduler::request(this);
+
+ return true;
+}
+
+
+bool JpgLoader::close()
+{
+ this->done();
+ clear();
+ return true;
+}
+
+
+unique_ptr<Surface> JpgLoader::bitmap()
+{
+ this->done();
+
+ if (!image) return nullptr;
+
+ auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
+ surface->buffer = (uint32_t*)(image);
+ surface->stride = w;
+ surface->w = w;
+ surface->h = h;
+ surface->cs = SwCanvas::ARGB8888;
+
+ return unique_ptr<Surface>(surface);
+}
+
+
+void JpgLoader::run(unsigned tid)
+{
+ image = jpgdDecompress(decoder);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h
new file mode 100644
index 0000000000..39732dbc3a
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_JPG_LOADER_H_
+#define _TVG_JPG_LOADER_H_
+
+#include "tvgTaskScheduler.h"
+#include "tvgJpgd.h"
+
+class JpgLoader : public LoadModule, public Task
+{
+private:
+ jpeg_decoder* decoder = nullptr;
+ char* data = nullptr;
+ unsigned char *image = nullptr;
+ bool freeData = false;
+
+ void clear();
+
+public:
+ ~JpgLoader();
+
+ using LoadModule::open;
+ bool open(const string& path) override;
+ bool open(const char* data, uint32_t size, bool copy) override;
+ bool read() override;
+ bool close() override;
+
+ unique_ptr<Surface> bitmap() override;
+ void run(unsigned tid) override;
+};
+
+#endif //_TVG_JPG_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp
new file mode 100644
index 0000000000..fa72734ec4
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp
@@ -0,0 +1,3028 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// jpgd.cpp - C++ class for JPEG decompression.
+// Public domain, Rich Geldreich <richgel99@gmail.com>
+// Alex Evans: Linear memory allocator (taken from jpge.h).
+// v1.04, May. 19, 2012: Code tweaks to fix VS2008 static code analysis warnings (all looked harmless)
+//
+// Supports progressive and baseline sequential JPEG image files, and the most common chroma subsampling factors: Y, H1V1, H2V1, H1V2, and H2V2.
+//
+// Chroma upsampling quality: H2V2 is upsampled in the frequency domain, H2V1 and H1V2 are upsampled using point sampling.
+// Chroma upsampling reference: "Fast Scheme for Image Size Change in the Compressed Domain"
+// http://vision.ai.uiuc.edu/~dugad/research/dct/index.html
+
+#include <memory.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include "tvgJpgd.h"
+
+#ifdef _MSC_VER
+ #pragma warning (disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable
+ #define JPGD_NORETURN __declspec(noreturn)
+#elif defined(__GNUC__)
+ #define JPGD_NORETURN __attribute__ ((noreturn))
+#else
+ #define JPGD_NORETURN
+#endif
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+// Set to 1 to enable freq. domain chroma upsampling on images using H2V2 subsampling (0=faster nearest neighbor sampling).
+// This is slower, but results in higher quality on images with highly saturated colors.
+#define JPGD_SUPPORT_FREQ_DOMAIN_UPSAMPLING 1
+
+#define JPGD_ASSERT(x)
+#define JPGD_MAX(a,b) (((a)>(b)) ? (a) : (b))
+#define JPGD_MIN(a,b) (((a)<(b)) ? (a) : (b))
+
+typedef int16_t jpgd_quant_t;
+typedef int16_t jpgd_block_t;
+
+// Success/failure error codes.
+enum jpgd_status
+{
+ JPGD_SUCCESS = 0, JPGD_FAILED = -1, JPGD_DONE = 1,
+ JPGD_BAD_DHT_COUNTS = -256, JPGD_BAD_DHT_INDEX, JPGD_BAD_DHT_MARKER, JPGD_BAD_DQT_MARKER, JPGD_BAD_DQT_TABLE,
+ JPGD_BAD_PRECISION, JPGD_BAD_HEIGHT, JPGD_BAD_WIDTH, JPGD_TOO_MANY_COMPONENTS,
+ JPGD_BAD_SOF_LENGTH, JPGD_BAD_VARIABLE_MARKER, JPGD_BAD_DRI_LENGTH, JPGD_BAD_SOS_LENGTH,
+ JPGD_BAD_SOS_COMP_ID, JPGD_W_EXTRA_BYTES_BEFORE_MARKER, JPGD_NO_ARITHMITIC_SUPPORT, JPGD_UNEXPECTED_MARKER,
+ JPGD_NOT_JPEG, JPGD_UNSUPPORTED_MARKER, JPGD_BAD_DQT_LENGTH, JPGD_TOO_MANY_BLOCKS,
+ JPGD_UNDEFINED_QUANT_TABLE, JPGD_UNDEFINED_HUFF_TABLE, JPGD_NOT_SINGLE_SCAN, JPGD_UNSUPPORTED_COLORSPACE,
+ JPGD_UNSUPPORTED_SAMP_FACTORS, JPGD_DECODE_ERROR, JPGD_BAD_RESTART_MARKER, JPGD_ASSERTION_ERROR,
+ JPGD_BAD_SOS_SPECTRAL, JPGD_BAD_SOS_SUCCESSIVE, JPGD_STREAM_READ, JPGD_NOTENOUGHMEM
+};
+
+enum
+{
+ JPGD_IN_BUF_SIZE = 8192, JPGD_MAX_BLOCKS_PER_MCU = 10, JPGD_MAX_HUFF_TABLES = 8, JPGD_MAX_QUANT_TABLES = 4,
+ JPGD_MAX_COMPONENTS = 4, JPGD_MAX_COMPS_IN_SCAN = 4, JPGD_MAX_BLOCKS_PER_ROW = 8192, JPGD_MAX_HEIGHT = 16384, JPGD_MAX_WIDTH = 16384
+};
+
+// Input stream interface.
+// Derive from this class to read input data from sources other than files or memory. Set m_eof_flag to true when no more data is available.
+// The decoder is rather greedy: it will keep on calling this method until its internal input buffer is full, or until the EOF flag is set.
+// It the input stream contains data after the JPEG stream's EOI (end of image) marker it will probably be pulled into the internal buffer.
+// Call the get_total_bytes_read() method to determine the actual size of the JPEG stream after successful decoding.
+struct jpeg_decoder_stream
+{
+ jpeg_decoder_stream() { }
+ virtual ~jpeg_decoder_stream() { }
+
+ // The read() method is called when the internal input buffer is empty.
+ // Parameters:
+ // pBuf - input buffer
+ // max_bytes_to_read - maximum bytes that can be written to pBuf
+ // pEOF_flag - set this to true if at end of stream (no more bytes remaining)
+ // Returns -1 on error, otherwise return the number of bytes actually written to the buffer (which may be 0).
+ // Notes: This method will be called in a loop until you set *pEOF_flag to true or the internal buffer is full.
+ virtual int read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag) = 0;
+};
+
+
+// stdio FILE stream class.
+class jpeg_decoder_file_stream : public jpeg_decoder_stream
+{
+ jpeg_decoder_file_stream(const jpeg_decoder_file_stream &);
+ jpeg_decoder_file_stream &operator =(const jpeg_decoder_file_stream &);
+
+ FILE *m_pFile = nullptr;
+ bool m_eof_flag = false;
+ bool m_error_flag = false;
+
+public:
+ jpeg_decoder_file_stream() {}
+ virtual ~jpeg_decoder_file_stream();
+ bool open(const char *Pfilename);
+ void close();
+ virtual int read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag);
+ };
+
+
+// Memory stream class.
+class jpeg_decoder_mem_stream : public jpeg_decoder_stream
+{
+ const uint8_t *m_pSrc_data;
+ uint32_t m_ofs, m_size;
+
+public:
+ jpeg_decoder_mem_stream() : m_pSrc_data(nullptr), m_ofs(0), m_size(0) {}
+ jpeg_decoder_mem_stream(const uint8_t *pSrc_data, uint32_t size) : m_pSrc_data(pSrc_data), m_ofs(0), m_size(size) {}
+ virtual ~jpeg_decoder_mem_stream() {}
+ bool open(const uint8_t *pSrc_data, uint32_t size);
+ void close() { m_pSrc_data = nullptr; m_ofs = 0; m_size = 0; }
+ virtual int read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag);
+};
+
+
+class jpeg_decoder
+{
+public:
+ // Call get_error_code() after constructing to determine if the stream is valid or not. You may call the get_width(), get_height(), etc.
+ // methods after the constructor is called. You may then either destruct the object, or begin decoding the image by calling begin_decoding(), then decode() on each scanline.
+ jpeg_decoder(jpeg_decoder_stream *pStream);
+ ~jpeg_decoder();
+
+ // Call this method after constructing the object to begin decompression.
+ // If JPGD_SUCCESS is returned you may then call decode() on each scanline.
+ int begin_decoding();
+ // Returns the next scan line.
+ // For grayscale images, pScan_line will point to a buffer containing 8-bit pixels (get_bytes_per_pixel() will return 1).
+ // Otherwise, it will always point to a buffer containing 32-bit RGBA pixels (A will always be 255, and get_bytes_per_pixel() will return 4).
+ // Returns JPGD_SUCCESS if a scan line has been returned.
+ // Returns JPGD_DONE if all scan lines have been returned.
+ // Returns JPGD_FAILED if an error occurred. Call get_error_code() for a more info.
+ int decode(const void** pScan_line, uint32_t* pScan_line_len);
+ inline jpgd_status get_error_code() const { return m_error_code; }
+ inline int get_width() const { return m_image_x_size; }
+ inline int get_height() const { return m_image_y_size; }
+ inline int get_num_components() const { return m_comps_in_frame; }
+ inline int get_bytes_per_pixel() const { return m_dest_bytes_per_pixel; }
+ inline int get_bytes_per_scan_line() const { return m_image_x_size * get_bytes_per_pixel(); }
+ // Returns the total number of bytes actually consumed by the decoder (which should equal the actual size of the JPEG file).
+ inline int get_total_bytes_read() const { return m_total_bytes_read; }
+
+private:
+ jpeg_decoder(const jpeg_decoder &);
+ jpeg_decoder &operator =(const jpeg_decoder &);
+
+ typedef void (*pDecode_block_func)(jpeg_decoder *, int, int, int);
+
+ struct huff_tables
+ {
+ bool ac_table;
+ uint32_t look_up[256];
+ uint32_t look_up2[256];
+ uint8_t code_size[256];
+ uint32_t tree[512];
+ };
+
+ struct coeff_buf
+ {
+ uint8_t *pData;
+ int block_num_x, block_num_y;
+ int block_len_x, block_len_y;
+ int block_size;
+ };
+
+ struct mem_block
+ {
+ mem_block *m_pNext;
+ size_t m_used_count;
+ size_t m_size;
+ char m_data[1];
+ };
+
+ jmp_buf m_jmp_state;
+ mem_block *m_pMem_blocks;
+ int m_image_x_size;
+ int m_image_y_size;
+ jpeg_decoder_stream *m_pStream;
+ int m_progressive_flag;
+ uint8_t m_huff_ac[JPGD_MAX_HUFF_TABLES];
+ uint8_t* m_huff_num[JPGD_MAX_HUFF_TABLES]; // pointer to number of Huffman codes per bit size
+ uint8_t* m_huff_val[JPGD_MAX_HUFF_TABLES]; // pointer to Huffman codes per bit size
+ jpgd_quant_t* m_quant[JPGD_MAX_QUANT_TABLES]; // pointer to quantization tables
+ int m_scan_type; // Gray, Yh1v1, Yh1v2, Yh2v1, Yh2v2 (CMYK111, CMYK4114 no longer supported)
+ int m_comps_in_frame; // # of components in frame
+ int m_comp_h_samp[JPGD_MAX_COMPONENTS]; // component's horizontal sampling factor
+ int m_comp_v_samp[JPGD_MAX_COMPONENTS]; // component's vertical sampling factor
+ int m_comp_quant[JPGD_MAX_COMPONENTS]; // component's quantization table selector
+ int m_comp_ident[JPGD_MAX_COMPONENTS]; // component's ID
+ int m_comp_h_blocks[JPGD_MAX_COMPONENTS];
+ int m_comp_v_blocks[JPGD_MAX_COMPONENTS];
+ int m_comps_in_scan; // # of components in scan
+ int m_comp_list[JPGD_MAX_COMPS_IN_SCAN]; // components in this scan
+ int m_comp_dc_tab[JPGD_MAX_COMPONENTS]; // component's DC Huffman coding table selector
+ int m_comp_ac_tab[JPGD_MAX_COMPONENTS]; // component's AC Huffman coding table selector
+ int m_spectral_start; // spectral selection start
+ int m_spectral_end; // spectral selection end
+ int m_successive_low; // successive approximation low
+ int m_successive_high; // successive approximation high
+ int m_max_mcu_x_size; // MCU's max. X size in pixels
+ int m_max_mcu_y_size; // MCU's max. Y size in pixels
+ int m_blocks_per_mcu;
+ int m_max_blocks_per_row;
+ int m_mcus_per_row, m_mcus_per_col;
+ int m_mcu_org[JPGD_MAX_BLOCKS_PER_MCU];
+ int m_total_lines_left; // total # lines left in image
+ int m_mcu_lines_left; // total # lines left in this MCU
+ int m_real_dest_bytes_per_scan_line;
+ int m_dest_bytes_per_scan_line; // rounded up
+ int m_dest_bytes_per_pixel; // 4 (RGB) or 1 (Y)
+ huff_tables* m_pHuff_tabs[JPGD_MAX_HUFF_TABLES];
+ coeff_buf* m_dc_coeffs[JPGD_MAX_COMPONENTS];
+ coeff_buf* m_ac_coeffs[JPGD_MAX_COMPONENTS];
+ int m_eob_run;
+ int m_block_y_mcu[JPGD_MAX_COMPONENTS];
+ uint8_t* m_pIn_buf_ofs;
+ int m_in_buf_left;
+ int m_tem_flag;
+ bool m_eof_flag;
+ uint8_t m_in_buf_pad_start[128];
+ uint8_t m_in_buf[JPGD_IN_BUF_SIZE + 128];
+ uint8_t m_in_buf_pad_end[128];
+ int m_bits_left;
+ uint32_t m_bit_buf;
+ int m_restart_interval;
+ int m_restarts_left;
+ int m_next_restart_num;
+ int m_max_mcus_per_row;
+ int m_max_blocks_per_mcu;
+ int m_expanded_blocks_per_mcu;
+ int m_expanded_blocks_per_row;
+ int m_expanded_blocks_per_component;
+ bool m_freq_domain_chroma_upsample;
+ int m_max_mcus_per_col;
+ uint32_t m_last_dc_val[JPGD_MAX_COMPONENTS];
+ jpgd_block_t* m_pMCU_coefficients;
+ int m_mcu_block_max_zag[JPGD_MAX_BLOCKS_PER_MCU];
+ uint8_t* m_pSample_buf;
+ int m_crr[256];
+ int m_cbb[256];
+ int m_crg[256];
+ int m_cbg[256];
+ uint8_t* m_pScan_line_0;
+ uint8_t* m_pScan_line_1;
+ jpgd_status m_error_code;
+ bool m_ready_flag;
+ int m_total_bytes_read;
+
+ void free_all_blocks();
+ JPGD_NORETURN void stop_decoding(jpgd_status status);
+ void *alloc(size_t n, bool zero = false);
+ void word_clear(void *p, uint16_t c, uint32_t n);
+ void prep_in_buffer();
+ void read_dht_marker();
+ void read_dqt_marker();
+ void read_sof_marker();
+ void skip_variable_marker();
+ void read_dri_marker();
+ void read_sos_marker();
+ int next_marker();
+ int process_markers();
+ void locate_soi_marker();
+ void locate_sof_marker();
+ int locate_sos_marker();
+ void init(jpeg_decoder_stream * pStream);
+ void create_look_ups();
+ void fix_in_buffer();
+ void transform_mcu(int mcu_row);
+ void transform_mcu_expand(int mcu_row);
+ coeff_buf* coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y);
+ inline jpgd_block_t *coeff_buf_getp(coeff_buf *cb, int block_x, int block_y);
+ void load_next_row();
+ void decode_next_row();
+ void make_huff_table(int index, huff_tables *pH);
+ void check_quant_tables();
+ void check_huff_tables();
+ void calc_mcu_block_order();
+ int init_scan();
+ void init_frame();
+ void process_restart();
+ void decode_scan(pDecode_block_func decode_block_func);
+ void init_progressive();
+ void init_sequential();
+ void decode_start();
+ void decode_init(jpeg_decoder_stream * pStream);
+ void H2V2Convert();
+ void H2V1Convert();
+ void H1V2Convert();
+ void H1V1Convert();
+ void gray_convert();
+ void expanded_convert();
+ void find_eoi();
+ inline uint32_t get_char();
+ inline uint32_t get_char(bool *pPadding_flag);
+ inline void stuff_char(uint8_t q);
+ inline uint8_t get_octet();
+ inline uint32_t get_bits(int num_bits);
+ inline uint32_t get_bits_no_markers(int numbits);
+ inline int huff_decode(huff_tables *pH);
+ inline int huff_decode(huff_tables *pH, int& extrabits);
+ static inline uint8_t clamp(int i);
+ static void decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y);
+ static void decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y);
+ static void decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y);
+ static void decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y);
+};
+
+
+// DCT coefficients are stored in this sequence.
+static int g_ZAG[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 };
+
+enum JPEG_MARKER
+{
+ M_SOF0 = 0xC0, M_SOF1 = 0xC1, M_SOF2 = 0xC2, M_SOF3 = 0xC3, M_SOF5 = 0xC5, M_SOF6 = 0xC6, M_SOF7 = 0xC7, M_JPG = 0xC8,
+ M_SOF9 = 0xC9, M_SOF10 = 0xCA, M_SOF11 = 0xCB, M_SOF13 = 0xCD, M_SOF14 = 0xCE, M_SOF15 = 0xCF, M_DHT = 0xC4, M_DAC = 0xCC,
+ M_RST0 = 0xD0, M_RST1 = 0xD1, M_RST2 = 0xD2, M_RST3 = 0xD3, M_RST4 = 0xD4, M_RST5 = 0xD5, M_RST6 = 0xD6, M_RST7 = 0xD7,
+ M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_DNL = 0xDC, M_DRI = 0xDD, M_DHP = 0xDE, M_EXP = 0xDF,
+ M_APP0 = 0xE0, M_APP15 = 0xEF, M_JPG0 = 0xF0, M_JPG13 = 0xFD, M_COM = 0xFE, M_TEM = 0x01, M_ERROR = 0x100, RST0 = 0xD0
+};
+
+enum JPEG_SUBSAMPLING { JPGD_GRAYSCALE = 0, JPGD_YH1V1, JPGD_YH2V1, JPGD_YH1V2, JPGD_YH2V2 };
+
+#define CONST_BITS 13
+#define PASS1_BITS 2
+#define SCALEDONE ((int32_t)1)
+#define DESCALE(x,n) (((x) + (SCALEDONE << ((n)-1))) >> (n))
+#define DESCALE_ZEROSHIFT(x,n) (((x) + (128 << (n)) + (SCALEDONE << ((n)-1))) >> (n))
+#define MULTIPLY(var, cnst) ((var) * (cnst))
+#define CLAMP(i) ((static_cast<uint32_t>(i) > 255) ? (((~i) >> 31) & 0xFF) : (i))
+
+#define FIX_0_298631336 ((int32_t)2446) /* FIX(0.298631336) */
+#define FIX_0_390180644 ((int32_t)3196) /* FIX(0.390180644) */
+#define FIX_0_541196100 ((int32_t)4433) /* FIX(0.541196100) */
+#define FIX_0_765366865 ((int32_t)6270) /* FIX(0.765366865) */
+#define FIX_0_899976223 ((int32_t)7373) /* FIX(0.899976223) */
+#define FIX_1_175875602 ((int32_t)9633) /* FIX(1.175875602) */
+#define FIX_1_501321110 ((int32_t)12299) /* FIX(1.501321110) */
+#define FIX_1_847759065 ((int32_t)15137) /* FIX(1.847759065) */
+#define FIX_1_961570560 ((int32_t)16069) /* FIX(1.961570560) */
+#define FIX_2_053119869 ((int32_t)16819) /* FIX(2.053119869) */
+#define FIX_2_562915447 ((int32_t)20995) /* FIX(2.562915447) */
+#define FIX_3_072711026 ((int32_t)25172) /* FIX(3.072711026) */
+
+
+// Compiler creates a fast path 1D IDCT for X non-zero columns
+template <int NONZERO_COLS>
+struct Row
+{
+ static void idct(int* pTemp, const jpgd_block_t* pSrc)
+ {
+ // ACCESS_COL() will be optimized at compile time to either an array access, or 0.
+ #define ACCESS_COL(x) (((x) < NONZERO_COLS) ? (int)pSrc[x] : 0)
+
+ const int z2 = ACCESS_COL(2), z3 = ACCESS_COL(6);
+ const int z1 = MULTIPLY(z2 + z3, FIX_0_541196100);
+ const int tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065);
+ const int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865);
+
+ const int tmp0 = (ACCESS_COL(0) + ACCESS_COL(4)) << CONST_BITS;
+ const int tmp1 = (ACCESS_COL(0) - ACCESS_COL(4)) << CONST_BITS;
+
+ const int tmp10 = tmp0 + tmp3, tmp13 = tmp0 - tmp3, tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2;
+
+ const int atmp0 = ACCESS_COL(7), atmp1 = ACCESS_COL(5), atmp2 = ACCESS_COL(3), atmp3 = ACCESS_COL(1);
+
+ const int bz1 = atmp0 + atmp3, bz2 = atmp1 + atmp2, bz3 = atmp0 + atmp2, bz4 = atmp1 + atmp3;
+ const int bz5 = MULTIPLY(bz3 + bz4, FIX_1_175875602);
+
+ const int az1 = MULTIPLY(bz1, - FIX_0_899976223);
+ const int az2 = MULTIPLY(bz2, - FIX_2_562915447);
+ const int az3 = MULTIPLY(bz3, - FIX_1_961570560) + bz5;
+ const int az4 = MULTIPLY(bz4, - FIX_0_390180644) + bz5;
+
+ const int btmp0 = MULTIPLY(atmp0, FIX_0_298631336) + az1 + az3;
+ const int btmp1 = MULTIPLY(atmp1, FIX_2_053119869) + az2 + az4;
+ const int btmp2 = MULTIPLY(atmp2, FIX_3_072711026) + az2 + az3;
+ const int btmp3 = MULTIPLY(atmp3, FIX_1_501321110) + az1 + az4;
+
+ pTemp[0] = DESCALE(tmp10 + btmp3, CONST_BITS-PASS1_BITS);
+ pTemp[7] = DESCALE(tmp10 - btmp3, CONST_BITS-PASS1_BITS);
+ pTemp[1] = DESCALE(tmp11 + btmp2, CONST_BITS-PASS1_BITS);
+ pTemp[6] = DESCALE(tmp11 - btmp2, CONST_BITS-PASS1_BITS);
+ pTemp[2] = DESCALE(tmp12 + btmp1, CONST_BITS-PASS1_BITS);
+ pTemp[5] = DESCALE(tmp12 - btmp1, CONST_BITS-PASS1_BITS);
+ pTemp[3] = DESCALE(tmp13 + btmp0, CONST_BITS-PASS1_BITS);
+ pTemp[4] = DESCALE(tmp13 - btmp0, CONST_BITS-PASS1_BITS);
+ }
+};
+
+
+template <>
+struct Row<0>
+{
+ static void idct(int* pTemp, const jpgd_block_t* pSrc)
+ {
+#ifdef _MSC_VER
+ pTemp; pSrc;
+#endif
+ }
+};
+
+
+template <>
+struct Row<1>
+{
+ static void idct(int* pTemp, const jpgd_block_t* pSrc)
+ {
+ const int dcval = (pSrc[0] << PASS1_BITS);
+
+ pTemp[0] = dcval;
+ pTemp[1] = dcval;
+ pTemp[2] = dcval;
+ pTemp[3] = dcval;
+ pTemp[4] = dcval;
+ pTemp[5] = dcval;
+ pTemp[6] = dcval;
+ pTemp[7] = dcval;
+ }
+};
+
+
+// Compiler creates a fast path 1D IDCT for X non-zero rows
+template <int NONZERO_ROWS>
+struct Col
+{
+ static void idct(uint8_t* pDst_ptr, const int* pTemp)
+ {
+ // ACCESS_ROW() will be optimized at compile time to either an array access, or 0.
+ #define ACCESS_ROW(x) (((x) < NONZERO_ROWS) ? pTemp[x * 8] : 0)
+
+ const int z2 = ACCESS_ROW(2);
+ const int z3 = ACCESS_ROW(6);
+
+ const int z1 = MULTIPLY(z2 + z3, FIX_0_541196100);
+ const int tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065);
+ const int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865);
+
+ const int tmp0 = (ACCESS_ROW(0) + ACCESS_ROW(4)) << CONST_BITS;
+ const int tmp1 = (ACCESS_ROW(0) - ACCESS_ROW(4)) << CONST_BITS;
+
+ const int tmp10 = tmp0 + tmp3, tmp13 = tmp0 - tmp3, tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2;
+
+ const int atmp0 = ACCESS_ROW(7), atmp1 = ACCESS_ROW(5), atmp2 = ACCESS_ROW(3), atmp3 = ACCESS_ROW(1);
+
+ const int bz1 = atmp0 + atmp3, bz2 = atmp1 + atmp2, bz3 = atmp0 + atmp2, bz4 = atmp1 + atmp3;
+ const int bz5 = MULTIPLY(bz3 + bz4, FIX_1_175875602);
+
+ const int az1 = MULTIPLY(bz1, - FIX_0_899976223);
+ const int az2 = MULTIPLY(bz2, - FIX_2_562915447);
+ const int az3 = MULTIPLY(bz3, - FIX_1_961570560) + bz5;
+ const int az4 = MULTIPLY(bz4, - FIX_0_390180644) + bz5;
+
+ const int btmp0 = MULTIPLY(atmp0, FIX_0_298631336) + az1 + az3;
+ const int btmp1 = MULTIPLY(atmp1, FIX_2_053119869) + az2 + az4;
+ const int btmp2 = MULTIPLY(atmp2, FIX_3_072711026) + az2 + az3;
+ const int btmp3 = MULTIPLY(atmp3, FIX_1_501321110) + az1 + az4;
+
+ int i = DESCALE_ZEROSHIFT(tmp10 + btmp3, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*0] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp10 - btmp3, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*7] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp11 + btmp2, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*1] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp11 - btmp2, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*6] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp12 + btmp1, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*2] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp12 - btmp1, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*5] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp13 + btmp0, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*3] = (uint8_t)CLAMP(i);
+
+ i = DESCALE_ZEROSHIFT(tmp13 - btmp0, CONST_BITS+PASS1_BITS+3);
+ pDst_ptr[8*4] = (uint8_t)CLAMP(i);
+ }
+};
+
+
+template <>
+struct Col<1>
+{
+ static void idct(uint8_t* pDst_ptr, const int* pTemp)
+ {
+ int dcval = DESCALE_ZEROSHIFT(pTemp[0], PASS1_BITS+3);
+ const uint8_t dcval_clamped = (uint8_t)CLAMP(dcval);
+ pDst_ptr[0*8] = dcval_clamped;
+ pDst_ptr[1*8] = dcval_clamped;
+ pDst_ptr[2*8] = dcval_clamped;
+ pDst_ptr[3*8] = dcval_clamped;
+ pDst_ptr[4*8] = dcval_clamped;
+ pDst_ptr[5*8] = dcval_clamped;
+ pDst_ptr[6*8] = dcval_clamped;
+ pDst_ptr[7*8] = dcval_clamped;
+ }
+};
+
+
+static const uint8_t s_idct_row_table[] = {
+ 1,0,0,0,0,0,0,0, 2,0,0,0,0,0,0,0, 2,1,0,0,0,0,0,0, 2,1,1,0,0,0,0,0, 2,2,1,0,0,0,0,0, 3,2,1,0,0,0,0,0, 4,2,1,0,0,0,0,0, 4,3,1,0,0,0,0,0,
+ 4,3,2,0,0,0,0,0, 4,3,2,1,0,0,0,0, 4,3,2,1,1,0,0,0, 4,3,2,2,1,0,0,0, 4,3,3,2,1,0,0,0, 4,4,3,2,1,0,0,0, 5,4,3,2,1,0,0,0, 6,4,3,2,1,0,0,0,
+ 6,5,3,2,1,0,0,0, 6,5,4,2,1,0,0,0, 6,5,4,3,1,0,0,0, 6,5,4,3,2,0,0,0, 6,5,4,3,2,1,0,0, 6,5,4,3,2,1,1,0, 6,5,4,3,2,2,1,0, 6,5,4,3,3,2,1,0,
+ 6,5,4,4,3,2,1,0, 6,5,5,4,3,2,1,0, 6,6,5,4,3,2,1,0, 7,6,5,4,3,2,1,0, 8,6,5,4,3,2,1,0, 8,7,5,4,3,2,1,0, 8,7,6,4,3,2,1,0, 8,7,6,5,3,2,1,0,
+ 8,7,6,5,4,2,1,0, 8,7,6,5,4,3,1,0, 8,7,6,5,4,3,2,0, 8,7,6,5,4,3,2,1, 8,7,6,5,4,3,2,2, 8,7,6,5,4,3,3,2, 8,7,6,5,4,4,3,2, 8,7,6,5,5,4,3,2,
+ 8,7,6,6,5,4,3,2, 8,7,7,6,5,4,3,2, 8,8,7,6,5,4,3,2, 8,8,8,6,5,4,3,2, 8,8,8,7,5,4,3,2, 8,8,8,7,6,4,3,2, 8,8,8,7,6,5,3,2, 8,8,8,7,6,5,4,2,
+ 8,8,8,7,6,5,4,3, 8,8,8,7,6,5,4,4, 8,8,8,7,6,5,5,4, 8,8,8,7,6,6,5,4, 8,8,8,7,7,6,5,4, 8,8,8,8,7,6,5,4, 8,8,8,8,8,6,5,4, 8,8,8,8,8,7,5,4,
+ 8,8,8,8,8,7,6,4, 8,8,8,8,8,7,6,5, 8,8,8,8,8,7,6,6, 8,8,8,8,8,7,7,6, 8,8,8,8,8,8,7,6, 8,8,8,8,8,8,8,6, 8,8,8,8,8,8,8,7, 8,8,8,8,8,8,8,8,
+};
+
+
+static const uint8_t s_idct_col_table[] = { 1, 1, 2, 3, 3, 3, 3, 3, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
+
+
+void idct(const jpgd_block_t* pSrc_ptr, uint8_t* pDst_ptr, int block_max_zag)
+{
+ JPGD_ASSERT(block_max_zag >= 1);
+ JPGD_ASSERT(block_max_zag <= 64);
+
+ if (block_max_zag <= 1) {
+ int k = ((pSrc_ptr[0] + 4) >> 3) + 128;
+ k = CLAMP(k);
+ k = k | (k<<8);
+ k = k | (k<<16);
+ for (int i = 8; i > 0; i--) {
+ *(int*)&pDst_ptr[0] = k;
+ *(int*)&pDst_ptr[4] = k;
+ pDst_ptr += 8;
+ }
+ return;
+ }
+
+ int temp[64];
+ const jpgd_block_t* pSrc = pSrc_ptr;
+ int* pTemp = temp;
+ const uint8_t* pRow_tab = &s_idct_row_table[(block_max_zag - 1) * 8];
+ int i;
+ for (i = 8; i > 0; i--, pRow_tab++) {
+ switch (*pRow_tab) {
+ case 0: Row<0>::idct(pTemp, pSrc); break;
+ case 1: Row<1>::idct(pTemp, pSrc); break;
+ case 2: Row<2>::idct(pTemp, pSrc); break;
+ case 3: Row<3>::idct(pTemp, pSrc); break;
+ case 4: Row<4>::idct(pTemp, pSrc); break;
+ case 5: Row<5>::idct(pTemp, pSrc); break;
+ case 6: Row<6>::idct(pTemp, pSrc); break;
+ case 7: Row<7>::idct(pTemp, pSrc); break;
+ case 8: Row<8>::idct(pTemp, pSrc); break;
+ }
+ pSrc += 8;
+ pTemp += 8;
+ }
+
+ pTemp = temp;
+
+ const int nonzero_rows = s_idct_col_table[block_max_zag - 1];
+ for (i = 8; i > 0; i--) {
+ switch (nonzero_rows) {
+ case 1: Col<1>::idct(pDst_ptr, pTemp); break;
+ case 2: Col<2>::idct(pDst_ptr, pTemp); break;
+ case 3: Col<3>::idct(pDst_ptr, pTemp); break;
+ case 4: Col<4>::idct(pDst_ptr, pTemp); break;
+ case 5: Col<5>::idct(pDst_ptr, pTemp); break;
+ case 6: Col<6>::idct(pDst_ptr, pTemp); break;
+ case 7: Col<7>::idct(pDst_ptr, pTemp); break;
+ case 8: Col<8>::idct(pDst_ptr, pTemp); break;
+ }
+ pTemp++;
+ pDst_ptr++;
+ }
+}
+
+
+void idct_4x4(const jpgd_block_t* pSrc_ptr, uint8_t* pDst_ptr)
+{
+ int temp[64];
+ int* pTemp = temp;
+ const jpgd_block_t* pSrc = pSrc_ptr;
+
+ for (int i = 4; i > 0; i--) {
+ Row<4>::idct(pTemp, pSrc);
+ pSrc += 8;
+ pTemp += 8;
+ }
+
+ pTemp = temp;
+
+ for (int i = 8; i > 0; i--) {
+ Col<4>::idct(pDst_ptr, pTemp);
+ pTemp++;
+ pDst_ptr++;
+ }
+}
+
+
+// Retrieve one character from the input stream.
+inline uint32_t jpeg_decoder::get_char()
+{
+ // Any bytes remaining in buffer?
+ if (!m_in_buf_left) {
+ // Try to get more bytes.
+ prep_in_buffer();
+ // Still nothing to get?
+ if (!m_in_buf_left) {
+ // Pad the end of the stream with 0xFF 0xD9 (EOI marker)
+ int t = m_tem_flag;
+ m_tem_flag ^= 1;
+ if (t) return 0xD9;
+ else return 0xFF;
+ }
+ }
+ uint32_t c = *m_pIn_buf_ofs++;
+ m_in_buf_left--;
+ return c;
+}
+
+
+// Same as previous method, except can indicate if the character is a pad character or not.
+inline uint32_t jpeg_decoder::get_char(bool *pPadding_flag)
+{
+ if (!m_in_buf_left) {
+ prep_in_buffer();
+ if (!m_in_buf_left) {
+ *pPadding_flag = true;
+ int t = m_tem_flag;
+ m_tem_flag ^= 1;
+ if (t) return 0xD9;
+ else return 0xFF;
+ }
+ }
+ *pPadding_flag = false;
+ uint32_t c = *m_pIn_buf_ofs++;
+ m_in_buf_left--;
+
+ return c;
+}
+
+
+// Inserts a previously retrieved character back into the input buffer.
+inline void jpeg_decoder::stuff_char(uint8_t q)
+{
+ *(--m_pIn_buf_ofs) = q;
+ m_in_buf_left++;
+}
+
+
+// Retrieves one character from the input stream, but does not read past markers. Will continue to return 0xFF when a marker is encountered.
+inline uint8_t jpeg_decoder::get_octet()
+{
+ bool padding_flag;
+ int c = get_char(&padding_flag);
+
+ if (c == 0xFF) {
+ if (padding_flag) return 0xFF;
+
+ c = get_char(&padding_flag);
+ if (padding_flag) {
+ stuff_char(0xFF);
+ return 0xFF;
+ }
+ if (c == 0x00) return 0xFF;
+ else {
+ stuff_char(static_cast<uint8_t>(c));
+ stuff_char(0xFF);
+ return 0xFF;
+ }
+ }
+ return static_cast<uint8_t>(c);
+}
+
+
+// Retrieves a variable number of bits from the input stream. Does not recognize markers.
+inline uint32_t jpeg_decoder::get_bits(int num_bits)
+{
+ if (!num_bits) return 0;
+
+ uint32_t i = m_bit_buf >> (32 - num_bits);
+
+ if ((m_bits_left -= num_bits) <= 0) {
+ m_bit_buf <<= (num_bits += m_bits_left);
+ uint32_t c1 = get_char();
+ uint32_t c2 = get_char();
+ m_bit_buf = (m_bit_buf & 0xFFFF0000) | (c1 << 8) | c2;
+ m_bit_buf <<= -m_bits_left;
+ m_bits_left += 16;
+ JPGD_ASSERT(m_bits_left >= 0);
+ }
+ else m_bit_buf <<= num_bits;
+
+ return i;
+}
+
+
+// Retrieves a variable number of bits from the input stream. Markers will not be read into the input bit buffer. Instead, an infinite number of all 1's will be returned when a marker is encountered.
+inline uint32_t jpeg_decoder::get_bits_no_markers(int num_bits)
+{
+ if (!num_bits)return 0;
+
+ uint32_t i = m_bit_buf >> (32 - num_bits);
+
+ if ((m_bits_left -= num_bits) <= 0) {
+ m_bit_buf <<= (num_bits += m_bits_left);
+ if ((m_in_buf_left < 2) || (m_pIn_buf_ofs[0] == 0xFF) || (m_pIn_buf_ofs[1] == 0xFF)) {
+ uint32_t c1 = get_octet();
+ uint32_t c2 = get_octet();
+ m_bit_buf |= (c1 << 8) | c2;
+ } else {
+ m_bit_buf |= ((uint32_t)m_pIn_buf_ofs[0] << 8) | m_pIn_buf_ofs[1];
+ m_in_buf_left -= 2;
+ m_pIn_buf_ofs += 2;
+ }
+ m_bit_buf <<= -m_bits_left;
+ m_bits_left += 16;
+ JPGD_ASSERT(m_bits_left >= 0);
+ } else m_bit_buf <<= num_bits;
+
+ return i;
+}
+
+
+// Decodes a Huffman encoded symbol.
+inline int jpeg_decoder::huff_decode(huff_tables *pH)
+{
+ int symbol;
+
+ // Check first 8-bits: do we have a complete symbol?
+ if ((symbol = pH->look_up[m_bit_buf >> 24]) < 0) {
+ // Decode more bits, use a tree traversal to find symbol.
+ int ofs = 23;
+ do {
+ symbol = pH->tree[-(int)(symbol + ((m_bit_buf >> ofs) & 1))];
+ ofs--;
+ } while (symbol < 0);
+ get_bits_no_markers(8 + (23 - ofs));
+ } else get_bits_no_markers(pH->code_size[symbol]);
+
+ return symbol;
+}
+
+
+// Decodes a Huffman encoded symbol.
+inline int jpeg_decoder::huff_decode(huff_tables *pH, int& extra_bits)
+{
+ int symbol;
+
+ // Check first 8-bits: do we have a complete symbol?
+ if ((symbol = pH->look_up2[m_bit_buf >> 24]) < 0) {
+ // Use a tree traversal to find symbol.
+ int ofs = 23;
+ do {
+ symbol = pH->tree[-(int)(symbol + ((m_bit_buf >> ofs) & 1))];
+ ofs--;
+ } while (symbol < 0);
+
+ get_bits_no_markers(8 + (23 - ofs));
+ extra_bits = get_bits_no_markers(symbol & 0xF);
+ } else {
+ JPGD_ASSERT(((symbol >> 8) & 31) == pH->code_size[symbol & 255] + ((symbol & 0x8000) ? (symbol & 15) : 0));
+
+ if (symbol & 0x8000) {
+ get_bits_no_markers((symbol >> 8) & 31);
+ extra_bits = symbol >> 16;
+ } else {
+ int code_size = (symbol >> 8) & 31;
+ int num_extra_bits = symbol & 0xF;
+ int bits = code_size + num_extra_bits;
+ if (bits <= (m_bits_left + 16)) extra_bits = get_bits_no_markers(bits) & ((1 << num_extra_bits) - 1);
+ else {
+ get_bits_no_markers(code_size);
+ extra_bits = get_bits_no_markers(num_extra_bits);
+ }
+ }
+ symbol &= 0xFF;
+ }
+ return symbol;
+}
+
+
+// Tables and macro used to fully decode the DPCM differences.
+static const int s_extend_test[16] = { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 };
+static const unsigned int s_extend_offset[16] = { 0, ((-1u)<<1) + 1, ((-1u)<<2) + 1, ((-1u)<<3) + 1, ((-1u)<<4) + 1, ((-1u)<<5) + 1, ((-1u)<<6) + 1, ((-1u)<<7) + 1, ((-1u)<<8) + 1, ((-1u)<<9) + 1, ((-1u)<<10) + 1, ((-1u)<<11) + 1, ((-1u)<<12) + 1, ((-1u)<<13) + 1, ((-1u)<<14) + 1, ((-1u)<<15) + 1 };
+
+// The logical AND's in this macro are to shut up static code analysis (aren't really necessary - couldn't find another way to do this)
+#define JPGD_HUFF_EXTEND(x, s) (((x) < s_extend_test[s & 15]) ? ((x) + s_extend_offset[s & 15]) : (x))
+
+
+// Clamps a value between 0-255.
+inline uint8_t jpeg_decoder::clamp(int i)
+{
+ if (static_cast<uint32_t>(i) > 255) i = (((~i) >> 31) & 0xFF);
+ return static_cast<uint8_t>(i);
+}
+
+
+namespace DCT_Upsample
+{
+ struct Matrix44
+ {
+ typedef int Element_Type;
+ enum { NUM_ROWS = 4, NUM_COLS = 4 };
+
+ Element_Type v[NUM_ROWS][NUM_COLS];
+
+ inline int rows() const { return NUM_ROWS; }
+ inline int cols() const { return NUM_COLS; }
+ inline const Element_Type & at(int r, int c) const { return v[r][c]; }
+ inline Element_Type & at(int r, int c) { return v[r][c]; }
+
+ inline Matrix44() {}
+
+ inline Matrix44& operator += (const Matrix44& a)
+ {
+ for (int r = 0; r < NUM_ROWS; r++) {
+ at(r, 0) += a.at(r, 0);
+ at(r, 1) += a.at(r, 1);
+ at(r, 2) += a.at(r, 2);
+ at(r, 3) += a.at(r, 3);
+ }
+ return *this;
+ }
+
+ inline Matrix44& operator -= (const Matrix44& a)
+ {
+ for (int r = 0; r < NUM_ROWS; r++) {
+ at(r, 0) -= a.at(r, 0);
+ at(r, 1) -= a.at(r, 1);
+ at(r, 2) -= a.at(r, 2);
+ at(r, 3) -= a.at(r, 3);
+ }
+ return *this;
+ }
+
+ friend inline Matrix44 operator + (const Matrix44& a, const Matrix44& b)
+ {
+ Matrix44 ret;
+ for (int r = 0; r < NUM_ROWS; r++) {
+ ret.at(r, 0) = a.at(r, 0) + b.at(r, 0);
+ ret.at(r, 1) = a.at(r, 1) + b.at(r, 1);
+ ret.at(r, 2) = a.at(r, 2) + b.at(r, 2);
+ ret.at(r, 3) = a.at(r, 3) + b.at(r, 3);
+ }
+ return ret;
+ }
+
+ friend inline Matrix44 operator - (const Matrix44& a, const Matrix44& b)
+ {
+ Matrix44 ret;
+ for (int r = 0; r < NUM_ROWS; r++) {
+ ret.at(r, 0) = a.at(r, 0) - b.at(r, 0);
+ ret.at(r, 1) = a.at(r, 1) - b.at(r, 1);
+ ret.at(r, 2) = a.at(r, 2) - b.at(r, 2);
+ ret.at(r, 3) = a.at(r, 3) - b.at(r, 3);
+ }
+ return ret;
+ }
+
+ static inline void add_and_store(jpgd_block_t* pDst, const Matrix44& a, const Matrix44& b)
+ {
+ for (int r = 0; r < 4; r++) {
+ pDst[0*8 + r] = static_cast<jpgd_block_t>(a.at(r, 0) + b.at(r, 0));
+ pDst[1*8 + r] = static_cast<jpgd_block_t>(a.at(r, 1) + b.at(r, 1));
+ pDst[2*8 + r] = static_cast<jpgd_block_t>(a.at(r, 2) + b.at(r, 2));
+ pDst[3*8 + r] = static_cast<jpgd_block_t>(a.at(r, 3) + b.at(r, 3));
+ }
+ }
+
+ static inline void sub_and_store(jpgd_block_t* pDst, const Matrix44& a, const Matrix44& b)
+ {
+ for (int r = 0; r < 4; r++) {
+ pDst[0*8 + r] = static_cast<jpgd_block_t>(a.at(r, 0) - b.at(r, 0));
+ pDst[1*8 + r] = static_cast<jpgd_block_t>(a.at(r, 1) - b.at(r, 1));
+ pDst[2*8 + r] = static_cast<jpgd_block_t>(a.at(r, 2) - b.at(r, 2));
+ pDst[3*8 + r] = static_cast<jpgd_block_t>(a.at(r, 3) - b.at(r, 3));
+ }
+ }
+ };
+
+ const int FRACT_BITS = 10;
+ const int SCALE = 1 << FRACT_BITS;
+
+ typedef int Temp_Type;
+ #define D(i) (((i) + (SCALE >> 1)) >> FRACT_BITS)
+ #define F(i) ((int)((i) * SCALE + .5f))
+
+ // Any decent C++ compiler will optimize this at compile time to a 0, or an array access.
+ #define AT(c, r) ((((c)>=NUM_COLS)||((r)>=NUM_ROWS)) ? 0 : pSrc[(c)+(r)*8])
+
+ // NUM_ROWS/NUM_COLS = # of non-zero rows/cols in input matrix
+ template<int NUM_ROWS, int NUM_COLS>
+ struct P_Q
+ {
+ static void calc(Matrix44& P, Matrix44& Q, const jpgd_block_t* pSrc)
+ {
+ // 4x8 = 4x8 times 8x8, matrix 0 is constant
+ const Temp_Type X000 = AT(0, 0);
+ const Temp_Type X001 = AT(0, 1);
+ const Temp_Type X002 = AT(0, 2);
+ const Temp_Type X003 = AT(0, 3);
+ const Temp_Type X004 = AT(0, 4);
+ const Temp_Type X005 = AT(0, 5);
+ const Temp_Type X006 = AT(0, 6);
+ const Temp_Type X007 = AT(0, 7);
+ const Temp_Type X010 = D(F(0.415735f) * AT(1, 0) + F(0.791065f) * AT(3, 0) + F(-0.352443f) * AT(5, 0) + F(0.277785f) * AT(7, 0));
+ const Temp_Type X011 = D(F(0.415735f) * AT(1, 1) + F(0.791065f) * AT(3, 1) + F(-0.352443f) * AT(5, 1) + F(0.277785f) * AT(7, 1));
+ const Temp_Type X012 = D(F(0.415735f) * AT(1, 2) + F(0.791065f) * AT(3, 2) + F(-0.352443f) * AT(5, 2) + F(0.277785f) * AT(7, 2));
+ const Temp_Type X013 = D(F(0.415735f) * AT(1, 3) + F(0.791065f) * AT(3, 3) + F(-0.352443f) * AT(5, 3) + F(0.277785f) * AT(7, 3));
+ const Temp_Type X014 = D(F(0.415735f) * AT(1, 4) + F(0.791065f) * AT(3, 4) + F(-0.352443f) * AT(5, 4) + F(0.277785f) * AT(7, 4));
+ const Temp_Type X015 = D(F(0.415735f) * AT(1, 5) + F(0.791065f) * AT(3, 5) + F(-0.352443f) * AT(5, 5) + F(0.277785f) * AT(7, 5));
+ const Temp_Type X016 = D(F(0.415735f) * AT(1, 6) + F(0.791065f) * AT(3, 6) + F(-0.352443f) * AT(5, 6) + F(0.277785f) * AT(7, 6));
+ const Temp_Type X017 = D(F(0.415735f) * AT(1, 7) + F(0.791065f) * AT(3, 7) + F(-0.352443f) * AT(5, 7) + F(0.277785f) * AT(7, 7));
+ const Temp_Type X020 = AT(4, 0);
+ const Temp_Type X021 = AT(4, 1);
+ const Temp_Type X022 = AT(4, 2);
+ const Temp_Type X023 = AT(4, 3);
+ const Temp_Type X024 = AT(4, 4);
+ const Temp_Type X025 = AT(4, 5);
+ const Temp_Type X026 = AT(4, 6);
+ const Temp_Type X027 = AT(4, 7);
+ const Temp_Type X030 = D(F(0.022887f) * AT(1, 0) + F(-0.097545f) * AT(3, 0) + F(0.490393f) * AT(5, 0) + F(0.865723f) * AT(7, 0));
+ const Temp_Type X031 = D(F(0.022887f) * AT(1, 1) + F(-0.097545f) * AT(3, 1) + F(0.490393f) * AT(5, 1) + F(0.865723f) * AT(7, 1));
+ const Temp_Type X032 = D(F(0.022887f) * AT(1, 2) + F(-0.097545f) * AT(3, 2) + F(0.490393f) * AT(5, 2) + F(0.865723f) * AT(7, 2));
+ const Temp_Type X033 = D(F(0.022887f) * AT(1, 3) + F(-0.097545f) * AT(3, 3) + F(0.490393f) * AT(5, 3) + F(0.865723f) * AT(7, 3));
+ const Temp_Type X034 = D(F(0.022887f) * AT(1, 4) + F(-0.097545f) * AT(3, 4) + F(0.490393f) * AT(5, 4) + F(0.865723f) * AT(7, 4));
+ const Temp_Type X035 = D(F(0.022887f) * AT(1, 5) + F(-0.097545f) * AT(3, 5) + F(0.490393f) * AT(5, 5) + F(0.865723f) * AT(7, 5));
+ const Temp_Type X036 = D(F(0.022887f) * AT(1, 6) + F(-0.097545f) * AT(3, 6) + F(0.490393f) * AT(5, 6) + F(0.865723f) * AT(7, 6));
+ const Temp_Type X037 = D(F(0.022887f) * AT(1, 7) + F(-0.097545f) * AT(3, 7) + F(0.490393f) * AT(5, 7) + F(0.865723f) * AT(7, 7));
+
+ // 4x4 = 4x8 times 8x4, matrix 1 is constant
+ P.at(0, 0) = X000;
+ P.at(0, 1) = D(X001 * F(0.415735f) + X003 * F(0.791065f) + X005 * F(-0.352443f) + X007 * F(0.277785f));
+ P.at(0, 2) = X004;
+ P.at(0, 3) = D(X001 * F(0.022887f) + X003 * F(-0.097545f) + X005 * F(0.490393f) + X007 * F(0.865723f));
+ P.at(1, 0) = X010;
+ P.at(1, 1) = D(X011 * F(0.415735f) + X013 * F(0.791065f) + X015 * F(-0.352443f) + X017 * F(0.277785f));
+ P.at(1, 2) = X014;
+ P.at(1, 3) = D(X011 * F(0.022887f) + X013 * F(-0.097545f) + X015 * F(0.490393f) + X017 * F(0.865723f));
+ P.at(2, 0) = X020;
+ P.at(2, 1) = D(X021 * F(0.415735f) + X023 * F(0.791065f) + X025 * F(-0.352443f) + X027 * F(0.277785f));
+ P.at(2, 2) = X024;
+ P.at(2, 3) = D(X021 * F(0.022887f) + X023 * F(-0.097545f) + X025 * F(0.490393f) + X027 * F(0.865723f));
+ P.at(3, 0) = X030;
+ P.at(3, 1) = D(X031 * F(0.415735f) + X033 * F(0.791065f) + X035 * F(-0.352443f) + X037 * F(0.277785f));
+ P.at(3, 2) = X034;
+ P.at(3, 3) = D(X031 * F(0.022887f) + X033 * F(-0.097545f) + X035 * F(0.490393f) + X037 * F(0.865723f));
+ // 40 muls 24 adds
+
+ // 4x4 = 4x8 times 8x4, matrix 1 is constant
+ Q.at(0, 0) = D(X001 * F(0.906127f) + X003 * F(-0.318190f) + X005 * F(0.212608f) + X007 * F(-0.180240f));
+ Q.at(0, 1) = X002;
+ Q.at(0, 2) = D(X001 * F(-0.074658f) + X003 * F(0.513280f) + X005 * F(0.768178f) + X007 * F(-0.375330f));
+ Q.at(0, 3) = X006;
+ Q.at(1, 0) = D(X011 * F(0.906127f) + X013 * F(-0.318190f) + X015 * F(0.212608f) + X017 * F(-0.180240f));
+ Q.at(1, 1) = X012;
+ Q.at(1, 2) = D(X011 * F(-0.074658f) + X013 * F(0.513280f) + X015 * F(0.768178f) + X017 * F(-0.375330f));
+ Q.at(1, 3) = X016;
+ Q.at(2, 0) = D(X021 * F(0.906127f) + X023 * F(-0.318190f) + X025 * F(0.212608f) + X027 * F(-0.180240f));
+ Q.at(2, 1) = X022;
+ Q.at(2, 2) = D(X021 * F(-0.074658f) + X023 * F(0.513280f) + X025 * F(0.768178f) + X027 * F(-0.375330f));
+ Q.at(2, 3) = X026;
+ Q.at(3, 0) = D(X031 * F(0.906127f) + X033 * F(-0.318190f) + X035 * F(0.212608f) + X037 * F(-0.180240f));
+ Q.at(3, 1) = X032;
+ Q.at(3, 2) = D(X031 * F(-0.074658f) + X033 * F(0.513280f) + X035 * F(0.768178f) + X037 * F(-0.375330f));
+ Q.at(3, 3) = X036;
+ // 40 muls 24 adds
+ }
+ };
+
+
+ template<int NUM_ROWS, int NUM_COLS>
+ struct R_S
+ {
+ static void calc(Matrix44& R, Matrix44& S, const jpgd_block_t* pSrc)
+ {
+ // 4x8 = 4x8 times 8x8, matrix 0 is constant
+ const Temp_Type X100 = D(F(0.906127f) * AT(1, 0) + F(-0.318190f) * AT(3, 0) + F(0.212608f) * AT(5, 0) + F(-0.180240f) * AT(7, 0));
+ const Temp_Type X101 = D(F(0.906127f) * AT(1, 1) + F(-0.318190f) * AT(3, 1) + F(0.212608f) * AT(5, 1) + F(-0.180240f) * AT(7, 1));
+ const Temp_Type X102 = D(F(0.906127f) * AT(1, 2) + F(-0.318190f) * AT(3, 2) + F(0.212608f) * AT(5, 2) + F(-0.180240f) * AT(7, 2));
+ const Temp_Type X103 = D(F(0.906127f) * AT(1, 3) + F(-0.318190f) * AT(3, 3) + F(0.212608f) * AT(5, 3) + F(-0.180240f) * AT(7, 3));
+ const Temp_Type X104 = D(F(0.906127f) * AT(1, 4) + F(-0.318190f) * AT(3, 4) + F(0.212608f) * AT(5, 4) + F(-0.180240f) * AT(7, 4));
+ const Temp_Type X105 = D(F(0.906127f) * AT(1, 5) + F(-0.318190f) * AT(3, 5) + F(0.212608f) * AT(5, 5) + F(-0.180240f) * AT(7, 5));
+ const Temp_Type X106 = D(F(0.906127f) * AT(1, 6) + F(-0.318190f) * AT(3, 6) + F(0.212608f) * AT(5, 6) + F(-0.180240f) * AT(7, 6));
+ const Temp_Type X107 = D(F(0.906127f) * AT(1, 7) + F(-0.318190f) * AT(3, 7) + F(0.212608f) * AT(5, 7) + F(-0.180240f) * AT(7, 7));
+ const Temp_Type X110 = AT(2, 0);
+ const Temp_Type X111 = AT(2, 1);
+ const Temp_Type X112 = AT(2, 2);
+ const Temp_Type X113 = AT(2, 3);
+ const Temp_Type X114 = AT(2, 4);
+ const Temp_Type X115 = AT(2, 5);
+ const Temp_Type X116 = AT(2, 6);
+ const Temp_Type X117 = AT(2, 7);
+ const Temp_Type X120 = D(F(-0.074658f) * AT(1, 0) + F(0.513280f) * AT(3, 0) + F(0.768178f) * AT(5, 0) + F(-0.375330f) * AT(7, 0));
+ const Temp_Type X121 = D(F(-0.074658f) * AT(1, 1) + F(0.513280f) * AT(3, 1) + F(0.768178f) * AT(5, 1) + F(-0.375330f) * AT(7, 1));
+ const Temp_Type X122 = D(F(-0.074658f) * AT(1, 2) + F(0.513280f) * AT(3, 2) + F(0.768178f) * AT(5, 2) + F(-0.375330f) * AT(7, 2));
+ const Temp_Type X123 = D(F(-0.074658f) * AT(1, 3) + F(0.513280f) * AT(3, 3) + F(0.768178f) * AT(5, 3) + F(-0.375330f) * AT(7, 3));
+ const Temp_Type X124 = D(F(-0.074658f) * AT(1, 4) + F(0.513280f) * AT(3, 4) + F(0.768178f) * AT(5, 4) + F(-0.375330f) * AT(7, 4));
+ const Temp_Type X125 = D(F(-0.074658f) * AT(1, 5) + F(0.513280f) * AT(3, 5) + F(0.768178f) * AT(5, 5) + F(-0.375330f) * AT(7, 5));
+ const Temp_Type X126 = D(F(-0.074658f) * AT(1, 6) + F(0.513280f) * AT(3, 6) + F(0.768178f) * AT(5, 6) + F(-0.375330f) * AT(7, 6));
+ const Temp_Type X127 = D(F(-0.074658f) * AT(1, 7) + F(0.513280f) * AT(3, 7) + F(0.768178f) * AT(5, 7) + F(-0.375330f) * AT(7, 7));
+ const Temp_Type X130 = AT(6, 0);
+ const Temp_Type X131 = AT(6, 1);
+ const Temp_Type X132 = AT(6, 2);
+ const Temp_Type X133 = AT(6, 3);
+ const Temp_Type X134 = AT(6, 4);
+ const Temp_Type X135 = AT(6, 5);
+ const Temp_Type X136 = AT(6, 6);
+ const Temp_Type X137 = AT(6, 7);
+ // 80 muls 48 adds
+
+ // 4x4 = 4x8 times 8x4, matrix 1 is constant
+ R.at(0, 0) = X100;
+ R.at(0, 1) = D(X101 * F(0.415735f) + X103 * F(0.791065f) + X105 * F(-0.352443f) + X107 * F(0.277785f));
+ R.at(0, 2) = X104;
+ R.at(0, 3) = D(X101 * F(0.022887f) + X103 * F(-0.097545f) + X105 * F(0.490393f) + X107 * F(0.865723f));
+ R.at(1, 0) = X110;
+ R.at(1, 1) = D(X111 * F(0.415735f) + X113 * F(0.791065f) + X115 * F(-0.352443f) + X117 * F(0.277785f));
+ R.at(1, 2) = X114;
+ R.at(1, 3) = D(X111 * F(0.022887f) + X113 * F(-0.097545f) + X115 * F(0.490393f) + X117 * F(0.865723f));
+ R.at(2, 0) = X120;
+ R.at(2, 1) = D(X121 * F(0.415735f) + X123 * F(0.791065f) + X125 * F(-0.352443f) + X127 * F(0.277785f));
+ R.at(2, 2) = X124;
+ R.at(2, 3) = D(X121 * F(0.022887f) + X123 * F(-0.097545f) + X125 * F(0.490393f) + X127 * F(0.865723f));
+ R.at(3, 0) = X130;
+ R.at(3, 1) = D(X131 * F(0.415735f) + X133 * F(0.791065f) + X135 * F(-0.352443f) + X137 * F(0.277785f));
+ R.at(3, 2) = X134;
+ R.at(3, 3) = D(X131 * F(0.022887f) + X133 * F(-0.097545f) + X135 * F(0.490393f) + X137 * F(0.865723f));
+ // 40 muls 24 adds
+ // 4x4 = 4x8 times 8x4, matrix 1 is constant
+ S.at(0, 0) = D(X101 * F(0.906127f) + X103 * F(-0.318190f) + X105 * F(0.212608f) + X107 * F(-0.180240f));
+ S.at(0, 1) = X102;
+ S.at(0, 2) = D(X101 * F(-0.074658f) + X103 * F(0.513280f) + X105 * F(0.768178f) + X107 * F(-0.375330f));
+ S.at(0, 3) = X106;
+ S.at(1, 0) = D(X111 * F(0.906127f) + X113 * F(-0.318190f) + X115 * F(0.212608f) + X117 * F(-0.180240f));
+ S.at(1, 1) = X112;
+ S.at(1, 2) = D(X111 * F(-0.074658f) + X113 * F(0.513280f) + X115 * F(0.768178f) + X117 * F(-0.375330f));
+ S.at(1, 3) = X116;
+ S.at(2, 0) = D(X121 * F(0.906127f) + X123 * F(-0.318190f) + X125 * F(0.212608f) + X127 * F(-0.180240f));
+ S.at(2, 1) = X122;
+ S.at(2, 2) = D(X121 * F(-0.074658f) + X123 * F(0.513280f) + X125 * F(0.768178f) + X127 * F(-0.375330f));
+ S.at(2, 3) = X126;
+ S.at(3, 0) = D(X131 * F(0.906127f) + X133 * F(-0.318190f) + X135 * F(0.212608f) + X137 * F(-0.180240f));
+ S.at(3, 1) = X132;
+ S.at(3, 2) = D(X131 * F(-0.074658f) + X133 * F(0.513280f) + X135 * F(0.768178f) + X137 * F(-0.375330f));
+ S.at(3, 3) = X136;
+ // 40 muls 24 adds
+ }
+ };
+} // end namespace DCT_Upsample
+
+
+// Unconditionally frees all allocated m_blocks.
+void jpeg_decoder::free_all_blocks()
+{
+ m_pStream = nullptr;
+ for (mem_block *b = m_pMem_blocks; b; ) {
+ mem_block *n = b->m_pNext;
+ free(b);
+ b = n;
+ }
+ m_pMem_blocks = nullptr;
+}
+
+
+// This method handles all errors. It will never return.
+// It could easily be changed to use C++ exceptions.
+JPGD_NORETURN void jpeg_decoder::stop_decoding(jpgd_status status)
+{
+ m_error_code = status;
+ free_all_blocks();
+ longjmp(m_jmp_state, status);
+}
+
+
+void *jpeg_decoder::alloc(size_t nSize, bool zero)
+{
+ nSize = (JPGD_MAX(nSize, 1) + 3) & ~3;
+ char *rv = nullptr;
+ for (mem_block *b = m_pMem_blocks; b; b = b->m_pNext) {
+ if ((b->m_used_count + nSize) <= b->m_size) {
+ rv = b->m_data + b->m_used_count;
+ b->m_used_count += nSize;
+ break;
+ }
+ }
+ if (!rv) {
+ int capacity = JPGD_MAX(32768 - 256, (nSize + 2047) & ~2047);
+ mem_block *b = (mem_block*)malloc(sizeof(mem_block) + capacity);
+ if (!b) stop_decoding(JPGD_NOTENOUGHMEM);
+ b->m_pNext = m_pMem_blocks; m_pMem_blocks = b;
+ b->m_used_count = nSize;
+ b->m_size = capacity;
+ rv = b->m_data;
+ }
+ if (zero) memset(rv, 0, nSize);
+ return rv;
+}
+
+
+void jpeg_decoder::word_clear(void *p, uint16_t c, uint32_t n)
+{
+ uint8_t *pD = (uint8_t*)p;
+ const uint8_t l = c & 0xFF, h = (c >> 8) & 0xFF;
+ while (n) {
+ pD[0] = l; pD[1] = h; pD += 2;
+ n--;
+ }
+}
+
+
+// Refill the input buffer.
+// This method will sit in a loop until (A) the buffer is full or (B)
+// the stream's read() method reports and end of file condition.
+void jpeg_decoder::prep_in_buffer()
+{
+ m_in_buf_left = 0;
+ m_pIn_buf_ofs = m_in_buf;
+
+ if (m_eof_flag) return;
+
+ do {
+ int bytes_read = m_pStream->read(m_in_buf + m_in_buf_left, JPGD_IN_BUF_SIZE - m_in_buf_left, &m_eof_flag);
+ if (bytes_read == -1) stop_decoding(JPGD_STREAM_READ);
+ m_in_buf_left += bytes_read;
+ } while ((m_in_buf_left < JPGD_IN_BUF_SIZE) && (!m_eof_flag));
+
+ m_total_bytes_read += m_in_buf_left;
+
+ // Pad the end of the block with M_EOI (prevents the decompressor from going off the rails if the stream is invalid).
+ // (This dates way back to when this decompressor was written in C/asm, and the all-asm Huffman decoder did some fancy things to increase perf.)
+ word_clear(m_pIn_buf_ofs + m_in_buf_left, 0xD9FF, 64);
+}
+
+
+// Read a Huffman code table.
+void jpeg_decoder::read_dht_marker()
+{
+ int i, index, count;
+ uint8_t huff_num[17];
+ uint8_t huff_val[256];
+ uint32_t num_left = get_bits(16);
+
+ if (num_left < 2) stop_decoding(JPGD_BAD_DHT_MARKER);
+ num_left -= 2;
+
+ while (num_left) {
+ index = get_bits(8);
+ huff_num[0] = 0;
+ count = 0;
+
+ for (i = 1; i <= 16; i++) {
+ huff_num[i] = static_cast<uint8_t>(get_bits(8));
+ count += huff_num[i];
+ }
+
+ if (count > 255) stop_decoding(JPGD_BAD_DHT_COUNTS);
+
+ for (i = 0; i < count; i++)
+ huff_val[i] = static_cast<uint8_t>(get_bits(8));
+
+ i = 1 + 16 + count;
+
+ if (num_left < (uint32_t)i) stop_decoding(JPGD_BAD_DHT_MARKER);
+ num_left -= i;
+
+ if ((index & 0x10) > 0x10) stop_decoding(JPGD_BAD_DHT_INDEX);
+ index = (index & 0x0F) + ((index & 0x10) >> 4) * (JPGD_MAX_HUFF_TABLES >> 1);
+ if (index >= JPGD_MAX_HUFF_TABLES) stop_decoding(JPGD_BAD_DHT_INDEX);
+
+ if (!m_huff_num[index]) m_huff_num[index] = (uint8_t *)alloc(17);
+ if (!m_huff_val[index]) m_huff_val[index] = (uint8_t *)alloc(256);
+
+ m_huff_ac[index] = (index & 0x10) != 0;
+ memcpy(m_huff_num[index], huff_num, 17);
+ memcpy(m_huff_val[index], huff_val, 256);
+ }
+}
+
+
+// Read a quantization table.
+void jpeg_decoder::read_dqt_marker()
+{
+ int n, i, prec;
+ uint32_t temp;
+ uint32_t num_left = get_bits(16);
+ if (num_left < 2) stop_decoding(JPGD_BAD_DQT_MARKER);
+ num_left -= 2;
+
+ while (num_left) {
+ n = get_bits(8);
+ prec = n >> 4;
+ n &= 0x0F;
+
+ if (n >= JPGD_MAX_QUANT_TABLES) stop_decoding(JPGD_BAD_DQT_TABLE);
+
+ if (!m_quant[n]) m_quant[n] = (jpgd_quant_t *)alloc(64 * sizeof(jpgd_quant_t));
+
+ // read quantization entries, in zag order
+ for (i = 0; i < 64; i++) {
+ temp = get_bits(8);
+ if (prec) temp = (temp << 8) + get_bits(8);
+ m_quant[n][i] = static_cast<jpgd_quant_t>(temp);
+ }
+ i = 64 + 1;
+ if (prec) i += 64;
+ if (num_left < (uint32_t)i) stop_decoding(JPGD_BAD_DQT_LENGTH);
+ num_left -= i;
+ }
+}
+
+
+// Read the start of frame (SOF) marker.
+void jpeg_decoder::read_sof_marker()
+{
+ int i;
+ uint32_t num_left = get_bits(16);
+
+ if (get_bits(8) != 8) stop_decoding(JPGD_BAD_PRECISION); /* precision: sorry, only 8-bit precision is supported right now */
+
+ m_image_y_size = get_bits(16);
+ if ((m_image_y_size < 1) || (m_image_y_size > JPGD_MAX_HEIGHT)) stop_decoding(JPGD_BAD_HEIGHT);
+
+ m_image_x_size = get_bits(16);
+ if ((m_image_x_size < 1) || (m_image_x_size > JPGD_MAX_WIDTH)) stop_decoding(JPGD_BAD_WIDTH);
+
+ m_comps_in_frame = get_bits(8);
+ if (m_comps_in_frame > JPGD_MAX_COMPONENTS) stop_decoding(JPGD_TOO_MANY_COMPONENTS);
+
+ if (num_left != (uint32_t)(m_comps_in_frame * 3 + 8)) stop_decoding(JPGD_BAD_SOF_LENGTH);
+
+ for (i = 0; i < m_comps_in_frame; i++) {
+ m_comp_ident[i] = get_bits(8);
+ m_comp_h_samp[i] = get_bits(4);
+ m_comp_v_samp[i] = get_bits(4);
+ m_comp_quant[i] = get_bits(8);
+ }
+}
+
+
+// Used to skip unrecognized markers.
+void jpeg_decoder::skip_variable_marker()
+{
+ uint32_t num_left = get_bits(16);
+ if (num_left < 2) stop_decoding(JPGD_BAD_VARIABLE_MARKER);
+ num_left -= 2;
+
+ while (num_left) {
+ get_bits(8);
+ num_left--;
+ }
+}
+
+
+// Read a define restart interval (DRI) marker.
+void jpeg_decoder::read_dri_marker()
+{
+ if (get_bits(16) != 4) stop_decoding(JPGD_BAD_DRI_LENGTH);
+ m_restart_interval = get_bits(16);
+}
+
+
+// Read a start of scan (SOS) marker.
+void jpeg_decoder::read_sos_marker()
+{
+ int i, ci, c, cc;
+ uint32_t num_left = get_bits(16);
+ int n = get_bits(8);
+
+ m_comps_in_scan = n;
+ num_left -= 3;
+
+ if ( (num_left != (uint32_t)(n * 2 + 3)) || (n < 1) || (n > JPGD_MAX_COMPS_IN_SCAN) ) stop_decoding(JPGD_BAD_SOS_LENGTH);
+
+ for (i = 0; i < n; i++) {
+ cc = get_bits(8);
+ c = get_bits(8);
+ num_left -= 2;
+
+ for (ci = 0; ci < m_comps_in_frame; ci++)
+ if (cc == m_comp_ident[ci]) break;
+
+ if (ci >= m_comps_in_frame) stop_decoding(JPGD_BAD_SOS_COMP_ID);
+
+ m_comp_list[i] = ci;
+ m_comp_dc_tab[ci] = (c >> 4) & 15;
+ m_comp_ac_tab[ci] = (c & 15) + (JPGD_MAX_HUFF_TABLES >> 1);
+ }
+ m_spectral_start = get_bits(8);
+ m_spectral_end = get_bits(8);
+ m_successive_high = get_bits(4);
+ m_successive_low = get_bits(4);
+
+ if (!m_progressive_flag) {
+ m_spectral_start = 0;
+ m_spectral_end = 63;
+ }
+ num_left -= 3;
+
+ while (num_left) { /* read past whatever is num_left */
+ get_bits(8);
+ num_left--;
+ }
+}
+
+
+// Finds the next marker.
+int jpeg_decoder::next_marker()
+{
+ uint32_t c, bytes = 0;
+
+ do {
+ do {
+ bytes++;
+ c = get_bits(8);
+ } while (c != 0xFF);
+
+ do {
+ c = get_bits(8);
+ } while (c == 0xFF);
+ } while (c == 0);
+
+ // If bytes > 0 here, there where extra bytes before the marker (not good).
+ return c;
+}
+
+
+// Process markers. Returns when an SOFx, SOI, EOI, or SOS marker is
+// encountered.
+int jpeg_decoder::process_markers()
+{
+ int c;
+
+ for ( ; ; ) {
+ c = next_marker();
+ switch (c) {
+ case M_SOF0:
+ case M_SOF1:
+ case M_SOF2:
+ case M_SOF3:
+ case M_SOF5:
+ case M_SOF6:
+ case M_SOF7:
+ // case M_JPG:
+ case M_SOF9:
+ case M_SOF10:
+ case M_SOF11:
+ case M_SOF13:
+ case M_SOF14:
+ case M_SOF15:
+ case M_SOI:
+ case M_EOI:
+ case M_SOS: return c;
+ case M_DHT: {
+ read_dht_marker();
+ break;
+ }
+ // No arithmitic support - dumb patents!
+ case M_DAC: {
+ stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT);
+ break;
+ }
+ case M_DQT: {
+ read_dqt_marker();
+ break;
+ }
+ case M_DRI: {
+ read_dri_marker();
+ break;
+ }
+ //case M_APP0: /* no need to read the JFIF marker */
+ case M_JPG:
+ case M_RST0: /* no parameters */
+ case M_RST1:
+ case M_RST2:
+ case M_RST3:
+ case M_RST4:
+ case M_RST5:
+ case M_RST6:
+ case M_RST7:
+ case M_TEM: {
+ stop_decoding(JPGD_UNEXPECTED_MARKER);
+ break;
+ }
+ default: { /* must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn or APP0 */
+ skip_variable_marker();
+ break;
+ }
+ }
+ }
+}
+
+
+// Finds the start of image (SOI) marker.
+// This code is rather defensive: it only checks the first 512 bytes to avoid
+// false positives.
+void jpeg_decoder::locate_soi_marker()
+{
+ uint32_t lastchar = get_bits(8);
+ uint32_t thischar = get_bits(8);
+
+ /* ok if it's a normal JPEG file without a special header */
+ if ((lastchar == 0xFF) && (thischar == M_SOI)) return;
+
+ uint32_t bytesleft = 4096; //512;
+
+ while (true) {
+ if (--bytesleft == 0) stop_decoding(JPGD_NOT_JPEG);
+
+ lastchar = thischar;
+ thischar = get_bits(8);
+
+ if (lastchar == 0xFF) {
+ if (thischar == M_SOI) break;
+ else if (thischar == M_EOI) stop_decoding(JPGD_NOT_JPEG); // get_bits will keep returning M_EOI if we read past the end
+ }
+ }
+
+ // Check the next character after marker: if it's not 0xFF, it can't be the start of the next marker, so the file is bad.
+ thischar = (m_bit_buf >> 24) & 0xFF;
+ if (thischar != 0xFF) stop_decoding(JPGD_NOT_JPEG);
+}
+
+
+// Find a start of frame (SOF) marker.
+void jpeg_decoder::locate_sof_marker()
+{
+ locate_soi_marker();
+ int c = process_markers();
+
+ switch (c) {
+ case M_SOF2: m_progressive_flag = true;
+ case M_SOF0: /* baseline DCT */
+ case M_SOF1: { /* extended sequential DCT */
+ read_sof_marker();
+ break;
+ }
+ case M_SOF9: { /* Arithmitic coding */
+ stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT);
+ break;
+ }
+ default: {
+ stop_decoding(JPGD_UNSUPPORTED_MARKER);
+ break;
+ }
+ }
+}
+
+
+// Find a start of scan (SOS) marker.
+int jpeg_decoder::locate_sos_marker()
+{
+ int c = process_markers();
+ if (c == M_EOI) return false;
+ else if (c != M_SOS) stop_decoding(JPGD_UNEXPECTED_MARKER);
+ read_sos_marker();
+ return true;
+}
+
+
+// Reset everything to default/uninitialized state.
+void jpeg_decoder::init(jpeg_decoder_stream *pStream)
+{
+ m_pMem_blocks = nullptr;
+ m_error_code = JPGD_SUCCESS;
+ m_ready_flag = false;
+ m_image_x_size = m_image_y_size = 0;
+ m_pStream = pStream;
+ m_progressive_flag = false;
+
+ memset(m_huff_ac, 0, sizeof(m_huff_ac));
+ memset(m_huff_num, 0, sizeof(m_huff_num));
+ memset(m_huff_val, 0, sizeof(m_huff_val));
+ memset(m_quant, 0, sizeof(m_quant));
+
+ m_scan_type = 0;
+ m_comps_in_frame = 0;
+
+ memset(m_comp_h_samp, 0, sizeof(m_comp_h_samp));
+ memset(m_comp_v_samp, 0, sizeof(m_comp_v_samp));
+ memset(m_comp_quant, 0, sizeof(m_comp_quant));
+ memset(m_comp_ident, 0, sizeof(m_comp_ident));
+ memset(m_comp_h_blocks, 0, sizeof(m_comp_h_blocks));
+ memset(m_comp_v_blocks, 0, sizeof(m_comp_v_blocks));
+
+ m_comps_in_scan = 0;
+ memset(m_comp_list, 0, sizeof(m_comp_list));
+ memset(m_comp_dc_tab, 0, sizeof(m_comp_dc_tab));
+ memset(m_comp_ac_tab, 0, sizeof(m_comp_ac_tab));
+
+ m_spectral_start = 0;
+ m_spectral_end = 0;
+ m_successive_low = 0;
+ m_successive_high = 0;
+ m_max_mcu_x_size = 0;
+ m_max_mcu_y_size = 0;
+ m_blocks_per_mcu = 0;
+ m_max_blocks_per_row = 0;
+ m_mcus_per_row = 0;
+ m_mcus_per_col = 0;
+ m_expanded_blocks_per_component = 0;
+ m_expanded_blocks_per_mcu = 0;
+ m_expanded_blocks_per_row = 0;
+ m_freq_domain_chroma_upsample = false;
+
+ memset(m_mcu_org, 0, sizeof(m_mcu_org));
+
+ m_total_lines_left = 0;
+ m_mcu_lines_left = 0;
+ m_real_dest_bytes_per_scan_line = 0;
+ m_dest_bytes_per_scan_line = 0;
+ m_dest_bytes_per_pixel = 0;
+
+ memset(m_pHuff_tabs, 0, sizeof(m_pHuff_tabs));
+
+ memset(m_dc_coeffs, 0, sizeof(m_dc_coeffs));
+ memset(m_ac_coeffs, 0, sizeof(m_ac_coeffs));
+ memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu));
+
+ m_eob_run = 0;
+
+ memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu));
+
+ m_pIn_buf_ofs = m_in_buf;
+ m_in_buf_left = 0;
+ m_eof_flag = false;
+ m_tem_flag = 0;
+
+ memset(m_in_buf_pad_start, 0, sizeof(m_in_buf_pad_start));
+ memset(m_in_buf, 0, sizeof(m_in_buf));
+ memset(m_in_buf_pad_end, 0, sizeof(m_in_buf_pad_end));
+
+ m_restart_interval = 0;
+ m_restarts_left = 0;
+ m_next_restart_num = 0;
+
+ m_max_mcus_per_row = 0;
+ m_max_blocks_per_mcu = 0;
+ m_max_mcus_per_col = 0;
+
+ memset(m_last_dc_val, 0, sizeof(m_last_dc_val));
+ m_pMCU_coefficients = nullptr;
+ m_pSample_buf = nullptr;
+
+ m_total_bytes_read = 0;
+
+ m_pScan_line_0 = nullptr;
+ m_pScan_line_1 = nullptr;
+
+ // Ready the input buffer.
+ prep_in_buffer();
+
+ // Prime the bit buffer.
+ m_bits_left = 16;
+ m_bit_buf = 0;
+
+ get_bits(16);
+ get_bits(16);
+
+ for (int i = 0; i < JPGD_MAX_BLOCKS_PER_MCU; i++) {
+ m_mcu_block_max_zag[i] = 64;
+ }
+}
+
+#define SCALEBITS 16
+#define ONE_HALF ((int) 1 << (SCALEBITS-1))
+#define FIX(x) ((int) ((x) * (1L<<SCALEBITS) + 0.5f))
+
+
+// Create a few tables that allow us to quickly convert YCbCr to RGB.
+void jpeg_decoder::create_look_ups()
+{
+ for (int i = 0; i <= 255; i++) {
+ int k = i - 128;
+ m_crr[i] = ( FIX(1.40200f) * k + ONE_HALF) >> SCALEBITS;
+ m_cbb[i] = ( FIX(1.77200f) * k + ONE_HALF) >> SCALEBITS;
+ m_crg[i] = (-FIX(0.71414f)) * k;
+ m_cbg[i] = (-FIX(0.34414f)) * k + ONE_HALF;
+ }
+}
+
+
+// This method throws back into the stream any bytes that where read
+// into the bit buffer during initial marker scanning.
+void jpeg_decoder::fix_in_buffer()
+{
+ // In case any 0xFF's where pulled into the buffer during marker scanning.
+ JPGD_ASSERT((m_bits_left & 7) == 0);
+
+ if (m_bits_left == 16) stuff_char( (uint8_t)(m_bit_buf & 0xFF));
+ if (m_bits_left >= 8) stuff_char( (uint8_t)((m_bit_buf >> 8) & 0xFF));
+
+ stuff_char((uint8_t)((m_bit_buf >> 16) & 0xFF));
+ stuff_char((uint8_t)((m_bit_buf >> 24) & 0xFF));
+
+ m_bits_left = 16;
+ get_bits_no_markers(16);
+ get_bits_no_markers(16);
+}
+
+
+void jpeg_decoder::transform_mcu(int mcu_row)
+{
+ jpgd_block_t* pSrc_ptr = m_pMCU_coefficients;
+ uint8_t* pDst_ptr = m_pSample_buf + mcu_row * m_blocks_per_mcu * 64;
+
+ for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) {
+ idct(pSrc_ptr, pDst_ptr, m_mcu_block_max_zag[mcu_block]);
+ pSrc_ptr += 64;
+ pDst_ptr += 64;
+ }
+}
+
+
+static const uint8_t s_max_rc[64] =
+{
+ 17, 18, 34, 50, 50, 51, 52, 52, 52, 68, 84, 84, 84, 84, 85, 86, 86, 86, 86, 86,
+ 102, 118, 118, 118, 118, 118, 118, 119, 120, 120, 120, 120, 120, 120, 120, 136,
+ 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
+ 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136
+};
+
+
+void jpeg_decoder::transform_mcu_expand(int mcu_row)
+{
+ jpgd_block_t* pSrc_ptr = m_pMCU_coefficients;
+ uint8_t* pDst_ptr = m_pSample_buf + mcu_row * m_expanded_blocks_per_mcu * 64;
+
+ // Y IDCT
+ int mcu_block;
+ for (mcu_block = 0; mcu_block < m_expanded_blocks_per_component; mcu_block++) {
+ idct(pSrc_ptr, pDst_ptr, m_mcu_block_max_zag[mcu_block]);
+ pSrc_ptr += 64;
+ pDst_ptr += 64;
+ }
+
+ // Chroma IDCT, with upsampling
+ jpgd_block_t temp_block[64];
+
+ for (int i = 0; i < 2; i++) {
+ DCT_Upsample::Matrix44 P, Q, R, S;
+ JPGD_ASSERT(m_mcu_block_max_zag[mcu_block] >= 1);
+ JPGD_ASSERT(m_mcu_block_max_zag[mcu_block] <= 64);
+
+ int max_zag = m_mcu_block_max_zag[mcu_block++] - 1;
+ if (max_zag <= 0) max_zag = 0; // should never happen, only here to shut up static analysis
+
+ switch (s_max_rc[max_zag]) {
+ case 1*16+1:
+ DCT_Upsample::P_Q<1, 1>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<1, 1>::calc(R, S, pSrc_ptr);
+ break;
+ case 1*16+2:
+ DCT_Upsample::P_Q<1, 2>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<1, 2>::calc(R, S, pSrc_ptr);
+ break;
+ case 2*16+2:
+ DCT_Upsample::P_Q<2, 2>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<2, 2>::calc(R, S, pSrc_ptr);
+ break;
+ case 3*16+2:
+ DCT_Upsample::P_Q<3, 2>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<3, 2>::calc(R, S, pSrc_ptr);
+ break;
+ case 3*16+3:
+ DCT_Upsample::P_Q<3, 3>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<3, 3>::calc(R, S, pSrc_ptr);
+ break;
+ case 3*16+4:
+ DCT_Upsample::P_Q<3, 4>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<3, 4>::calc(R, S, pSrc_ptr);
+ break;
+ case 4*16+4:
+ DCT_Upsample::P_Q<4, 4>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<4, 4>::calc(R, S, pSrc_ptr);
+ break;
+ case 5*16+4:
+ DCT_Upsample::P_Q<5, 4>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<5, 4>::calc(R, S, pSrc_ptr);
+ break;
+ case 5*16+5:
+ DCT_Upsample::P_Q<5, 5>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<5, 5>::calc(R, S, pSrc_ptr);
+ break;
+ case 5*16+6:
+ DCT_Upsample::P_Q<5, 6>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<5, 6>::calc(R, S, pSrc_ptr);
+ break;
+ case 6*16+6:
+ DCT_Upsample::P_Q<6, 6>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<6, 6>::calc(R, S, pSrc_ptr);
+ break;
+ case 7*16+6:
+ DCT_Upsample::P_Q<7, 6>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<7, 6>::calc(R, S, pSrc_ptr);
+ break;
+ case 7*16+7:
+ DCT_Upsample::P_Q<7, 7>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<7, 7>::calc(R, S, pSrc_ptr);
+ break;
+ case 7*16+8:
+ DCT_Upsample::P_Q<7, 8>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<7, 8>::calc(R, S, pSrc_ptr);
+ break;
+ case 8*16+8:
+ DCT_Upsample::P_Q<8, 8>::calc(P, Q, pSrc_ptr);
+ DCT_Upsample::R_S<8, 8>::calc(R, S, pSrc_ptr);
+ break;
+ default:
+ JPGD_ASSERT(false);
+ }
+ DCT_Upsample::Matrix44 a(P + Q); P -= Q;
+ DCT_Upsample::Matrix44& b = P;
+ DCT_Upsample::Matrix44 c(R + S); R -= S;
+ DCT_Upsample::Matrix44& d = R;
+
+ DCT_Upsample::Matrix44::add_and_store(temp_block, a, c);
+ idct_4x4(temp_block, pDst_ptr);
+ pDst_ptr += 64;
+
+ DCT_Upsample::Matrix44::sub_and_store(temp_block, a, c);
+ idct_4x4(temp_block, pDst_ptr);
+ pDst_ptr += 64;
+
+ DCT_Upsample::Matrix44::add_and_store(temp_block, b, d);
+ idct_4x4(temp_block, pDst_ptr);
+ pDst_ptr += 64;
+
+ DCT_Upsample::Matrix44::sub_and_store(temp_block, b, d);
+ idct_4x4(temp_block, pDst_ptr);
+ pDst_ptr += 64;
+ pSrc_ptr += 64;
+ }
+}
+
+
+// Loads and dequantizes the next row of (already decoded) coefficients.
+// Progressive images only.
+void jpeg_decoder::load_next_row()
+{
+ int i;
+ jpgd_block_t *p;
+ jpgd_quant_t *q;
+ int mcu_row, mcu_block, row_block = 0;
+ int component_num, component_id;
+ int block_x_mcu[JPGD_MAX_COMPONENTS];
+
+ memset(block_x_mcu, 0, JPGD_MAX_COMPONENTS * sizeof(int));
+
+ for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) {
+ int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0;
+
+ for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) {
+ component_id = m_mcu_org[mcu_block];
+ q = m_quant[m_comp_quant[component_id]];
+ p = m_pMCU_coefficients + 64 * mcu_block;
+
+ jpgd_block_t* pAC = coeff_buf_getp(m_ac_coeffs[component_id], block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs);
+ jpgd_block_t* pDC = coeff_buf_getp(m_dc_coeffs[component_id], block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs);
+ p[0] = pDC[0];
+ memcpy(&p[1], &pAC[1], 63 * sizeof(jpgd_block_t));
+
+ for (i = 63; i > 0; i--) {
+ if (p[g_ZAG[i]]) break;
+ }
+
+ m_mcu_block_max_zag[mcu_block] = i + 1;
+
+ for ( ; i >= 0; i--) {
+ if (p[g_ZAG[i]]) {
+ p[g_ZAG[i]] = static_cast<jpgd_block_t>(p[g_ZAG[i]] * q[i]);
+ }
+ }
+
+ row_block++;
+
+ if (m_comps_in_scan == 1) block_x_mcu[component_id]++;
+ else {
+ if (++block_x_mcu_ofs == m_comp_h_samp[component_id]) block_x_mcu_ofs = 0;
+ if (++block_y_mcu_ofs == m_comp_v_samp[component_id]) {
+ block_y_mcu_ofs = 0;
+ block_x_mcu[component_id] += m_comp_h_samp[component_id];
+ }
+ }
+ }
+ if (m_freq_domain_chroma_upsample) transform_mcu_expand(mcu_row);
+ else transform_mcu(mcu_row);
+ }
+ if (m_comps_in_scan == 1) m_block_y_mcu[m_comp_list[0]]++;
+ else {
+ for (component_num = 0; component_num < m_comps_in_scan; component_num++) {
+ component_id = m_comp_list[component_num];
+ m_block_y_mcu[component_id] += m_comp_v_samp[component_id];
+ }
+ }
+}
+
+
+// Restart interval processing.
+void jpeg_decoder::process_restart()
+{
+ int i;
+ int c = 0;
+
+ // Align to a byte boundry
+ // FIXME: Is this really necessary? get_bits_no_markers() never reads in markers!
+ //get_bits_no_markers(m_bits_left & 7);
+
+ // Let's scan a little bit to find the marker, but not _too_ far.
+ // 1536 is a "fudge factor" that determines how much to scan.
+ for (i = 1536; i > 0; i--) {
+ if (get_char() == 0xFF) break;
+ }
+ if (i == 0) stop_decoding(JPGD_BAD_RESTART_MARKER);
+
+ for ( ; i > 0; i--) {
+ if ((c = get_char()) != 0xFF) break;
+ }
+ if (i == 0) stop_decoding(JPGD_BAD_RESTART_MARKER);
+
+ // Is it the expected marker? If not, something bad happened.
+ if (c != (m_next_restart_num + M_RST0)) stop_decoding(JPGD_BAD_RESTART_MARKER);
+
+ // Reset each component's DC prediction values.
+ memset(&m_last_dc_val, 0, m_comps_in_frame * sizeof(uint32_t));
+
+ m_eob_run = 0;
+ m_restarts_left = m_restart_interval;
+ m_next_restart_num = (m_next_restart_num + 1) & 7;
+
+ // Get the bit buffer going again...
+ m_bits_left = 16;
+ get_bits_no_markers(16);
+ get_bits_no_markers(16);
+}
+
+
+static inline int dequantize_ac(int c, int q)
+{
+ c *= q;
+ return c;
+}
+
+// Decodes and dequantizes the next row of coefficients.
+void jpeg_decoder::decode_next_row()
+{
+ int row_block = 0;
+
+ for (int mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) {
+ if ((m_restart_interval) && (m_restarts_left == 0)) process_restart();
+
+ jpgd_block_t* p = m_pMCU_coefficients;
+
+ for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++, p += 64) {
+ int component_id = m_mcu_org[mcu_block];
+ jpgd_quant_t* q = m_quant[m_comp_quant[component_id]];
+
+ int r, s;
+ s = huff_decode(m_pHuff_tabs[m_comp_dc_tab[component_id]], r);
+ s = JPGD_HUFF_EXTEND(r, s);
+
+ m_last_dc_val[component_id] = (s += m_last_dc_val[component_id]);
+
+ p[0] = static_cast<jpgd_block_t>(s * q[0]);
+
+ int prev_num_set = m_mcu_block_max_zag[mcu_block];
+ huff_tables *pH = m_pHuff_tabs[m_comp_ac_tab[component_id]];
+ int k;
+ for (k = 1; k < 64; k++) {
+ int extra_bits;
+ s = huff_decode(pH, extra_bits);
+ r = s >> 4;
+ s &= 15;
+
+ if (s) {
+ if (r) {
+ if ((k + r) > 63) stop_decoding(JPGD_DECODE_ERROR);
+ if (k < prev_num_set) {
+ int n = JPGD_MIN(r, prev_num_set - k);
+ int kt = k;
+ while (n--) p[g_ZAG[kt++]] = 0;
+ }
+ k += r;
+ }
+ s = JPGD_HUFF_EXTEND(extra_bits, s);
+ JPGD_ASSERT(k < 64);
+ p[g_ZAG[k]] = static_cast<jpgd_block_t>(dequantize_ac(s, q[k])); //s * q[k];
+ } else {
+ if (r == 15) {
+ if ((k + 16) > 64) stop_decoding(JPGD_DECODE_ERROR);
+ if (k < prev_num_set) {
+ int n = JPGD_MIN(16, prev_num_set - k);
+ int kt = k;
+ while (n--) {
+ JPGD_ASSERT(kt <= 63);
+ p[g_ZAG[kt++]] = 0;
+ }
+ }
+ k += 16 - 1; // - 1 because the loop counter is k
+ JPGD_ASSERT(p[g_ZAG[k]] == 0);
+ } else break;
+ }
+ }
+
+ if (k < prev_num_set) {
+ int kt = k;
+ while (kt < prev_num_set) p[g_ZAG[kt++]] = 0;
+ }
+
+ m_mcu_block_max_zag[mcu_block] = k;
+ row_block++;
+ }
+ if (m_freq_domain_chroma_upsample) transform_mcu_expand(mcu_row);
+ else transform_mcu(mcu_row);
+ m_restarts_left--;
+ }
+}
+
+
+// YCbCr H1V1 (1x1:1:1, 3 m_blocks per MCU) to RGB
+void jpeg_decoder::H1V1Convert()
+{
+ int row = m_max_mcu_y_size - m_mcu_lines_left;
+ uint8_t *d = m_pScan_line_0;
+ uint8_t *s = m_pSample_buf + row * 8;
+
+ for (int i = m_max_mcus_per_row; i > 0; i--) {
+ for (int j = 0; j < 8; j++) {
+ int y = s[j];
+ int cb = s[64+j];
+ int cr = s[128+j];
+
+ d[0] = clamp(y + m_crr[cr]);
+ d[1] = clamp(y + ((m_crg[cr] + m_cbg[cb]) >> 16));
+ d[2] = clamp(y + m_cbb[cb]);
+ d[3] = 255;
+ d += 4;
+ }
+ s += 64*3;
+ }
+}
+
+
+// YCbCr H2V1 (2x1:1:1, 4 m_blocks per MCU) to RGB
+void jpeg_decoder::H2V1Convert()
+{
+ int row = m_max_mcu_y_size - m_mcu_lines_left;
+ uint8_t *d0 = m_pScan_line_0;
+ uint8_t *y = m_pSample_buf + row * 8;
+ uint8_t *c = m_pSample_buf + 2*64 + row * 8;
+
+ for (int i = m_max_mcus_per_row; i > 0; i--) {
+ for (int l = 0; l < 2; l++) {
+ for (int j = 0; j < 4; j++) {
+ int cb = c[0];
+ int cr = c[64];
+
+ int rc = m_crr[cr];
+ int gc = ((m_crg[cr] + m_cbg[cb]) >> 16);
+ int bc = m_cbb[cb];
+
+ int yy = y[j<<1];
+ d0[0] = clamp(yy+rc);
+ d0[1] = clamp(yy+gc);
+ d0[2] = clamp(yy+bc);
+ d0[3] = 255;
+
+ yy = y[(j<<1)+1];
+ d0[4] = clamp(yy+rc);
+ d0[5] = clamp(yy+gc);
+ d0[6] = clamp(yy+bc);
+ d0[7] = 255;
+ d0 += 8;
+ c++;
+ }
+ y += 64;
+ }
+ y += 64*4 - 64*2;
+ c += 64*4 - 8;
+ }
+}
+
+
+// YCbCr H2V1 (1x2:1:1, 4 m_blocks per MCU) to RGB
+void jpeg_decoder::H1V2Convert()
+{
+ int row = m_max_mcu_y_size - m_mcu_lines_left;
+ uint8_t *d0 = m_pScan_line_0;
+ uint8_t *d1 = m_pScan_line_1;
+ uint8_t *y;
+ uint8_t *c;
+
+ if (row < 8) y = m_pSample_buf + row * 8;
+ else y = m_pSample_buf + 64*1 + (row & 7) * 8;
+
+ c = m_pSample_buf + 64*2 + (row >> 1) * 8;
+
+ for (int i = m_max_mcus_per_row; i > 0; i--) {
+ for (int j = 0; j < 8; j++) {
+ int cb = c[0+j];
+ int cr = c[64+j];
+
+ int rc = m_crr[cr];
+ int gc = ((m_crg[cr] + m_cbg[cb]) >> 16);
+ int bc = m_cbb[cb];
+
+ int yy = y[j];
+ d0[0] = clamp(yy+rc);
+ d0[1] = clamp(yy+gc);
+ d0[2] = clamp(yy+bc);
+ d0[3] = 255;
+
+ yy = y[8+j];
+ d1[0] = clamp(yy+rc);
+ d1[1] = clamp(yy+gc);
+ d1[2] = clamp(yy+bc);
+ d1[3] = 255;
+
+ d0 += 4;
+ d1 += 4;
+ }
+ y += 64*4;
+ c += 64*4;
+ }
+}
+
+
+// YCbCr H2V2 (2x2:1:1, 6 m_blocks per MCU) to RGB
+void jpeg_decoder::H2V2Convert()
+{
+ int row = m_max_mcu_y_size - m_mcu_lines_left;
+ uint8_t *d0 = m_pScan_line_0;
+ uint8_t *d1 = m_pScan_line_1;
+ uint8_t *y;
+ uint8_t *c;
+
+ if (row < 8) y = m_pSample_buf + row * 8;
+ else y = m_pSample_buf + 64*2 + (row & 7) * 8;
+
+ c = m_pSample_buf + 64*4 + (row >> 1) * 8;
+
+ for (int i = m_max_mcus_per_row; i > 0; i--) {
+ for (int l = 0; l < 2; l++) {
+ for (int j = 0; j < 8; j += 2) {
+ int cb = c[0];
+ int cr = c[64];
+
+ int rc = m_crr[cr];
+ int gc = ((m_crg[cr] + m_cbg[cb]) >> 16);
+ int bc = m_cbb[cb];
+
+ int yy = y[j];
+ d0[0] = clamp(yy+rc);
+ d0[1] = clamp(yy+gc);
+ d0[2] = clamp(yy+bc);
+ d0[3] = 255;
+
+ yy = y[j+1];
+ d0[4] = clamp(yy+rc);
+ d0[5] = clamp(yy+gc);
+ d0[6] = clamp(yy+bc);
+ d0[7] = 255;
+
+ yy = y[j+8];
+ d1[0] = clamp(yy+rc);
+ d1[1] = clamp(yy+gc);
+ d1[2] = clamp(yy+bc);
+ d1[3] = 255;
+
+ yy = y[j+8+1];
+ d1[4] = clamp(yy+rc);
+ d1[5] = clamp(yy+gc);
+ d1[6] = clamp(yy+bc);
+ d1[7] = 255;
+
+ d0 += 8;
+ d1 += 8;
+
+ c++;
+ }
+ y += 64;
+ }
+ y += 64*6 - 64*2;
+ c += 64*6 - 8;
+ }
+}
+
+
+// Y (1 block per MCU) to 8-bit grayscale
+void jpeg_decoder::gray_convert()
+{
+ int row = m_max_mcu_y_size - m_mcu_lines_left;
+ uint8_t *d = m_pScan_line_0;
+ uint8_t *s = m_pSample_buf + row * 8;
+
+ for (int i = m_max_mcus_per_row; i > 0; i--) {
+ *(uint32_t *)d = *(uint32_t *)s;
+ *(uint32_t *)(&d[4]) = *(uint32_t *)(&s[4]);
+ s += 64;
+ d += 8;
+ }
+}
+
+
+void jpeg_decoder::expanded_convert()
+{
+ int row = m_max_mcu_y_size - m_mcu_lines_left;
+ uint8_t* Py = m_pSample_buf + (row / 8) * 64 * m_comp_h_samp[0] + (row & 7) * 8;
+ uint8_t* d = m_pScan_line_0;
+
+ for (int i = m_max_mcus_per_row; i > 0; i--) {
+ for (int k = 0; k < m_max_mcu_x_size; k += 8) {
+ const int Y_ofs = k * 8;
+ const int Cb_ofs = Y_ofs + 64 * m_expanded_blocks_per_component;
+ const int Cr_ofs = Y_ofs + 64 * m_expanded_blocks_per_component * 2;
+ for (int j = 0; j < 8; j++) {
+ int y = Py[Y_ofs + j];
+ int cb = Py[Cb_ofs + j];
+ int cr = Py[Cr_ofs + j];
+
+ d[0] = clamp(y + m_crr[cr]);
+ d[1] = clamp(y + ((m_crg[cr] + m_cbg[cb]) >> 16));
+ d[2] = clamp(y + m_cbb[cb]);
+ d[3] = 255;
+
+ d += 4;
+ }
+ }
+ Py += 64 * m_expanded_blocks_per_mcu;
+ }
+}
+
+
+// Find end of image (EOI) marker, so we can return to the user the exact size of the input stream.
+void jpeg_decoder::find_eoi()
+{
+ if (!m_progressive_flag) {
+ // Attempt to read the EOI marker.
+ //get_bits_no_markers(m_bits_left & 7);
+
+ // Prime the bit buffer
+ m_bits_left = 16;
+ get_bits(16);
+ get_bits(16);
+
+ // The next marker _should_ be EOI
+ process_markers();
+ }
+ m_total_bytes_read -= m_in_buf_left;
+}
+
+
+int jpeg_decoder::decode(const void** pScan_line, uint32_t* pScan_line_len)
+{
+ if ((m_error_code) || (!m_ready_flag)) return JPGD_FAILED;
+ if (m_total_lines_left == 0) return JPGD_DONE;
+ if (m_mcu_lines_left == 0) {
+ if (setjmp(m_jmp_state)) return JPGD_FAILED;
+ if (m_progressive_flag) load_next_row();
+ else decode_next_row();
+ // Find the EOI marker if that was the last row.
+ if (m_total_lines_left <= m_max_mcu_y_size) find_eoi();
+ m_mcu_lines_left = m_max_mcu_y_size;
+ }
+
+ if (m_freq_domain_chroma_upsample) {
+ expanded_convert();
+ *pScan_line = m_pScan_line_0;
+ } else {
+ switch (m_scan_type) {
+ case JPGD_YH2V2: {
+ if ((m_mcu_lines_left & 1) == 0) {
+ H2V2Convert();
+ *pScan_line = m_pScan_line_0;
+ }
+ else *pScan_line = m_pScan_line_1;
+ break;
+ }
+ case JPGD_YH2V1: {
+ H2V1Convert();
+ *pScan_line = m_pScan_line_0;
+ break;
+ }
+ case JPGD_YH1V2: {
+ if ((m_mcu_lines_left & 1) == 0) {
+ H1V2Convert();
+ *pScan_line = m_pScan_line_0;
+ } else *pScan_line = m_pScan_line_1;
+ break;
+ }
+ case JPGD_YH1V1: {
+ H1V1Convert();
+ *pScan_line = m_pScan_line_0;
+ break;
+ }
+ case JPGD_GRAYSCALE: {
+ gray_convert();
+ *pScan_line = m_pScan_line_0;
+ break;
+ }
+ }
+ }
+
+ *pScan_line_len = m_real_dest_bytes_per_scan_line;
+ m_mcu_lines_left--;
+ m_total_lines_left--;
+
+ return JPGD_SUCCESS;
+}
+
+
+// Creates the tables needed for efficient Huffman decoding.
+void jpeg_decoder::make_huff_table(int index, huff_tables *pH)
+{
+ int p, i, l, si;
+ uint8_t huffsize[257];
+ uint32_t huffcode[257];
+ uint32_t code;
+ uint32_t subtree;
+ int code_size;
+ int lastp;
+ int nextfreeentry;
+ int currententry;
+
+ pH->ac_table = m_huff_ac[index] != 0;
+ p = 0;
+
+ for (l = 1; l <= 16; l++) {
+ for (i = 1; i <= m_huff_num[index][l]; i++) {
+ huffsize[p++] = static_cast<uint8_t>(l);
+ }
+ }
+
+ huffsize[p] = 0;
+ lastp = p;
+ code = 0;
+ si = huffsize[0];
+ p = 0;
+
+ while (huffsize[p]) {
+ while (huffsize[p] == si) {
+ huffcode[p++] = code;
+ code++;
+ }
+ code <<= 1;
+ si++;
+ }
+
+ memset(pH->look_up, 0, sizeof(pH->look_up));
+ memset(pH->look_up2, 0, sizeof(pH->look_up2));
+ memset(pH->tree, 0, sizeof(pH->tree));
+ memset(pH->code_size, 0, sizeof(pH->code_size));
+
+ nextfreeentry = -1;
+ p = 0;
+
+ while (p < lastp) {
+ i = m_huff_val[index][p];
+ code = huffcode[p];
+ code_size = huffsize[p];
+ pH->code_size[i] = static_cast<uint8_t>(code_size);
+
+ if (code_size <= 8) {
+ code <<= (8 - code_size);
+ for (l = 1 << (8 - code_size); l > 0; l--) {
+ JPGD_ASSERT(i < 256);
+ pH->look_up[code] = i;
+ bool has_extrabits = false;
+ int extra_bits = 0;
+ int num_extra_bits = i & 15;
+ int bits_to_fetch = code_size;
+
+ if (num_extra_bits) {
+ int total_codesize = code_size + num_extra_bits;
+ if (total_codesize <= 8) {
+ has_extrabits = true;
+ extra_bits = ((1 << num_extra_bits) - 1) & (code >> (8 - total_codesize));
+ JPGD_ASSERT(extra_bits <= 0x7FFF);
+ bits_to_fetch += num_extra_bits;
+ }
+ }
+ if (!has_extrabits) pH->look_up2[code] = i | (bits_to_fetch << 8);
+ else pH->look_up2[code] = i | 0x8000 | (extra_bits << 16) | (bits_to_fetch << 8);
+ code++;
+ }
+ } else {
+ subtree = (code >> (code_size - 8)) & 0xFF;
+ currententry = pH->look_up[subtree];
+
+ if (currententry == 0) {
+ pH->look_up[subtree] = currententry = nextfreeentry;
+ pH->look_up2[subtree] = currententry = nextfreeentry;
+ nextfreeentry -= 2;
+ }
+
+ code <<= (16 - (code_size - 8));
+
+ for (l = code_size; l > 9; l--) {
+ if ((code & 0x8000) == 0) currententry--;
+ if (pH->tree[-currententry - 1] == 0) {
+ pH->tree[-currententry - 1] = nextfreeentry;
+ currententry = nextfreeentry;
+ nextfreeentry -= 2;
+ } else currententry = pH->tree[-currententry - 1];
+ code <<= 1;
+ }
+ if ((code & 0x8000) == 0) currententry--;
+ pH->tree[-currententry - 1] = i;
+ }
+ p++;
+ }
+}
+
+
+// Verifies the quantization tables needed for this scan are available.
+void jpeg_decoder::check_quant_tables()
+{
+ for (int i = 0; i < m_comps_in_scan; i++) {
+ if (m_quant[m_comp_quant[m_comp_list[i]]] == nullptr) stop_decoding(JPGD_UNDEFINED_QUANT_TABLE);
+ }
+}
+
+
+// Verifies that all the Huffman tables needed for this scan are available.
+void jpeg_decoder::check_huff_tables()
+{
+ for (int i = 0; i < m_comps_in_scan; i++) {
+ if ((m_spectral_start == 0) && (m_huff_num[m_comp_dc_tab[m_comp_list[i]]] == nullptr)) stop_decoding(JPGD_UNDEFINED_HUFF_TABLE);
+ if ((m_spectral_end > 0) && (m_huff_num[m_comp_ac_tab[m_comp_list[i]]] == nullptr)) stop_decoding(JPGD_UNDEFINED_HUFF_TABLE);
+ }
+
+ for (int i = 0; i < JPGD_MAX_HUFF_TABLES; i++) {
+ if (m_huff_num[i]) {
+ if (!m_pHuff_tabs[i]) m_pHuff_tabs[i] = (huff_tables *)alloc(sizeof(huff_tables));
+ make_huff_table(i, m_pHuff_tabs[i]);
+ }
+ }
+}
+
+
+// Determines the component order inside each MCU.
+// Also calcs how many MCU's are on each row, etc.
+void jpeg_decoder::calc_mcu_block_order()
+{
+ int component_num, component_id;
+ int max_h_samp = 0, max_v_samp = 0;
+
+ for (component_id = 0; component_id < m_comps_in_frame; component_id++) {
+ if (m_comp_h_samp[component_id] > max_h_samp) {
+ max_h_samp = m_comp_h_samp[component_id];
+ }
+ if (m_comp_v_samp[component_id] > max_v_samp) {
+ max_v_samp = m_comp_v_samp[component_id];
+ }
+ }
+
+ for (component_id = 0; component_id < m_comps_in_frame; component_id++) {
+ m_comp_h_blocks[component_id] = ((((m_image_x_size * m_comp_h_samp[component_id]) + (max_h_samp - 1)) / max_h_samp) + 7) / 8;
+ m_comp_v_blocks[component_id] = ((((m_image_y_size * m_comp_v_samp[component_id]) + (max_v_samp - 1)) / max_v_samp) + 7) / 8;
+ }
+
+ if (m_comps_in_scan == 1) {
+ m_mcus_per_row = m_comp_h_blocks[m_comp_list[0]];
+ m_mcus_per_col = m_comp_v_blocks[m_comp_list[0]];
+ } else {
+ m_mcus_per_row = (((m_image_x_size + 7) / 8) + (max_h_samp - 1)) / max_h_samp;
+ m_mcus_per_col = (((m_image_y_size + 7) / 8) + (max_v_samp - 1)) / max_v_samp;
+ }
+
+ if (m_comps_in_scan == 1) {
+ m_mcu_org[0] = m_comp_list[0];
+ m_blocks_per_mcu = 1;
+ } else {
+ m_blocks_per_mcu = 0;
+
+ for (component_num = 0; component_num < m_comps_in_scan; component_num++) {
+ int num_blocks;
+ component_id = m_comp_list[component_num];
+ num_blocks = m_comp_h_samp[component_id] * m_comp_v_samp[component_id];
+ while (num_blocks--) m_mcu_org[m_blocks_per_mcu++] = component_id;
+ }
+ }
+}
+
+
+// Starts a new scan.
+int jpeg_decoder::init_scan()
+{
+ if (!locate_sos_marker()) return false;
+
+ calc_mcu_block_order();
+ check_huff_tables();
+ check_quant_tables();
+
+ memset(m_last_dc_val, 0, m_comps_in_frame * sizeof(uint32_t));
+
+ m_eob_run = 0;
+
+ if (m_restart_interval) {
+ m_restarts_left = m_restart_interval;
+ m_next_restart_num = 0;
+ }
+ fix_in_buffer();
+ return true;
+}
+
+
+// Starts a frame. Determines if the number of components or sampling factors
+// are supported.
+void jpeg_decoder::init_frame()
+{
+ int i;
+
+ if (m_comps_in_frame == 1) {
+ if ((m_comp_h_samp[0] != 1) || (m_comp_v_samp[0] != 1)) stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
+ m_scan_type = JPGD_GRAYSCALE;
+ m_max_blocks_per_mcu = 1;
+ m_max_mcu_x_size = 8;
+ m_max_mcu_y_size = 8;
+ } else if (m_comps_in_frame == 3) {
+ if (((m_comp_h_samp[1] != 1) || (m_comp_v_samp[1] != 1)) || ((m_comp_h_samp[2] != 1) || (m_comp_v_samp[2] != 1)))
+ stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
+
+ if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) {
+ m_scan_type = JPGD_YH1V1;
+ m_max_blocks_per_mcu = 3;
+ m_max_mcu_x_size = 8;
+ m_max_mcu_y_size = 8;
+ } else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) {
+ m_scan_type = JPGD_YH2V1;
+ m_max_blocks_per_mcu = 4;
+ m_max_mcu_x_size = 16;
+ m_max_mcu_y_size = 8;
+ } else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 2)) {
+ m_scan_type = JPGD_YH1V2;
+ m_max_blocks_per_mcu = 4;
+ m_max_mcu_x_size = 8;
+ m_max_mcu_y_size = 16;
+ } else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) {
+ m_scan_type = JPGD_YH2V2;
+ m_max_blocks_per_mcu = 6;
+ m_max_mcu_x_size = 16;
+ m_max_mcu_y_size = 16;
+ } else stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
+ } else stop_decoding(JPGD_UNSUPPORTED_COLORSPACE);
+
+ m_max_mcus_per_row = (m_image_x_size + (m_max_mcu_x_size - 1)) / m_max_mcu_x_size;
+ m_max_mcus_per_col = (m_image_y_size + (m_max_mcu_y_size - 1)) / m_max_mcu_y_size;
+
+ // These values are for the *destination* pixels: after conversion.
+ if (m_scan_type == JPGD_GRAYSCALE) m_dest_bytes_per_pixel = 1;
+ else m_dest_bytes_per_pixel = 4;
+
+ m_dest_bytes_per_scan_line = ((m_image_x_size + 15) & 0xFFF0) * m_dest_bytes_per_pixel;
+ m_real_dest_bytes_per_scan_line = (m_image_x_size * m_dest_bytes_per_pixel);
+
+ // Initialize two scan line buffers.
+ m_pScan_line_0 = (uint8_t *)alloc(m_dest_bytes_per_scan_line, true);
+ if ((m_scan_type == JPGD_YH1V2) || (m_scan_type == JPGD_YH2V2)) {
+ m_pScan_line_1 = (uint8_t *)alloc(m_dest_bytes_per_scan_line, true);
+ }
+
+ m_max_blocks_per_row = m_max_mcus_per_row * m_max_blocks_per_mcu;
+
+ // Should never happen
+ if (m_max_blocks_per_row > JPGD_MAX_BLOCKS_PER_ROW) stop_decoding(JPGD_ASSERTION_ERROR);
+
+ // Allocate the coefficient buffer, enough for one MCU
+ m_pMCU_coefficients = (jpgd_block_t*)alloc(m_max_blocks_per_mcu * 64 * sizeof(jpgd_block_t));
+
+ for (i = 0; i < m_max_blocks_per_mcu; i++) {
+ m_mcu_block_max_zag[i] = 64;
+ }
+
+ m_expanded_blocks_per_component = m_comp_h_samp[0] * m_comp_v_samp[0];
+ m_expanded_blocks_per_mcu = m_expanded_blocks_per_component * m_comps_in_frame;
+ m_expanded_blocks_per_row = m_max_mcus_per_row * m_expanded_blocks_per_mcu;
+ // Freq. domain chroma upsampling is only supported for H2V2 subsampling factor (the most common one I've seen).
+ m_freq_domain_chroma_upsample = false;
+#if JPGD_SUPPORT_FREQ_DOMAIN_UPSAMPLING
+ m_freq_domain_chroma_upsample = (m_expanded_blocks_per_mcu == 4*3);
+#endif
+
+ if (m_freq_domain_chroma_upsample)
+ m_pSample_buf = (uint8_t *)alloc(m_expanded_blocks_per_row * 64);
+ else
+ m_pSample_buf = (uint8_t *)alloc(m_max_blocks_per_row * 64);
+
+ m_total_lines_left = m_image_y_size;
+ m_mcu_lines_left = 0;
+ create_look_ups();
+}
+
+
+// The coeff_buf series of methods originally stored the coefficients
+// into a "virtual" file which was located in EMS, XMS, or a disk file. A cache
+// was used to make this process more efficient. Now, we can store the entire
+// thing in RAM.
+jpeg_decoder::coeff_buf* jpeg_decoder::coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y)
+{
+ coeff_buf* cb = (coeff_buf*)alloc(sizeof(coeff_buf));
+ cb->block_num_x = block_num_x;
+ cb->block_num_y = block_num_y;
+ cb->block_len_x = block_len_x;
+ cb->block_len_y = block_len_y;
+ cb->block_size = (block_len_x * block_len_y) * sizeof(jpgd_block_t);
+ cb->pData = (uint8_t *)alloc(cb->block_size * block_num_x * block_num_y, true);
+ return cb;
+}
+
+
+inline jpgd_block_t *jpeg_decoder::coeff_buf_getp(coeff_buf *cb, int block_x, int block_y)
+{
+ JPGD_ASSERT((block_x < cb->block_num_x) && (block_y < cb->block_num_y));
+ return (jpgd_block_t *)(cb->pData + block_x * cb->block_size + block_y * (cb->block_size * cb->block_num_x));
+}
+
+
+// The following methods decode the various types of m_blocks encountered
+// in progressively encoded images.
+void jpeg_decoder::decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y)
+{
+ int s, r;
+ jpgd_block_t *p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y);
+
+ if ((s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_dc_tab[component_id]])) != 0) {
+ r = pD->get_bits_no_markers(s);
+ s = JPGD_HUFF_EXTEND(r, s);
+ }
+ pD->m_last_dc_val[component_id] = (s += pD->m_last_dc_val[component_id]);
+ p[0] = static_cast<jpgd_block_t>(s << pD->m_successive_low);
+}
+
+
+void jpeg_decoder::decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y)
+{
+ if (pD->get_bits_no_markers(1)) {
+ jpgd_block_t *p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y);
+ p[0] |= (1 << pD->m_successive_low);
+ }
+}
+
+
+void jpeg_decoder::decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y)
+{
+ int k, s, r;
+
+ if (pD->m_eob_run) {
+ pD->m_eob_run--;
+ return;
+ }
+ jpgd_block_t *p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y);
+
+ for (k = pD->m_spectral_start; k <= pD->m_spectral_end; k++) {
+ s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_ac_tab[component_id]]);
+ r = s >> 4;
+ s &= 15;
+ if (s) {
+ if ((k += r) > 63) pD->stop_decoding(JPGD_DECODE_ERROR);
+ r = pD->get_bits_no_markers(s);
+ s = JPGD_HUFF_EXTEND(r, s);
+ p[g_ZAG[k]] = static_cast<jpgd_block_t>(s << pD->m_successive_low);
+ } else {
+ if (r == 15) {
+ if ((k += 15) > 63) pD->stop_decoding(JPGD_DECODE_ERROR);
+ } else {
+ pD->m_eob_run = 1 << r;
+ if (r) pD->m_eob_run += pD->get_bits_no_markers(r);
+ pD->m_eob_run--;
+ break;
+ }
+ }
+ }
+}
+
+
+void jpeg_decoder::decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y)
+{
+ int s, k, r;
+ int p1 = 1 << pD->m_successive_low;
+ int m1 = (-1) << pD->m_successive_low;
+ jpgd_block_t *p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y);
+
+ JPGD_ASSERT(pD->m_spectral_end <= 63);
+
+ k = pD->m_spectral_start;
+
+ if (pD->m_eob_run == 0) {
+ for ( ; k <= pD->m_spectral_end; k++) {
+ s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_ac_tab[component_id]]);
+ r = s >> 4;
+ s &= 15;
+ if (s) {
+ if (s != 1) pD->stop_decoding(JPGD_DECODE_ERROR);
+ if (pD->get_bits_no_markers(1)) s = p1;
+ else s = m1;
+ } else {
+ if (r != 15) {
+ pD->m_eob_run = 1 << r;
+ if (r) pD->m_eob_run += pD->get_bits_no_markers(r);
+ break;
+ }
+ }
+
+ do {
+ jpgd_block_t *this_coef = p + g_ZAG[k & 63];
+
+ if (*this_coef != 0) {
+ if (pD->get_bits_no_markers(1)) {
+ if ((*this_coef & p1) == 0) {
+ if (*this_coef >= 0) *this_coef = static_cast<jpgd_block_t>(*this_coef + p1);
+ else *this_coef = static_cast<jpgd_block_t>(*this_coef + m1);
+ }
+ }
+ } else {
+ if (--r < 0) break;
+ }
+ k++;
+ } while (k <= pD->m_spectral_end);
+
+ if ((s) && (k < 64)) {
+ p[g_ZAG[k]] = static_cast<jpgd_block_t>(s);
+ }
+ }
+ }
+
+ if (pD->m_eob_run > 0) {
+ for ( ; k <= pD->m_spectral_end; k++) {
+ jpgd_block_t *this_coef = p + g_ZAG[k & 63]; // logical AND to shut up static code analysis
+
+ if (*this_coef != 0) {
+ if (pD->get_bits_no_markers(1)) {
+ if ((*this_coef & p1) == 0) {
+ if (*this_coef >= 0) *this_coef = static_cast<jpgd_block_t>(*this_coef + p1);
+ else *this_coef = static_cast<jpgd_block_t>(*this_coef + m1);
+ }
+ }
+ }
+ }
+ pD->m_eob_run--;
+ }
+}
+
+
+// Decode a scan in a progressively encoded image.
+void jpeg_decoder::decode_scan(pDecode_block_func decode_block_func)
+{
+ int mcu_row, mcu_col, mcu_block;
+ int block_x_mcu[JPGD_MAX_COMPONENTS], m_block_y_mcu[JPGD_MAX_COMPONENTS];
+
+ memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu));
+
+ for (mcu_col = 0; mcu_col < m_mcus_per_col; mcu_col++) {
+ int component_num, component_id;
+ memset(block_x_mcu, 0, sizeof(block_x_mcu));
+
+ for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) {
+ int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0;
+
+ if ((m_restart_interval) && (m_restarts_left == 0)) process_restart();
+
+ for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) {
+ component_id = m_mcu_org[mcu_block];
+ decode_block_func(this, component_id, block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs);
+
+ if (m_comps_in_scan == 1) block_x_mcu[component_id]++;
+ else {
+ if (++block_x_mcu_ofs == m_comp_h_samp[component_id]) {
+ block_x_mcu_ofs = 0;
+
+ if (++block_y_mcu_ofs == m_comp_v_samp[component_id]) {
+ block_y_mcu_ofs = 0;
+ block_x_mcu[component_id] += m_comp_h_samp[component_id];
+ }
+ }
+ }
+ }
+ m_restarts_left--;
+ }
+
+ if (m_comps_in_scan == 1) m_block_y_mcu[m_comp_list[0]]++;
+ else {
+ for (component_num = 0; component_num < m_comps_in_scan; component_num++) {
+ component_id = m_comp_list[component_num];
+ m_block_y_mcu[component_id] += m_comp_v_samp[component_id];
+ }
+ }
+ }
+}
+
+
+// Decode a progressively encoded image.
+void jpeg_decoder::init_progressive()
+{
+ int i;
+
+ if (m_comps_in_frame == 4) stop_decoding(JPGD_UNSUPPORTED_COLORSPACE);
+
+ // Allocate the coefficient buffers.
+ for (i = 0; i < m_comps_in_frame; i++) {
+ m_dc_coeffs[i] = coeff_buf_open(m_max_mcus_per_row * m_comp_h_samp[i], m_max_mcus_per_col * m_comp_v_samp[i], 1, 1);
+ m_ac_coeffs[i] = coeff_buf_open(m_max_mcus_per_row * m_comp_h_samp[i], m_max_mcus_per_col * m_comp_v_samp[i], 8, 8);
+ }
+
+ while (true) {
+ int dc_only_scan, refinement_scan;
+ pDecode_block_func decode_block_func;
+
+ if (!init_scan()) break;
+
+ dc_only_scan = (m_spectral_start == 0);
+ refinement_scan = (m_successive_high != 0);
+
+ if ((m_spectral_start > m_spectral_end) || (m_spectral_end > 63)) stop_decoding(JPGD_BAD_SOS_SPECTRAL);
+
+ if (dc_only_scan) {
+ if (m_spectral_end) stop_decoding(JPGD_BAD_SOS_SPECTRAL);
+ } else if (m_comps_in_scan != 1) { /* AC scans can only contain one component */
+ stop_decoding(JPGD_BAD_SOS_SPECTRAL);
+ }
+
+ if ((refinement_scan) && (m_successive_low != m_successive_high - 1)) stop_decoding(JPGD_BAD_SOS_SUCCESSIVE);
+
+ if (dc_only_scan) {
+ if (refinement_scan) decode_block_func = decode_block_dc_refine;
+ else decode_block_func = decode_block_dc_first;
+ } else {
+ if (refinement_scan) decode_block_func = decode_block_ac_refine;
+ else decode_block_func = decode_block_ac_first;
+ }
+ decode_scan(decode_block_func);
+ m_bits_left = 16;
+ get_bits(16);
+ get_bits(16);
+ }
+
+ m_comps_in_scan = m_comps_in_frame;
+
+ for (i = 0; i < m_comps_in_frame; i++) {
+ m_comp_list[i] = i;
+ }
+
+ calc_mcu_block_order();
+}
+
+
+void jpeg_decoder::init_sequential()
+{
+ if (!init_scan()) stop_decoding(JPGD_UNEXPECTED_MARKER);
+}
+
+
+void jpeg_decoder::decode_start()
+{
+ init_frame();
+ if (m_progressive_flag) init_progressive();
+ else init_sequential();
+}
+
+
+void jpeg_decoder::decode_init(jpeg_decoder_stream *pStream)
+{
+ init(pStream);
+ locate_sof_marker();
+}
+
+
+jpeg_decoder::jpeg_decoder(jpeg_decoder_stream *pStream)
+{
+ if (setjmp(m_jmp_state)) return;
+ decode_init(pStream);
+}
+
+
+int jpeg_decoder::begin_decoding()
+{
+ if (m_ready_flag) return JPGD_SUCCESS;
+ if (m_error_code) return JPGD_FAILED;
+ if (setjmp(m_jmp_state)) return JPGD_FAILED;
+
+ decode_start();
+ m_ready_flag = true;
+
+ return JPGD_SUCCESS;
+}
+
+
+jpeg_decoder::~jpeg_decoder()
+{
+ free_all_blocks();
+ delete(m_pStream);
+}
+
+
+void jpeg_decoder_file_stream::close()
+{
+ if (m_pFile) {
+ fclose(m_pFile);
+ m_pFile = nullptr;
+ }
+ m_eof_flag = false;
+ m_error_flag = false;
+}
+
+
+jpeg_decoder_file_stream::~jpeg_decoder_file_stream()
+{
+ close();
+}
+
+
+bool jpeg_decoder_file_stream::open(const char *Pfilename)
+{
+ close();
+
+ m_eof_flag = false;
+ m_error_flag = false;
+
+#if defined(_MSC_VER)
+ m_pFile = nullptr;
+ fopen_s(&m_pFile, Pfilename, "rb");
+#else
+ m_pFile = fopen(Pfilename, "rb");
+#endif
+ return m_pFile != nullptr;
+}
+
+
+int jpeg_decoder_file_stream::read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag)
+{
+ if (!m_pFile) return -1;
+
+ if (m_eof_flag) {
+ *pEOF_flag = true;
+ return 0;
+ }
+
+ if (m_error_flag) return -1;
+
+ int bytes_read = static_cast<int>(fread(pBuf, 1, max_bytes_to_read, m_pFile));
+ if (bytes_read < max_bytes_to_read) {
+ if (ferror(m_pFile)) {
+ m_error_flag = true;
+ return -1;
+ }
+ m_eof_flag = true;
+ *pEOF_flag = true;
+ }
+ return bytes_read;
+}
+
+
+bool jpeg_decoder_mem_stream::open(const uint8_t *pSrc_data, uint32_t size)
+{
+ close();
+ m_pSrc_data = pSrc_data;
+ m_ofs = 0;
+ m_size = size;
+ return true;
+}
+
+
+int jpeg_decoder_mem_stream::read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag)
+{
+ *pEOF_flag = false;
+ if (!m_pSrc_data) return -1;
+
+ uint32_t bytes_remaining = m_size - m_ofs;
+ if ((uint32_t)max_bytes_to_read > bytes_remaining) {
+ max_bytes_to_read = bytes_remaining;
+ *pEOF_flag = true;
+ }
+ memcpy(pBuf, m_pSrc_data + m_ofs, max_bytes_to_read);
+ m_ofs += max_bytes_to_read;
+
+ return max_bytes_to_read;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+jpeg_decoder* jpgdHeader(const char* data, int size, int* width, int* height)
+{
+ auto decoder = new jpeg_decoder(new jpeg_decoder_mem_stream((const uint8_t*)data, size));
+ if (decoder->get_error_code() != JPGD_SUCCESS) {
+ delete(decoder);
+ return nullptr;
+ }
+
+ if (width) *width = decoder->get_width();
+ if (height) *height = decoder->get_height();
+
+ return decoder;
+}
+
+
+jpeg_decoder* jpgdHeader(const char* filename, int* width, int* height)
+{
+ auto fileStream = new jpeg_decoder_file_stream();
+ if (!fileStream->open(filename)) return nullptr;
+
+ auto decoder = new jpeg_decoder(fileStream);
+ if (decoder->get_error_code() != JPGD_SUCCESS) {
+ delete(decoder);
+ return nullptr;
+ }
+
+ if (width) *width = decoder->get_width();
+ if (height) *height = decoder->get_height();
+
+ return decoder;
+}
+
+
+void jpgdDelete(jpeg_decoder* decoder)
+{
+ delete(decoder);
+}
+
+
+unsigned char* jpgdDecompress(jpeg_decoder* decoder)
+{
+ if (!decoder) return nullptr;
+
+ int req_comps = 4; //TODO: fixed 4 channel components now?
+ if ((req_comps != 1) && (req_comps != 3) && (req_comps != 4)) return nullptr;
+
+ auto image_width = decoder->get_width();
+ auto image_height = decoder->get_height();
+ //auto actual_comps = decoder->get_num_components();
+
+ if (decoder->begin_decoding() != JPGD_SUCCESS) return nullptr;
+
+ const int dst_bpl = image_width * req_comps;
+ uint8_t *pImage_data = (uint8_t*)malloc(dst_bpl * image_height);
+ if (!pImage_data) return nullptr;
+
+ for (int y = 0; y < image_height; y++) {
+ const uint8_t* pScan_line;
+ uint32_t scan_line_len;
+ if (decoder->decode((const void**)&pScan_line, &scan_line_len) != JPGD_SUCCESS) {
+ free(pImage_data);
+ return nullptr;
+ }
+
+ uint8_t *pDst = pImage_data + y * dst_bpl;
+
+ //Return as BGRA
+ if ((req_comps == 4) && (decoder->get_num_components() == 3)) {
+ for (int x = 0; x < image_width; x++) {
+ pDst[0] = pScan_line[x*4+2];
+ pDst[1] = pScan_line[x*4+1];
+ pDst[2] = pScan_line[x*4+0];
+ pDst[3] = 255;
+ pDst += 4;
+ }
+ } else if (((req_comps == 1) && (decoder->get_num_components() == 1)) || ((req_comps == 4) && (decoder->get_num_components() == 3))) {
+ memcpy(pDst, pScan_line, dst_bpl);
+ } else if (decoder->get_num_components() == 1) {
+ if (req_comps == 3) {
+ for (int x = 0; x < image_width; x++) {
+ uint8_t luma = pScan_line[x];
+ pDst[0] = luma;
+ pDst[1] = luma;
+ pDst[2] = luma;
+ pDst += 3;
+ }
+ } else {
+ for (int x = 0; x < image_width; x++) {
+ uint8_t luma = pScan_line[x];
+ pDst[0] = luma;
+ pDst[1] = luma;
+ pDst[2] = luma;
+ pDst[3] = 255;
+ pDst += 4;
+ }
+ }
+ } else if (decoder->get_num_components() == 3) {
+ if (req_comps == 1) {
+ const int YR = 19595, YG = 38470, YB = 7471;
+ for (int x = 0; x < image_width; x++) {
+ int r = pScan_line[x*4+0];
+ int g = pScan_line[x*4+1];
+ int b = pScan_line[x*4+2];
+ *pDst++ = static_cast<uint8_t>((r * YR + g * YG + b * YB + 32768) >> 16);
+ }
+ } else {
+ for (int x = 0; x < image_width; x++) {
+ pDst[0] = pScan_line[x*4+0];
+ pDst[1] = pScan_line[x*4+1];
+ pDst[2] = pScan_line[x*4+2];
+ pDst += 3;
+ }
+ }
+ }
+ }
+ return pImage_data;
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h
new file mode 100644
index 0000000000..d32ffd99d4
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// jpgd.h - C++ class for JPEG decompression.
+// Public domain, Rich Geldreich <richgel99@gmail.com>
+#ifndef _TVG_JPGD_H_
+#define _TVG_JPGD_H_
+
+class jpeg_decoder;
+
+jpeg_decoder* jpgdHeader(const char* data, int size, int* width, int* height);
+jpeg_decoder* jpgdHeader(const char* filename, int* width, int* height);
+unsigned char* jpgdDecompress(jpeg_decoder* decoder);
+void jpgdDelete(jpeg_decoder* decoder);
+
+#endif //_TVG_JPGD_H_
diff --git a/thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp b/thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp
new file mode 100644
index 0000000000..f2dfa02875
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp
@@ -0,0 +1,2647 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ LodePNG version 20200306
+
+ Copyright (c) 2005-2020 Lode Vandevenne
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any sourcedistribution.
+*/
+
+#include <cstdlib>
+#include "tvgLodePng.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/
+ #pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/
+ #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/
+#endif /*_MSC_VER */
+
+
+/* convince the compiler to inline a function, for use when this measurably improves performance */
+/* inline is not available in C90, but use it when supported by the compiler */
+#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L))
+ #define LODEPNG_INLINE inline
+#else
+ #define LODEPNG_INLINE /* not available */
+#endif
+
+/* restrict is not available in C90, but use it when supported by the compiler */
+#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\
+ (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \
+ (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus))
+ #define LODEPNG_RESTRICT __restrict
+#else
+ #define LODEPNG_RESTRICT /* not available */
+#endif
+
+#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b))
+#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x))
+
+
+/* Replacements for C library functions such as memcpy and strlen, to support platforms
+where a full C library is not available. The compiler can recognize them and compile
+to something as fast. */
+
+static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, const void* LODEPNG_RESTRICT src, size_t size)
+{
+ size_t i;
+ for (i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i];
+}
+
+
+static void lodepng_memset(void* LODEPNG_RESTRICT dst, int value, size_t num)
+{
+ size_t i;
+ for (i = 0; i < num; i++) ((char*)dst)[i] = (char)value;
+}
+
+
+/* does not check memory out of bounds, do not use on untrusted data */
+static size_t lodepng_strlen(const char* a)
+{
+ const char* orig = a;
+ /* avoid warning about unused function in case of disabled COMPILE... macros */
+ (void)(&lodepng_strlen);
+ while (*a) a++;
+ return (size_t)(a - orig);
+}
+
+
+/* Safely check if adding two integers will overflow (no undefined
+behavior, compiler removing the code, etc...) and output result. */
+static int lodepng_addofl(size_t a, size_t b, size_t* result)
+{
+ *result = a + b; /* Unsigned addition is well defined and safe in C90 */
+ return *result < a;
+}
+
+
+/* Safely check if multiplying two integers will overflow (no undefined
+behavior, compiler removing the code, etc...) and output result. */
+static int lodepng_mulofl(size_t a, size_t b, size_t* result)
+{
+ *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */
+ return (a != 0 && *result / a != b);
+}
+
+
+/* Safely check if a + b > c, even if overflow could happen. */
+static int lodepng_gtofl(size_t a, size_t b, size_t c)
+{
+ size_t d;
+ if (lodepng_addofl(a, b, &d)) return 1;
+ return d > c;
+}
+
+
+/*
+ Often in case of an error a value is assigned to a variable and then it breaks
+ out of a loop (to go to the cleanup phase of a function). This macro does that.
+ It makes the error handling code shorter and more readable.
+
+ Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83);
+*/
+#define CERROR_BREAK(errorvar, code){\
+ errorvar = code;\
+ break;\
+}
+
+/* version of CERROR_BREAK that assumes the common case where the error variable is named "error" */
+#define ERROR_BREAK(code) CERROR_BREAK(error, code)
+
+/* Set error var to the error code, and return it.*/
+#define CERROR_RETURN_ERROR(errorvar, code){\
+ errorvar = code;\
+ return code;\
+}
+
+/* Try the code, if it returns error, also return the error. */
+#define CERROR_TRY_RETURN(call){\
+ unsigned error = call;\
+ if(error) return error;\
+}
+
+/* Set error var to the error code, and return from the void function. */
+#define CERROR_RETURN(errorvar, code){\
+ errorvar = code;\
+ return;\
+}
+
+
+/* dynamic vector of unsigned chars */
+struct ucvector
+{
+ unsigned char* data;
+ size_t size; /*used size*/
+ size_t allocsize; /*allocated size*/
+};
+
+
+/* returns 1 if success, 0 if failure ==> nothing done */
+static unsigned ucvector_resize(ucvector* p, size_t size)
+{
+ if (size > p->allocsize) {
+ size_t newsize = size + (p->allocsize >> 1u);
+ void* data = realloc(p->data, newsize);
+ if(data) {
+ p->allocsize = newsize;
+ p->data = (unsigned char*)data;
+ }
+ else return 0; /*error: not enough memory*/
+ }
+ p->size = size;
+ return 1; /*success*/
+}
+
+
+static ucvector ucvector_init(unsigned char* buffer, size_t size)
+{
+ ucvector v;
+ v.data = buffer;
+ v.allocsize = v.size = size;
+ return v;
+}
+
+
+static unsigned lodepng_read32bitInt(const unsigned char* buffer)
+{
+ return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]);
+}
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* // End of common code and tools. Begin of Zlib related code. // */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+struct LodePNGBitReader
+{
+ const unsigned char* data;
+ size_t size; /*size of data in bytes*/
+ size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/
+ size_t bp;
+ unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/
+};
+
+
+/* data size argument is in bytes. Returns error if size too large causing overflow */
+static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size)
+{
+ size_t temp;
+ reader->data = data;
+ reader->size = size;
+ /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */
+ if (lodepng_mulofl(size, 8u, &reader->bitsize)) return 105;
+ /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and
+ trying to ensure 32 more bits*/
+ if (lodepng_addofl(reader->bitsize, 64u, &temp)) return 105;
+ reader->bp = 0;
+ reader->buffer = 0;
+ return 0; /*ok*/
+ }
+
+/*
+ ensureBits functions:
+ Ensures the reader can at least read nbits bits in one or more readBits calls,
+ safely even if not enough bits are available.
+ Returns 1 if there are enough bits available, 0 if not.
+*/
+
+/*See ensureBits documentation above. This one ensures exactly 1 bit */
+/*static unsigned ensureBits1(LodePNGBitReader* reader) {
+ if(reader->bp >= reader->bitsize) return 0;
+ reader->buffer = (unsigned)reader->data[reader->bp >> 3u] >> (reader->bp & 7u);
+ return 1;
+}*/
+
+/*See ensureBits documentation above. This one ensures up to 9 bits */
+static unsigned ensureBits9(LodePNGBitReader* reader, size_t nbits)
+{
+ size_t start = reader->bp >> 3u;
+ size_t size = reader->size;
+ if (start + 1u < size) {
+ reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u);
+ reader->buffer >>= (reader->bp & 7u);
+ return 1;
+ } else {
+ reader->buffer = 0;
+ if (start + 0u < size) reader->buffer |= reader->data[start + 0];
+ reader->buffer >>= (reader->bp & 7u);
+ return reader->bp + nbits <= reader->bitsize;
+ }
+}
+
+
+/*See ensureBits documentation above. This one ensures up to 17 bits */
+static unsigned ensureBits17(LodePNGBitReader* reader, size_t nbits)
+{
+ size_t start = reader->bp >> 3u;
+ size_t size = reader->size;
+ if (start + 2u < size) {
+ reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u);
+ reader->buffer >>= (reader->bp & 7u);
+ return 1;
+ } else {
+ reader->buffer = 0;
+ if (start + 0u < size) reader->buffer |= reader->data[start + 0];
+ if (start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u);
+ reader->buffer >>= (reader->bp & 7u);
+ return reader->bp + nbits <= reader->bitsize;
+ }
+}
+
+
+/*See ensureBits documentation above. This one ensures up to 25 bits */
+static LODEPNG_INLINE unsigned ensureBits25(LodePNGBitReader* reader, size_t nbits)
+{
+ size_t start = reader->bp >> 3u;
+ size_t size = reader->size;
+ if (start + 3u < size) {
+ reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u);
+ reader->buffer >>= (reader->bp & 7u);
+ return 1;
+ } else {
+ reader->buffer = 0;
+ if (start + 0u < size) reader->buffer |= reader->data[start + 0];
+ if (start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u);
+ if (start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u);
+ reader->buffer >>= (reader->bp & 7u);
+ return reader->bp + nbits <= reader->bitsize;
+ }
+}
+
+
+/*See ensureBits documentation above. This one ensures up to 32 bits */
+static LODEPNG_INLINE unsigned ensureBits32(LodePNGBitReader* reader, size_t nbits)
+{
+ size_t start = reader->bp >> 3u;
+ size_t size = reader->size;
+ if(start + 4u < size) {
+ reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u);
+ reader->buffer >>= (reader->bp & 7u);
+ reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u)));
+ return 1;
+ } else {
+ reader->buffer = 0;
+ if (start + 0u < size) reader->buffer |= reader->data[start + 0];
+ if (start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u);
+ if (start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u);
+ if (start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u);
+ reader->buffer >>= (reader->bp & 7u);
+ return reader->bp + nbits <= reader->bitsize;
+ }
+}
+
+
+/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */
+static unsigned peekBits(LodePNGBitReader* reader, size_t nbits)
+{
+ /* The shift allows nbits to be only up to 31. */
+ return reader->buffer & ((1u << nbits) - 1u);
+}
+
+
+/* Must have enough bits available with ensureBits */
+static void advanceBits(LodePNGBitReader* reader, size_t nbits)
+{
+ reader->buffer >>= nbits;
+ reader->bp += nbits;
+}
+
+
+/* Must have enough bits available with ensureBits */
+static unsigned readBits(LodePNGBitReader* reader, size_t nbits)
+{
+ unsigned result = peekBits(reader, nbits);
+ advanceBits(reader, nbits);
+ return result;
+}
+
+
+/* Public for testing only. steps and result must have numsteps values. */
+unsigned lode_png_test_bitreader(const unsigned char* data, size_t size, size_t numsteps, const size_t* steps, unsigned* result)
+{
+ size_t i;
+ LodePNGBitReader reader;
+ unsigned error = LodePNGBitReader_init(&reader, data, size);
+ if (error) return 0;
+ for (i = 0; i < numsteps; i++) {
+ size_t step = steps[i];
+ unsigned ok;
+ if (step > 25) ok = ensureBits32(&reader, step);
+ else if (step > 17) ok = ensureBits25(&reader, step);
+ else if (step > 9) ok = ensureBits17(&reader, step);
+ else ok = ensureBits9(&reader, step);
+ if (!ok) return 0;
+ result[i] = readBits(&reader, step);
+ }
+ return 1;
+}
+
+
+static unsigned reverseBits(unsigned bits, unsigned num)
+{
+ /*TODO: implement faster lookup table based version when needed*/
+ unsigned i, result = 0;
+ for (i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i;
+ return result;
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Deflate - Huffman / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#define FIRST_LENGTH_CODE_INDEX 257
+#define LAST_LENGTH_CODE_INDEX 285
+/*256 literals, the end code, some length codes, and 2 unused codes*/
+#define NUM_DEFLATE_CODE_SYMBOLS 288
+/*the distance codes have their own symbols, 30 used, 2 unused*/
+#define NUM_DISTANCE_SYMBOLS 32
+/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/
+#define NUM_CODE_LENGTH_CODES 19
+
+/*the base lengths represented by codes 257-285*/
+static const unsigned LENGTHBASE[29]
+ = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
+ 67, 83, 99, 115, 131, 163, 195, 227, 258};
+
+/*the extra bits used by codes 257-285 (added to base length)*/
+static const unsigned LENGTHEXTRA[29]
+ = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 0};
+
+/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/
+static const unsigned DISTANCEBASE[30]
+ = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513,
+ 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
+
+/*the extra bits of backwards distances (added to base)*/
+static const unsigned DISTANCEEXTRA[30]
+ = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8,
+ 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
+
+/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman
+tree of the dynamic huffman tree lengths is generated*/
+static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES]
+ = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*
+Huffman tree struct, containing multiple representations of the tree
+*/
+struct HuffmanTree
+{
+ unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/
+ unsigned* lengths; /*the lengths of the huffman codes*/
+ unsigned maxbitlen; /*maximum number of bits a single code can get*/
+ unsigned numcodes; /*number of symbols in the alphabet = number of codes*/
+ /* for reading only */
+ unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/
+ unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/
+};
+
+
+static void HuffmanTree_init(HuffmanTree* tree)
+{
+ tree->codes = 0;
+ tree->lengths = 0;
+ tree->table_len = 0;
+ tree->table_value = 0;
+}
+
+
+static void HuffmanTree_cleanup(HuffmanTree* tree)
+{
+ free(tree->codes);
+ free(tree->lengths);
+ free(tree->table_len);
+ free(tree->table_value);
+}
+
+
+/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/
+/* values 8u and 9u work the fastest */
+#define FIRSTBITS 9u
+
+/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination,
+which is possible in case of only 0 or 1 present symbols. */
+#define INVALIDSYMBOL 65535u
+
+/* make table for huffman decoding */
+static unsigned HuffmanTree_makeTable(HuffmanTree* tree)
+{
+ static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/
+ static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u;
+ size_t i, numpresent, pointer, size; /*total table size*/
+ unsigned* maxlens = (unsigned*)malloc(headsize * sizeof(unsigned));
+ if (!maxlens) return 83; /*alloc fail*/
+
+ /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/
+ lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens));
+ for (i = 0; i < tree->numcodes; i++) {
+ unsigned symbol = tree->codes[i];
+ unsigned l = tree->lengths[i];
+ unsigned index;
+ if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/
+ /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/
+ index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS);
+ maxlens[index] = LODEPNG_MAX(maxlens[index], l);
+ }
+ /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */
+ size = headsize;
+ for (i = 0; i < headsize; ++i) {
+ unsigned l = maxlens[i];
+ if (l > FIRSTBITS) size += (1u << (l - FIRSTBITS));
+ }
+ tree->table_len = (unsigned char*)malloc(size * sizeof(*tree->table_len));
+ tree->table_value = (unsigned short*)malloc(size * sizeof(*tree->table_value));
+ if (!tree->table_len || !tree->table_value) {
+ free(maxlens);
+ /* freeing tree->table values is done at a higher scope */
+ return 83; /*alloc fail*/
+ }
+ /*initialize with an invalid length to indicate unused entries*/
+ for (i = 0; i < size; ++i) tree->table_len[i] = 16;
+
+ /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/
+ pointer = headsize;
+ for (i = 0; i < headsize; ++i) {
+ unsigned l = maxlens[i];
+ if(l <= FIRSTBITS) continue;
+ tree->table_len[i] = l;
+ tree->table_value[i] = pointer;
+ pointer += (1u << (l - FIRSTBITS));
+ }
+ free(maxlens);
+
+ /*fill in the first table for short symbols, or secondary table for long symbols*/
+ numpresent = 0;
+ for (i = 0; i < tree->numcodes; ++i) {
+ unsigned l = tree->lengths[i];
+ unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/
+ /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/
+ unsigned reverse = reverseBits(symbol, l);
+ if (l == 0) continue;
+ numpresent++;
+
+ if (l <= FIRSTBITS) {
+ /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/
+ unsigned num = 1u << (FIRSTBITS - l);
+ unsigned j;
+ for (j = 0; j < num; ++j) {
+ /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/
+ unsigned index = reverse | (j << l);
+ if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/
+ tree->table_len[index] = l;
+ tree->table_value[index] = i;
+ }
+ } else {
+ /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/
+ /*the FIRSTBITS MSBs of the symbol are the first table index*/
+ unsigned index = reverse & mask;
+ unsigned maxlen = tree->table_len[index];
+ /*log2 of secondary table length, should be >= l - FIRSTBITS*/
+ unsigned tablelen = maxlen - FIRSTBITS;
+ unsigned start = tree->table_value[index]; /*starting index in secondary table*/
+ unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/
+ unsigned j;
+ if (maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/
+ for (j = 0; j < num; ++j) {
+ unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */
+ unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS)));
+ tree->table_len[index2] = l;
+ tree->table_value[index2] = i;
+ }
+ }
+ }
+
+ if (numpresent < 2) {
+ /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits,
+ but deflate uses 1 bit instead. In case of 0 symbols, no symbols can
+ appear at all, but such huffman tree could still exist (e.g. if distance
+ codes are never used). In both cases, not all symbols of the table will be
+ filled in. Fill them in with an invalid symbol value so returning them from
+ huffmanDecodeSymbol will cause error. */
+ for (i = 0; i < size; ++i) {
+ if (tree->table_len[i] == 16) {
+ /* As length, use a value smaller than FIRSTBITS for the head table,
+ and a value larger than FIRSTBITS for the secondary table, to ensure
+ valid behavior for advanceBits when reading this symbol. */
+ tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1);
+ tree->table_value[i] = INVALIDSYMBOL;
+ }
+ }
+ } else {
+ /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes.
+ If that is not the case (due to too long length codes), the table will not
+ have been fully used, and this is an error (not all bit combinations can be
+ decoded): an oversubscribed huffman tree, indicated by error 55. */
+ for (i = 0; i < size; ++i) {
+ if (tree->table_len[i] == 16) return 55;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ Second step for the ...makeFromLengths and ...makeFromFrequencies functions.
+ numcodes, lengths and maxbitlen must already be filled in correctly. return
+ value is error.
+*/
+static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree)
+{
+ unsigned* blcount;
+ unsigned* nextcode;
+ unsigned error = 0;
+ unsigned bits, n;
+
+ tree->codes = (unsigned*)malloc(tree->numcodes * sizeof(unsigned));
+ blcount = (unsigned*)malloc((tree->maxbitlen + 1) * sizeof(unsigned));
+ nextcode = (unsigned*)malloc((tree->maxbitlen + 1) * sizeof(unsigned));
+ if (!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/
+
+ if (!error) {
+ for (n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0;
+ /*step 1: count number of instances of each code length*/
+ for (bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]];
+ /*step 2: generate the nextcode values*/
+ for(bits = 1; bits <= tree->maxbitlen; ++bits) {
+ nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u;
+ }
+ /*step 3: generate all the codes*/
+ for (n = 0; n != tree->numcodes; ++n) {
+ if (tree->lengths[n] != 0) {
+ tree->codes[n] = nextcode[tree->lengths[n]]++;
+ /*remove superfluous bits from the code*/
+ tree->codes[n] &= ((1u << tree->lengths[n]) - 1u);
+ }
+ }
+ }
+
+ free(blcount);
+ free(nextcode);
+
+ if (!error) error = HuffmanTree_makeTable(tree);
+ return error;
+}
+
+
+/*
+ given the code lengths (as stored in the PNG file), generate the tree as defined
+ by Deflate. maxbitlen is the maximum bits that a code in the tree can have.
+ return value is error.
+*/
+static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, size_t numcodes, unsigned maxbitlen)
+{
+ unsigned i;
+ tree->lengths = (unsigned*)malloc(numcodes * sizeof(unsigned));
+ if (!tree->lengths) return 83; /*alloc fail*/
+ for (i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i];
+ tree->numcodes = (unsigned)numcodes; /*number of symbols*/
+ tree->maxbitlen = maxbitlen;
+ return HuffmanTree_makeFromLengths2(tree);
+}
+
+
+/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/
+static unsigned generateFixedLitLenTree(HuffmanTree* tree)
+{
+ unsigned i, error = 0;
+ unsigned* bitlen = (unsigned*)malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned));
+ if (!bitlen) return 83; /*alloc fail*/
+
+ /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/
+ for (i = 0; i <= 143; ++i) bitlen[i] = 8;
+ for (i = 144; i <= 255; ++i) bitlen[i] = 9;
+ for (i = 256; i <= 279; ++i) bitlen[i] = 7;
+ for (i = 280; i <= 287; ++i) bitlen[i] = 8;
+
+ error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15);
+
+ free(bitlen);
+ return error;
+}
+
+
+/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/
+static unsigned generateFixedDistanceTree(HuffmanTree* tree)
+{
+ unsigned i, error = 0;
+ unsigned* bitlen = (unsigned*)malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned));
+ if (!bitlen) return 83; /*alloc fail*/
+
+ /*there are 32 distance codes, but 30-31 are unused*/
+ for (i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5;
+ error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15);
+
+ free(bitlen);
+ return error;
+}
+
+
+/*
+ returns the code. The bit reader must already have been ensured at least 15 bits
+*/
+static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree)
+{
+ unsigned short code = peekBits(reader, FIRSTBITS);
+ unsigned short l = codetree->table_len[code];
+ unsigned short value = codetree->table_value[code];
+ if (l <= FIRSTBITS) {
+ advanceBits(reader, l);
+ return value;
+ } else {
+ unsigned index2;
+ advanceBits(reader, FIRSTBITS);
+ index2 = value + peekBits(reader, l - FIRSTBITS);
+ advanceBits(reader, codetree->table_len[index2] - FIRSTBITS);
+ return codetree->table_value[index2];
+ }
+}
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Inflator (Decompressor) / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*get the tree of a deflated block with fixed tree, as specified in the deflate specification
+Returns error code.*/
+static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d)
+{
+ unsigned error = generateFixedLitLenTree(tree_ll);
+ if (error) return error;
+ return generateFixedDistanceTree(tree_d);
+}
+
+
+/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/
+static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, LodePNGBitReader* reader)
+{
+ /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/
+ unsigned error = 0;
+ unsigned n, HLIT, HDIST, HCLEN, i;
+
+ /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/
+ unsigned* bitlen_ll = 0; /*lit,len code lengths*/
+ unsigned* bitlen_d = 0; /*dist code lengths*/
+ /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/
+ unsigned* bitlen_cl = 0;
+ HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/
+
+ if (!ensureBits17(reader, 14)) return 49; /*error: the bit pointer is or will go past the memory*/
+
+ /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/
+ HLIT = readBits(reader, 5) + 257;
+ /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/
+ HDIST = readBits(reader, 5) + 1;
+ /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/
+ HCLEN = readBits(reader, 4) + 4;
+
+ bitlen_cl = (unsigned*)malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned));
+ if(!bitlen_cl) return 83 /*alloc fail*/;
+
+ HuffmanTree_init(&tree_cl);
+
+ while (!error) {
+ /*read the code length codes out of 3 * (amount of code length codes) bits*/
+ if (lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) {
+ ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/
+ }
+ for (i = 0; i != HCLEN; ++i) {
+ ensureBits9(reader, 3); /*out of bounds already checked above */
+ bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3);
+ }
+ for (i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) {
+ bitlen_cl[CLCL_ORDER[i]] = 0;
+ }
+
+ error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7);
+ if(error) break;
+
+ /*now we can use this tree to read the lengths for the tree that this function will return*/
+ bitlen_ll = (unsigned*)malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned));
+ bitlen_d = (unsigned*)malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned));
+ if (!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/);
+ lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll));
+ lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d));
+
+ /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/
+ i = 0;
+ while (i < HLIT + HDIST) {
+ unsigned code;
+ ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/
+ code = huffmanDecodeSymbol(reader, &tree_cl);
+ if (code <= 15) /*a length code*/ {
+ if (i < HLIT) bitlen_ll[i] = code;
+ else bitlen_d[i - HLIT] = code;
+ ++i;
+ } else if (code == 16) /*repeat previous*/ {
+ unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/
+ unsigned value; /*set value to the previous code*/
+
+ if (i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/
+
+ replength += readBits(reader, 2);
+
+ if (i < HLIT + 1) value = bitlen_ll[i - 1];
+ else value = bitlen_d[i - HLIT - 1];
+ /*repeat this value in the next lengths*/
+ for (n = 0; n < replength; ++n) {
+ if (i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/
+ if (i < HLIT) bitlen_ll[i] = value;
+ else bitlen_d[i - HLIT] = value;
+ ++i;
+ }
+ } else if(code == 17) /*repeat "0" 3-10 times*/ {
+ unsigned replength = 3; /*read in the bits that indicate repeat length*/
+ replength += readBits(reader, 3);
+
+ /*repeat this value in the next lengths*/
+ for (n = 0; n < replength; ++n) {
+ if (i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/
+
+ if (i < HLIT) bitlen_ll[i] = 0;
+ else bitlen_d[i - HLIT] = 0;
+ ++i;
+ }
+ } else if(code == 18) /*repeat "0" 11-138 times*/ {
+ unsigned replength = 11; /*read in the bits that indicate repeat length*/
+ replength += readBits(reader, 7);
+
+ /*repeat this value in the next lengths*/
+ for (n = 0; n < replength; ++n) {
+ if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/
+
+ if(i < HLIT) bitlen_ll[i] = 0;
+ else bitlen_d[i - HLIT] = 0;
+ ++i;
+ }
+ } else /*if(code == INVALIDSYMBOL)*/ {
+ ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/
+ }
+ /*check if any of the ensureBits above went out of bounds*/
+ if (reader->bp > reader->bitsize) {
+ /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
+ (10=no endcode, 11=wrong jump outside of tree)*/
+ /* TODO: revise error codes 10,11,50: the above comment is no longer valid */
+ ERROR_BREAK(50); /*error, bit pointer jumps past memory*/
+ }
+ }
+ if (error) break;
+
+ if (bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/
+
+ /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/
+ error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15);
+ if (error) break;
+ error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15);
+
+ break; /*end of error-while*/
+ }
+
+ free(bitlen_cl);
+ free(bitlen_ll);
+ free(bitlen_d);
+ HuffmanTree_cleanup(&tree_cl);
+
+ return error;
+}
+
+
+/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/
+static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, unsigned btype)
+{
+ unsigned error = 0;
+ HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/
+ HuffmanTree tree_d; /*the huffman tree for distance codes*/
+
+ HuffmanTree_init(&tree_ll);
+ HuffmanTree_init(&tree_d);
+
+ if (btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d);
+ else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader);
+
+ while (!error) /*decode all symbols until end reached, breaks at end code*/ {
+ /*code_ll is literal, length or end code*/
+ unsigned code_ll;
+ ensureBits25(reader, 20); /* up to 15 for the huffman symbol, up to 5 for the length extra bits */
+ code_ll = huffmanDecodeSymbol(reader, &tree_ll);
+ if (code_ll <= 255) /*literal symbol*/ {
+ if (!ucvector_resize(out, out->size + 1)) ERROR_BREAK(83 /*alloc fail*/);
+ out->data[out->size - 1] = (unsigned char)code_ll;
+ } else if (code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ {
+ unsigned code_d, distance;
+ unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/
+ size_t start, backward, length;
+
+ /*part 1: get length base*/
+ length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX];
+
+ /*part 2: get extra bits and add the value of that to length*/
+ numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX];
+ if (numextrabits_l != 0) {
+ /* bits already ensured above */
+ length += readBits(reader, numextrabits_l);
+ }
+
+ /*part 3: get distance code*/
+ ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */
+ code_d = huffmanDecodeSymbol(reader, &tree_d);
+ if (code_d > 29) {
+ if (code_d <= 31) {
+ ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/
+ } else /* if(code_d == INVALIDSYMBOL) */{
+ ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/
+ }
+ }
+ distance = DISTANCEBASE[code_d];
+
+ /*part 4: get extra bits from distance*/
+ numextrabits_d = DISTANCEEXTRA[code_d];
+ if (numextrabits_d != 0) {
+ /* bits already ensured above */
+ distance += readBits(reader, numextrabits_d);
+ }
+
+ /*part 5: fill in all the out[n] values based on the length and dist*/
+ start = out->size;
+ if (distance > start) ERROR_BREAK(52); /*too long backward distance*/
+ backward = start - distance;
+
+ if (!ucvector_resize(out, out->size + length)) ERROR_BREAK(83 /*alloc fail*/);
+ if (distance < length) {
+ size_t forward;
+ lodepng_memcpy(out->data + start, out->data + backward, distance);
+ start += distance;
+ for (forward = distance; forward < length; ++forward) {
+ out->data[start++] = out->data[backward++];
+ }
+ } else {
+ lodepng_memcpy(out->data + start, out->data + backward, length);
+ }
+ } else if (code_ll == 256) {
+ break; /*end code, break the loop*/
+ } else /*if(code_ll == INVALIDSYMBOL)*/ {
+ ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/
+ }
+ /*check if any of the ensureBits above went out of bounds*/
+ if (reader->bp > reader->bitsize) {
+ /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
+ (10=no endcode, 11=wrong jump outside of tree)*/
+ /* TODO: revise error codes 10,11,50: the above comment is no longer valid */
+ ERROR_BREAK(51); /*error, bit pointer jumps past memory*/
+ }
+ }
+
+ HuffmanTree_cleanup(&tree_ll);
+ HuffmanTree_cleanup(&tree_d);
+
+ return error;
+}
+
+
+static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, const LodePNGDecompressSettings* settings)
+{
+ size_t bytepos;
+ size_t size = reader->size;
+ unsigned LEN, NLEN, error = 0;
+
+ /*go to first boundary of byte*/
+ bytepos = (reader->bp + 7u) >> 3u;
+
+ /*read LEN (2 bytes) and NLEN (2 bytes)*/
+ if (bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/
+ LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2;
+ NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2;
+
+ /*check if 16-bit NLEN is really the one's complement of LEN*/
+ if (!settings->ignore_nlen && LEN + NLEN != 65535) {
+ return 21; /*error: NLEN is not one's complement of LEN*/
+ }
+
+ if (!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/
+
+ /*read the literal data: LEN bytes are now stored in the out buffer*/
+ if (bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/
+
+ lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN);
+ bytepos += LEN;
+
+ reader->bp = bytepos << 3u;
+
+ return error;
+}
+
+
+static unsigned lodepng_inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings)
+{
+ unsigned BFINAL = 0;
+ LodePNGBitReader reader;
+ unsigned error = LodePNGBitReader_init(&reader, in, insize);
+
+ if (error) return error;
+
+ while (!BFINAL) {
+ unsigned BTYPE;
+ if (!ensureBits9(&reader, 3)) return 52; /*error, bit pointer will jump past memory*/
+ BFINAL = readBits(&reader, 1);
+ BTYPE = readBits(&reader, 2);
+
+ if (BTYPE == 3) return 20; /*error: invalid BTYPE*/
+ else if (BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/
+ else error = inflateHuffmanBlock(out, &reader, BTYPE); /*compression, BTYPE 01 or 10*/
+
+ if (error) return error;
+ }
+
+ return error;
+}
+
+
+static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings)
+{
+ if (settings->custom_inflate) {
+ unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings);
+ out->allocsize = out->size;
+ return error;
+ } else {
+ return lodepng_inflatev(out, in, insize, settings);
+ }
+}
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Adler32 / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len)
+{
+ unsigned s1 = adler & 0xffffu;
+ unsigned s2 = (adler >> 16u) & 0xffffu;
+
+ while (len != 0u) {
+ unsigned i;
+ /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/
+ unsigned amount = len > 5552u ? 5552u : len;
+ len -= amount;
+ for (i = 0; i != amount; ++i) {
+ s1 += (*data++);
+ s2 += s1;
+ }
+ s1 %= 65521u;
+ s2 %= 65521u;
+ }
+
+ return (s2 << 16u) | s1;
+}
+
+/*Return the adler32 of the bytes data[0..len-1]*/
+static unsigned adler32(const unsigned char* data, unsigned len)
+{
+ return update_adler32(1u, data, len);
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Zlib / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+static unsigned lodepng_zlib_decompressv(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings)
+{
+ unsigned error = 0;
+ unsigned CM, CINFO, FDICT;
+
+ if (insize < 2) return 53; /*error, size of zlib data too small*/
+ /*read information from zlib header*/
+ if ((in[0] * 256 + in[1]) % 31 != 0) {
+ /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/
+ return 24;
+ }
+
+ CM = in[0] & 15;
+ CINFO = (in[0] >> 4) & 15;
+ /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/
+ FDICT = (in[1] >> 5) & 1;
+ /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/
+
+ if (CM != 8 || CINFO > 7) {
+ /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/
+ return 25;
+ }
+ if (FDICT != 0) {
+ /*error: the specification of PNG says about the zlib stream:
+ "The additional flags shall not specify a preset dictionary."*/
+ return 26;
+ }
+
+ error = inflatev(out, in + 2, insize - 2, settings);
+ if (error) return error;
+
+ if (!settings->ignore_adler32) {
+ unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]);
+ unsigned checksum = adler32(out->data, (unsigned)(out->size));
+ if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/
+ }
+
+ return 0; /*no error*/
+}
+
+
+/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */
+static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings)
+{
+ if(settings->custom_zlib) {
+ return settings->custom_zlib(out, outsize, in, insize, settings);
+ } else {
+ unsigned error;
+ ucvector v = ucvector_init(*out, *outsize);
+ if (expected_size) {
+ /*reserve the memory to avoid intermediate reallocations*/
+ ucvector_resize(&v, *outsize + expected_size);
+ v.size = *outsize;
+ }
+ error = lodepng_zlib_decompressv(&v, in, insize, settings);
+ *out = v.data;
+ *outsize = v.size;
+ return error;
+ }
+}
+
+
+static void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings)
+{
+ settings->ignore_adler32 = 0;
+ settings->ignore_nlen = 0;
+ settings->custom_zlib = 0;
+ settings->custom_inflate = 0;
+ settings->custom_context = 0;
+}
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* // End of Zlib related code. Begin of PNG related code. // */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+
+#if 0 //thorvg don't use crc
+/* CRC polynomial: 0xedb88320 */
+static unsigned lodepng_crc32_table[256] = {
+ 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u,
+ 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u,
+ 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u,
+ 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u,
+ 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u,
+ 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u,
+ 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u,
+ 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u,
+ 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u,
+ 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u,
+ 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u,
+ 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u,
+ 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u,
+ 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u,
+ 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u,
+ 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u,
+ 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u,
+ 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u,
+ 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u,
+ 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u,
+ 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u,
+ 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u,
+ 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u,
+ 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u,
+ 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u,
+ 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u,
+ 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u,
+ 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u,
+ 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u,
+ 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u,
+ 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u,
+ 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u
+};
+
+
+/* Calculate CRC32 of buffer
+ Return the CRC of the bytes buf[0..len-1]. */
+static unsigned lodepng_crc32(const unsigned char* data, size_t length)
+{
+ unsigned r = 0xffffffffu;
+ size_t i;
+ for (i = 0; i < length; ++i) {
+ r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u);
+ }
+ return r ^ 0xffffffffu;
+}
+#endif
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Reading and writing PNG color channel bits / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first,
+so LodePNGBitWriter and LodePNGBitReader can't be used for those. */
+
+static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream)
+{
+ unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1);
+ ++(*bitpointer);
+ return result;
+}
+
+
+/* TODO: make this faster */
+static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits)
+{
+ unsigned result = 0;
+ size_t i;
+ for (i = 0 ; i < nbits; ++i) {
+ result <<= 1u;
+ result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream);
+ }
+ return result;
+}
+
+
+static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit)
+{
+ /*the current bit in bitstream may be 0 or 1 for this to work*/
+ if (bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u))));
+ else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u)));
+ ++(*bitpointer);
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / PNG chunks / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*
+ The lodepng_chunk functions are normally not needed, except to traverse the
+ unknown chunks stored in the LodePNGInfo struct, or add new ones to it.
+ It also allows traversing the chunks of an encoded PNG file yourself.
+
+ The chunk pointer always points to the beginning of the chunk itself, that is
+ the first byte of the 4 length bytes.
+
+ In the PNG file format, chunks have the following format:
+ -4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer)
+ -4 bytes chunk type (ASCII a-z,A-Z only, see below)
+ -length bytes of data (may be 0 bytes if length was 0)
+ -4 bytes of CRC, computed on chunk name + data
+
+ The first chunk starts at the 8th byte of the PNG file, the entire rest of the file
+ exists out of concatenated chunks with the above format.
+
+ PNG standard chunk ASCII naming conventions:
+ -First byte: uppercase = critical, lowercase = ancillary
+ -Second byte: uppercase = public, lowercase = private
+ -Third byte: must be uppercase
+ -Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy
+*/
+
+
+/*
+ Gets the length of the data of the chunk. Total chunk length has 12 bytes more.
+ There must be at least 4 bytes to read from. If the result value is too large,
+ it may be corrupt data.
+*/
+static unsigned lodepng_chunk_length(const unsigned char* chunk)
+{
+ return lodepng_read32bitInt(&chunk[0]);
+}
+
+
+/* check if the type is the given type */
+static unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type)
+{
+ if (lodepng_strlen(type) != 4) return 0;
+ return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]);
+}
+
+
+/* 0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard) */
+static unsigned char lodepng_chunk_ancillary(const unsigned char* chunk)
+{
+ return ((chunk[4] & 32) != 0);
+}
+
+
+static const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk)
+{
+ return &chunk[8];
+}
+
+#if 0 //thorvg don't use crc
+/* returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!) */
+static unsigned lodepng_chunk_check_crc(const unsigned char* chunk)
+{
+ unsigned length = lodepng_chunk_length(chunk);
+ unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]);
+ /*the CRC is taken of the data and the 4 chunk type letters, not the length*/
+ unsigned checksum = lodepng_crc32(&chunk[4], length + 4);
+ if (CRC != checksum) return 1;
+ else return 0;
+}
+#endif
+
+static const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end)
+{
+ if (chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/
+ if (chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47
+ && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) {
+ /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */
+ return chunk + 8;
+ } else {
+ size_t total_chunk_length;
+ const unsigned char* result;
+ if (lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end;
+ result = chunk + total_chunk_length;
+ if (result < chunk) return end; /*pointer overflow*/
+ return result;
+ }
+}
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Color types, channels, bits / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype.
+Return value is a LodePNG error code.*/
+static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd)
+{
+ switch(colortype) {
+ case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break;
+ case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break;
+ case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break;
+ case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break;
+ case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break;
+ case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */
+ default: return 31; /* invalid color type */
+ }
+ return 0; /*allowed color type / bits combination*/
+}
+
+
+static unsigned getNumColorChannels(LodePNGColorType colortype)
+{
+ switch(colortype) {
+ case LCT_GREY: return 1;
+ case LCT_RGB: return 3;
+ case LCT_PALETTE: return 1;
+ case LCT_GREY_ALPHA: return 2;
+ case LCT_RGBA: return 4;
+ case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */
+ default: return 0; /*invalid color type*/
+ }
+}
+
+
+static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth)
+{
+ /*bits per pixel is amount of channels * bits per channel*/
+ return getNumColorChannels(colortype) * bitdepth;
+}
+
+
+static void lodepng_color_mode_init(LodePNGColorMode* info)
+{
+ info->key_defined = 0;
+ info->key_r = info->key_g = info->key_b = 0;
+ info->colortype = LCT_RGBA;
+ info->bitdepth = 8;
+ info->palette = 0;
+ info->palettesize = 0;
+}
+
+
+/*allocates palette memory if needed, and initializes all colors to black*/
+static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info)
+{
+ size_t i;
+ /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/
+ /*the palette must have room for up to 256 colors with 4 bytes each.*/
+ if (!info->palette) info->palette = (unsigned char*)malloc(1024);
+ if (!info->palette) return; /*alloc fail*/
+ for (i = 0; i != 256; ++i) {
+ /*Initialize all unused colors with black, the value used for invalid palette indices.
+ This is an error according to the PNG spec, but common PNG decoders make it black instead.
+ That makes color conversion slightly faster due to no error handling needed.*/
+ info->palette[i * 4 + 0] = 0;
+ info->palette[i * 4 + 1] = 0;
+ info->palette[i * 4 + 2] = 0;
+ info->palette[i * 4 + 3] = 255;
+ }
+}
+
+static void lodepng_palette_clear(LodePNGColorMode* info)
+{
+ if (info->palette) free(info->palette);
+ info->palette = 0;
+ info->palettesize = 0;
+}
+
+
+static void lodepng_color_mode_cleanup(LodePNGColorMode* info)
+{
+ lodepng_palette_clear(info);
+}
+
+
+/*return value is error code (0 means no error)*/
+static unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source)
+{
+ lodepng_color_mode_cleanup(dest);
+ lodepng_memcpy(dest, source, sizeof(LodePNGColorMode));
+ if (source->palette) {
+ dest->palette = (unsigned char*)malloc(1024);
+ if (!dest->palette && source->palettesize) return 83; /*alloc fail*/
+ lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4);
+ }
+ return 0;
+}
+
+
+static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b)
+{
+ size_t i;
+ if (a->colortype != b->colortype) return 0;
+ if (a->bitdepth != b->bitdepth) return 0;
+ if (a->key_defined != b->key_defined) return 0;
+ if (a->key_defined) {
+ if(a->key_r != b->key_r) return 0;
+ if(a->key_g != b->key_g) return 0;
+ if(a->key_b != b->key_b) return 0;
+ }
+ if (a->palettesize != b->palettesize) return 0;
+ for (i = 0; i != a->palettesize * 4; ++i) {
+ if (a->palette[i] != b->palette[i]) return 0;
+ }
+ return 1;
+}
+
+
+static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth)
+{
+ size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth);
+ size_t n = (size_t)w * (size_t)h;
+ return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u;
+}
+
+
+/* Returns the byte size of a raw image buffer with given width, height and color mode */
+static size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color)
+{
+ return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth);
+}
+
+
+/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer,
+and in addition has one extra byte per line: the filter byte. So this gives a larger
+result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */
+static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp)
+{
+ /* + 1 for the filter byte, and possibly plus padding bits per line. */
+ /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */
+ size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u;
+ return (size_t)h * line;
+}
+
+
+/* Safely checks whether size_t overflow can be caused due to amount of pixels.
+ This check is overcautious rather than precise. If this check indicates no overflow,
+ you can safely compute in a size_t (but not an unsigned):
+ -(size_t)w * (size_t)h * 8
+ -amount of bytes in IDAT (including filter, padding and Adam7 bytes)
+ -amount of bytes in raw color model
+ Returns 1 if overflow possible, 0 if not. */
+static int lodepng_pixel_overflow(unsigned w, unsigned h, const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor)
+{
+ size_t bpp = LODEPNG_MAX(lodepng_get_bpp_lct(pngcolor->colortype, pngcolor->bitdepth), lodepng_get_bpp_lct(rawcolor->colortype, rawcolor->bitdepth));
+ size_t numpixels, total;
+ size_t line; /* bytes per line in worst case */
+
+ if (lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1;
+ if (lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */
+
+ /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */
+ if (lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1;
+ if (lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1;
+
+ if (lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */
+ if (lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */
+
+ return 0; /* no overflow */
+}
+
+
+static void lodepng_info_init(LodePNGInfo* info)
+{
+ lodepng_color_mode_init(&info->color);
+ info->interlace_method = 0;
+ info->compression_method = 0;
+ info->filter_method = 0;
+}
+
+
+static void lodepng_info_cleanup(LodePNGInfo* info)
+{
+ lodepng_color_mode_cleanup(&info->color);
+}
+
+
+/* index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to */
+static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in)
+{
+ unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/
+ /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/
+ unsigned p = index & m;
+ in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/
+ in = in << (bits * (m - p));
+ if(p == 0) out[index * bits / 8u] = in;
+ else out[index * bits / 8u] |= in;
+}
+
+/*
+ One node of a color tree
+ This is the data structure used to count the number of unique colors and to get a palette
+ index for a color. It's like an octree, but because the alpha channel is used too, each
+ node has 16 instead of 8 children.
+*/
+struct ColorTree
+{
+ ColorTree* children[16]; /* up to 16 pointers to ColorTree of next level */
+ int index; /* the payload. Only has a meaningful value if this is in the last level */
+};
+
+static void color_tree_init(ColorTree* tree)
+{
+ lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children));
+ tree->index = -1;
+}
+
+static void color_tree_cleanup(ColorTree* tree)
+{
+ int i;
+ for (i = 0; i != 16; ++i) {
+ if(tree->children[i]) {
+ color_tree_cleanup(tree->children[i]);
+ free(tree->children[i]);
+ }
+ }
+}
+
+
+/* returns -1 if color not present, its index otherwise */
+static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ int bit = 0;
+ for (bit = 0; bit < 8; ++bit) {
+ int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1);
+ if (!tree->children[i]) return -1;
+ else tree = tree->children[i];
+ }
+ return tree ? tree->index : -1;
+}
+
+
+/* color is not allowed to already exist.
+ Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")
+ Returns error code, or 0 if ok */
+static unsigned color_tree_add(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index)
+{
+ int bit;
+ for (bit = 0; bit < 8; ++bit) {
+ int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1);
+ if (!tree->children[i]) {
+ tree->children[i] = (ColorTree*)malloc(sizeof(ColorTree));
+ if (!tree->children[i]) return 83; /*alloc fail*/
+ color_tree_init(tree->children[i]);
+ }
+ tree = tree->children[i];
+ }
+ tree->index = (int)index;
+ return 0;
+}
+
+/* put a pixel, given its RGBA color, into image of any color type */
+static unsigned rgba8ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ if (mode->colortype == LCT_GREY) {
+ unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/
+ if (mode->bitdepth == 8) out[i] = gray;
+ else if (mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray;
+ else {
+ /*take the most significant bits of gray*/
+ gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u);
+ addColorBits(out, i, mode->bitdepth, gray);
+ }
+ } else if (mode->colortype == LCT_RGB) {
+ if (mode->bitdepth == 8) {
+ out[i * 3 + 0] = r;
+ out[i * 3 + 1] = g;
+ out[i * 3 + 2] = b;
+ } else {
+ out[i * 6 + 0] = out[i * 6 + 1] = r;
+ out[i * 6 + 2] = out[i * 6 + 3] = g;
+ out[i * 6 + 4] = out[i * 6 + 5] = b;
+ }
+ } else if(mode->colortype == LCT_PALETTE) {
+ int index = color_tree_get(tree, r, g, b, a);
+ if (index < 0) return 82; /*color not in palette*/
+ if (mode->bitdepth == 8) out[i] = index;
+ else addColorBits(out, i, mode->bitdepth, (unsigned)index);
+ } else if (mode->colortype == LCT_GREY_ALPHA) {
+ unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/
+ if (mode->bitdepth == 8) {
+ out[i * 2 + 0] = gray;
+ out[i * 2 + 1] = a;
+ } else if (mode->bitdepth == 16) {
+ out[i * 4 + 0] = out[i * 4 + 1] = gray;
+ out[i * 4 + 2] = out[i * 4 + 3] = a;
+ }
+ } else if (mode->colortype == LCT_RGBA) {
+ if (mode->bitdepth == 8) {
+ out[i * 4 + 0] = r;
+ out[i * 4 + 1] = g;
+ out[i * 4 + 2] = b;
+ out[i * 4 + 3] = a;
+ } else {
+ out[i * 8 + 0] = out[i * 8 + 1] = r;
+ out[i * 8 + 2] = out[i * 8 + 3] = g;
+ out[i * 8 + 4] = out[i * 8 + 5] = b;
+ out[i * 8 + 6] = out[i * 8 + 7] = a;
+ }
+ }
+ return 0; /*no error*/
+}
+
+
+/* put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type */
+static void rgba16ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, unsigned short r, unsigned short g, unsigned short b, unsigned short a)
+{
+ if (mode->colortype == LCT_GREY) {
+ unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/
+ out[i * 2 + 0] = (gray >> 8) & 255;
+ out[i * 2 + 1] = gray & 255;
+ } else if (mode->colortype == LCT_RGB) {
+ out[i * 6 + 0] = (r >> 8) & 255;
+ out[i * 6 + 1] = r & 255;
+ out[i * 6 + 2] = (g >> 8) & 255;
+ out[i * 6 + 3] = g & 255;
+ out[i * 6 + 4] = (b >> 8) & 255;
+ out[i * 6 + 5] = b & 255;
+ } else if (mode->colortype == LCT_GREY_ALPHA) {
+ unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/
+ out[i * 4 + 0] = (gray >> 8) & 255;
+ out[i * 4 + 1] = gray & 255;
+ out[i * 4 + 2] = (a >> 8) & 255;
+ out[i * 4 + 3] = a & 255;
+ } else if (mode->colortype == LCT_RGBA) {
+ out[i * 8 + 0] = (r >> 8) & 255;
+ out[i * 8 + 1] = r & 255;
+ out[i * 8 + 2] = (g >> 8) & 255;
+ out[i * 8 + 3] = g & 255;
+ out[i * 8 + 4] = (b >> 8) & 255;
+ out[i * 8 + 5] = b & 255;
+ out[i * 8 + 6] = (a >> 8) & 255;
+ out[i * 8 + 7] = a & 255;
+ }
+}
+
+
+/* Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type. */
+static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode)
+{
+ if (mode->colortype == LCT_GREY) {
+ if (mode->bitdepth == 8) {
+ *r = *g = *b = in[i];
+ if (mode->key_defined && *r == mode->key_r) *a = 0;
+ else *a = 255;
+ } else if (mode->bitdepth == 16) {
+ *r = *g = *b = in[i * 2 + 0];
+ if (mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0;
+ else *a = 255;
+ } else {
+ unsigned highest = ((1U << mode->bitdepth) - 1U); /* highest possible value for this bit depth */
+ size_t j = i * mode->bitdepth;
+ unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth);
+ *r = *g = *b = (value * 255) / highest;
+ if (mode->key_defined && value == mode->key_r) *a = 0;
+ else *a = 255;
+ }
+ } else if (mode->colortype == LCT_RGB) {
+ if (mode->bitdepth == 8) {
+ *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2];
+ if (mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0;
+ else *a = 255;
+ } else {
+ *r = in[i * 6 + 0];
+ *g = in[i * 6 + 2];
+ *b = in[i * 6 + 4];
+ if (mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r
+ && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g
+ && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0;
+ else *a = 255;
+ }
+ } else if (mode->colortype == LCT_PALETTE) {
+ unsigned index;
+ if (mode->bitdepth == 8) index = in[i];
+ else {
+ size_t j = i * mode->bitdepth;
+ index = readBitsFromReversedStream(&j, in, mode->bitdepth);
+ }
+ /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */
+ *r = mode->palette[index * 4 + 0];
+ *g = mode->palette[index * 4 + 1];
+ *b = mode->palette[index * 4 + 2];
+ *a = mode->palette[index * 4 + 3];
+ } else if (mode->colortype == LCT_GREY_ALPHA) {
+ if (mode->bitdepth == 8) {
+ *r = *g = *b = in[i * 2 + 0];
+ *a = in[i * 2 + 1];
+ } else {
+ *r = *g = *b = in[i * 4 + 0];
+ *a = in[i * 4 + 2];
+ }
+ } else if (mode->colortype == LCT_RGBA) {
+ if (mode->bitdepth == 8) {
+ *r = in[i * 4 + 0];
+ *g = in[i * 4 + 1];
+ *b = in[i * 4 + 2];
+ *a = in[i * 4 + 3];
+ } else {
+ *r = in[i * 8 + 0];
+ *g = in[i * 8 + 2];
+ *b = in[i * 8 + 4];
+ *a = in[i * 8 + 6];
+ }
+ }
+}
+
+
+/* Similar to getPixelColorRGBA8, but with all the for loops inside of the color
+ mode test cases, optimized to convert the colors much faster, when converting
+ to the common case of RGBA with 8 bit per channel. buffer must be RGBA with
+ enough memory.*/
+static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode)
+{
+ unsigned num_channels = 4;
+ size_t i;
+ if (mode->colortype == LCT_GREY) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i];
+ buffer[3] = 255;
+ }
+ if (mode->key_defined) {
+ buffer -= numpixels * num_channels;
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ if(buffer[0] == mode->key_r) buffer[3] = 0;
+ }
+ }
+ } else if (mode->bitdepth == 16) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i * 2];
+ buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255;
+ }
+ } else {
+ unsigned highest = ((1U << mode->bitdepth) - 1U); /* highest possible value for this bit depth */
+ size_t j = 0;
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth);
+ buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest;
+ buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255;
+ }
+ }
+ } else if (mode->colortype == LCT_RGB) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ //lodepng_memcpy(buffer, &in[i * 3], 3);
+ //Convert colortype to LCT_BGR?
+ buffer[0] = in[i * 3 + 2];
+ buffer[1] = in[i * 3 + 1];
+ buffer[2] = in[i * 3 + 0];
+ buffer[3] = 255;
+ }
+ if (mode->key_defined) {
+ buffer -= numpixels * num_channels;
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ if (buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0;
+ }
+ }
+ } else {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = in[i * 6 + 0];
+ buffer[1] = in[i * 6 + 2];
+ buffer[2] = in[i * 6 + 4];
+ buffer[3] = mode->key_defined
+ && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r
+ && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g
+ && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255;
+ }
+ }
+ } else if (mode->colortype == LCT_PALETTE) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ unsigned index = in[i];
+ /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */
+ lodepng_memcpy(buffer, &mode->palette[index * 4], 4);
+ }
+ } else {
+ size_t j = 0;
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth);
+ /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */
+ lodepng_memcpy(buffer, &mode->palette[index * 4], 4);
+ }
+ }
+ } else if (mode->colortype == LCT_GREY_ALPHA) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0];
+ buffer[3] = in[i * 2 + 1];
+ }
+ } else {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0];
+ buffer[3] = in[i * 4 + 2];
+ }
+ }
+ } else if (mode->colortype == LCT_RGBA) {
+ if (mode->bitdepth == 8) {
+ lodepng_memcpy(buffer, in, numpixels * 4);
+ } else {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = in[i * 8 + 0];
+ buffer[1] = in[i * 8 + 2];
+ buffer[2] = in[i * 8 + 4];
+ buffer[3] = in[i * 8 + 6];
+ }
+ }
+ }
+}
+
+
+/* Similar to getPixelColorsRGBA8, but with 3-channel RGB output. */
+static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode)
+{
+ const unsigned num_channels = 3;
+ size_t i;
+ if (mode->colortype == LCT_GREY) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i];
+ }
+ } else if (mode->bitdepth == 16) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i * 2];
+ }
+ } else {
+ unsigned highest = ((1U << mode->bitdepth) - 1U); /* highest possible value for this bit depth */
+ size_t j = 0;
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth);
+ buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest;
+ }
+ }
+ } else if (mode->colortype == LCT_RGB) {
+ if (mode->bitdepth == 8) {
+ lodepng_memcpy(buffer, in, numpixels * 3);
+ } else {
+ for(i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = in[i * 6 + 0];
+ buffer[1] = in[i * 6 + 2];
+ buffer[2] = in[i * 6 + 4];
+ }
+ }
+ } else if (mode->colortype == LCT_PALETTE) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ unsigned index = in[i];
+ /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */
+ lodepng_memcpy(buffer, &mode->palette[index * 4], 3);
+ }
+ } else {
+ size_t j = 0;
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth);
+ /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */
+ lodepng_memcpy(buffer, &mode->palette[index * 4], 3);
+ }
+ }
+ } else if (mode->colortype == LCT_GREY_ALPHA) {
+ if (mode->bitdepth == 8) {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0];
+ }
+ } else {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0];
+ }
+ }
+ } else if (mode->colortype == LCT_RGBA) {
+ if (mode->bitdepth == 8) {
+ for(i = 0; i != numpixels; ++i, buffer += num_channels) {
+ lodepng_memcpy(buffer, &in[i * 4], 3);
+ }
+ } else {
+ for (i = 0; i != numpixels; ++i, buffer += num_channels) {
+ buffer[0] = in[i * 8 + 0];
+ buffer[1] = in[i * 8 + 2];
+ buffer[2] = in[i * 8 + 4];
+ }
+ }
+ }
+}
+
+
+/* Get RGBA16 color of pixel with index i (y * width + x) from the raw image with
+ given color type, but the given color type must be 16-bit itself. */
+static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode)
+{
+ if (mode->colortype == LCT_GREY) {
+ *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1];
+ if (mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0;
+ else *a = 65535;
+ } else if (mode->colortype == LCT_RGB) {
+ *r = 256u * in[i * 6 + 0] + in[i * 6 + 1];
+ *g = 256u * in[i * 6 + 2] + in[i * 6 + 3];
+ *b = 256u * in[i * 6 + 4] + in[i * 6 + 5];
+ if (mode->key_defined
+ && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r
+ && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g
+ && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0;
+ else *a = 65535;
+ } else if (mode->colortype == LCT_GREY_ALPHA) {
+ *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1];
+ *a = 256u * in[i * 4 + 2] + in[i * 4 + 3];
+ } else if (mode->colortype == LCT_RGBA) {
+ *r = 256u * in[i * 8 + 0] + in[i * 8 + 1];
+ *g = 256u * in[i * 8 + 2] + in[i * 8 + 3];
+ *b = 256u * in[i * 8 + 4] + in[i * 8 + 5];
+ *a = 256u * in[i * 8 + 6] + in[i * 8 + 7];
+ }
+}
+
+/*
+ Converts raw buffer from one color type to another color type, based on
+ LodePNGColorMode structs to describe the input and output color type.
+ See the reference manual at the end of this header file to see which color conversions are supported.
+ return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported)
+ The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel
+ of the output color type (lodepng_get_bpp).
+ For < 8 bpp images, there should not be padding bits at the end of scanlines.
+ For 16-bit per channel colors, uses big endian format like PNG does.
+ Return value is LodePNG error code
+*/
+static unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h)
+{
+ size_t i;
+ ColorTree tree;
+ size_t numpixels = (size_t)w * (size_t)h;
+ unsigned error = 0;
+
+ if (mode_in->colortype == LCT_PALETTE && !mode_in->palette) {
+ return 107; /* error: must provide palette if input mode is palette */
+ }
+
+ if (lodepng_color_mode_equal(mode_out, mode_in)) {
+ size_t numbytes = lodepng_get_raw_size(w, h, mode_in);
+ lodepng_memcpy(out, in, numbytes);
+ return 0;
+ }
+
+ if (mode_out->colortype == LCT_PALETTE) {
+ size_t palettesize = mode_out->palettesize;
+ const unsigned char* palette = mode_out->palette;
+ size_t palsize = (size_t)1u << mode_out->bitdepth;
+ /* if the user specified output palette but did not give the values, assume
+ they want the values of the input color type (assuming that one is palette).
+ Note that we never create a new palette ourselves.*/
+ if (palettesize == 0) {
+ palettesize = mode_in->palettesize;
+ palette = mode_in->palette;
+ /* if the input was also palette with same bitdepth, then the color types are also
+ equal, so copy literally. This to preserve the exact indices that were in the PNG
+ even in case there are duplicate colors in the palette.*/
+ if (mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) {
+ size_t numbytes = lodepng_get_raw_size(w, h, mode_in);
+ lodepng_memcpy(out, in, numbytes);
+ return 0;
+ }
+ }
+ if (palettesize < palsize) palsize = palettesize;
+ color_tree_init(&tree);
+ for (i = 0; i != palsize; ++i) {
+ const unsigned char* p = &palette[i * 4];
+ error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i);
+ if (error) break;
+ }
+ }
+
+ if (!error) {
+ if (mode_in->bitdepth == 16 && mode_out->bitdepth == 16) {
+ for (i = 0; i != numpixels; ++i) {
+ unsigned short r = 0, g = 0, b = 0, a = 0;
+ getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in);
+ rgba16ToPixel(out, i, mode_out, r, g, b, a);
+ }
+ } else if (mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) {
+ getPixelColorsRGBA8(out, numpixels, in, mode_in);
+ } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) {
+ getPixelColorsRGB8(out, numpixels, in, mode_in);
+ } else {
+ unsigned char r = 0, g = 0, b = 0, a = 0;
+ for (i = 0; i != numpixels; ++i) {
+ getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in);
+ error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a);
+ if (error) break;
+ }
+ }
+ }
+
+ if (mode_out->colortype == LCT_PALETTE) {
+ color_tree_cleanup(&tree);
+ }
+
+ return error;
+}
+
+
+/* Paeth predictor, used by PNG filter type 4
+ The parameters are of type short, but should come from unsigned chars, the shorts
+ are only needed to make the paeth calculation correct.
+*/
+static unsigned char paethPredictor(short a, short b, short c)
+{
+ short pa = LODEPNG_ABS(b - c);
+ short pb = LODEPNG_ABS(a - c);
+ short pc = LODEPNG_ABS(a + b - c - c);
+ /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */
+ if (pb < pa) { a = b; pa = pb; }
+ return (pc < pa) ? c : a;
+}
+
+
+/*shared values used by multiple Adam7 related functions*/
+static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/
+static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/
+static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/
+static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/
+
+/* Outputs various dimensions and positions in the image related to the Adam7 reduced images.
+ passw: output containing the width of the 7 passes
+ passh: output containing the height of the 7 passes
+ filter_passstart: output containing the index of the start and end of each
+ reduced image with filter bytes
+ padded_passstart output containing the index of the start and end of each
+ reduced image when without filter bytes but with padded scanlines
+ passstart: output containing the index of the start and end of each reduced
+ image without padding between scanlines, but still padding between the images
+ w, h: width and height of non-interlaced image
+ bpp: bits per pixel
+ "padded" is only relevant if bpp is less than 8 and a scanline or image does not
+ end at a full byte */
+static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp)
+{
+ /* the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass */
+ unsigned i;
+
+ /* calculate width and height in pixels of each pass */
+ for (i = 0; i != 7; ++i) {
+ passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i];
+ passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i];
+ if(passw[i] == 0) passh[i] = 0;
+ if(passh[i] == 0) passw[i] = 0;
+ }
+
+ filter_passstart[0] = padded_passstart[0] = passstart[0] = 0;
+ for (i = 0; i != 7; ++i) {
+ /* if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte) */
+ filter_passstart[i + 1] = filter_passstart[i]
+ + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0);
+ /* bits padded if needed to fill full byte at end of each scanline */
+ padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u);
+ /* only padded at end of reduced image */
+ passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u;
+ }
+}
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / PNG Decoder / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned char filterType, size_t length)
+{
+ /* For PNG filter method 0
+ unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte,
+ the filter works byte per byte (bytewidth = 1)
+ precon is the previous unfiltered scanline, recon the result, scanline the current one
+ the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead
+ recon and scanline MAY be the same memory address! precon must be disjoint. */
+
+ size_t i;
+ switch (filterType) {
+ case 0: {
+ if (bytewidth == 4) {
+ for (i = 0; i < length; i += 4) {
+ //RGBA -> BGRA
+ recon[i + 0] = scanline[i + 2];
+ recon[i + 1] = scanline[i + 1];
+ recon[i + 2] = scanline[i + 0];
+ recon[i + 3] = scanline[i + 3];
+ }
+ } else {
+ for (i = 0; i != length; ++i) recon[i] = scanline[i];
+ }
+ break;
+ }
+ case 1: {
+ for (i = 0; i != bytewidth; ++i) recon[i] = scanline[i];
+ for (i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth];
+ break;
+ }
+ case 2: {
+ if (precon) {
+ for (i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i];
+ } else {
+ for (i = 0; i != length; ++i) recon[i] = scanline[i];
+ }
+ break;
+ }
+ case 3: {
+ if (precon) {
+ for (i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u);
+ for (i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1u);
+ } else {
+ for (i = 0; i != bytewidth; ++i) recon[i] = scanline[i];
+ for (i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1u);
+ }
+ break;
+ }
+ case 4: {
+ if (precon) {
+ for (i = 0; i != bytewidth; ++i) {
+ recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/
+ }
+
+ /* Unroll independent paths of the paeth predictor. A 6x and 8x version would also be possible but that
+ adds too much code. Whether this actually speeds anything up at all depends on compiler and settings. */
+ if (bytewidth >= 4) {
+ for (; i + 3 < length; i += 4) {
+ size_t j = i - bytewidth;
+ unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3];
+ unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3];
+ unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3];
+ unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3];
+ recon[i + 0] = s0 + paethPredictor(r0, p0, q0);
+ recon[i + 1] = s1 + paethPredictor(r1, p1, q1);
+ recon[i + 2] = s2 + paethPredictor(r2, p2, q2);
+ recon[i + 3] = s3 + paethPredictor(r3, p3, q3);
+ }
+ } else if (bytewidth >= 3) {
+ for (; i + 2 < length; i += 3) {
+ size_t j = i - bytewidth;
+ unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2];
+ unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2];
+ unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2];
+ unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2];
+ recon[i + 0] = s0 + paethPredictor(r0, p0, q0);
+ recon[i + 1] = s1 + paethPredictor(r1, p1, q1);
+ recon[i + 2] = s2 + paethPredictor(r2, p2, q2);
+ }
+ } else if (bytewidth >= 2) {
+ for (; i + 1 < length; i += 2) {
+ size_t j = i - bytewidth;
+ unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1];
+ unsigned char r0 = recon[j + 0], r1 = recon[j + 1];
+ unsigned char p0 = precon[i + 0], p1 = precon[i + 1];
+ unsigned char q0 = precon[j + 0], q1 = precon[j + 1];
+ recon[i + 0] = s0 + paethPredictor(r0, p0, q0);
+ recon[i + 1] = s1 + paethPredictor(r1, p1, q1);
+ }
+ }
+
+ for (; i != length; ++i) {
+ recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]));
+ }
+ } else {
+ for (i = 0; i != bytewidth; ++i) {
+ recon[i] = scanline[i];
+ }
+ for (i = bytewidth; i < length; ++i) {
+ /* paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth] */
+ recon[i] = (scanline[i] + recon[i - bytewidth]);
+ }
+ }
+ break;
+ }
+ default: return 36; /* error: invalid filter type given */
+ }
+ return 0;
+}
+
+
+static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp)
+{
+ /* For PNG filter method 0
+ this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times)
+ out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline
+ w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel
+ in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) */
+
+ unsigned y;
+ unsigned char* prevline = 0;
+
+ /* bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise */
+ size_t bytewidth = (bpp + 7u) / 8u;
+ /* the width of a scanline in bytes, not including the filter type */
+ size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u;
+
+ for (y = 0; y < h; ++y) {
+ size_t outindex = linebytes * y;
+ size_t inindex = (1 + linebytes) * y; /* the extra filterbyte added to each row */
+ unsigned char filterType = in[inindex];
+ CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes));
+ prevline = &out[outindex];
+ }
+
+ return 0;
+}
+
+/* in: Adam7 interlaced image, with no padding bits between scanlines, but between
+ reduced images so that each reduced image starts at a byte.
+ out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h
+ bpp: bits per pixel
+ out has the following size in bits: w * h * bpp.
+ in is possibly bigger due to padding bits between reduced images.
+ out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation
+ (because that's likely a little bit faster)
+ NOTE: comments about padding bits are only relevant if bpp < 8 */
+static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp)
+{
+ unsigned passw[7], passh[7];
+ size_t filter_passstart[8], padded_passstart[8], passstart[8];
+ unsigned i;
+
+ Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
+
+ if (bpp >= 8) {
+ for(i = 0; i != 7; ++i) {
+ unsigned x, y, b;
+ size_t bytewidth = bpp / 8u;
+ for (y = 0; y < passh[i]; ++y)
+ for (x = 0; x < passw[i]; ++x) {
+ size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth;
+ size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth;
+ for (b = 0; b < bytewidth; ++b) {
+ out[pixeloutstart + b] = in[pixelinstart + b];
+ }
+ }
+ }
+ } else /* bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers */ {
+ for (i = 0; i != 7; ++i) {
+ unsigned x, y, b;
+ unsigned ilinebits = bpp * passw[i];
+ unsigned olinebits = bpp * w;
+ size_t obp, ibp; /* bit pointers (for out and in buffer) */
+ for (y = 0; y < passh[i]; ++y)
+ for (x = 0; x < passw[i]; ++x) {
+ ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp);
+ obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp;
+ for (b = 0; b < bpp; ++b) {
+ unsigned char bit = readBitFromReversedStream(&ibp, in);
+ setBitOfReversedStream(&obp, out, bit);
+ }
+ }
+ }
+ }
+}
+
+
+static void removePaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h)
+{
+ /* After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need
+ to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers
+ for the Adam7 code, the color convert code and the output to the user.
+ in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must
+ have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits
+ also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7
+ only useful if (ilinebits - olinebits) is a value in the range 1..7 */
+ unsigned y;
+ size_t diff = ilinebits - olinebits;
+ size_t ibp = 0, obp = 0; /*input and output bit pointers*/
+ for (y = 0; y < h; ++y) {
+ size_t x;
+ for (x = 0; x < olinebits; ++x) {
+ unsigned char bit = readBitFromReversedStream(&ibp, in);
+ setBitOfReversedStream(&obp, out, bit);
+ }
+ ibp += diff;
+ }
+}
+
+
+/* out must be buffer big enough to contain full image, and in must contain the full decompressed data from
+ the IDAT chunks (with filter index bytes and possible padding bits)
+ return value is error */
+static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, unsigned w, unsigned h, const LodePNGInfo* info_png)
+{
+ /* This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype.
+ Steps:
+ *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8)
+ *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace
+ NOTE: the in buffer will be overwritten with intermediate data! */
+ unsigned bpp = lodepng_get_bpp_lct(info_png->color.colortype, info_png->color.bitdepth);
+ if (bpp == 0) return 31; /* error: invalid colortype */
+
+ if (info_png->interlace_method == 0) {
+ if (bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) {
+ CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp));
+ removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h);
+ }
+ /* we can immediately filter into the out buffer, no other steps needed */
+ else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp));
+ } else /* interlace_method is 1 (Adam7) */ {
+ unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8];
+ unsigned i;
+
+ Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
+
+ for (i = 0; i != 7; ++i) {
+ CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp));
+ /* TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline,
+ move bytes instead of bits or move not at all */
+ if (bpp < 8) {
+ /* remove padding bits in scanlines; after this there still may be padding
+ bits between the different reduced images: each reduced image still starts nicely at a byte */
+ removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]);
+ }
+ }
+ Adam7_deinterlace(out, in, w, h, bpp);
+ }
+ return 0;
+}
+
+
+static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength)
+{
+ unsigned pos = 0, i;
+ color->palettesize = chunkLength / 3u;
+ if (color->palettesize == 0 || color->palettesize > 256) return 38; /* error: palette too small or big */
+ lodepng_color_mode_alloc_palette(color);
+ if (!color->palette && color->palettesize) {
+ color->palettesize = 0;
+ return 83; /* alloc fail */
+ }
+
+ for (i = 0; i != color->palettesize; ++i) {
+ color->palette[4 * i + 0] = data[pos++]; /*R*/
+ color->palette[4 * i + 1] = data[pos++]; /*G*/
+ color->palette[4 * i + 2] = data[pos++]; /*B*/
+ color->palette[4 * i + 3] = 255; /*alpha*/
+ }
+
+ return 0; /* OK */
+}
+
+
+static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength)
+{
+ unsigned i;
+ if (color->colortype == LCT_PALETTE) {
+ /* error: more alpha values given than there are palette entries */
+ if (chunkLength > color->palettesize) return 39;
+
+ for (i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i];
+ } else if (color->colortype == LCT_GREY) {
+ /* error: this chunk must be 2 bytes for grayscale image */
+ if (chunkLength != 2) return 30;
+
+ color->key_defined = 1;
+ color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1];
+ } else if (color->colortype == LCT_RGB) {
+ /* error: this chunk must be 6 bytes for RGB image */
+ if (chunkLength != 6) return 41;
+
+ color->key_defined = 1;
+ color->key_r = 256u * data[0] + data[1];
+ color->key_g = 256u * data[2] + data[3];
+ color->key_b = 256u * data[4] + data[5];
+ }
+ else return 42; /* error: tRNS chunk not allowed for other color models */
+
+ return 0; /* OK */
+}
+
+
+/* read a PNG, the result will be in the same color type as the PNG (hence "generic") */
+static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize)
+{
+ unsigned char IEND = 0;
+ const unsigned char* chunk;
+ unsigned char* idat; /*the data from idat chunks, zlib compressed*/
+ size_t idatsize = 0;
+ unsigned char* scanlines = 0;
+ size_t scanlines_size = 0, expected_size = 0;
+ size_t outsize = 0;
+
+ /* safe output values in case error happens */
+ *out = 0;
+ *w = *h = 0;
+
+ state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/
+ if (state->error) return;
+
+ if (lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) {
+ CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/
+ }
+
+ /*the input filesize is a safe upper bound for the sum of idat chunks size*/
+ idat = (unsigned char*)malloc(insize);
+ if (!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/
+
+ chunk = &in[33]; /*first byte of the first chunk after the header*/
+
+ /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk.
+ IDAT data is put at the start of the in buffer*/
+ while (!IEND && !state->error) {
+ unsigned chunkLength;
+ const unsigned char* data; /*the data in the chunk*/
+
+ /*error: size of the in buffer too small to contain next chunk*/
+ if ((size_t)((chunk - in) + 12) > insize || chunk < in) {
+ if (state->decoder.ignore_end) break; /*other errors may still happen though*/
+ CERROR_BREAK(state->error, 30);
+ }
+
+ /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/
+ chunkLength = lodepng_chunk_length(chunk);
+ /*error: chunk length larger than the max PNG chunk size*/
+ if (chunkLength > 2147483647) {
+ if (state->decoder.ignore_end) break; /*other errors may still happen though*/
+ CERROR_BREAK(state->error, 63);
+ }
+
+ if ((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) {
+ CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/
+ }
+
+ data = lodepng_chunk_data_const(chunk);
+
+ /*for unknown chunk order*/
+ //unsigned unknown = 0;
+
+ /*IDAT chunk, containing compressed image data*/
+ if (lodepng_chunk_type_equals(chunk, "IDAT")) {
+ size_t newsize;
+ if (lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95);
+ if (newsize > insize) CERROR_BREAK(state->error, 95);
+ lodepng_memcpy(idat + idatsize, data, chunkLength);
+ idatsize += chunkLength;
+ } else if (lodepng_chunk_type_equals(chunk, "IEND")) {
+ /*IEND chunk*/
+ IEND = 1;
+ } else if (lodepng_chunk_type_equals(chunk, "PLTE")) {
+ /*palette chunk (PLTE)*/
+ state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength);
+ if (state->error) break;
+ } else if (lodepng_chunk_type_equals(chunk, "tRNS")) {
+ /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled
+ in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that
+ affects the alpha channel of pixels. */
+ state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength);
+ if (state->error) break;
+ } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ {
+ /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/
+ if (!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) {
+ CERROR_BREAK(state->error, 69);
+ }
+ //unknown = 1;
+ }
+
+#if 0 //We don't use CRC
+ if (!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ {
+ if (lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/
+ }
+#endif
+ if (!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize);
+ }
+
+ if (state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) {
+ state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */
+ }
+
+ if (!state->error) {
+ /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation.
+ If the decompressed size does not match the prediction, the image must be corrupt.*/
+ if (state->info_png.interlace_method == 0) {
+ size_t bpp = lodepng_get_bpp_lct(state->info_png.color.colortype, state->info_png.color.bitdepth);
+ expected_size = lodepng_get_raw_size_idat(*w, *h, bpp);
+ } else {
+ size_t bpp = lodepng_get_bpp_lct(state->info_png.color.colortype, state->info_png.color.bitdepth);
+ /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/
+ expected_size = 0;
+ expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp);
+ if (*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp);
+ expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp);
+ if (*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp);
+ expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp);
+ if (*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp);
+ expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp);
+ }
+ state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings);
+ }
+
+ if (!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/
+ free(idat);
+
+ if (!state->error) {
+ outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color);
+ *out = (unsigned char*)malloc(outsize);
+ if (!*out) state->error = 83; /*alloc fail*/
+ }
+ if (!state->error) {
+ lodepng_memset(*out, 0, outsize);
+ state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png);
+ }
+ free(scanlines);
+}
+
+
+static void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings)
+{
+ settings->color_convert = 1;
+ settings->ignore_crc = 0;
+ settings->ignore_critical = 0;
+ settings->ignore_end = 0;
+ lodepng_decompress_settings_init(&settings->zlibsettings);
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+/*read the information from the header and store it in the LodePNGInfo. return value is error*/
+unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize)
+{
+ unsigned width, height;
+ LodePNGInfo* info = &state->info_png;
+ if (insize == 0 || in == 0) {
+ CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/
+ }
+ if (insize < 33) {
+ CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/
+ }
+
+ /* when decoding a new PNG image, make sure all parameters created after previous decoding are reset */
+ /* TODO: remove this. One should use a new LodePNGState for new sessions */
+ lodepng_info_cleanup(info);
+ lodepng_info_init(info);
+
+ if (in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) {
+ CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/
+ }
+ if (lodepng_chunk_length(in + 8) != 13) {
+ CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/
+ }
+ if (!lodepng_chunk_type_equals(in + 8, "IHDR")) {
+ CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/
+ }
+
+ /*read the values given in the header*/
+ width = lodepng_read32bitInt(&in[16]);
+ height = lodepng_read32bitInt(&in[20]);
+ /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/
+ if (w) *w = width;
+ if (h) *h = height;
+ info->color.bitdepth = in[24];
+ info->color.colortype = (LodePNGColorType)in[25];
+ info->compression_method = in[26];
+ info->filter_method = in[27];
+ info->interlace_method = in[28];
+
+ /*errors returned only after the parsing so other values are still output*/
+
+ /*error: invalid image size*/
+ if (width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93);
+ /*error: invalid colortype or bitdepth combination*/
+ state->error = checkColorValidity(info->color.colortype, info->color.bitdepth);
+ if (state->error) return state->error;
+ /*error: only compression method 0 is allowed in the specification*/
+ if (info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32);
+ /*error: only filter method 0 is allowed in the specification*/
+ if (info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33);
+ /*error: only interlace methods 0 and 1 exist in the specification*/
+ if (info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34);
+
+#if 0 //thorvg don't use crc
+ if (!state->decoder.ignore_crc) {
+ unsigned CRC = lodepng_read32bitInt(&in[29]);
+ unsigned checksum = lodepng_crc32(&in[12], 17);
+ if (CRC != checksum) {
+ CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/
+ }
+ }
+#endif
+ return state->error;
+}
+
+
+unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize)
+{
+ *out = 0;
+ decodeGeneric(out, w, h, state, in, insize);
+ if (state->error) return state->error;
+ if (!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) {
+ /*same color type, no copying or converting of data needed*/
+ /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype
+ the raw image has to the end user*/
+ if (!state->decoder.color_convert) {
+ state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color);
+ if (state->error) return state->error;
+ }
+ } else { /*color conversion needed*/
+ unsigned char* data = *out;
+ size_t outsize;
+
+ /*TODO: check if this works according to the statement in the documentation: "The converter can convert
+ from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/
+ if (!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) && !(state->info_raw.bitdepth == 8)) {
+ return 56; /*unsupported color mode conversion*/
+ }
+
+ outsize = lodepng_get_raw_size(*w, *h, &state->info_raw);
+ *out = (unsigned char*)malloc(outsize);
+ if (!(*out)) {
+ state->error = 83; /*alloc fail*/
+ }
+ else state->error = lodepng_convert(*out, data, &state->info_raw, &state->info_png.color, *w, *h);
+ free(data);
+ }
+ return state->error;
+}
+
+
+void lodepng_state_init(LodePNGState* state)
+{
+ lodepng_decoder_settings_init(&state->decoder);
+ lodepng_color_mode_init(&state->info_raw);
+ lodepng_info_init(&state->info_png);
+ state->error = 1;
+}
+
+
+void lodepng_state_cleanup(LodePNGState* state)
+{
+ lodepng_color_mode_cleanup(&state->info_raw);
+ lodepng_info_cleanup(&state->info_png);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/loaders/png/tvgLodePng.h b/thirdparty/thorvg/src/loaders/png/tvgLodePng.h
new file mode 100644
index 0000000000..02e1feeec5
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/png/tvgLodePng.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ LodePNG version 20200306
+
+ Copyright (c) 2005-2020 Lode Vandevenne
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#ifndef _TVG_LODEPNG_H_
+#define _TVG_LODEPNG_H_
+
+#include <stddef.h>
+
+/*The PNG color types (also used for raw image).*/
+enum LodePNGColorType
+{
+ LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/
+ LCT_RGB = 2, /*RGB: 8,16 bit*/
+ LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/
+ LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/
+ LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/
+ /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid
+ byte value from 0 to 255 that could be present in an invalid PNG file header. Do
+ not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use
+ the valid color type names above, or numeric values like 1 or 7 when checking for
+ particular disallowed color type byte values, or cast to integer to print it.*/
+ LCT_MAX_OCTET_VALUE = 255
+};
+
+/*Settings for zlib decompression*/
+struct LodePNGDecompressSettings
+{
+ /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */
+ unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/
+ unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/
+
+ /*use custom zlib decoder instead of built in one (default: null)*/
+ unsigned (*custom_zlib)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*);
+ /*use custom deflate decoder instead of built in one (default: null) if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate)*/
+ unsigned (*custom_inflate)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*);
+
+ const void* custom_context; /*optional custom settings for custom functions*/
+};
+
+/*
+ Color mode of an image. Contains all information required to decode the pixel
+ bits to RGBA colors. This information is the same as used in the PNG file
+ format, and is used both for PNG and raw image data in LodePNG.
+*/
+struct LodePNGColorMode
+{
+ /*header (IHDR)*/
+ LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/
+ unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/
+
+ /*
+ palette (PLTE and tRNS)
+
+ Dynamically allocated with the colors of the palette, including alpha.
+ This field may not be allocated directly, use lodepng_color_mode_init first,
+ then lodepng_palette_add per color to correctly initialize it (to ensure size
+ of exactly 1024 bytes).
+
+ The alpha channels must be set as well, set them to 255 for opaque images.
+
+ When decoding, by default you can ignore this palette, since LodePNG already
+ fills the palette colors in the pixels of the raw RGBA output.
+
+ The palette is only supported for color type 3.
+ */
+ unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/
+ size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/
+
+ /*
+ transparent color key (tRNS)
+
+ This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit.
+ For grayscale PNGs, r, g and b will all 3 be set to the same.
+
+ When decoding, by default you can ignore this information, since LodePNG sets
+ pixels with this key to transparent already in the raw RGBA output.
+
+ The color key is only supported for color types 0 and 2.
+ */
+ unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/
+ unsigned key_r; /*red/grayscale component of color key*/
+ unsigned key_g; /*green component of color key*/
+ unsigned key_b; /*blue component of color key*/
+};
+
+/*Information about the PNG image, except pixels, width and height.*/
+struct LodePNGInfo
+{
+ /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/
+ unsigned compression_method;/*compression method of the original file. Always 0.*/
+ unsigned filter_method; /*filter method of the original file*/
+ unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/
+ LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/
+};
+
+/*
+ Settings for the decoder. This contains settings for the PNG and the Zlib
+ decoder, but not the Info settings from the Info structs.
+*/
+struct LodePNGDecoderSettings
+{
+ LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/
+
+ /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */
+ unsigned ignore_crc; /*ignore CRC checksums*/
+ unsigned ignore_critical; /*ignore unknown critical chunks*/
+ unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/
+ /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable
+ errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some
+ strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters
+ in string keys, etc... */
+
+ unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/
+};
+
+/*The settings, state and information for extended encoding and decoding.*/
+struct LodePNGState
+{
+ LodePNGDecoderSettings decoder; /*the decoding settings*/
+ LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/
+ LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/
+ unsigned error;
+};
+
+void lodepng_state_init(LodePNGState* state);
+void lodepng_state_cleanup(LodePNGState* state);
+unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize);
+unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize);
+
+#endif //_TVG_LODEPNG_H_ \ No newline at end of file
diff --git a/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp
new file mode 100644
index 0000000000..c6d95be5ba
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <memory.h>
+#include "tvgLoader.h"
+#include "tvgPngLoader.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+static inline uint32_t PREMULTIPLY(uint32_t c)
+{
+ auto a = (c >> 24);
+ return (c & 0xff000000) + ((((c >> 8) & 0xff) * a) & 0xff00) + ((((c & 0x00ff00ff) * a) >> 8) & 0x00ff00ff);
+}
+
+
+static void _premultiply(uint32_t* data, uint32_t w, uint32_t h)
+{
+ auto buffer = data;
+ for (uint32_t y = 0; y < h; ++y, buffer += w) {
+ auto src = buffer;
+ for (uint32_t x = 0; x < w; ++x, ++src) {
+ *src = PREMULTIPLY(*src);
+ }
+ }
+}
+
+
+void PngLoader::clear()
+{
+ lodepng_state_cleanup(&state);
+
+ if (freeData) free(data);
+ data = nullptr;
+ size = 0;
+ freeData = false;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+PngLoader::PngLoader()
+{
+ lodepng_state_init(&state);
+}
+
+
+PngLoader::~PngLoader()
+{
+ if (freeData) free(data);
+}
+
+
+bool PngLoader::open(const string& path)
+{
+ clear();
+
+ auto pngFile = fopen(path.c_str(), "rb");
+ if (!pngFile) return false;
+
+ auto ret = false;
+
+ //determine size
+ if (fseek(pngFile, 0, SEEK_END) < 0) goto finalize;
+ if (((size = ftell(pngFile)) < 1)) goto finalize;
+ if (fseek(pngFile, 0, SEEK_SET)) goto finalize;
+
+ data = (unsigned char *) malloc(size);
+ if (!data) goto finalize;
+
+ freeData = true;
+
+ if (fread(data, size, 1, pngFile) < 1) goto failure;
+
+ lodepng_state_init(&state);
+
+ unsigned int width, height;
+ if (lodepng_inspect(&width, &height, &state, data, size) > 0) goto failure;
+
+ w = static_cast<float>(width);
+ h = static_cast<float>(height);
+ ret = true;
+
+ goto finalize;
+
+failure:
+ clear();
+
+finalize:
+ fclose(pngFile);
+ return ret;
+}
+
+
+bool PngLoader::open(const char* data, uint32_t size, bool copy)
+{
+ clear();
+
+ lodepng_state_init(&state);
+
+ unsigned int width, height;
+ if (lodepng_inspect(&width, &height, &state, (unsigned char*)(data), size) > 0) return false;
+
+ if (copy) {
+ this->data = (unsigned char *) malloc(size);
+ if (!this->data) return false;
+ memcpy((unsigned char *)this->data, data, size);
+ freeData = true;
+ } else {
+ this->data = (unsigned char *) data;
+ freeData = false;
+ }
+
+ w = static_cast<float>(width);
+ h = static_cast<float>(height);
+ this->size = size;
+
+ return true;
+}
+
+
+bool PngLoader::read()
+{
+ if (!data || w <= 0 || h <= 0) return false;
+
+ TaskScheduler::request(this);
+
+ return true;
+}
+
+
+bool PngLoader::close()
+{
+ this->done();
+ clear();
+ return true;
+}
+
+
+unique_ptr<Surface> PngLoader::bitmap()
+{
+ this->done();
+
+ if (!image) return nullptr;
+
+ auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
+ surface->buffer = (uint32_t*)(image);
+ surface->stride = w;
+ surface->w = w;
+ surface->h = h;
+ surface->cs = SwCanvas::ARGB8888;
+
+ return unique_ptr<Surface>(surface);
+}
+
+
+void PngLoader::run(unsigned tid)
+{
+ auto width = static_cast<unsigned>(w);
+ auto height = static_cast<unsigned>(h);
+
+ lodepng_decode(&image, &width, &height, &state, data, size);
+
+ _premultiply((uint32_t*)(image), width, height);
+} \ No newline at end of file
diff --git a/thirdparty/thorvg/src/loaders/png/tvgPngLoader.h b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.h
new file mode 100644
index 0000000000..34dbeed012
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_PNG_LOADER_H_
+#define _TVG_PNG_LOADER_H_
+
+#include "tvgLodePng.h"
+#include "tvgTaskScheduler.h"
+
+
+class PngLoader : public LoadModule, public Task
+{
+private:
+ LodePNGState state;
+ unsigned char* data = nullptr;
+ unsigned char *image = nullptr;
+ unsigned long size = 0;
+ bool freeData = false;
+
+ void clear();
+
+public:
+ PngLoader();
+ ~PngLoader();
+
+ using LoadModule::open;
+ bool open(const string& path) override;
+ bool open(const char* data, uint32_t size, bool copy) override;
+ bool read() override;
+ bool close() override;
+
+ unique_ptr<Surface> bitmap() override;
+ void run(unsigned tid) override;
+};
+
+#endif //_TVG_PNG_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
new file mode 100644
index 0000000000..d7d425b119
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <fstream>
+#include <string.h>
+#include "tvgLoader.h"
+#include "tvgRawLoader.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+RawLoader::~RawLoader()
+{
+ if (copy && content) {
+ free((void*)content);
+ content = nullptr;
+ }
+}
+
+
+bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool copy)
+{
+ if (!data || w == 0 || h == 0) return false;
+
+ this->w = (float)w;
+ this->h = (float)h;
+ this->copy = copy;
+
+ if (copy) {
+ content = (uint32_t*)malloc(sizeof(uint32_t) * w * h);
+ if (!content) return false;
+ memcpy((void*)content, data, sizeof(uint32_t) * w * h);
+ }
+ else content = data;
+
+ return true;
+}
+
+
+bool RawLoader::read()
+{
+ return true;
+}
+
+
+bool RawLoader::close()
+{
+ return true;
+}
+
+
+unique_ptr<Surface> RawLoader::bitmap()
+{
+ if (!content) return nullptr;
+
+ auto surface = static_cast<Surface*>(malloc(sizeof(Surface)));
+ surface->buffer = (uint32_t*)(content);
+ surface->stride = w;
+ surface->w = w;
+ surface->h = h;
+ surface->cs = SwCanvas::ARGB8888;
+
+ return unique_ptr<Surface>(surface);
+}
diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h
new file mode 100644
index 0000000000..20fa332981
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_RAW_LOADER_H_
+#define _TVG_RAW_LOADER_H_
+
+class RawLoader : public LoadModule
+{
+public:
+ const uint32_t* content = nullptr;
+ bool copy = false;
+
+ ~RawLoader();
+
+ using LoadModule::open;
+ bool open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) override;
+ bool read() override;
+ bool close() override;
+
+ unique_ptr<Surface> bitmap() override;
+};
+
+
+#endif //_TVG_RAW_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
new file mode 100644
index 0000000000..def8ae169a
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -0,0 +1,3007 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright notice for the EFL:
+
+ * Copyright (C) EFL developers (see AUTHORS)
+
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+
+#include <fstream>
+#include <float.h>
+#include <math.h>
+#include "tvgLoader.h"
+#include "tvgXmlParser.h"
+#include "tvgSvgLoader.h"
+#include "tvgSvgSceneBuilder.h"
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+/*
+ * According to: https://www.w3.org/TR/SVG2/coords.html#Units
+ * and: https://www.w3.org/TR/css-values-4/#absolute-lengths
+ */
+#define PX_PER_IN 96 //1 in = 96 px
+#define PX_PER_PC 16 //1 pc = 1/6 in -> PX_PER_IN/6
+#define PX_PER_PT 1.333333f //1 pt = 1/72 in -> PX_PER_IN/72
+#define PX_PER_MM 3.779528f //1 in = 25.4 mm -> PX_PER_IN/25.4
+#define PX_PER_CM 37.79528f //1 in = 2.54 cm -> PX_PER_IN/2.54
+
+
+typedef SvgNode* (*FactoryMethod)(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength);
+typedef SvgStyleGradient* (*GradientFactoryMethod)(SvgLoaderData* loader, const char* buf, unsigned bufLength);
+
+
+static char* _skipSpace(const char* str, const char* end)
+{
+ while (((end && str < end) || (!end && *str != '\0')) && isspace(*str)) {
+ ++str;
+ }
+ return (char*) str;
+}
+
+
+static char* _copyId(const char* str)
+{
+ if (!str) return nullptr;
+
+ return strdup(str);
+}
+
+
+static const char* _skipComma(const char* content)
+{
+ content = _skipSpace(content, nullptr);
+ if (*content == ',') return content + 1;
+ return content;
+}
+
+
+static bool _parseNumber(const char** content, float* number)
+{
+ char* end = nullptr;
+
+ *number = svgUtilStrtof(*content, &end);
+ //If the start of string is not number
+ if ((*content) == end) return false;
+ //Skip comma if any
+ *content = _skipComma(end);
+ return true;
+}
+
+/**
+ * According to https://www.w3.org/TR/SVG/coords.html#Units
+ */
+static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type)
+{
+ float parsedValue = svgUtilStrtof(str, nullptr);
+
+ if (strstr(str, "cm")) parsedValue *= PX_PER_CM;
+ else if (strstr(str, "mm")) parsedValue *= PX_PER_MM;
+ else if (strstr(str, "pt")) parsedValue *= PX_PER_PT;
+ else if (strstr(str, "pc")) parsedValue *= PX_PER_PC;
+ else if (strstr(str, "in")) parsedValue *= PX_PER_IN;
+ else if (strstr(str, "%")) {
+ if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0) * svgParse->global.h;
+ else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0) * svgParse->global.w;
+ else //if other then it's radius
+ {
+ float max = (float)svgParse->global.w;
+ if (max < svgParse->global.h)
+ max = (float)svgParse->global.h;
+ parsedValue = (parsedValue / 100.0) * max;
+ }
+ }
+ //TODO: Implement 'em', 'ex' attributes
+
+ return parsedValue;
+}
+
+
+static float _gradientToFloat(const SvgParser* svgParse, const char* str, bool& isPercentage)
+{
+ char* end = nullptr;
+
+ float parsedValue = svgUtilStrtof(str, &end);
+ isPercentage = false;
+
+ if (strstr(str, "%")) {
+ parsedValue = parsedValue / 100.0;
+ isPercentage = true;
+ }
+ else if (strstr(str, "cm")) parsedValue *= PX_PER_CM;
+ else if (strstr(str, "mm")) parsedValue *= PX_PER_MM;
+ else if (strstr(str, "pt")) parsedValue *= PX_PER_PT;
+ else if (strstr(str, "pc")) parsedValue *= PX_PER_PC;
+ else if (strstr(str, "in")) parsedValue *= PX_PER_IN;
+ //TODO: Implement 'em', 'ex' attributes
+
+ return parsedValue;
+}
+
+
+static float _toOffset(const char* str)
+{
+ char* end = nullptr;
+ auto strEnd = str + strlen(str);
+
+ float parsedValue = svgUtilStrtof(str, &end);
+
+ end = _skipSpace(end, nullptr);
+ auto ptr = strstr(str, "%");
+
+ if (ptr) {
+ parsedValue = parsedValue / 100.0;
+ if (end != ptr || (end + 1) != strEnd) return 0;
+ } else if (end != strEnd) return 0;
+
+ return parsedValue;
+}
+
+
+static int _toOpacity(const char* str)
+{
+ char* end = nullptr;
+ float opacity = svgUtilStrtof(str, &end);
+
+ if (end) {
+ if (end[0] == '%' && end[1] == '\0') return lrint(opacity * 2.55f);
+ else if (*end == '\0') return lrint(opacity * 255);
+ }
+ return 255;
+}
+
+
+#define _PARSE_TAG(Type, Name, Name1, Tags_Array, Default) \
+ static Type _to##Name1(const char* str) \
+ { \
+ unsigned int i; \
+ \
+ for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \
+ if (!strcmp(str, Tags_Array[i].tag)) return Tags_Array[i].Name; \
+ } \
+ return Default; \
+ }
+
+
+/* parse the line cap used during stroking a path.
+ * Value: butt | round | square | inherit
+ * Initial: butt
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static constexpr struct
+{
+ StrokeCap lineCap;
+ const char* tag;
+} lineCapTags[] = {
+ { StrokeCap::Butt, "butt" },
+ { StrokeCap::Round, "round" },
+ { StrokeCap::Square, "square" }
+};
+
+
+_PARSE_TAG(StrokeCap, lineCap, LineCap, lineCapTags, StrokeCap::Butt)
+
+
+/* parse the line join used during stroking a path.
+ * Value: miter | round | bevel | inherit
+ * Initial: miter
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static constexpr struct
+{
+ StrokeJoin lineJoin;
+ const char* tag;
+} lineJoinTags[] = {
+ { StrokeJoin::Miter, "miter" },
+ { StrokeJoin::Round, "round" },
+ { StrokeJoin::Bevel, "bevel" }
+};
+
+
+_PARSE_TAG(StrokeJoin, lineJoin, LineJoin, lineJoinTags, StrokeJoin::Miter)
+
+
+/* parse the fill rule used during filling a path.
+ * Value: nonzero | evenodd | inherit
+ * Initial: nonzero
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static constexpr struct
+{
+ FillRule fillRule;
+ const char* tag;
+} fillRuleTags[] = {
+ { FillRule::EvenOdd, "evenodd" }
+};
+
+
+_PARSE_TAG(FillRule, fillRule, FillRule, fillRuleTags, FillRule::Winding)
+
+
+/* parse the dash pattern used during stroking a path.
+ * Value: none | <dasharray> | inherit
+ * Initial: none
+ * https://www.w3.org/TR/SVG/painting.html
+ */
+static void _parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* dash)
+{
+ if (!strncmp(str, "none", 4)) return;
+
+ char *end = nullptr;
+
+ while (*str) {
+ str = _skipComma(str);
+ float parsedValue = svgUtilStrtof(str, &end);
+ if (str == end) break;
+ if (parsedValue <= 0.0f) break;
+ if (*end == '%') {
+ ++end;
+ //Refers to the diagonal length of the viewport.
+ //https://www.w3.org/TR/SVG2/coords.html#Units
+ parsedValue = (sqrtf(pow(loader->svgParse->global.w, 2) + pow(loader->svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f);
+ }
+ (*dash).array.push(parsedValue);
+ str = end;
+ }
+ //If dash array size is 1, it means that dash and gap size are the same.
+ if ((*dash).array.count == 1) (*dash).array.push((*dash).array.data[0]);
+}
+
+
+static char* _idFromUrl(const char* url)
+{
+ url = _skipSpace(url, nullptr);
+ if ((*url) == '(') {
+ ++url;
+ url = _skipSpace(url, nullptr);
+ }
+
+ if ((*url) == '\'') ++url;
+ if ((*url) == '#') ++url;
+
+ int i = 0;
+ while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i;
+
+ //custom strndup() for portability
+ int len = strlen(url);
+ if (i < len) len = i;
+
+ auto ret = (char*) malloc(len + 1);
+ if (!ret) return 0;
+ ret[len] = '\0';
+ return (char*) memcpy(ret, url, len);
+}
+
+
+static unsigned char _parserColor(const char* value, char** end)
+{
+ float r;
+
+ r = svgUtilStrtof(value, end);
+ *end = _skipSpace(*end, nullptr);
+ if (**end == '%') r = 255 * r / 100;
+ *end = _skipSpace(*end, nullptr);
+
+ if (r < 0 || r > 255) {
+ *end = nullptr;
+ return 0;
+ }
+
+ return lrint(r);
+}
+
+
+static constexpr struct
+{
+ const char* name;
+ unsigned int value;
+} colors[] = {
+ { "aliceblue", 0xfff0f8ff },
+ { "antiquewhite", 0xfffaebd7 },
+ { "aqua", 0xff00ffff },
+ { "aquamarine", 0xff7fffd4 },
+ { "azure", 0xfff0ffff },
+ { "beige", 0xfff5f5dc },
+ { "bisque", 0xffffe4c4 },
+ { "black", 0xff000000 },
+ { "blanchedalmond", 0xffffebcd },
+ { "blue", 0xff0000ff },
+ { "blueviolet", 0xff8a2be2 },
+ { "brown", 0xffa52a2a },
+ { "burlywood", 0xffdeb887 },
+ { "cadetblue", 0xff5f9ea0 },
+ { "chartreuse", 0xff7fff00 },
+ { "chocolate", 0xffd2691e },
+ { "coral", 0xffff7f50 },
+ { "cornflowerblue", 0xff6495ed },
+ { "cornsilk", 0xfffff8dc },
+ { "crimson", 0xffdc143c },
+ { "cyan", 0xff00ffff },
+ { "darkblue", 0xff00008b },
+ { "darkcyan", 0xff008b8b },
+ { "darkgoldenrod", 0xffb8860b },
+ { "darkgray", 0xffa9a9a9 },
+ { "darkgrey", 0xffa9a9a9 },
+ { "darkgreen", 0xff006400 },
+ { "darkkhaki", 0xffbdb76b },
+ { "darkmagenta", 0xff8b008b },
+ { "darkolivegreen", 0xff556b2f },
+ { "darkorange", 0xffff8c00 },
+ { "darkorchid", 0xff9932cc },
+ { "darkred", 0xff8b0000 },
+ { "darksalmon", 0xffe9967a },
+ { "darkseagreen", 0xff8fbc8f },
+ { "darkslateblue", 0xff483d8b },
+ { "darkslategray", 0xff2f4f4f },
+ { "darkslategrey", 0xff2f4f4f },
+ { "darkturquoise", 0xff00ced1 },
+ { "darkviolet", 0xff9400d3 },
+ { "deeppink", 0xffff1493 },
+ { "deepskyblue", 0xff00bfff },
+ { "dimgray", 0xff696969 },
+ { "dimgrey", 0xff696969 },
+ { "dodgerblue", 0xff1e90ff },
+ { "firebrick", 0xffb22222 },
+ { "floralwhite", 0xfffffaf0 },
+ { "forestgreen", 0xff228b22 },
+ { "fuchsia", 0xffff00ff },
+ { "gainsboro", 0xffdcdcdc },
+ { "ghostwhite", 0xfff8f8ff },
+ { "gold", 0xffffd700 },
+ { "goldenrod", 0xffdaa520 },
+ { "gray", 0xff808080 },
+ { "grey", 0xff808080 },
+ { "green", 0xff008000 },
+ { "greenyellow", 0xffadff2f },
+ { "honeydew", 0xfff0fff0 },
+ { "hotpink", 0xffff69b4 },
+ { "indianred", 0xffcd5c5c },
+ { "indigo", 0xff4b0082 },
+ { "ivory", 0xfffffff0 },
+ { "khaki", 0xfff0e68c },
+ { "lavender", 0xffe6e6fa },
+ { "lavenderblush", 0xfffff0f5 },
+ { "lawngreen", 0xff7cfc00 },
+ { "lemonchiffon", 0xfffffacd },
+ { "lightblue", 0xffadd8e6 },
+ { "lightcoral", 0xfff08080 },
+ { "lightcyan", 0xffe0ffff },
+ { "lightgoldenrodyellow", 0xfffafad2 },
+ { "lightgray", 0xffd3d3d3 },
+ { "lightgrey", 0xffd3d3d3 },
+ { "lightgreen", 0xff90ee90 },
+ { "lightpink", 0xffffb6c1 },
+ { "lightsalmon", 0xffffa07a },
+ { "lightseagreen", 0xff20b2aa },
+ { "lightskyblue", 0xff87cefa },
+ { "lightslategray", 0xff778899 },
+ { "lightslategrey", 0xff778899 },
+ { "lightsteelblue", 0xffb0c4de },
+ { "lightyellow", 0xffffffe0 },
+ { "lime", 0xff00ff00 },
+ { "limegreen", 0xff32cd32 },
+ { "linen", 0xfffaf0e6 },
+ { "magenta", 0xffff00ff },
+ { "maroon", 0xff800000 },
+ { "mediumaquamarine", 0xff66cdaa },
+ { "mediumblue", 0xff0000cd },
+ { "mediumorchid", 0xffba55d3 },
+ { "mediumpurple", 0xff9370d8 },
+ { "mediumseagreen", 0xff3cb371 },
+ { "mediumslateblue", 0xff7b68ee },
+ { "mediumspringgreen", 0xff00fa9a },
+ { "mediumturquoise", 0xff48d1cc },
+ { "mediumvioletred", 0xffc71585 },
+ { "midnightblue", 0xff191970 },
+ { "mintcream", 0xfff5fffa },
+ { "mistyrose", 0xffffe4e1 },
+ { "moccasin", 0xffffe4b5 },
+ { "navajowhite", 0xffffdead },
+ { "navy", 0xff000080 },
+ { "oldlace", 0xfffdf5e6 },
+ { "olive", 0xff808000 },
+ { "olivedrab", 0xff6b8e23 },
+ { "orange", 0xffffa500 },
+ { "orangered", 0xffff4500 },
+ { "orchid", 0xffda70d6 },
+ { "palegoldenrod", 0xffeee8aa },
+ { "palegreen", 0xff98fb98 },
+ { "paleturquoise", 0xffafeeee },
+ { "palevioletred", 0xffd87093 },
+ { "papayawhip", 0xffffefd5 },
+ { "peachpuff", 0xffffdab9 },
+ { "peru", 0xffcd853f },
+ { "pink", 0xffffc0cb },
+ { "plum", 0xffdda0dd },
+ { "powderblue", 0xffb0e0e6 },
+ { "purple", 0xff800080 },
+ { "red", 0xffff0000 },
+ { "rosybrown", 0xffbc8f8f },
+ { "royalblue", 0xff4169e1 },
+ { "saddlebrown", 0xff8b4513 },
+ { "salmon", 0xfffa8072 },
+ { "sandybrown", 0xfff4a460 },
+ { "seagreen", 0xff2e8b57 },
+ { "seashell", 0xfffff5ee },
+ { "sienna", 0xffa0522d },
+ { "silver", 0xffc0c0c0 },
+ { "skyblue", 0xff87ceeb },
+ { "slateblue", 0xff6a5acd },
+ { "slategray", 0xff708090 },
+ { "slategrey", 0xff708090 },
+ { "snow", 0xfffffafa },
+ { "springgreen", 0xff00ff7f },
+ { "steelblue", 0xff4682b4 },
+ { "tan", 0xffd2b48c },
+ { "teal", 0xff008080 },
+ { "thistle", 0xffd8bfd8 },
+ { "tomato", 0xffff6347 },
+ { "turquoise", 0xff40e0d0 },
+ { "violet", 0xffee82ee },
+ { "wheat", 0xfff5deb3 },
+ { "white", 0xffffffff },
+ { "whitesmoke", 0xfff5f5f5 },
+ { "yellow", 0xffffff00 },
+ { "yellowgreen", 0xff9acd32 }
+};
+
+
+static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref)
+{
+ unsigned int len = strlen(str);
+ char *red, *green, *blue;
+ unsigned char tr, tg, tb;
+
+ if (len == 4 && str[0] == '#') {
+ //Case for "#456" should be interprete as "#445566"
+ if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) {
+ char tmp[3] = { '\0', '\0', '\0' };
+ tmp[0] = str[1];
+ tmp[1] = str[1];
+ *r = strtol(tmp, nullptr, 16);
+ tmp[0] = str[2];
+ tmp[1] = str[2];
+ *g = strtol(tmp, nullptr, 16);
+ tmp[0] = str[3];
+ tmp[1] = str[3];
+ *b = strtol(tmp, nullptr, 16);
+ }
+ } else if (len == 7 && str[0] == '#') {
+ if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) {
+ char tmp[3] = { '\0', '\0', '\0' };
+ tmp[0] = str[1];
+ tmp[1] = str[2];
+ *r = strtol(tmp, nullptr, 16);
+ tmp[0] = str[3];
+ tmp[1] = str[4];
+ *g = strtol(tmp, nullptr, 16);
+ tmp[0] = str[5];
+ tmp[1] = str[6];
+ *b = strtol(tmp, nullptr, 16);
+ }
+ } else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') {
+ tr = _parserColor(str + 4, &red);
+ if (red && *red == ',') {
+ tg = _parserColor(red + 1, &green);
+ if (green && *green == ',') {
+ tb = _parserColor(green + 1, &blue);
+ if (blue && blue[0] == ')' && blue[1] == '\0') {
+ *r = tr;
+ *g = tg;
+ *b = tb;
+ }
+ }
+ }
+ } else if (len >= 3 && !strncmp(str, "url", 3)) {
+ *ref = _idFromUrl((const char*)(str + 3));
+ } else {
+ //Handle named color
+ for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) {
+ if (!strcasecmp(colors[i].name, str)) {
+ *r = (((uint8_t*)(&(colors[i].value)))[2]);
+ *g = (((uint8_t*)(&(colors[i].value)))[1]);
+ *b = (((uint8_t*)(&(colors[i].value)))[0]);
+ return;
+ }
+ }
+ }
+}
+
+
+static char* _parseNumbersArray(char* str, float* points, int* ptCount, int len)
+{
+ int count = 0;
+ char* end = nullptr;
+
+ str = _skipSpace(str, nullptr);
+ while ((count < len) && (isdigit(*str) || *str == '-' || *str == '+' || *str == '.')) {
+ points[count++] = svgUtilStrtof(str, &end);
+ str = end;
+ str = _skipSpace(str, nullptr);
+ if (*str == ',') ++str;
+ //Eat the rest of space
+ str = _skipSpace(str, nullptr);
+ }
+ *ptCount = count;
+ return str;
+}
+
+
+enum class MatrixState {
+ Unknown,
+ Matrix,
+ Translate,
+ Rotate,
+ Scale,
+ SkewX,
+ SkewY
+};
+
+
+#define MATRIX_DEF(Name, Value) \
+ { \
+#Name, sizeof(#Name), Value \
+ }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ MatrixState state;
+} matrixTags[] = {
+ MATRIX_DEF(matrix, MatrixState::Matrix),
+ MATRIX_DEF(translate, MatrixState::Translate),
+ MATRIX_DEF(rotate, MatrixState::Rotate),
+ MATRIX_DEF(scale, MatrixState::Scale),
+ MATRIX_DEF(skewX, MatrixState::SkewX),
+ MATRIX_DEF(skewY, MatrixState::SkewY)
+};
+
+
+static void _matrixCompose(const Matrix* m1, const Matrix* m2, Matrix* dst)
+{
+ auto a11 = (m1->e11 * m2->e11) + (m1->e12 * m2->e21) + (m1->e13 * m2->e31);
+ auto a12 = (m1->e11 * m2->e12) + (m1->e12 * m2->e22) + (m1->e13 * m2->e32);
+ auto a13 = (m1->e11 * m2->e13) + (m1->e12 * m2->e23) + (m1->e13 * m2->e33);
+
+ auto a21 = (m1->e21 * m2->e11) + (m1->e22 * m2->e21) + (m1->e23 * m2->e31);
+ auto a22 = (m1->e21 * m2->e12) + (m1->e22 * m2->e22) + (m1->e23 * m2->e32);
+ auto a23 = (m1->e21 * m2->e13) + (m1->e22 * m2->e23) + (m1->e23 * m2->e33);
+
+ auto a31 = (m1->e31 * m2->e11) + (m1->e32 * m2->e21) + (m1->e33 * m2->e31);
+ auto a32 = (m1->e31 * m2->e12) + (m1->e32 * m2->e22) + (m1->e33 * m2->e32);
+ auto a33 = (m1->e31 * m2->e13) + (m1->e32 * m2->e23) + (m1->e33 * m2->e33);
+
+ dst->e11 = a11;
+ dst->e12 = a12;
+ dst->e13 = a13;
+ dst->e21 = a21;
+ dst->e22 = a22;
+ dst->e23 = a23;
+ dst->e31 = a31;
+ dst->e32 = a32;
+ dst->e33 = a33;
+}
+
+
+/* parse transform attribute
+ * https://www.w3.org/TR/SVG/coords.html#TransformAttribute
+ */
+static Matrix* _parseTransformationMatrix(const char* value)
+{
+ const int POINT_CNT = 8;
+
+ auto matrix = (Matrix*)malloc(sizeof(Matrix));
+ if (!matrix) return nullptr;
+ *matrix = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+
+ float points[POINT_CNT];
+ int ptCount = 0;
+ char* str = (char*)value;
+ char* end = str + strlen(str);
+
+ while (str < end) {
+ auto state = MatrixState::Unknown;
+
+ if (isspace(*str) || (*str == ',')) {
+ ++str;
+ continue;
+ }
+ for (unsigned int i = 0; i < sizeof(matrixTags) / sizeof(matrixTags[0]); i++) {
+ if (!strncmp(matrixTags[i].tag, str, matrixTags[i].sz - 1)) {
+ state = matrixTags[i].state;
+ str += (matrixTags[i].sz - 1);
+ break;
+ }
+ }
+ if (state == MatrixState::Unknown) goto error;
+
+ str = _skipSpace(str, end);
+ if (*str != '(') goto error;
+ ++str;
+ str = _parseNumbersArray(str, points, &ptCount, POINT_CNT);
+ if (*str != ')') goto error;
+ ++str;
+
+ if (state == MatrixState::Matrix) {
+ if (ptCount != 6) goto error;
+ Matrix tmp = {points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1};
+ _matrixCompose(matrix, &tmp, matrix);
+ } else if (state == MatrixState::Translate) {
+ if (ptCount == 1) {
+ Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1};
+ _matrixCompose(matrix, &tmp, matrix);
+ } else if (ptCount == 2) {
+ Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1};
+ _matrixCompose(matrix, &tmp, matrix);
+ } else goto error;
+ } else if (state == MatrixState::Rotate) {
+ //Transform to signed.
+ points[0] = fmod(points[0], 360);
+ if (points[0] < 0) points[0] += 360;
+ auto c = cosf(points[0] * (M_PI / 180.0));
+ auto s = sinf(points[0] * (M_PI / 180.0));
+ if (ptCount == 1) {
+ Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ } else if (ptCount == 3) {
+ Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ } else {
+ goto error;
+ }
+ } else if (state == MatrixState::Scale) {
+ if (ptCount < 1 || ptCount > 2) goto error;
+ auto sx = points[0];
+ auto sy = sx;
+ if (ptCount == 2) sy = points[1];
+ Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
+ _matrixCompose(matrix, &tmp, matrix);
+ }
+ }
+ return matrix;
+error:
+ if (matrix) free(matrix);
+ return nullptr;
+}
+
+
+#define LENGTH_DEF(Name, Value) \
+ { \
+#Name, sizeof(#Name), Value \
+ }
+
+
+/*
+// TODO - remove?
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ SvgLengthType type;
+} lengthTags[] = {
+ LENGTH_DEF(%, SvgLengthType::Percent),
+ LENGTH_DEF(px, SvgLengthType::Px),
+ LENGTH_DEF(pc, SvgLengthType::Pc),
+ LENGTH_DEF(pt, SvgLengthType::Pt),
+ LENGTH_DEF(mm, SvgLengthType::Mm),
+ LENGTH_DEF(cm, SvgLengthType::Cm),
+ LENGTH_DEF(in, SvgLengthType::In)
+};
+
+static float _parseLength(const char* str, SvgLengthType* type)
+{
+ float value;
+ int sz = strlen(str);
+
+ *type = SvgLengthType::Px;
+ for (unsigned int i = 0; i < sizeof(lengthTags) / sizeof(lengthTags[0]); i++) {
+ if (lengthTags[i].sz - 1 == sz && !strncmp(lengthTags[i].tag, str, sz)) *type = lengthTags[i].type;
+ }
+ value = svgUtilStrtof(str, nullptr);
+ return value;
+}
+*/
+
+static bool _parseStyleAttr(void* data, const char* key, const char* value);
+static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style);
+
+
+static bool _attrParseSvgNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgDocNode* doc = &(node->node.doc);
+
+ if (!strcmp(key, "width")) {
+ doc->w = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
+ } else if (!strcmp(key, "height")) {
+ doc->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical);
+ } else if (!strcmp(key, "viewBox")) {
+ if (_parseNumber(&value, &doc->vx)) {
+ if (_parseNumber(&value, &doc->vy)) {
+ if (_parseNumber(&value, &doc->vw)) {
+ _parseNumber(&value, &doc->vh);
+ loader->svgParse->global.h = (uint32_t)doc->vh;
+ }
+ loader->svgParse->global.w = (uint32_t)doc->vw;
+ }
+ loader->svgParse->global.y = (int)doc->vy;
+ }
+ loader->svgParse->global.x = (int)doc->vx;
+ } else if (!strcmp(key, "preserveAspectRatio")) {
+ if (!strcmp(value, "none")) doc->preserveAspect = false;
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ }
+#ifdef THORVG_LOG_ENABLED
+ else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON ) {
+ TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value);
+ }
+#endif
+ else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+//https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint
+static void _handlePaintAttr(SvgPaint* paint, const char* value)
+{
+ if (!strcmp(value, "none")) {
+ //No paint property
+ paint->none = true;
+ return;
+ }
+ paint->none = false;
+ if (!strcmp(value, "currentColor")) {
+ paint->curColor = true;
+ return;
+ }
+ _toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url);
+}
+
+
+static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ style->curColorSet = true;
+ _toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr);
+}
+
+
+static void _handleFillAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ style->fill.flags = (SvgFillFlags)((int)style->fill.flags | (int)SvgFillFlags::Paint);
+ _handlePaintAttr(&style->fill.paint, value);
+}
+
+
+static void _handleStrokeAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ style->stroke.flags = (SvgStrokeFlags)((int)style->stroke.flags | (int)SvgStrokeFlags::Paint);
+ _handlePaintAttr(&style->stroke.paint, value);
+}
+
+
+static void _handleStrokeOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Opacity);
+ node->style->stroke.opacity = _toOpacity(value);
+}
+
+static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Dash);
+ _parseDashArray(loader, value, &node->style->stroke.dash);
+}
+
+static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Width);
+ node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
+}
+
+
+static void _handleStrokeLineCapAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Cap);
+ node->style->stroke.cap = _toLineCap(value);
+}
+
+
+static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Join);
+ node->style->stroke.join = _toLineJoin(value);
+}
+
+
+static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::FillRule);
+ node->style->fill.fillRule = _toFillRule(value);
+}
+
+
+static void _handleOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->opacity = _toOpacity(value);
+}
+
+
+static void _handleFillOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::Opacity);
+ node->style->fill.opacity = _toOpacity(value);
+}
+
+
+static void _handleTransformAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ node->transform = _parseTransformationMatrix(value);
+}
+
+
+static void _handleClipPathAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ int len = strlen(value);
+ if (len >= 3 && !strncmp(value, "url", 3)) {
+ if (style->clipPath.url) free(style->clipPath.url);
+ style->clipPath.url = _idFromUrl((const char*)(value + 3));
+ }
+}
+
+
+static void _handleMaskAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ SvgStyleProperty* style = node->style;
+ int len = strlen(value);
+ if (len >= 3 && !strncmp(value, "url", 3)) {
+ if (style->mask.url) free(style->mask.url);
+ style->mask.url = _idFromUrl((const char*)(value + 3));
+ }
+}
+
+
+static void _handleDisplayAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
+{
+ //TODO : The display attribute can have various values as well as "none".
+ // The default is "inline" which means visible and "none" means invisible.
+ // Depending on the type of node, additional functionality may be required.
+ // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display
+ if (!strcmp(value, "none")) node->display = false;
+ else node->display = true;
+}
+
+
+typedef void (*styleMethod)(SvgLoaderData* loader, SvgNode* node, const char* value);
+
+#define STYLE_DEF(Name, Name1, Flag) { #Name, sizeof(#Name), _handle##Name1##Attr, Flag }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ styleMethod tagHandler;
+ SvgStyleFlags flag;
+} styleTags[] = {
+ STYLE_DEF(color, Color, SvgStyleFlags::Color),
+ STYLE_DEF(fill, Fill, SvgStyleFlags::Fill),
+ STYLE_DEF(fill-rule, FillRule, SvgStyleFlags::FillRule),
+ STYLE_DEF(fill-opacity, FillOpacity, SvgStyleFlags::FillOpacity),
+ STYLE_DEF(opacity, Opacity, SvgStyleFlags::Opacity),
+ STYLE_DEF(stroke, Stroke, SvgStyleFlags::Stroke),
+ STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth),
+ STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin),
+ STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap),
+ STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity),
+ STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray),
+ STYLE_DEF(transform, Transform, SvgStyleFlags::Transform),
+ STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath),
+ STYLE_DEF(mask, Mask, SvgStyleFlags::Mask),
+ STYLE_DEF(display, Display, SvgStyleFlags::Display)
+};
+
+
+static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ int sz;
+ if (!key || !value) return false;
+
+ //Trim the white space
+ key = _skipSpace(key, nullptr);
+ value = _skipSpace(value, nullptr);
+
+ sz = strlen(key);
+ for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) {
+ if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) {
+ if (style) {
+ styleTags[i].tagHandler(loader, node, value);
+ node->style->flags = (SvgStyleFlags)((int)node->style->flags | (int)styleTags[i].flag);
+ } else if (!((int)node->style->flags & (int)styleTags[i].flag)) {
+ styleTags[i].tagHandler(loader, node, value);
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+static bool _parseStyleAttr(void* data, const char* key, const char* value)
+{
+ return _parseStyleAttr(data, key, value, true);
+}
+
+
+/* parse g node
+ * https://www.w3.org/TR/SVG/struct.html#Groups
+ */
+static bool _attrParseGNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "transform")) {
+ node->transform = _parseTransformationMatrix(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+/* parse clipPath node
+ * https://www.w3.org/TR/SVG/struct.html#Groups
+ */
+static bool _attrParseClipPathNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgCompositeNode* comp = &(node->node.comp);
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "transform")) {
+ node->transform = _parseTransformationMatrix(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "clipPathUnits")) {
+ if (!strcmp(value, "objectBoundingBox")) comp->userSpace = false;
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static bool _attrParseMaskNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgCompositeNode* comp = &(node->node.comp);
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "transform")) {
+ node->transform = _parseTransformationMatrix(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "maskContentUnits")) {
+ if (!strcmp(value, "objectBoundingBox")) comp->userSpace = false;
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
+{
+ SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode));
+
+ if (!node) return nullptr;
+
+ //Default fill property
+ node->style = (SvgStyleProperty*)calloc(1, sizeof(SvgStyleProperty));
+
+ if (!node->style) {
+ free(node);
+ return nullptr;
+ }
+
+ //Update the default value of stroke and fill
+ //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint
+ node->style->fill.paint.none = false;
+ //Default fill opacity is 1
+ node->style->fill.opacity = 255;
+ node->style->opacity = 255;
+ //Default current color is not set
+ node->style->fill.paint.curColor = false;
+ node->style->curColorSet = false;
+ //Default fill rule is nonzero
+ node->style->fill.fillRule = FillRule::Winding;
+
+ //Default stroke is none
+ node->style->stroke.paint.none = true;
+ //Default stroke opacity is 1
+ node->style->stroke.opacity = 255;
+ //Default stroke current color is not set
+ node->style->stroke.paint.curColor = false;
+ //Default stroke width is 1
+ node->style->stroke.width = 1;
+ //Default line cap is butt
+ node->style->stroke.cap = StrokeCap::Butt;
+ //Default line join is miter
+ node->style->stroke.join = StrokeJoin::Miter;
+ node->style->stroke.scale = 1.0;
+
+ //Default display is true("inline").
+ node->display = true;
+
+ node->parent = parent;
+ node->type = type;
+
+ if (parent) parent->child.push(node);
+ return node;
+}
+
+
+static SvgNode* _createDefsNode(TVG_UNUSED SvgLoaderData* loader, TVG_UNUSED SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ if (loader->def && loader->doc->node.doc.defs) return loader->def;
+ SvgNode* node = _createNode(nullptr, SvgNodeType::Defs);
+
+ loader->def = node;
+ loader->doc->node.doc.defs = node;
+ return node;
+}
+
+
+static SvgNode* _createGNode(TVG_UNUSED SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::G);
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseGNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Doc);
+ if (!loader->svgParse->node) return nullptr;
+ SvgDocNode* doc = &(loader->svgParse->node->node.doc);
+
+ loader->svgParse->global.w = 0;
+ loader->svgParse->global.h = 0;
+
+ doc->preserveAspect = true;
+ simpleXmlParseAttributes(buf, bufLength, _attrParseSvgNode, loader);
+
+ if (loader->svgParse->global.w == 0) {
+ if (doc->w < FLT_EPSILON) loader->svgParse->global.w = 1;
+ else loader->svgParse->global.w = (uint32_t)doc->w;
+ }
+ if (loader->svgParse->global.h == 0) {
+ if (doc->h < FLT_EPSILON) loader->svgParse->global.h = 1;
+ else loader->svgParse->global.h = (uint32_t)doc->h;
+ }
+
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createMaskNode(SvgLoaderData* loader, SvgNode* parent, TVG_UNUSED const char* buf, TVG_UNUSED unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Mask);
+ if (!loader->svgParse->node) return nullptr;
+
+ loader->svgParse->node->node.comp.userSpace = true;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseMaskNode, loader);
+
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createClipPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::ClipPath);
+ if (!loader->svgParse->node) return nullptr;
+
+ loader->svgParse->node->display = false;
+ loader->svgParse->node->node.comp.userSpace = true;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseClipPathNode, loader);
+
+ return loader->svgParse->node;
+}
+
+static bool _attrParsePathNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgPathNode* path = &(node->node.path);
+
+ if (!strcmp(key, "d")) {
+ //Temporary: need to copy
+ path->path = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Path);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParsePathNode, loader);
+
+ return loader->svgParse->node;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} circleTags[] = {
+ {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgCircleNode, cx)},
+ {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgCircleNode, cy)},
+ {"r", SvgParserLengthType::Other, sizeof("r"), offsetof(SvgCircleNode, r)}
+};
+
+
+/* parse the attributes for a circle element.
+ * https://www.w3.org/TR/SVG/shapes.html#CircleElement
+ */
+static bool _attrParseCircleNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgCircleNode* circle = &(node->node.circle);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)circle;
+ for (unsigned int i = 0; i < sizeof(circleTags) / sizeof(circleTags[0]); i++) {
+ if (circleTags[i].sz - 1 == sz && !strncmp(circleTags[i].tag, key, sz)) {
+ *((float*)(array + circleTags[i].offset)) = _toFloat(loader->svgParse, value, circleTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createCircleNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Circle);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseCircleNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} ellipseTags[] = {
+ {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgEllipseNode, cx)},
+ {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgEllipseNode, cy)},
+ {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgEllipseNode, rx)},
+ {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgEllipseNode, ry)}
+};
+
+
+/* parse the attributes for an ellipse element.
+ * https://www.w3.org/TR/SVG/shapes.html#EllipseElement
+ */
+static bool _attrParseEllipseNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgEllipseNode* ellipse = &(node->node.ellipse);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)ellipse;
+ for (unsigned int i = 0; i < sizeof(ellipseTags) / sizeof(ellipseTags[0]); i++) {
+ if (ellipseTags[i].sz - 1 == sz && !strncmp(ellipseTags[i].tag, key, sz)) {
+ *((float*)(array + ellipseTags[i].offset)) = _toFloat(loader->svgParse, value, ellipseTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Ellipse);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseEllipseNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static bool _attrParsePolygonPoints(const char* str, float** points, int* ptCount)
+{
+ float tmp[50];
+ int tmpCount = 0;
+ int count = 0;
+ float num;
+ float *pointArray = nullptr, *tmpArray;
+
+ while (_parseNumber(&str, &num)) {
+ tmp[tmpCount++] = num;
+ if (tmpCount == 50) {
+ tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
+ if (!tmpArray) goto error_alloc;
+ pointArray = tmpArray;
+ memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
+ count += tmpCount;
+ tmpCount = 0;
+ }
+ }
+
+ if (tmpCount > 0) {
+ tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
+ if (!tmpArray) goto error_alloc;
+ pointArray = tmpArray;
+ memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
+ count += tmpCount;
+ }
+ *ptCount = count;
+ *points = pointArray;
+ return true;
+
+error_alloc:
+ return false;
+}
+
+
+/* parse the attributes for a polygon element.
+ * https://www.w3.org/TR/SVG/shapes.html#PolylineElement
+ */
+static bool _attrParsePolygonNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgPolygonNode* polygon = nullptr;
+
+ if (node->type == SvgNodeType::Polygon) polygon = &(node->node.polygon);
+ else polygon = &(node->node.polyline);
+
+ if (!strcmp(key, "points")) {
+ return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createPolygonNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Polygon);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _createPolylineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Polyline);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader);
+ return loader->svgParse->node;
+}
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} rectTags[] = {
+ {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
+ {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
+ {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
+ {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
+ {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgRectNode, rx)},
+ {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgRectNode, ry)}
+};
+
+
+/* parse the attributes for a rect element.
+ * https://www.w3.org/TR/SVG/shapes.html#RectElement
+ */
+static bool _attrParseRectNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgRectNode* rect = &(node->node.rect);
+ unsigned char* array;
+ bool ret = true;
+ int sz = strlen(key);
+
+ array = (unsigned char*)rect;
+ for (unsigned int i = 0; i < sizeof(rectTags) / sizeof(rectTags[0]); i++) {
+ if (rectTags[i].sz - 1 == sz && !strncmp(rectTags[i].tag, key, sz)) {
+ *((float*)(array + rectTags[i].offset)) = _toFloat(loader->svgParse, value, rectTags[i].type);
+
+ //Case if only rx or ry is declared
+ if (!strncmp(rectTags[i].tag, "rx", sz)) rect->hasRx = true;
+ if (!strncmp(rectTags[i].tag, "ry", sz)) rect->hasRy = true;
+
+ if ((rect->rx >= FLT_EPSILON) && (rect->ry < FLT_EPSILON) && rect->hasRx && !rect->hasRy) rect->ry = rect->rx;
+ if ((rect->ry >= FLT_EPSILON) && (rect->rx < FLT_EPSILON) && !rect->hasRx && rect->hasRy) rect->rx = rect->ry;
+ return ret;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ ret = simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ ret = _parseStyleAttr(loader, key, value, false);
+ }
+
+ return ret;
+}
+
+
+static SvgNode* _createRectNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Rect);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ loader->svgParse->node->node.rect.hasRx = loader->svgParse->node->node.rect.hasRy = false;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseRectNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} lineTags[] = {
+ {"x1", SvgParserLengthType::Horizontal, sizeof("x1"), offsetof(SvgLineNode, x1)},
+ {"y1", SvgParserLengthType::Vertical, sizeof("y1"), offsetof(SvgLineNode, y1)},
+ {"x2", SvgParserLengthType::Horizontal, sizeof("x2"), offsetof(SvgLineNode, x2)},
+ {"y2", SvgParserLengthType::Vertical, sizeof("y2"), offsetof(SvgLineNode, y2)}
+};
+
+
+/* parse the attributes for a line element.
+ * https://www.w3.org/TR/SVG/shapes.html#LineElement
+ */
+static bool _attrParseLineNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgLineNode* line = &(node->node.line);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)line;
+ for (unsigned int i = 0; i < sizeof(lineTags) / sizeof(lineTags[0]); i++) {
+ if (lineTags[i].sz - 1 == sz && !strncmp(lineTags[i].tag, key, sz)) {
+ *((float*)(array + lineTags[i].offset)) = _toFloat(loader->svgParse, value, lineTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value, false);
+ }
+ return true;
+}
+
+
+static SvgNode* _createLineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Line);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseLineNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static char* _idFromHref(const char* href)
+{
+ href = _skipSpace(href, nullptr);
+ if ((*href) == '#') href++;
+ return strdup(href);
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} imageTags[] = {
+ {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
+ {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
+ {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
+ {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)},
+};
+
+
+/* parse the attributes for a image element.
+ * https://www.w3.org/TR/SVG/embedded.html#ImageElement
+ */
+static bool _attrParseImageNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode* node = loader->svgParse->node;
+ SvgImageNode* image = &(node->node.image);
+ unsigned char* array;
+ int sz = strlen(key);
+
+ array = (unsigned char*)image;
+ for (unsigned int i = 0; i < sizeof(imageTags) / sizeof(imageTags[0]); i++) {
+ if (imageTags[i].sz - 1 == sz && !strncmp(imageTags[i].tag, key, sz)) {
+ *((float*)(array + imageTags[i].offset)) = _toFloat(loader->svgParse, value, imageTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ image->href = _idFromHref(value);
+ } else if (!strcmp(key, "id")) {
+ if (node->id && value) free(node->id);
+ node->id = _copyId(value);
+ } else if (!strcmp(key, "style")) {
+ return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader);
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _parseStyleAttr(loader, key, value);
+ }
+ return true;
+}
+
+
+static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Image);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseImageNode, loader);
+ return loader->svgParse->node;
+}
+
+
+static SvgNode* _getDefsNode(SvgNode* node)
+{
+ if (!node) return nullptr;
+
+ while (node->parent != nullptr) {
+ node = node->parent;
+ }
+
+ if (node->type == SvgNodeType::Doc) return node->node.doc.defs;
+
+ return nullptr;
+}
+
+
+static SvgNode* _findChildById(const SvgNode* node, const char* id)
+{
+ if (!node) return nullptr;
+
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ if (((*child)->id) && !strcmp((*child)->id, id)) return (*child);
+ }
+ return nullptr;
+}
+
+static SvgNode* _findNodeById(SvgNode *node, const char* id)
+{
+ SvgNode* result = nullptr;
+ if (node->id && !strcmp(node->id, id)) return node;
+
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ result = _findNodeById(*child, id);
+ if (result) break;
+ }
+ }
+ return result;
+}
+
+static void _cloneGradStops(Array<Fill::ColorStop>& dst, const Array<Fill::ColorStop>& src)
+{
+ for (uint32_t i = 0; i < src.count; ++i) {
+ dst.push(src.data[i]);
+ }
+}
+
+
+static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from)
+{
+ if (!from) return nullptr;
+
+ auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient)));
+ if (!grad) return nullptr;
+
+ grad->type = from->type;
+ grad->id = from->id ? _copyId(from->id) : nullptr;
+ grad->ref = from->ref ? _copyId(from->ref) : nullptr;
+ grad->spread = from->spread;
+ grad->userSpace = from->userSpace;
+
+ if (from->transform) {
+ grad->transform = (Matrix*)calloc(1, sizeof(Matrix));
+ if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix));
+ }
+
+ if (grad->type == SvgGradientType::Linear) {
+ grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
+ if (!grad->linear) goto error_grad_alloc;
+ memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient));
+ } else if (grad->type == SvgGradientType::Radial) {
+ grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
+ if (!grad->radial) goto error_grad_alloc;
+ memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient));
+ }
+
+ _cloneGradStops(grad->stops, from->stops);
+
+ return grad;
+
+error_grad_alloc:
+ if (grad) {
+ grad->clear();
+ free(grad);
+ }
+ return nullptr;
+}
+
+
+static void _copyAttr(SvgNode* to, const SvgNode* from)
+{
+ //Copy matrix attribute
+ if (from->transform) {
+ to->transform = (Matrix*)malloc(sizeof(Matrix));
+ if (to->transform) *to->transform = *from->transform;
+ }
+ //Copy style attribute
+ *to->style = *from->style;
+ if (from->style->fill.paint.url) to->style->fill.paint.url = strdup(from->style->fill.paint.url);
+ if (from->style->stroke.paint.url) to->style->stroke.paint.url = strdup(from->style->stroke.paint.url);
+ if (from->style->clipPath.url) to->style->clipPath.url = strdup(from->style->clipPath.url);
+ if (from->style->mask.url) to->style->mask.url = strdup(from->style->mask.url);
+
+ //Copy node attribute
+ switch (from->type) {
+ case SvgNodeType::Circle: {
+ to->node.circle.cx = from->node.circle.cx;
+ to->node.circle.cy = from->node.circle.cy;
+ to->node.circle.r = from->node.circle.r;
+ break;
+ }
+ case SvgNodeType::Ellipse: {
+ to->node.ellipse.cx = from->node.ellipse.cx;
+ to->node.ellipse.cy = from->node.ellipse.cy;
+ to->node.ellipse.rx = from->node.ellipse.rx;
+ to->node.ellipse.ry = from->node.ellipse.ry;
+ break;
+ }
+ case SvgNodeType::Rect: {
+ to->node.rect.x = from->node.rect.x;
+ to->node.rect.y = from->node.rect.y;
+ to->node.rect.w = from->node.rect.w;
+ to->node.rect.h = from->node.rect.h;
+ to->node.rect.rx = from->node.rect.rx;
+ to->node.rect.ry = from->node.rect.ry;
+ to->node.rect.hasRx = from->node.rect.hasRx;
+ to->node.rect.hasRy = from->node.rect.hasRy;
+ break;
+ }
+ case SvgNodeType::Line: {
+ to->node.line.x1 = from->node.line.x1;
+ to->node.line.y1 = from->node.line.y1;
+ to->node.line.x2 = from->node.line.x2;
+ to->node.line.y2 = from->node.line.y2;
+ break;
+ }
+ case SvgNodeType::Path: {
+ if (from->node.path.path) to->node.path.path = strdup(from->node.path.path);
+ break;
+ }
+ case SvgNodeType::Polygon: {
+ to->node.polygon.pointsCount = from->node.polygon.pointsCount;
+ to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float));
+ memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float));
+ break;
+ }
+ case SvgNodeType::Polyline: {
+ to->node.polyline.pointsCount = from->node.polyline.pointsCount;
+ to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float));
+ memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float));
+ break;
+ }
+ case SvgNodeType::Image: {
+ to->node.image.x = from->node.image.x;
+ to->node.image.y = from->node.image.y;
+ to->node.image.w = from->node.image.w;
+ to->node.image.h = from->node.image.h;
+ if (from->node.image.href) to->node.image.href = strdup(from->node.image.href);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+
+static void _cloneNode(SvgNode* from, SvgNode* parent)
+{
+ SvgNode* newNode;
+ if (!from || !parent) return;
+
+ newNode = _createNode(parent, from->type);
+
+ if (!newNode) return;
+
+ _copyAttr(newNode, from);
+
+ auto child = from->child.data;
+ for (uint32_t i = 0; i < from->child.count; ++i, ++child) {
+ _cloneNode(*child, newNode);
+ }
+}
+
+
+static void _postponeCloneNode(SvgLoaderData* loader, SvgNode *node, char* id) {
+ loader->cloneNodes.push({node, id});
+}
+
+
+static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes) {
+ for (uint32_t i = 0; i < cloneNodes->count; ++i) {
+ auto nodeIdPair = cloneNodes->data[i];
+ auto defs = _getDefsNode(nodeIdPair.node);
+ auto nodeFrom = _findChildById(defs, nodeIdPair.id);
+ _cloneNode(nodeFrom, nodeIdPair.node);
+ free(nodeIdPair.id);
+ }
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ SvgParserLengthType type;
+ int sz;
+ size_t offset;
+} useTags[] = {
+ {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)},
+ {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)},
+ {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)},
+ {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)}
+};
+
+
+static bool _attrParseUseNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgNode *defs, *nodeFrom, *node = loader->svgParse->node;
+ char* id;
+
+ SvgUseNode* use = &(node->node.use);
+ int sz = strlen(key);
+ unsigned char* array = (unsigned char*)use;
+ for (unsigned int i = 0; i < sizeof(useTags) / sizeof(useTags[0]); i++) {
+ if (useTags[i].sz - 1 == sz && !strncmp(useTags[i].tag, key, sz)) {
+ *((float*)(array + useTags[i].offset)) = _toFloat(loader->svgParse, value, useTags[i].type);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ id = _idFromHref(value);
+ defs = _getDefsNode(node);
+ nodeFrom = _findChildById(defs, id);
+ if (nodeFrom) {
+ _cloneNode(nodeFrom, node);
+ free(id);
+ } else {
+ //some svg export software include <defs> element at the end of the file
+ //if so the 'from' element won't be found now and we have to repeat finding
+ //after the whole file is parsed
+ _postponeCloneNode(loader, node, id);
+ }
+ } else if (!strcmp(key, "clip-path")) {
+ _handleClipPathAttr(loader, node, value);
+ } else if (!strcmp(key, "mask")) {
+ _handleMaskAttr(loader, node, value);
+ } else {
+ return _attrParseGNode(data, key, value);
+ }
+ return true;
+}
+
+
+static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength)
+{
+ loader->svgParse->node = _createNode(parent, SvgNodeType::Use);
+
+ if (!loader->svgParse->node) return nullptr;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseUseNode, loader);
+ return loader->svgParse->node;
+}
+
+//TODO: Implement 'text' primitive
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ FactoryMethod tagHandler;
+} graphicsTags[] = {
+ {"use", sizeof("use"), _createUseNode},
+ {"circle", sizeof("circle"), _createCircleNode},
+ {"ellipse", sizeof("ellipse"), _createEllipseNode},
+ {"path", sizeof("path"), _createPathNode},
+ {"polygon", sizeof("polygon"), _createPolygonNode},
+ {"rect", sizeof("rect"), _createRectNode},
+ {"polyline", sizeof("polyline"), _createPolylineNode},
+ {"line", sizeof("line"), _createLineNode},
+ {"image", sizeof("image"), _createImageNode}
+};
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ FactoryMethod tagHandler;
+} groupTags[] = {
+ {"defs", sizeof("defs"), _createDefsNode},
+ {"g", sizeof("g"), _createGNode},
+ {"svg", sizeof("svg"), _createSvgNode},
+ {"mask", sizeof("mask"), _createMaskNode},
+ {"clipPath", sizeof("clipPath"), _createClipPathNode}
+};
+
+
+#define FIND_FACTORY(Short_Name, Tags_Array) \
+ static FactoryMethod \
+ _find##Short_Name##Factory(const char* name) \
+ { \
+ unsigned int i; \
+ int sz = strlen(name); \
+ \
+ for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \
+ if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \
+ return Tags_Array[i].tagHandler; \
+ } \
+ } \
+ return nullptr; \
+ }
+
+FIND_FACTORY(Group, groupTags)
+FIND_FACTORY(Graphics, graphicsTags)
+
+
+FillSpread _parseSpreadValue(const char* value)
+{
+ auto spread = FillSpread::Pad;
+
+ if (!strcmp(value, "reflect")) {
+ spread = FillSpread::Reflect;
+ } else if (!strcmp(value, "repeat")) {
+ spread = FillSpread::Repeat;
+ }
+
+ return spread;
+}
+
+
+static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->cx = _gradientToFloat(loader->svgParse, value, radial->isCxPercentage);
+ if (!loader->svgParse->gradient.parsedFx) {
+ radial->fx = radial->cx;
+ radial->isFxPercentage = radial->isCxPercentage;
+ }
+}
+
+
+static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->cy = _gradientToFloat(loader->svgParse, value, radial->isCyPercentage);
+ if (!loader->svgParse->gradient.parsedFy) {
+ radial->fy = radial->cy;
+ radial->isFyPercentage = radial->isCyPercentage;
+ }
+}
+
+
+static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->fx = _gradientToFloat(loader->svgParse, value, radial->isFxPercentage);
+ loader->svgParse->gradient.parsedFx = true;
+}
+
+
+static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->fy = _gradientToFloat(loader->svgParse, value, radial->isFyPercentage);
+ loader->svgParse->gradient.parsedFy = true;
+}
+
+
+static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value)
+{
+ radial->r = _gradientToFloat(loader->svgParse, value, radial->isRPercentage);
+}
+
+
+static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isCxPercentage) radial->cx = radial->cx / loader->svgParse->global.w;
+}
+
+
+static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isCyPercentage) radial->cy = radial->cy / loader->svgParse->global.h;
+}
+
+
+static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isFxPercentage) radial->fx = radial->fx / loader->svgParse->global.w;
+}
+
+
+static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ if (userSpace && !radial->isFyPercentage) radial->fy = radial->fy / loader->svgParse->global.h;
+}
+
+
+static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace)
+{
+ // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
+ if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrtf(2.0));
+}
+
+
+typedef void (*radialMethod)(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value);
+typedef void (*radialMethodRecalc)(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace);
+
+
+#define RADIAL_DEF(Name, Name1) \
+ { \
+#Name, sizeof(#Name), _handleRadial##Name1##Attr, _recalcRadial##Name1##Attr \
+ }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ radialMethod tagHandler;
+ radialMethodRecalc tagRecalc;
+} radialTags[] = {
+ RADIAL_DEF(cx, Cx),
+ RADIAL_DEF(cy, Cy),
+ RADIAL_DEF(fx, Fx),
+ RADIAL_DEF(fy, Fy),
+ RADIAL_DEF(r, R)
+};
+
+
+static bool _attrParseRadialGradientNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgStyleGradient* grad = loader->svgParse->styleGrad;
+ SvgRadialGradient* radial = grad->radial;
+ int sz = strlen(key);
+
+ for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
+ if (radialTags[i].sz - 1 == sz && !strncmp(radialTags[i].tag, key, sz)) {
+ radialTags[i].tagHandler(loader, radial, value);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ grad->id = _copyId(value);
+ } else if (!strcmp(key, "spreadMethod")) {
+ grad->spread = _parseSpreadValue(value);
+ } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ grad->ref = _idFromHref(value);
+ } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) {
+ grad->userSpace = true;
+ } else if (!strcmp(key, "gradientTransform")) {
+ grad->transform = _parseTransformationMatrix(value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength)
+{
+ auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient)));
+ loader->svgParse->styleGrad = grad;
+
+ grad->type = SvgGradientType::Radial;
+ grad->userSpace = false;
+ grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient));
+ if (!grad->radial) {
+ grad->clear();
+ free(grad);
+ return nullptr;
+ }
+ /**
+ * Default values of gradient transformed into global percentage
+ */
+ grad->radial->cx = 0.5f;
+ grad->radial->cy = 0.5f;
+ grad->radial->fx = 0.5f;
+ grad->radial->fy = 0.5f;
+ grad->radial->r = 0.5f;
+ grad->radial->isCxPercentage = true;
+ grad->radial->isCyPercentage = true;
+ grad->radial->isFxPercentage = true;
+ grad->radial->isFyPercentage = true;
+ grad->radial->isRPercentage = true;
+
+ loader->svgParse->gradient.parsedFx = false;
+ loader->svgParse->gradient.parsedFy = false;
+ simpleXmlParseAttributes(buf, bufLength,
+ _attrParseRadialGradientNode, loader);
+
+ for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
+ radialTags[i].tagRecalc(loader, grad->radial, grad->userSpace);
+ }
+
+ return loader->svgParse->styleGrad;
+}
+
+
+static bool _attrParseStopsStyle(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ auto stop = &loader->svgParse->gradStop;
+
+ if (!strcmp(key, "stop-opacity")) {
+ stop->a = _toOpacity(value);
+ loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopOpacity);
+ } else if (!strcmp(key, "stop-color")) {
+ _toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
+ loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopColor);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool _attrParseStops(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ auto stop = &loader->svgParse->gradStop;
+
+ if (!strcmp(key, "offset")) {
+ stop->offset = _toOffset(value);
+ } else if (!strcmp(key, "stop-opacity")) {
+ if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopOpacity)) {
+ stop->a = _toOpacity(value);
+ }
+ } else if (!strcmp(key, "stop-color")) {
+ if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopColor)) {
+ _toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
+ }
+ } else if (!strcmp(key, "style")) {
+ simpleXmlParseW3CAttribute(value, _attrParseStopsStyle, data);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static void _handleLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->x1 = _gradientToFloat(loader->svgParse, value, linear->isX1Percentage);
+}
+
+
+static void _handleLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->y1 = _gradientToFloat(loader->svgParse, value, linear->isY1Percentage);
+}
+
+
+static void _handleLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->x2 = _gradientToFloat(loader->svgParse, value, linear->isX2Percentage);
+}
+
+
+static void _handleLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value)
+{
+ linear->y2 = _gradientToFloat(loader->svgParse, value, linear->isY2Percentage);
+}
+
+
+static void _recalcLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isX1Percentage) linear->x1 = linear->x1 / loader->svgParse->global.w;
+}
+
+
+static void _recalcLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isY1Percentage) linear->y1 = linear->y1 / loader->svgParse->global.h;
+}
+
+
+static void _recalcLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isX2Percentage) linear->x2 = linear->x2 / loader->svgParse->global.w;
+}
+
+
+static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace)
+{
+ if (userSpace && !linear->isY2Percentage) linear->y2 = linear->y2 / loader->svgParse->global.h;
+}
+
+
+typedef void (*Linear_Method)(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value);
+typedef void (*Linear_Method_Recalc)(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace);
+
+
+#define LINEAR_DEF(Name, Name1) \
+ { \
+#Name, sizeof(#Name), _handleLinear##Name1##Attr, _recalcLinear##Name1##Attr \
+ }
+
+
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ Linear_Method tagHandler;
+ Linear_Method_Recalc tagRecalc;
+} linear_tags[] = {
+ LINEAR_DEF(x1, X1),
+ LINEAR_DEF(y1, Y1),
+ LINEAR_DEF(x2, X2),
+ LINEAR_DEF(y2, Y2)
+};
+
+
+static bool _attrParseLinearGradientNode(void* data, const char* key, const char* value)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ SvgStyleGradient* grad = loader->svgParse->styleGrad;
+ SvgLinearGradient* linear = grad->linear;
+ int sz = strlen(key);
+
+ for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
+ if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) {
+ linear_tags[i].tagHandler(loader, linear, value);
+ return true;
+ }
+ }
+
+ if (!strcmp(key, "id")) {
+ grad->id = _copyId(value);
+ } else if (!strcmp(key, "spreadMethod")) {
+ grad->spread = _parseSpreadValue(value);
+ } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) {
+ grad->ref = _idFromHref(value);
+ } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) {
+ grad->userSpace = true;
+ } else if (!strcmp(key, "gradientTransform")) {
+ grad->transform = _parseTransformationMatrix(value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength)
+{
+ auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient)));
+ loader->svgParse->styleGrad = grad;
+
+ grad->type = SvgGradientType::Linear;
+ grad->userSpace = false;
+ grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient));
+ if (!grad->linear) {
+ grad->clear();
+ free(grad);
+ return nullptr;
+ }
+ /**
+ * Default value of x2 is 100% - transformed to the global percentage
+ */
+ grad->linear->x2 = 1.0f;
+ grad->linear->isX2Percentage = true;
+
+ simpleXmlParseAttributes(buf, bufLength, _attrParseLinearGradientNode, loader);
+
+ for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
+ linear_tags[i].tagRecalc(loader, grad->linear, grad->userSpace);
+ }
+
+ return loader->svgParse->styleGrad;
+}
+
+
+#define GRADIENT_DEF(Name, Name1) \
+ { \
+#Name, sizeof(#Name), _create##Name1 \
+ }
+
+
+/**
+ * In the case when the gradients lengths are given as numbers (not percentages)
+ * in the current user coordinate system, they are recalculated into percentages
+ * related to the canvas width and height.
+ */
+static constexpr struct
+{
+ const char* tag;
+ int sz;
+ GradientFactoryMethod tagHandler;
+} gradientTags[] = {
+ GRADIENT_DEF(linearGradient, LinearGradient),
+ GRADIENT_DEF(radialGradient, RadialGradient)
+};
+
+
+static GradientFactoryMethod _findGradientFactory(const char* name)
+{
+ int sz = strlen(name);
+
+ for (unsigned int i = 0; i < sizeof(gradientTags) / sizeof(gradientTags[0]); i++) {
+ if (gradientTags[i].sz - 1 == sz && !strncmp(gradientTags[i].tag, name, sz)) {
+ return gradientTags[i].tagHandler;
+ }
+ }
+ return nullptr;
+}
+
+
+static constexpr struct
+{
+ const char* tag;
+ size_t sz;
+} popArray[] = {
+ {"g", sizeof("g")},
+ {"svg", sizeof("svg")},
+ {"defs", sizeof("defs")},
+ {"mask", sizeof("mask")},
+ {"clipPath", sizeof("clipPath")}
+};
+
+
+static void _svgLoaderParerXmlClose(SvgLoaderData* loader, const char* content)
+{
+ content = _skipSpace(content, nullptr);
+
+ for (unsigned int i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) {
+ if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) {
+ loader->stack.pop();
+ break;
+ }
+ }
+
+ loader->level--;
+}
+
+
+static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length, bool empty)
+{
+ const char* attrs = nullptr;
+ int attrsLength = 0;
+ int sz = length;
+ char tagName[20] = "";
+ FactoryMethod method;
+ GradientFactoryMethod gradientMethod;
+ SvgNode *node = nullptr, *parent = nullptr;
+ loader->level++;
+ attrs = simpleXmlFindAttributesTag(content, length);
+
+ if (!attrs) {
+ //Parse the empty tag
+ attrs = content;
+ while ((attrs != nullptr) && *attrs != '>') attrs++;
+ }
+
+ if (attrs) {
+ //Find out the tag name starting from content till sz length
+ sz = attrs - content;
+ while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
+ if ((unsigned)sz >= sizeof(tagName)) return;
+ strncpy(tagName, content, sz);
+ tagName[sz] = '\0';
+ attrsLength = length - sz;
+ }
+
+ if ((method = _findGroupFactory(tagName))) {
+ //Group
+ if (!loader->doc) {
+ if (strcmp(tagName, "svg")) return; //Not a valid svg document
+ node = method(loader, nullptr, attrs, attrsLength);
+ loader->doc = node;
+ } else {
+ if (!strcmp(tagName, "svg")) return; //Already loaded <svg>(SvgNodeType::Doc) tag
+ if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
+ else parent = loader->doc;
+ node = method(loader, parent, attrs, attrsLength);
+ }
+
+ if (!node) return;
+ if (node->type != SvgNodeType::Defs || !empty) {
+ loader->stack.push(node);
+ }
+ } else if ((method = _findGraphicsFactory(tagName))) {
+ if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1];
+ else parent = loader->doc;
+ node = method(loader, parent, attrs, attrsLength);
+ } else if ((gradientMethod = _findGradientFactory(tagName))) {
+ SvgStyleGradient* gradient;
+ gradient = gradientMethod(loader, attrs, attrsLength);
+ //FIXME: The current parsing structure does not distinguish end tags.
+ // There is no way to know if the currently parsed gradient is in defs.
+ // If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs.
+ // But finally, the loader has a gradient style list regardless of defs.
+ // This is only to support this when multiple gradients are declared, even if no defs are declared.
+ // refer to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
+ if (loader->def && loader->doc->node.doc.defs) {
+ loader->def->node.defs.gradients.push(gradient);
+ } else {
+ loader->gradients.push(gradient);
+ }
+ loader->latestGradient = gradient;
+ } else if (!strcmp(tagName, "stop")) {
+ if (!loader->latestGradient) {
+ TVGLOG("SVG", "Stop element is used outside of the Gradient element");
+ return;
+ }
+ /* default value for opacity */
+ loader->svgParse->gradStop = {0.0f, 0, 0, 0, 255};
+ simpleXmlParseAttributes(attrs, attrsLength, _attrParseStops, loader);
+ loader->latestGradient->stops.push(loader->svgParse->gradStop);
+ } else if (!isIgnoreUnsupportedLogElements(tagName)) {
+ TVGLOG("SVG", "Unsupported elements used [Elements: %s]", tagName);
+ }
+}
+
+
+static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content, unsigned int length)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+
+ switch (type) {
+ case SimpleXMLType::Open: {
+ _svgLoaderParserXmlOpen(loader, content, length, false);
+ break;
+ }
+ case SimpleXMLType::OpenEmpty: {
+ _svgLoaderParserXmlOpen(loader, content, length, true);
+ break;
+ }
+ case SimpleXMLType::Close: {
+ _svgLoaderParerXmlClose(loader, content);
+ break;
+ }
+ case SimpleXMLType::Data:
+ case SimpleXMLType::CData:
+ case SimpleXMLType::DoctypeChild: {
+ break;
+ }
+ case SimpleXMLType::Ignored:
+ case SimpleXMLType::Comment:
+ case SimpleXMLType::Doctype: {
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent)
+{
+ if (parent == nullptr) return;
+ //Inherit the property of parent if not present in child.
+ //Fill
+ if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) {
+ child->fill.paint.color = parent->fill.paint.color;
+ child->fill.paint.none = parent->fill.paint.none;
+ child->fill.paint.curColor = parent->fill.paint.curColor;
+ if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url);
+ } else if (child->fill.paint.curColor && !child->curColorSet) {
+ child->color = parent->color;
+ }
+ if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) {
+ child->fill.opacity = parent->fill.opacity;
+ }
+ if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) {
+ child->fill.fillRule = parent->fill.fillRule;
+ }
+ //Stroke
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) {
+ child->stroke.paint.color = parent->stroke.paint.color;
+ child->stroke.paint.none = parent->stroke.paint.none;
+ child->stroke.paint.curColor = parent->stroke.paint.curColor;
+ child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url) : nullptr;
+ } else if (child->stroke.paint.curColor && !child->curColorSet) {
+ child->color = parent->color;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) {
+ child->stroke.opacity = parent->stroke.opacity;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) {
+ child->stroke.width = parent->stroke.width;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) {
+ if (parent->stroke.dash.array.count > 0) {
+ child->stroke.dash.array.clear();
+ child->stroke.dash.array.reserve(parent->stroke.dash.array.count);
+ for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) {
+ child->stroke.dash.array.push(parent->stroke.dash.array.data[i]);
+ }
+ }
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) {
+ child->stroke.cap = parent->stroke.cap;
+ }
+ if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) {
+ child->stroke.join = parent->stroke.join;
+ }
+}
+
+
+static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node){
+#ifdef THORVG_LOG_ENABLED
+ auto type = simpleXmlNodeTypeToString(node->type);
+
+ if (!node->display && node->type != SvgNodeType::ClipPath) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type);
+ if (node->style->opacity == 0) TVGLOG("SVG", "Inefficient elements used [Opacity is zero][Node Type : %s]", type);
+ if (node->style->fill.opacity == 0 && node->style->stroke.opacity == 0) TVGLOG("SVG", "Inefficient elements used [Fill opacity and stroke opacity are zero][Node Type : %s]", type);
+
+ switch (node->type) {
+ case SvgNodeType::Path: {
+ if (!node->node.path.path) TVGLOG("SVG", "Inefficient elements used [Empty path][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Ellipse: {
+ if (node->node.ellipse.rx == 0 && node->node.ellipse.ry == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Polygon:
+ case SvgNodeType::Polyline: {
+ if (node->node.polygon.pointsCount < 2) TVGLOG("SVG", "Inefficient elements used [Invalid Polygon][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Circle: {
+ if (node->node.circle.r == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Rect: {
+ if (node->node.rect.w == 0 && node->node.rect.h) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ case SvgNodeType::Line: {
+ if (node->node.line.x1 == node->node.line.x2 && node->node.line.y1 == node->node.line.y2) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type);
+ break;
+ }
+ default: break;
+ }
+#endif
+}
+
+
+static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle)
+{
+ _styleInherit(node->style, parentStyle);
+ _inefficientNodeCheck(node);
+
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _updateStyle(*child, node->style);
+ }
+}
+
+
+static SvgStyleGradient* _gradientDup(Array<SvgStyleGradient*>* gradients, const char* id)
+{
+ SvgStyleGradient* result = nullptr;
+
+ auto gradList = gradients->data;
+
+ for (uint32_t i = 0; i < gradients->count; ++i) {
+ if (!strcmp((*gradList)->id, id)) {
+ result = _cloneGradient(*gradList);
+ break;
+ }
+ ++gradList;
+ }
+
+ if (result && result->ref) {
+ gradList = gradients->data;
+ for (uint32_t i = 0; i < gradients->count; ++i) {
+ if (!strcmp((*gradList)->id, result->ref)) {
+ if (result->stops.count == 0) _cloneGradStops(result->stops, (*gradList)->stops);
+ //TODO: Properly inherit other property
+ break;
+ }
+ ++gradList;
+ }
+ }
+
+ return result;
+}
+
+
+static void _updateGradient(SvgNode* node, Array<SvgStyleGradient*>* gradients)
+{
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _updateGradient(*child, gradients);
+ }
+ } else {
+ if (node->style->fill.paint.url) {
+ if (node->style->fill.paint.gradient) {
+ node->style->fill.paint.gradient->clear();
+ free(node->style->fill.paint.gradient);
+ }
+ node->style->fill.paint.gradient = _gradientDup(gradients, node->style->fill.paint.url);
+ }
+ if (node->style->stroke.paint.url) {
+ if (node->style->stroke.paint.gradient) {
+ node->style->stroke.paint.gradient->clear();
+ free(node->style->stroke.paint.gradient);
+ }
+ node->style->stroke.paint.gradient = _gradientDup(gradients, node->style->stroke.paint.url);
+ }
+ }
+}
+
+
+static void _updateComposite(SvgNode* node, SvgNode* root)
+{
+ if (node->style->clipPath.url && !node->style->clipPath.node) {
+ SvgNode* findResult = _findNodeById(root, node->style->clipPath.url);
+ if (findResult) node->style->clipPath.node = findResult;
+ }
+ if (node->style->mask.url && !node->style->mask.node) {
+ SvgNode* findResult = _findNodeById(root, node->style->mask.url);
+ if (findResult) node->style->mask.node = findResult;
+ }
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _updateComposite(*child, root);
+ }
+ }
+}
+
+
+static void _freeNodeStyle(SvgStyleProperty* style)
+{
+ if (!style) return;
+
+ //style->clipPath.node and style->mask.node has only the addresses of node. Therefore, node is released from _freeNode.
+ free(style->clipPath.url);
+ free(style->mask.url);
+
+ if (style->fill.paint.gradient) {
+ style->fill.paint.gradient->clear();
+ free(style->fill.paint.gradient);
+ }
+ if (style->stroke.paint.gradient) {
+ style->stroke.paint.gradient->clear();
+ free(style->stroke.paint.gradient);
+ }
+ free(style->fill.paint.url);
+ free(style->stroke.paint.url);
+ style->stroke.dash.array.reset();
+ free(style);
+}
+
+
+static void _freeNode(SvgNode* node)
+{
+ if (!node) return;
+
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ _freeNode(*child);
+ }
+ node->child.reset();
+
+ free(node->id);
+ free(node->transform);
+ _freeNodeStyle(node->style);
+ switch (node->type) {
+ case SvgNodeType::Path: {
+ free(node->node.path.path);
+ break;
+ }
+ case SvgNodeType::Polygon: {
+ free(node->node.polygon.points);
+ break;
+ }
+ case SvgNodeType::Polyline: {
+ free(node->node.polyline.points);
+ break;
+ }
+ case SvgNodeType::Doc: {
+ _freeNode(node->node.doc.defs);
+ break;
+ }
+ case SvgNodeType::Defs: {
+ auto gradients = node->node.defs.gradients.data;
+ for (size_t i = 0; i < node->node.defs.gradients.count; ++i) {
+ (*gradients)->clear();
+ free(*gradients);
+ ++gradients;
+ }
+ node->node.defs.gradients.reset();
+ break;
+ }
+ case SvgNodeType::Image: {
+ free(node->node.image.href);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ free(node);
+}
+
+
+static bool _svgLoaderParserForValidCheckXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length)
+{
+ const char* attrs = nullptr;
+ int sz = length;
+ char tagName[20] = "";
+ FactoryMethod method;
+ SvgNode *node = nullptr;
+ int attrsLength = 0;
+ loader->level++;
+ attrs = simpleXmlFindAttributesTag(content, length);
+
+ if (!attrs) {
+ //Parse the empty tag
+ attrs = content;
+ while ((attrs != nullptr) && *attrs != '>') attrs++;
+ }
+
+ if (attrs) {
+ sz = attrs - content;
+ while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
+ if ((unsigned)sz >= sizeof(tagName)) return false;
+ strncpy(tagName, content, sz);
+ tagName[sz] = '\0';
+ attrsLength = length - sz;
+ }
+
+ if ((method = _findGroupFactory(tagName))) {
+ if (!loader->doc) {
+ if (strcmp(tagName, "svg")) return true; //Not a valid svg document
+ node = method(loader, nullptr, attrs, attrsLength);
+ loader->doc = node;
+ loader->stack.push(node);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static bool _svgLoaderParserForValidCheck(void* data, SimpleXMLType type, const char* content, unsigned int length)
+{
+ SvgLoaderData* loader = (SvgLoaderData*)data;
+ bool res = true;;
+
+ switch (type) {
+ case SimpleXMLType::Open:
+ case SimpleXMLType::OpenEmpty: {
+ //If 'res' is false, it means <svg> tag is found.
+ res = _svgLoaderParserForValidCheckXmlOpen(loader, content, length);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return res;
+}
+
+
+void SvgLoader::clear()
+{
+ if (copy) free((char*)content);
+ size = 0;
+ content = nullptr;
+ copy = false;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+SvgLoader::SvgLoader()
+{
+}
+
+
+SvgLoader::~SvgLoader()
+{
+ close();
+}
+
+
+void SvgLoader::run(unsigned tid)
+{
+ if (!simpleXmlParse(content, size, true, _svgLoaderParser, &(loaderData))) return;
+
+ if (loaderData.doc) {
+ _updateStyle(loaderData.doc, nullptr);
+ auto defs = loaderData.doc->node.doc.defs;
+ if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients);
+
+ if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients);
+
+ _updateComposite(loaderData.doc, loaderData.doc);
+ if (defs) _updateComposite(loaderData.doc, defs);
+
+ if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes);
+ }
+ root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, preserveAspect, svgPath);
+}
+
+
+bool SvgLoader::header()
+{
+ //For valid check, only <svg> tag is parsed first.
+ //If the <svg> tag is found, the loaded file is valid and stores viewbox information.
+ //After that, the remaining content data is parsed in order with async.
+ loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser));
+ if (!loaderData.svgParse) return false;
+
+ loaderData.svgParse->flags = SvgStopStyleFlags::StopDefault;
+
+ simpleXmlParse(content, size, true, _svgLoaderParserForValidCheck, &(loaderData));
+
+ if (loaderData.doc && loaderData.doc->type == SvgNodeType::Doc) {
+ //Return the brief resource info such as viewbox:
+ vx = loaderData.doc->node.doc.vx;
+ vy = loaderData.doc->node.doc.vy;
+ w = vw = loaderData.doc->node.doc.vw;
+ h = vh = loaderData.doc->node.doc.vh;
+
+ //Override size
+ if (loaderData.doc->node.doc.w > 0) {
+ w = loaderData.doc->node.doc.w;
+ if (vw < FLT_EPSILON) vw = w;
+ }
+ if (loaderData.doc->node.doc.h > 0) {
+ h = loaderData.doc->node.doc.h;
+ if (vh < FLT_EPSILON) vh = h;
+ }
+
+ preserveAspect = loaderData.doc->node.doc.preserveAspect;
+ } else {
+ TVGLOG("SVG", "No SVG File. There is no <svg/>");
+ return false;
+ }
+
+ return true;
+}
+
+
+bool SvgLoader::open(const char* data, uint32_t size, bool copy)
+{
+ clear();
+
+ if (copy) {
+ content = (char*)malloc(size);
+ if (!content) return false;
+ memcpy((char*)content, data, size);
+ } else content = data;
+
+ this->size = size;
+ this->copy = copy;
+
+ return header();
+}
+
+
+bool SvgLoader::open(const string& path)
+{
+ clear();
+
+ ifstream f;
+ f.open(path);
+
+ if (!f.is_open()) return false;
+
+ svgPath = path;
+ getline(f, filePath, '\0');
+ f.close();
+
+ if (filePath.empty()) return false;
+
+ content = filePath.c_str();
+ size = filePath.size();
+
+ return header();
+}
+
+
+bool SvgLoader::resize(Paint* paint, float w, float h)
+{
+ if (!paint) return false;
+
+ auto sx = w / this->w;
+ auto sy = h / this->h;
+
+ if (preserveAspect) {
+ //Scale
+ auto scale = sx < sy ? sx : sy;
+ paint->scale(scale);
+ //Align
+ auto tx = 0.0f;
+ auto ty = 0.0f;
+ auto tw = this->w * scale;
+ auto th = this->h * scale;
+ if (tw > th) ty -= (h - th) * 0.5f;
+ else tx -= (w - tw) * 0.5f;
+ paint->translate(-tx, -ty);
+ } else {
+ //Align
+ auto tx = 0.0f;
+ auto ty = 0.0f;
+ auto tw = this->w * sx;
+ auto th = this->h * sy;
+ if (tw > th) ty -= (h - th) * 0.5f;
+ else tx -= (w - tw) * 0.5f;
+
+ Matrix m = {sx, 0, -tx, 0, sy, -ty, 0, 0, 1};
+ paint->transform(m);
+ }
+ return true;
+}
+
+
+bool SvgLoader::read()
+{
+ if (!content || size == 0) return false;
+
+ TaskScheduler::request(this);
+
+ return true;
+}
+
+
+bool SvgLoader::close()
+{
+ this->done();
+
+ if (loaderData.svgParse) {
+ free(loaderData.svgParse);
+ loaderData.svgParse = nullptr;
+ }
+ auto gradients = loaderData.gradients.data;
+ for (size_t i = 0; i < loaderData.gradients.count; ++i) {
+ (*gradients)->clear();
+ free(*gradients);
+ ++gradients;
+ }
+ loaderData.gradients.reset();
+
+ _freeNode(loaderData.doc);
+ loaderData.doc = nullptr;
+ loaderData.stack.reset();
+
+ clear();
+
+ return true;
+}
+
+
+unique_ptr<Paint> SvgLoader::paint()
+{
+ this->done();
+ if (root) return move(root);
+ else return nullptr;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
new file mode 100644
index 0000000000..468f05801d
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SVG_LOADER_H_
+#define _TVG_SVG_LOADER_H_
+
+#include "tvgTaskScheduler.h"
+#include "tvgSvgLoaderCommon.h"
+
+class SvgLoader : public LoadModule, public Task
+{
+public:
+ string filePath;
+ string svgPath = "";
+ const char* content = nullptr;
+ uint32_t size = 0;
+
+ SvgLoaderData loaderData;
+ unique_ptr<Scene> root;
+
+ bool copy = false;
+
+ SvgLoader();
+ ~SvgLoader();
+
+ using LoadModule::open;
+ bool open(const string& path) override;
+ bool open(const char* data, uint32_t size, bool copy) override;
+ bool resize(Paint* paint, float w, float h) override;
+ bool read() override;
+ bool close() override;
+ unique_ptr<Paint> paint() override;
+
+private:
+ bool header();
+ void clear();
+ void run(unsigned tid) override;
+};
+
+
+#endif //_TVG_SVG_LOADER_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
new file mode 100644
index 0000000000..cceef915f0
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_SVG_LOADER_COMMON_H_
+#define _TVG_SVG_LOADER_COMMON_H_
+
+#include "tvgCommon.h"
+#include "tvgArray.h"
+
+struct SvgNode;
+struct SvgStyleGradient;
+
+//NOTE: Please update simpleXmlNodeTypeToString() as well.
+enum class SvgNodeType
+{
+ Doc,
+ G,
+ Defs,
+ Animation,
+ Arc,
+ Circle,
+ Ellipse,
+ Image,
+ Line,
+ Path,
+ Polygon,
+ Polyline,
+ Rect,
+ Text,
+ TextArea,
+ Tspan,
+ Use,
+ Video,
+ ClipPath,
+ Mask,
+ Unknown
+};
+
+/*
+// TODO - remove?
+enum class SvgLengthType
+{
+ Percent,
+ Px,
+ Pc,
+ Pt,
+ Mm,
+ Cm,
+ In,
+};
+*/
+
+enum class SvgFillFlags
+{
+ Paint = 0x01,
+ Opacity = 0x02,
+ Gradient = 0x04,
+ FillRule = 0x08,
+ ClipPath = 0x16
+};
+
+enum class SvgStrokeFlags
+{
+ Paint = 0x1,
+ Opacity = 0x2,
+ Gradient = 0x4,
+ Scale = 0x8,
+ Width = 0x10,
+ Cap = 0x20,
+ Join = 0x40,
+ Dash = 0x80,
+};
+
+enum class SvgGradientType
+{
+ Linear,
+ Radial
+};
+
+enum class SvgStyleFlags
+{
+ Color = 0x01,
+ Fill = 0x02,
+ FillRule = 0x04,
+ FillOpacity = 0x08,
+ Opacity = 0x010,
+ Stroke = 0x20,
+ StrokeWidth = 0x40,
+ StrokeLineJoin = 0x80,
+ StrokeLineCap = 0x100,
+ StrokeOpacity = 0x200,
+ StrokeDashArray = 0x400,
+ Transform = 0x800,
+ ClipPath = 0x1000,
+ Mask = 0x2000,
+ Display = 0x4000
+};
+
+enum class SvgStopStyleFlags
+{
+ StopDefault = 0x0,
+ StopOpacity = 0x01,
+ StopColor = 0x02
+};
+
+enum class SvgFillRule
+{
+ Winding = 0,
+ OddEven = 1
+};
+
+//Length type to recalculate %, pt, pc, mm, cm etc
+enum class SvgParserLengthType
+{
+ Vertical,
+ Horizontal,
+ //In case of, for example, radius of radial gradient
+ Other
+};
+
+struct SvgDocNode
+{
+ float w;
+ float h;
+ float vx;
+ float vy;
+ float vw;
+ float vh;
+ SvgNode* defs;
+ bool preserveAspect;
+};
+
+struct SvgGNode
+{
+};
+
+struct SvgDefsNode
+{
+ Array<SvgStyleGradient*> gradients;
+};
+
+struct SvgUseNode
+{
+ float x, y, w, h;
+};
+
+
+struct SvgEllipseNode
+{
+ float cx;
+ float cy;
+ float rx;
+ float ry;
+};
+
+struct SvgCircleNode
+{
+ float cx;
+ float cy;
+ float r;
+};
+
+struct SvgRectNode
+{
+ float x;
+ float y;
+ float w;
+ float h;
+ float rx;
+ float ry;
+ bool hasRx;
+ bool hasRy;
+};
+
+struct SvgLineNode
+{
+ float x1;
+ float y1;
+ float x2;
+ float y2;
+};
+
+struct SvgImageNode
+{
+ float x, y, w, h;
+ char* href;
+};
+
+struct SvgPathNode
+{
+ char* path;
+};
+
+struct SvgPolygonNode
+{
+ int pointsCount;
+ float* points;
+};
+
+struct SvgCompositeNode
+{
+ bool userSpace;
+};
+
+struct SvgLinearGradient
+{
+ float x1;
+ float y1;
+ float x2;
+ float y2;
+ bool isX1Percentage;
+ bool isY1Percentage;
+ bool isX2Percentage;
+ bool isY2Percentage;
+};
+
+struct SvgRadialGradient
+{
+ float cx;
+ float cy;
+ float fx;
+ float fy;
+ float r;
+ bool isCxPercentage;
+ bool isCyPercentage;
+ bool isFxPercentage;
+ bool isFyPercentage;
+ bool isRPercentage;
+};
+
+struct SvgComposite
+{
+ char *url;
+ SvgNode* node;
+ bool applying; //flag for checking circular dependency.
+};
+
+struct SvgColor
+{
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+};
+
+struct SvgPaint
+{
+ SvgStyleGradient* gradient;
+ char *url;
+ SvgColor color;
+ bool none;
+ bool curColor;
+};
+
+struct SvgDash
+{
+ Array<float> array;
+};
+
+struct SvgStyleGradient
+{
+ SvgGradientType type;
+ char* id;
+ char* ref;
+ FillSpread spread;
+ SvgRadialGradient* radial;
+ SvgLinearGradient* linear;
+ Matrix* transform;
+ Array<Fill::ColorStop> stops;
+ bool userSpace;
+
+ void clear()
+ {
+ stops.reset();
+ free(transform);
+ free(radial);
+ free(linear);
+ free(ref);
+ free(id);
+ }
+};
+
+struct SvgStyleFill
+{
+ SvgFillFlags flags;
+ SvgPaint paint;
+ int opacity;
+ FillRule fillRule;
+};
+
+struct SvgStyleStroke
+{
+ SvgStrokeFlags flags;
+ SvgPaint paint;
+ int opacity;
+ float scale;
+ float width;
+ float centered;
+ StrokeCap cap;
+ StrokeJoin join;
+ SvgDash dash;
+ int dashCount;
+};
+
+struct SvgStyleProperty
+{
+ SvgStyleFill fill;
+ SvgStyleStroke stroke;
+ SvgComposite clipPath;
+ SvgComposite mask;
+ int opacity;
+ SvgColor color;
+ bool curColorSet;
+ SvgStyleFlags flags;
+};
+
+struct SvgNode
+{
+ SvgNodeType type;
+ SvgNode* parent;
+ Array<SvgNode*> child;
+ char *id;
+ SvgStyleProperty *style;
+ Matrix* transform;
+ union {
+ SvgGNode g;
+ SvgDocNode doc;
+ SvgDefsNode defs;
+ SvgUseNode use;
+ SvgCircleNode circle;
+ SvgEllipseNode ellipse;
+ SvgPolygonNode polygon;
+ SvgPolygonNode polyline;
+ SvgRectNode rect;
+ SvgPathNode path;
+ SvgLineNode line;
+ SvgImageNode image;
+ SvgCompositeNode comp;
+ } node;
+ bool display;
+ ~SvgNode();
+};
+
+struct SvgParser
+{
+ SvgNode* node;
+ SvgStyleGradient* styleGrad;
+ Fill::ColorStop gradStop;
+ SvgStopStyleFlags flags;
+ struct
+ {
+ int x, y;
+ uint32_t w, h;
+ } global;
+ struct
+ {
+ bool parsedFx;
+ bool parsedFy;
+ } gradient;
+};
+
+struct SvgNodeIdPair
+{
+ SvgNode* node;
+ char *id;
+};
+
+struct SvgLoaderData
+{
+ Array<SvgNode *> stack = {nullptr, 0, 0};
+ SvgNode* doc = nullptr;
+ SvgNode* def = nullptr;
+ Array<SvgStyleGradient*> gradients;
+ SvgStyleGradient* latestGradient = nullptr; //For stops
+ SvgParser* svgParse = nullptr;
+ Array<SvgNodeIdPair> cloneNodes;
+ int level = 0;
+ bool result = false;
+};
+
+/*
+ * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtod-l-wcstod-wcstod-l?view=vs-2017
+ *
+ * src should be one of the following form :
+ *
+ * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits]
+ * [whitespace] [sign] {INF | INFINITY}
+ * [whitespace] [sign] NAN [sequence]
+ *
+ * No hexadecimal form supported
+ * no sequence supported after NAN
+ */
+float customStrtof(const char *nptr, char **endptr);
+
+#endif
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
new file mode 100644
index 0000000000..2b62315de8
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright notice for the EFL:
+
+ * Copyright (C) EFL developers (see AUTHORS)
+
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
+
+#include <math.h>
+#include <clocale>
+#include <ctype.h>
+#include "tvgSvgLoaderCommon.h"
+#include "tvgSvgPath.h"
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static char* _skipComma(const char* content)
+{
+ while (*content && isspace(*content)) {
+ content++;
+ }
+ if (*content == ',') return (char*)content + 1;
+ return (char*)content;
+}
+
+
+static bool _parseNumber(char** content, float* number)
+{
+ char* end = NULL;
+ *number = svgUtilStrtof(*content, &end);
+ //If the start of string is not number
+ if ((*content) == end) return false;
+ //Skip comma if any
+ *content = _skipComma(end);
+ return true;
+}
+
+
+static bool _parseFlag(char** content, int* number)
+{
+ char* end = NULL;
+ if (*(*content) != '0' && *(*content) != '1') return false;
+ *number = *(*content) - '0';
+ *content += 1;
+ end = *content;
+ *content = _skipComma(end);
+
+ return true;
+}
+
+
+void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
+{
+ float cxp, cyp, cx, cy;
+ float sx, sy;
+ float cosPhi, sinPhi;
+ float dx2, dy2;
+ float x1p, y1p;
+ float x1p2, y1p2;
+ float rx2, ry2;
+ float lambda;
+ float c;
+ float at;
+ float theta1, deltaTheta;
+ float nat;
+ float delta, bcp;
+ float cosPhiRx, cosPhiRy;
+ float sinPhiRx, sinPhiRy;
+ float cosTheta1, sinTheta1;
+ int segments;
+
+ //Some helpful stuff is available here:
+ //http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+ sx = cur->x;
+ sy = cur->y;
+
+ //If start and end points are identical, then no arc is drawn
+ if ((fabsf(x - sx) < (1.0f / 256.0f)) && (fabsf(y - sy) < (1.0f / 256.0f))) return;
+
+ //Correction of out-of-range radii, see F6.6.1 (step 2)
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+
+ angle = angle * M_PI / 180.0f;
+ cosPhi = cosf(angle);
+ sinPhi = sinf(angle);
+ dx2 = (sx - x) / 2.0f;
+ dy2 = (sy - y) / 2.0f;
+ x1p = cosPhi * dx2 + sinPhi * dy2;
+ y1p = cosPhi * dy2 - sinPhi * dx2;
+ x1p2 = x1p * x1p;
+ y1p2 = y1p * y1p;
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+ lambda = (x1p2 / rx2) + (y1p2 / ry2);
+
+ //Correction of out-of-range radii, see F6.6.2 (step 4)
+ if (lambda > 1.0f) {
+ //See F6.6.3
+ float lambdaRoot = sqrtf(lambda);
+
+ rx *= lambdaRoot;
+ ry *= lambdaRoot;
+ //Update rx2 and ry2
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+ }
+
+ c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
+
+ //Check if there is no possible solution
+ //(i.e. we can't do a square root of a negative value)
+ if (c < 0.0f) {
+ //Scale uniformly until we have a single solution
+ //(see F6.2) i.e. when c == 0.0
+ float scale = sqrtf(1.0f - c / (rx2 * ry2));
+ rx *= scale;
+ ry *= scale;
+ //Update rx2 and ry2
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+
+ //Step 2 (F6.5.2) - simplified since c == 0.0
+ cxp = 0.0f;
+ cyp = 0.0f;
+ //Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
+ cx = 0.0f;
+ cy = 0.0f;
+ } else {
+ //Complete c calculation
+ c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
+ //Inverse sign if Fa == Fs
+ if (largeArc == sweep) c = -c;
+
+ //Step 2 (F6.5.2)
+ cxp = c * (rx * y1p / ry);
+ cyp = c * (-ry * x1p / rx);
+
+ //Step 3 (F6.5.3 first part)
+ cx = cosPhi * cxp - sinPhi * cyp;
+ cy = sinPhi * cxp + cosPhi * cyp;
+ }
+
+ //Step 3 (F6.5.3 second part) we now have the center point of the ellipse
+ cx += (sx + x) / 2.0f;
+ cy += (sy + y) / 2.0f;
+
+ //Sstep 4 (F6.5.4)
+ //We dont' use arccos (as per w3c doc), see
+ //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
+ //Note: atan2 (0.0, 1.0) == 0.0
+ at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
+ theta1 = (at < 0.0f) ? 2.0f * M_PI + at : at;
+
+ nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
+ deltaTheta = (nat < at) ? 2.0f * M_PI - at + nat : nat - at;
+
+ if (sweep) {
+ //Ensure delta theta < 0 or else add 360 degrees
+ if (deltaTheta < 0.0f) deltaTheta += (float)(2.0f * M_PI);
+ } else {
+ //Ensure delta theta > 0 or else substract 360 degrees
+ if (deltaTheta > 0.0f) deltaTheta -= (float)(2.0f * M_PI);
+ }
+
+ //Add several cubic bezier to approximate the arc
+ //(smaller than 90 degrees)
+ //We add one extra segment because we want something
+ //Smaller than 90deg (i.e. not 90 itself)
+ segments = static_cast<int>(fabsf(deltaTheta / float(M_PI_2)) + 1.0f);
+ delta = deltaTheta / segments;
+
+ //http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
+ bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
+
+ cosPhiRx = cosPhi * rx;
+ cosPhiRy = cosPhi * ry;
+ sinPhiRx = sinPhi * rx;
+ sinPhiRy = sinPhi * ry;
+
+ cosTheta1 = cosf(theta1);
+ sinTheta1 = sinf(theta1);
+
+ for (int i = 0; i < segments; ++i) {
+ //End angle (for this segment) = current + delta
+ float c1x, c1y, ex, ey, c2x, c2y;
+ float theta2 = theta1 + delta;
+ float cosTheta2 = cosf(theta2);
+ float sinTheta2 = sinf(theta2);
+ Point p[3];
+
+ //First control point (based on start point sx,sy)
+ c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
+ c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
+
+ //End point (for this segment)
+ ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
+ ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
+
+ //Second control point (based on end point ex,ey)
+ c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
+ c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {c1x, c1y};
+ p[1] = {c2x, c2y};
+ p[2] = {ex, ey};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = p[1];
+ *cur = p[2];
+
+ //Next start point is the current end point (same for angle)
+ sx = ex;
+ sy = ey;
+ theta1 = theta2;
+ //Avoid recomputations
+ cosTheta1 = cosTheta2;
+ sinTheta1 = sinTheta2;
+ }
+}
+
+static int _numberCount(char cmd)
+{
+ int count = 0;
+ switch (cmd) {
+ case 'M':
+ case 'm':
+ case 'L':
+ case 'l':
+ case 'T':
+ case 't': {
+ count = 2;
+ break;
+ }
+ case 'C':
+ case 'c':
+ case 'E':
+ case 'e': {
+ count = 6;
+ break;
+ }
+ case 'H':
+ case 'h':
+ case 'V':
+ case 'v': {
+ count = 1;
+ break;
+ }
+ case 'S':
+ case 's':
+ case 'Q':
+ case 'q': {
+ count = 4;
+ break;
+ }
+ case 'A':
+ case 'a': {
+ count = 7;
+ break;
+ }
+ default:
+ break;
+ }
+ return count;
+}
+
+
+static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic)
+{
+ switch (cmd) {
+ case 'm':
+ case 'l':
+ case 'c':
+ case 's':
+ case 'q':
+ case 't': {
+ for (int i = 0; i < count - 1; i += 2) {
+ arr[i] = arr[i] + cur->x;
+ arr[i + 1] = arr[i + 1] + cur->y;
+ }
+ break;
+ }
+ case 'h': {
+ arr[0] = arr[0] + cur->x;
+ break;
+ }
+ case 'v': {
+ arr[0] = arr[0] + cur->y;
+ break;
+ }
+ case 'a': {
+ arr[5] = arr[5] + cur->x;
+ arr[6] = arr[6] + cur->y;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ switch (cmd) {
+ case 'm':
+ case 'M': {
+ Point p = {arr[0], arr[1]};
+ cmds->push(PathCommand::MoveTo);
+ pts->push(p);
+ *cur = {arr[0], arr[1]};
+ *startPoint = {arr[0], arr[1]};
+ break;
+ }
+ case 'l':
+ case 'L': {
+ Point p = {arr[0], arr[1]};
+ cmds->push(PathCommand::LineTo);
+ pts->push(p);
+ *cur = {arr[0], arr[1]};
+ break;
+ }
+ case 'c':
+ case 'C': {
+ Point p[3];
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {arr[0], arr[1]};
+ p[1] = {arr[2], arr[3]};
+ p[2] = {arr[4], arr[5]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = p[1];
+ *cur = p[2];
+ *isQuadratic = false;
+ break;
+ }
+ case 's':
+ case 'S': {
+ Point p[3], ctrl;
+ if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) &&
+ !(*isQuadratic)) {
+ ctrl.x = 2 * cur->x - curCtl->x;
+ ctrl.y = 2 * cur->y - curCtl->y;
+ } else {
+ ctrl = *cur;
+ }
+ cmds->push(PathCommand::CubicTo);
+ p[0] = ctrl;
+ p[1] = {arr[0], arr[1]};
+ p[2] = {arr[2], arr[3]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = p[1];
+ *cur = p[2];
+ *isQuadratic = false;
+ break;
+ }
+ 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);
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {ctrl_x0, ctrl_y0};
+ p[1] = {ctrl_x1, ctrl_y1};
+ p[2] = {arr[2], arr[3]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = {arr[0], arr[1]};
+ *cur = p[2];
+ *isQuadratic = true;
+ break;
+ }
+ case 't':
+ case 'T': {
+ Point p[3], ctrl;
+ if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) &&
+ *isQuadratic) {
+ ctrl.x = 2 * cur->x - curCtl->x;
+ ctrl.y = 2 * cur->y - curCtl->y;
+ } 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);
+ cmds->push(PathCommand::CubicTo);
+ p[0] = {ctrl_x0, ctrl_y0};
+ p[1] = {ctrl_x1, ctrl_y1};
+ p[2] = {arr[0], arr[1]};
+ pts->push(p[0]);
+ pts->push(p[1]);
+ pts->push(p[2]);
+ *curCtl = {ctrl.x, ctrl.y};
+ *cur = p[2];
+ *isQuadratic = true;
+ break;
+ }
+ case 'h':
+ case 'H': {
+ Point p = {arr[0], cur->y};
+ cmds->push(PathCommand::LineTo);
+ pts->push(p);
+ cur->x = arr[0];
+ break;
+ }
+ case 'v':
+ case 'V': {
+ Point p = {cur->x, arr[0]};
+ cmds->push(PathCommand::LineTo);
+ pts->push(p);
+ cur->y = arr[0];
+ break;
+ }
+ case 'z':
+ case 'Z': {
+ cmds->push(PathCommand::Close);
+ *cur = *startPoint;
+ break;
+ }
+ case 'a':
+ case 'A': {
+ _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], arr[0], arr[1], arr[2], arr[3], arr[4]);
+ *cur = *curCtl = {arr[5], arr[6]};
+ *isQuadratic = false;
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static char* _nextCommand(char* path, char* cmd, float* arr, int* count)
+{
+ int large, sweep;
+
+ path = _skipComma(path);
+ if (isalpha(*path)) {
+ *cmd = *path;
+ path++;
+ *count = _numberCount(*cmd);
+ } else {
+ if (*cmd == 'm') *cmd = 'l';
+ else if (*cmd == 'M') *cmd = 'L';
+ }
+ if (*count == 7) {
+ //Special case for arc command
+ if (_parseNumber(&path, &arr[0])) {
+ if (_parseNumber(&path, &arr[1])) {
+ if (_parseNumber(&path, &arr[2])) {
+ if (_parseFlag(&path, &large)) {
+ if (_parseFlag(&path, &sweep)) {
+ if (_parseNumber(&path, &arr[5])) {
+ if (_parseNumber(&path, &arr[6])) {
+ arr[3] = (float)large;
+ arr[4] = (float)sweep;
+ return path;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ *count = 0;
+ return NULL;
+ }
+ for (int i = 0; i < *count; i++) {
+ if (!_parseNumber(&path, &arr[i])) {
+ *count = 0;
+ return NULL;
+ }
+ path = _skipComma(path);
+ }
+ return path;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+bool svgPathToTvgPath(const char* svgPath, Array<PathCommand>& cmds, Array<Point>& pts)
+{
+ float numberArray[7];
+ int numberCount = 0;
+ Point cur = { 0, 0 };
+ Point curCtl = { 0, 0 };
+ Point startPoint = { 0, 0 };
+ char cmd = 0;
+ bool isQuadratic = false;
+ char* path = (char*)svgPath;
+ char* curLocale;
+
+ curLocale = setlocale(LC_NUMERIC, NULL);
+ if (curLocale) curLocale = strdup(curLocale);
+ setlocale(LC_NUMERIC, "POSIX");
+
+ while ((path[0] != '\0')) {
+ path = _nextCommand(path, &cmd, numberArray, &numberCount);
+ if (!path) break;
+ if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic)) break;
+ }
+
+ setlocale(LC_NUMERIC, curLocale);
+ if (curLocale) free(curLocale);
+
+ return true;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h
new file mode 100644
index 0000000000..7f26c4a213
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SVG_PATH_H_
+#define _TVG_SVG_PATH_H_
+
+#include <tvgCommon.h>
+
+bool svgPathToTvgPath(const char* svgPath, Array<PathCommand>& cmds, Array<Point>& pts);
+
+#endif //_TVG_SVG_PATH_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
new file mode 100644
index 0000000000..8701fe32b1
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright notice for the EFL:
+
+ * Copyright (C) EFL developers (see AUTHORS)
+
+ * All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <string>
+#include "tvgMath.h"
+#include "tvgSvgLoaderCommon.h"
+#include "tvgSvgSceneBuilder.h"
+#include "tvgSvgPath.h"
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct Box
+{
+ float x, y, w, h;
+};
+
+
+static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
+static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask);
+
+
+static inline bool _isGroupType(SvgNodeType type)
+{
+ if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true;
+ return false;
+}
+
+
+//According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
+//a stroke width should be ignored for bounding box calculations
+static Box _boundingBox(const Shape* shape)
+{
+ float x, y, w, h;
+ shape->bounds(&x, &y, &w, &h, false);
+
+ if (auto strokeW = shape->strokeWidth()) {
+ x += 0.5f * strokeW;
+ y += 0.5f * strokeW;
+ w -= strokeW;
+ h -= strokeW;
+ }
+
+ return {x, y, w, h};
+}
+
+
+static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
+{
+ gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
+ gradTransf->e12 *= mBBox->e11;
+ gradTransf->e11 *= mBBox->e11;
+
+ gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
+ gradTransf->e22 *= mBBox->e22;
+ gradTransf->e21 *= mBBox->e22;
+}
+
+
+static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
+{
+ Fill::ColorStop* stops;
+ int stopCount = 0;
+ auto fillGrad = LinearGradient::gen();
+
+ bool isTransform = (g->transform ? true : false);
+ Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+ if (isTransform) finalTransform = *g->transform;
+
+ if (g->userSpace) {
+ g->linear->x1 = g->linear->x1 * vBox.w;
+ g->linear->y1 = g->linear->y1 * vBox.h;
+ g->linear->x2 = g->linear->x2 * vBox.w;
+ g->linear->y2 = g->linear->y2 * vBox.h;
+ } else {
+ Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
+ if (isTransform) _transformMultiply(&m, &finalTransform);
+ else {
+ finalTransform = m;
+ isTransform = true;
+ }
+ }
+
+ if (isTransform) fillGrad->transform(finalTransform);
+
+ fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
+ fillGrad->spread(g->spread);
+
+ //Update the stops
+ stopCount = g->stops.count;
+ if (stopCount > 0) {
+ stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
+ if (!stops) return fillGrad;
+ auto prevOffset = 0.0f;
+ for (uint32_t i = 0; i < g->stops.count; ++i) {
+ auto colorStop = &g->stops.data[i];
+ //Use premultiplied color
+ stops[i].r = colorStop->r;
+ stops[i].g = colorStop->g;
+ stops[i].b = colorStop->b;
+ stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
+ stops[i].offset = colorStop->offset;
+ //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
+ if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
+ else if (colorStop->offset > 1) stops[i].offset = 1;
+ prevOffset = stops[i].offset;
+ }
+ fillGrad->colorStops(stops, stopCount);
+ free(stops);
+ }
+ return fillGrad;
+}
+
+
+static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
+{
+ Fill::ColorStop *stops;
+ int stopCount = 0;
+ auto fillGrad = RadialGradient::gen();
+
+ bool isTransform = (g->transform ? true : false);
+ Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+ if (isTransform) finalTransform = *g->transform;
+
+ if (g->userSpace) {
+ //The radius scalling is done according to the Units section:
+ //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
+ g->radial->cx = g->radial->cx * vBox.w;
+ g->radial->cy = g->radial->cy * vBox.h;
+ g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
+ g->radial->fx = g->radial->fx * vBox.w;
+ g->radial->fy = g->radial->fy * vBox.h;
+ } else {
+ Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
+ if (isTransform) _transformMultiply(&m, &finalTransform);
+ else {
+ finalTransform = m;
+ isTransform = true;
+ }
+ }
+
+ if (isTransform) fillGrad->transform(finalTransform);
+
+ //TODO: Tvg is not support to focal
+ //if (g->radial->fx != 0 && g->radial->fy != 0) {
+ // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
+ //}
+ fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
+ fillGrad->spread(g->spread);
+
+ //Update the stops
+ stopCount = g->stops.count;
+ if (stopCount > 0) {
+ stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
+ if (!stops) return fillGrad;
+ auto prevOffset = 0.0f;
+ for (uint32_t i = 0; i < g->stops.count; ++i) {
+ auto colorStop = &g->stops.data[i];
+ //Use premultiplied color
+ stops[i].r = colorStop->r;
+ stops[i].g = colorStop->g;
+ stops[i].b = colorStop->b;
+ stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
+ stops[i].offset = colorStop->offset;
+ //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
+ if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
+ else if (colorStop->offset > 1) stops[i].offset = 1;
+ prevOffset = stops[i].offset;
+ }
+ fillGrad->colorStops(stops, stopCount);
+ free(stops);
+ }
+ return fillGrad;
+}
+
+
+static bool _appendChildShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
+{
+ auto valid = false;
+
+ if (_appendShape(node, shape, vBox, svgPath)) valid = true;
+
+ if (node->child.count > 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ if (_appendChildShape(*child, shape, vBox, svgPath)) valid = true;
+ }
+ }
+
+ return valid;
+}
+
+
+static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ /* ClipPath */
+ /* Do not drop in Circular Dependency for ClipPath.
+ Composition can be applied recursively if its children nodes have composition target to this one. */
+ if (node->style->clipPath.applying) {
+ TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
+ } else {
+ auto compNode = node->style->clipPath.node;
+ if (compNode && compNode->child.count > 0) {
+ node->style->clipPath.applying = true;
+
+ auto comp = Shape::gen();
+ comp->fill(255, 255, 255, 255);
+ if (node->transform) comp->transform(*node->transform);
+
+ auto child = compNode->child.data;
+ auto valid = false; //Composite only when valid shapes are existed
+
+ for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
+ if (_appendChildShape(*child, comp.get(), vBox, svgPath)) valid = true;
+ }
+
+ if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);
+
+ node->style->clipPath.applying = false;
+ }
+ }
+
+ /* Mask */
+ /* Do not drop in Circular Dependency for Mask.
+ Composition can be applied recursively if its children nodes have composition target to this one. */
+ if (node->style->mask.applying) {
+ TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
+ } else {
+ auto compNode = node->style->mask.node;
+ if (compNode && compNode->child.count > 0) {
+ node->style->mask.applying = true;
+
+ auto comp = _sceneBuildHelper(compNode, vBox, svgPath, true);
+ if (comp) {
+ if (node->transform) comp->transform(*node->transform);
+ paint->composite(move(comp), CompositeMethod::AlphaMask);
+ }
+
+ node->style->mask.applying = false;
+ }
+ }
+}
+
+
+static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath)
+{
+ SvgStyleProperty* style = node->style;
+
+ if (node->transform) vg->transform(*node->transform);
+ if (node->type == SvgNodeType::Doc || !node->display) return;
+
+ //If fill property is nullptr then do nothing
+ if (style->fill.paint.none) {
+ //Do nothing
+ } else if (style->fill.paint.gradient) {
+ Box bBox = vBox;
+ if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
+
+ if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
+ auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
+ vg->fill(move(linear));
+ } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
+ auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
+ vg->fill(move(radial));
+ }
+ } else if (style->fill.paint.url) {
+ //TODO: Apply the color pointed by url
+ } else if (style->fill.paint.curColor) {
+ //Apply the current style color
+ vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
+ } else {
+ //Apply the fill color
+ vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
+ }
+
+ //Apply the fill rule
+ vg->fill((tvg::FillRule)style->fill.fillRule);
+
+ //Apply node opacity
+ if (style->opacity < 255) vg->opacity(style->opacity);
+
+ if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
+
+ //Apply the stroke style property
+ vg->stroke(style->stroke.width);
+ vg->stroke(style->stroke.cap);
+ vg->stroke(style->stroke.join);
+ if (style->stroke.dash.array.count > 0) {
+ vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
+ }
+
+ //If stroke property is nullptr then do nothing
+ if (style->stroke.paint.none) {
+ //Do nothing
+ } else if (style->stroke.paint.gradient) {
+ Box bBox = vBox;
+ if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
+
+ if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
+ auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
+ vg->stroke(move(linear));
+ } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
+ auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
+ vg->stroke(move(radial));
+ }
+ } else if (style->stroke.paint.url) {
+ //TODO: Apply the color pointed by url
+ } else if (style->stroke.paint.curColor) {
+ //Apply the current style color
+ vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
+ } else {
+ //Apply the stroke color
+ vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
+ }
+
+ _applyComposition(vg, node, vBox, svgPath);
+}
+
+
+static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ auto shape = Shape::gen();
+ if (_appendShape(node, shape.get(), vBox, svgPath)) return shape;
+ else return nullptr;
+}
+
+
+static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
+{
+ Array<PathCommand> cmds;
+ Array<Point> pts;
+
+ switch (node->type) {
+ case SvgNodeType::Path: {
+ if (node->node.path.path) {
+ if (svgPathToTvgPath(node->node.path.path, cmds, pts)) {
+ shape->appendPath(cmds.data, cmds.count, pts.data, pts.count);
+ }
+ }
+ break;
+ }
+ case SvgNodeType::Ellipse: {
+ shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
+ break;
+ }
+ case SvgNodeType::Polygon: {
+ if (node->node.polygon.pointsCount < 2) break;
+ shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
+ for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
+ shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
+ }
+ shape->close();
+ break;
+ }
+ case SvgNodeType::Polyline: {
+ if (node->node.polygon.pointsCount < 2) break;
+ shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
+ for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
+ shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
+ }
+ break;
+ }
+ case SvgNodeType::Circle: {
+ shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
+ break;
+ }
+ case SvgNodeType::Rect: {
+ shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
+ break;
+ }
+ case SvgNodeType::Line: {
+ shape->moveTo(node->node.line.x1, node->node.line.y1);
+ shape->lineTo(node->node.line.x2, node->node.line.y2);
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ _applyProperty(node, shape, vBox, svgPath);
+ return true;
+}
+
+
+enum class imageMimeTypeEncoding
+{
+ base64 = 0x1,
+ utf8 = 0x2
+};
+constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
+ return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
+}
+constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
+ return (static_cast<int>(a) & static_cast<int>(b));
+}
+
+
+static constexpr struct
+{
+ const char* name;
+ int sz;
+ imageMimeTypeEncoding encoding;
+} imageMimeTypes[] = {
+ {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
+ {"png", sizeof("png"), imageMimeTypeEncoding::base64},
+ {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
+};
+
+
+static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
+ if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
+ *href += sizeof("image/") - 1;
+
+ //RFC2397 data:[<mediatype>][;base64],<data>
+ //mediatype := [ type "/" subtype ] *( ";" parameter )
+ //parameter := attribute "=" value
+ for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
+ if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
+ *href += imageMimeTypes[i].sz - 1;
+ *mimetype = imageMimeTypes[i].name;
+
+ while (**href && **href != ',') {
+ while (**href && **href != ';') ++(*href);
+ if (!**href) return false;
+ ++(*href);
+
+ if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
+ if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
+ *href += sizeof("base64,") - 1;
+ *encoding = imageMimeTypeEncoding::base64;
+ return true; //valid base64
+ }
+ }
+ if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
+ if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
+ *href += sizeof("utf8,") - 1;
+ *encoding = imageMimeTypeEncoding::utf8;
+ return true; //valid utf8
+ }
+ }
+ }
+ //no encoding defined
+ if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
+ ++(*href);
+ *encoding = imageMimeTypeEncoding::utf8;
+ return true; //allow no encoding defined if utf8 expected
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+
+static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ if (!node->node.image.href) return nullptr;
+ auto picture = Picture::gen();
+
+ const char* href = node->node.image.href;
+ if (!strncmp(href, "data:", sizeof("data:") - 1)) {
+ href += sizeof("data:") - 1;
+ const char* mimetype;
+ imageMimeTypeEncoding encoding;
+ if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
+ if (encoding == imageMimeTypeEncoding::base64) {
+ string decoded = svgUtilBase64Decode(href);
+ if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
+ } else {
+ string decoded = svgUtilURLDecode(href);
+ if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
+ }
+ } else {
+ if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
+ //TODO: protect against recursive svg image loading
+ //Temporarily disable embedded svg:
+ const char *dot = strrchr(href, '.');
+ if (dot && !strcmp(dot, ".svg")) {
+ TVGLOG("SVG", "Embedded svg file is disabled.");
+ return nullptr;
+ }
+ string imagePath = href;
+ if (strncmp(href, "/", 1)) {
+ auto last = svgPath.find_last_of("/");
+ imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath;
+ }
+ if (picture->load(imagePath) != Result::Success) return nullptr;
+ }
+
+ float w, h;
+ if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
+ auto sx = node->node.image.w / w;
+ auto sy = node->node.image.h / h;
+ Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
+ picture->transform(m);
+ }
+
+ _applyComposition(picture.get(), node, vBox, svgPath);
+ return picture;
+}
+
+
+static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath)
+{
+ auto scene = _sceneBuildHelper(node, vBox, svgPath, false);
+ if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
+ scene->translate(node->node.use.x, node->node.use.y);
+ }
+ if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
+ //TODO: handle width/height properties
+ }
+ return scene;
+}
+
+
+static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox, const string& svgPath, bool mask)
+{
+ if (_isGroupType(node->type) || mask) {
+ auto scene = Scene::gen();
+ if (!mask && node->transform) scene->transform(*node->transform);
+
+ if (node->display && node->style->opacity != 0) {
+ auto child = node->child.data;
+ for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
+ if (_isGroupType((*child)->type)) {
+ if ((*child)->type == SvgNodeType::Use)
+ scene->push(_useBuildHelper(*child, vBox, svgPath));
+ else
+ scene->push(_sceneBuildHelper(*child, vBox, svgPath, false));
+ } else if ((*child)->type == SvgNodeType::Image) {
+ auto image = _imageBuildHelper(*child, vBox, svgPath);
+ if (image) scene->push(move(image));
+ } else if ((*child)->type != SvgNodeType::Mask) {
+ auto shape = _shapeBuildHelper(*child, vBox, svgPath);
+ if (shape) scene->push(move(shape));
+ }
+ }
+ _applyComposition(scene.get(), node, vBox, svgPath);
+ scene->opacity(node->style->opacity);
+ }
+ return scene;
+ }
+ return nullptr;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
+{
+ if (!node || (node->type != SvgNodeType::Doc)) return nullptr;
+
+ Box vBox = {vx, vy, vw, vh};
+ auto docNode = _sceneBuildHelper(node, vBox, svgPath, false);
+
+ if (!mathEqual(w, vw) || !mathEqual(h, vh)) {
+ auto sx = w / vw;
+ auto sy = h / vh;
+
+ if (preserveAspect) {
+ //Scale
+ auto scale = sx < sy ? sx : sy;
+ docNode->scale(scale);
+ //Align
+ auto tvx = vx * scale;
+ auto tvy = vy * scale;
+ auto tvw = vw * scale;
+ auto tvh = vh * scale;
+ if (vw > vh) tvy -= (h - tvh) * 0.5f;
+ else tvx -= (w - tvw) * 0.5f;
+ docNode->translate(-tvx, -tvy);
+ } else {
+ //Align
+ auto tvx = vx * sx;
+ auto tvy = vy * sy;
+ auto tvw = vw * sx;
+ auto tvh = vh * sy;
+ if (tvw > tvh) tvy -= (h - tvh) * 0.5f;
+ else tvx -= (w - tvw) * 0.5f;
+ Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
+ docNode->transform(m);
+ }
+ } else if (!mathZero(vx) || !mathZero(vy)) {
+ docNode->translate(-vx, -vy);
+ }
+
+ auto viewBoxClip = Shape::gen();
+ viewBoxClip->appendRect(0, 0, w, h, 0, 0);
+ viewBoxClip->fill(0, 0, 0, 255);
+
+ auto compositeLayer = Scene::gen();
+ compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
+ compositeLayer->push(move(docNode));
+
+ auto root = Scene::gen();
+ root->push(move(compositeLayer));
+
+ return root;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
new file mode 100644
index 0000000000..4232aca612
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SVG_SCENE_BUILDER_H_
+#define _TVG_SVG_SCENE_BUILDER_H_
+
+#include "tvgCommon.h"
+
+unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath);
+
+#endif //_TVG_SVG_SCENE_BUILDER_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
new file mode 100644
index 0000000000..d5b9cdcf7b
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <math.h>
+#include <memory.h>
+#include "tvgSvgUtil.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static inline bool _floatExact(float a, float b)
+{
+ return memcmp(&a, &b, sizeof(float)) == 0;
+}
+
+static uint8_t _hexCharToDec(const char c)
+{
+ if (c >= 'a') return c - 'a' + 10;
+ else if (c >= 'A') return c - 'A' + 10;
+ else return c - '0';
+}
+
+static uint8_t _base64Value(const char chr)
+{
+ if (chr >= 'A' && chr <= 'Z') return chr - 'A';
+ else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1;
+ else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
+ else if (chr == '+' || chr == '-') return 62;
+ else return 63;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+
+/*
+ * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160
+ *
+ * src should be one of the following form :
+ *
+ * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits]
+ * [whitespace] [sign] {INF | INFINITY}
+ * [whitespace] [sign] NAN [sequence]
+ *
+ * No hexadecimal form supported
+ * no sequence supported after NAN
+ */
+float svgUtilStrtof(const char *nPtr, char **endPtr)
+{
+ if (endPtr) *endPtr = (char*)(nPtr);
+ if (!nPtr) return 0.0f;
+
+ auto a = nPtr;
+ auto iter = nPtr;
+ auto val = 0.0f;
+ unsigned long long integerPart = 0;
+ int minus = 1;
+
+ //ignore leading whitespaces
+ while (isspace(*iter)) iter++;
+
+ //signed or not
+ if (*iter == '-') {
+ minus = -1;
+ iter++;
+ } else if (*iter == '+') {
+ iter++;
+ }
+
+ if (tolower(*iter) == 'i') {
+ if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3;
+ else goto error;
+
+ if (tolower(*(iter + 3)) == 'i') {
+ if ((tolower(*(iter + 4)) == 'n') && (tolower(*(iter + 5)) == 'i') && (tolower(*(iter + 6)) == 't') && (tolower(*(iter + 7)) == 'y')) iter += 5;
+ else goto error;
+ }
+ if (endPtr) *endPtr = (char *)(iter);
+ return (minus == -1) ? -INFINITY : INFINITY;
+ }
+
+ if (tolower(*iter) == 'n') {
+ if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3;
+ else goto error;
+
+ if (endPtr) *endPtr = (char *)(iter);
+ return (minus == -1) ? -NAN : NAN;
+ }
+
+ //Optional: integer part before dot
+ if (isdigit(*iter)) {
+ for (; isdigit(*iter); iter++) {
+ integerPart = integerPart * 10ULL + (unsigned long long)(*iter - '0');
+ }
+ a = iter;
+ } else if (*iter != '.') {
+ goto success;
+ }
+
+ val = static_cast<float>(integerPart);
+
+ //Optional: decimal part after dot
+ if (*iter == '.') {
+ unsigned long long decimalPart = 0;
+ unsigned long long pow10 = 1;
+ int count = 0;
+
+ iter++;
+
+ if (isdigit(*iter)) {
+ for (; isdigit(*iter); iter++, count++) {
+ if (count < 19) {
+ decimalPart = decimalPart * 10ULL + + static_cast<unsigned long long>(*iter - '0');
+ pow10 *= 10ULL;
+ }
+ }
+ }
+ val += static_cast<float>(decimalPart) / static_cast<float>(pow10);
+ a = iter;
+ }
+
+ //Optional: exponent
+ if (*iter == 'e' || *iter == 'E') {
+ ++iter;
+
+ //Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em
+ if ((*iter == 'm') || (*iter == 'M')) {
+ //TODO: We don't support font em unit now, but has to multiply val * font size later...
+ a = iter + 1;
+ goto success;
+ }
+
+ //signed or not
+ int minus_e = 1;
+
+ if (*iter == '-') {
+ minus_e = -1;
+ ++iter;
+ } else if (*iter == '+') {
+ iter++;
+ }
+
+ unsigned int exponentPart = 0;
+
+ if (isdigit(*iter)) {
+ while (*iter == '0') iter++;
+ for (; isdigit(*iter); iter++) {
+ exponentPart = exponentPart * 10U + static_cast<unsigned int>(*iter - '0');
+ }
+ } else if (!isdigit(*(a - 1))) {
+ a = nPtr;
+ goto success;
+ } else if (*iter == 0) {
+ goto success;
+ }
+
+ //if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast<int>(exponentPart)) <= -308)) {
+ if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast<int>(exponentPart)) <= -38)) {
+ //val *= 1.0e-308f;
+ val *= 1.0e-38f;
+ a = iter;
+ goto success;
+ }
+
+ a = iter;
+ auto scale = 1.0f;
+
+ while (exponentPart >= 8U) {
+ scale *= 1E8;
+ exponentPart -= 8U;
+ }
+ while (exponentPart > 0U) {
+ scale *= 10.0f;
+ exponentPart--;
+ }
+ val = (minus_e == -1) ? (val / scale) : (val * scale);
+ } else if ((iter > nPtr) && !isdigit(*(iter - 1))) {
+ a = nPtr;
+ goto success;
+ }
+
+success:
+ if (endPtr) *endPtr = (char*)(a);
+ return minus * val;
+
+error:
+ if (endPtr) *endPtr = (char*)(nPtr);
+ return 0.0f;
+}
+
+
+string svgUtilURLDecode(const char *src)
+{
+ if (!src) return nullptr;
+
+ auto length = strlen(src);
+ if (length == 0) return nullptr;
+
+ string decoded;
+ decoded.reserve(length);
+
+ char a, b;
+ while (*src) {
+ if (*src == '%' &&
+ ((a = src[1]) && (b = src[2])) &&
+ (isxdigit(a) && isxdigit(b))) {
+ decoded += (_hexCharToDec(a) << 4) + _hexCharToDec(b);
+ src+=3;
+ } else if (*src == '+') {
+ decoded += ' ';
+ src++;
+ } else {
+ decoded += *src++;
+ }
+ }
+ return decoded;
+}
+
+
+string svgUtilBase64Decode(const char *src)
+{
+ if (!src) return nullptr;
+
+ auto length = strlen(src);
+ if (length == 0) return nullptr;
+
+ string decoded;
+ decoded.reserve(3*(1+(length >> 2)));
+
+ while (*src && *(src+1)) {
+ if (*src <= 0x20) {
+ ++src;
+ continue;
+ }
+
+ auto value1 = _base64Value(src[0]);
+ auto value2 = _base64Value(src[1]);
+ decoded += (value1 << 2) + ((value2 & 0x30) >> 4);
+
+ if (!src[2] || src[2] == '=' || src[2] == '.') break;
+ auto value3 = _base64Value(src[2]);
+ decoded += ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2);
+
+ if (!src[3] || src[3] == '=' || src[3] == '.') break;
+ auto value4 = _base64Value(src[3]);
+ decoded += ((value3 & 0x03) << 6) + value4;
+ src += 4;
+ }
+ return decoded;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h
new file mode 100644
index 0000000000..4320cfed4e
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SVG_UTIL_H_
+#define _TVG_SVG_UTIL_H_
+
+#include "tvgCommon.h"
+
+float svgUtilStrtof(const char *nPtr, char **endPtr);
+
+string svgUtilURLDecode(const char *src);
+string svgUtilBase64Decode(const char *src);
+
+#endif //_TVG_SVG_UTIL_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
new file mode 100644
index 0000000000..2e3d5928d9
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <string>
+
+#ifdef _WIN32
+ #include <malloc.h>
+#else
+ #include <alloca.h>
+#endif
+
+#include "tvgXmlParser.h"
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
+{
+#ifdef THORVG_LOG_ENABLED
+ const auto attributesNum = 6;
+ const struct
+ {
+ const char* tag;
+ bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
+ const char* value;
+ } attributes[] = {
+ {"id", false, nullptr},
+ {"data-name", false, nullptr},
+ {"overflow", false, "visible"},
+ {"version", false, nullptr},
+ {"xmlns", true, nullptr},
+ {"xml:space", false, nullptr},
+ };
+
+ for (unsigned int i = 0; i < attributesNum; ++i) {
+ if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
+ if (attributes[i].value && tagValue) {
+ if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
+ return true;
+ } else continue;
+ }
+ return true;
+ }
+ }
+ return false;
+#endif
+ return true;
+}
+
+
+static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if (isspace((unsigned char)*itr)) break;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if (!isspace((unsigned char)*itr)) break;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
+{
+ for (itr--; itr > itrStart; itr--) {
+ if (!isspace((unsigned char)*itr)) break;
+ }
+ return itr + 1;
+}
+
+
+static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
+{
+ auto p = itr;
+ while (itr < itrEnd && *itr == '&') {
+ for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
+ if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
+ itr += xmlEntityLength[i];
+ break;
+ }
+ }
+ if (itr == p) break;
+ p = itr;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
+{
+ auto p = itr;
+ while (itr > itrStart && *(itr - 1) == ';') {
+ for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
+ if (itr - xmlEntityLength[i] > itrStart &&
+ strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
+ itr -= xmlEntityLength[i];
+ break;
+ }
+ }
+ if (itr == p) break;
+ p = itr;
+ }
+ return itr;
+}
+
+
+static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
+{
+ itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
+ auto p = itr;
+ while (true) {
+ if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
+ else break;
+ if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
+ else break;
+ }
+ return itr;
+}
+
+
+static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
+{
+ itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
+ auto p = itr;
+ while (true) {
+ if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
+ else break;
+ if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
+ else break;
+ }
+ return itr;
+}
+
+
+static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
+{
+ return (const char*)memchr(itr, '<', itrEnd - itr);
+}
+
+
+static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
+{
+ bool insideQuote = false;
+ for (; itr < itrEnd; itr++) {
+ if (*itr == '"') insideQuote = !insideQuote;
+ if (!insideQuote) {
+ if ((*itr == '>') || (*itr == '<'))
+ return itr;
+ }
+ }
+ return nullptr;
+}
+
+
+static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
+ }
+ return nullptr;
+}
+
+
+static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
+ }
+ return nullptr;
+}
+
+
+static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
+{
+ for (; itr < itrEnd; itr++) {
+ if (*itr == '>') return itr;
+ }
+ return nullptr;
+}
+
+
+static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
+{
+ toff = 0;
+ if (itr[1] == '/') {
+ toff = 1;
+ return SimpleXMLType::Close;
+ } else if (itr[1] == '?') {
+ toff = 1;
+ return SimpleXMLType::Processing;
+ } else if (itr[1] == '!') {
+ if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) {
+ toff = sizeof("!DOCTYPE") - 1;
+ return SimpleXMLType::Doctype;
+ } else if (itr + sizeof("<!>") - 1 < itrEnd) {
+ toff = sizeof("!") - 1;
+ return SimpleXMLType::DoctypeChild;
+ } else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
+ toff = sizeof("![CDATA[") - 1;
+ return SimpleXMLType::CData;
+ } else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
+ toff = sizeof("!--") - 1;
+ return SimpleXMLType::Comment;
+ }
+ return SimpleXMLType::Open;
+ }
+ return SimpleXMLType::Open;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
+{
+#ifdef THORVG_LOG_ENABLED
+ static const char* TYPE_NAMES[] = {
+ "Svg",
+ "G",
+ "Defs",
+ "Animation",
+ "Arc",
+ "Circle",
+ "Ellipse",
+ "Image",
+ "Line",
+ "Path",
+ "Polygon",
+ "Polyline",
+ "Rect",
+ "Text",
+ "TextArea",
+ "Tspan",
+ "Use",
+ "Video",
+ "ClipPath",
+ "Mask",
+ "Unknown",
+ };
+ return TYPE_NAMES[(int) type];
+#endif
+ return nullptr;
+}
+
+
+bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
+{
+#ifdef THORVG_LOG_ENABLED
+ const auto elementsNum = 1;
+ const char* const elements[] = { "title" };
+
+ for (unsigned int i = 0; i < elementsNum; ++i) {
+ if (!strncmp(tagName, elements[i], strlen(tagName))) {
+ return true;
+ }
+ }
+ return false;
+#else
+ return true;
+#endif
+}
+
+
+bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
+{
+ const char *itr = buf, *itrEnd = buf + bufLength;
+ char* tmpBuf = (char*)alloca(bufLength + 1);
+
+ if (!buf || !func) return false;
+
+ while (itr < itrEnd) {
+ const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
+ const char *key, *keyEnd, *value, *valueEnd;
+ char* tval;
+
+ if (p == itrEnd) return true;
+
+ key = p;
+ for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
+ if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
+ }
+ if (keyEnd == itrEnd) return false;
+ if (keyEnd == key) continue;
+
+ if (*keyEnd == '=') value = keyEnd + 1;
+ else {
+ value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
+ if (!value) return false;
+ value++;
+ }
+ keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
+
+ value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
+ if (value == itrEnd) return false;
+
+ if ((*value == '"') || (*value == '\'')) {
+ valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
+ if (!valueEnd) return false;
+ value++;
+ } else {
+ valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
+ }
+
+ itr = valueEnd + 1;
+
+ value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
+ valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
+
+ memcpy(tmpBuf, key, keyEnd - key);
+ tmpBuf[keyEnd - key] = '\0';
+
+ tval = tmpBuf + (keyEnd - key) + 1;
+ int i = 0;
+ while (value < valueEnd) {
+ value = _simpleXmlSkipXmlEntities(value, valueEnd);
+ tval[i++] = *value;
+ value++;
+ }
+ tval[i] = '\0';
+
+ if (!func((void*)data, tmpBuf, tval)) {
+ if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
+ TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", tmpBuf, tval ? tval : "NONE");
+ }
+ }
+ }
+ return true;
+}
+
+
+bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
+{
+ const char *itr = buf, *itrEnd = buf + bufLength;
+
+ if (!buf || !func) return false;
+
+ while (itr < itrEnd) {
+ if (itr[0] == '<') {
+ //Invalid case
+ if (itr + 1 >= itrEnd) return false;
+
+ size_t toff = 0;
+ SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
+
+ const char* p;
+ if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
+ else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
+ else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
+ else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
+
+ if (p) {
+ //Invalid case: '<' nested
+ if (*p == '<') return false;
+ const char *start, *end;
+
+ start = itr + 1 + toff;
+ end = p;
+
+ switch (type) {
+ case SimpleXMLType::Open: {
+ if (p[-1] == '/') {
+ type = SimpleXMLType::OpenEmpty;
+ end--;
+ }
+ break;
+ }
+ case SimpleXMLType::CData: {
+ if (!memcmp(p - 2, "]]", 2)) end -= 2;
+ break;
+ }
+ case SimpleXMLType::Processing: {
+ if (p[-1] == '?') end--;
+ break;
+ }
+ case SimpleXMLType::Comment: {
+ if (!memcmp(p - 2, "--", 2)) end -= 2;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ if (strip && (type != SimpleXMLType::CData)) {
+ start = _skipWhiteSpacesAndXmlEntities(start, end);
+ end = _unskipWhiteSpacesAndXmlEntities(end, start);
+ }
+
+ if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
+
+ itr = p + 1;
+ } else {
+ return false;
+ }
+ } else {
+ const char *p, *end;
+
+ if (strip) {
+ p = itr;
+ p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
+ if (p) {
+ if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
+ itr = p;
+ }
+ }
+
+ p = _simpleXmlFindStartTag(itr, itrEnd);
+ if (!p) p = itrEnd;
+
+ end = p;
+ if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
+
+ if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
+
+ if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
+
+ itr = p;
+ }
+ }
+ return true;
+}
+
+
+bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data)
+{
+ const char* end;
+ char* key;
+ char* val;
+ char* next;
+
+ if (!buf) return false;
+
+ end = buf + strlen(buf);
+ key = (char*)alloca(end - buf + 1);
+ val = (char*)alloca(end - buf + 1);
+
+ if (buf == end) return true;
+
+ do {
+ char* sep = (char*)strchr(buf, ':');
+ next = (char*)strchr(buf, ';');
+
+ key[0] = '\0';
+ val[0] = '\0';
+
+ if (next == nullptr && sep != nullptr) {
+ memcpy(key, buf, sep - buf);
+ key[sep - buf] = '\0';
+
+ memcpy(val, sep + 1, end - sep - 1);
+ val[end - sep - 1] = '\0';
+ } else if (sep < next && sep != nullptr) {
+ memcpy(key, buf, sep - buf);
+ key[sep - buf] = '\0';
+
+ memcpy(val, sep + 1, next - sep - 1);
+ val[next - sep - 1] = '\0';
+ } else if (next) {
+ memcpy(key, buf, next - buf);
+ key[next - buf] = '\0';
+ }
+
+ if (key[0]) {
+ key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
+ key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
+ val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
+ val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
+
+ if (!func((void*)data, key, val)) {
+ if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
+ TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", key, val ? val : "NONE");
+ }
+ }
+ }
+
+ buf = next + 1;
+ } while (next != nullptr);
+
+ return true;
+}
+
+
+const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
+{
+ const char *itr = buf, *itrEnd = buf + bufLength;
+
+ for (; itr < itrEnd; itr++) {
+ if (!isspace((unsigned char)*itr)) {
+ //User skip tagname and already gave it the attributes.
+ if (*itr == '=') return buf;
+ } else {
+ itr = _simpleXmlUnskipXmlEntities(itr, buf);
+ if (itr == itrEnd) return nullptr;
+ return itr;
+ }
+ }
+
+ return nullptr;
+}
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h
new file mode 100644
index 0000000000..d96a631528
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_SIMPLE_XML_PARSER_H_
+#define _TVG_SIMPLE_XML_PARSER_H_
+
+#include "tvgSvgLoaderCommon.h"
+
+#define NUMBER_OF_XML_ENTITIES 8
+const char* const xmlEntity[] = {"&quot;", "&nbsp;", "&apos;", "&amp;", "&lt;", "&gt;", "&#035;", "&#039;"};
+const int xmlEntityLength[] = {6, 6, 6, 5, 4, 4, 6, 6};
+
+enum class SimpleXMLType
+{
+ Open = 0, //!< \<tag attribute="value"\>
+ OpenEmpty, //!< \<tag attribute="value" /\>
+ Close, //!< \</tag\>
+ Data, //!< tag text data
+ CData, //!< \<![cdata[something]]\>
+ Error, //!< error contents
+ Processing, //!< \<?xml ... ?\> \<?php .. ?\>
+ Doctype, //!< \<!doctype html
+ Comment, //!< \<!-- something --\>
+ Ignored, //!< whatever is ignored by parser, like whitespace
+ DoctypeChild //!< \<!doctype_child
+};
+
+typedef bool (*simpleXMLCb)(void* data, SimpleXMLType type, const char* content, unsigned int length);
+typedef bool (*simpleXMLAttributeCb)(void* data, const char* key, const char* value);
+
+bool simpleXmlParseAttributes(const char* buf, unsigned buflen, simpleXMLAttributeCb func, const void* data);
+bool simpleXmlParse(const char* buf, unsigned buflen, bool strip, simpleXMLCb func, const void* data);
+bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data);
+const char *simpleXmlFindAttributesTag(const char* buf, unsigned buflen);
+bool isIgnoreUnsupportedLogElements(const char* tagName);
+const char* simpleXmlNodeTypeToString(SvgNodeType type);
+
+#endif //_TVG_SIMPLE_XML_PARSER_H_
diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp b/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp
new file mode 100644
index 0000000000..b0364b1055
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgBinInterpreter.cpp
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <memory.h>
+
+#ifdef _WIN32
+ #include <malloc.h>
+#else
+ #include <alloca.h>
+#endif
+
+#include "tvgTvgCommon.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+struct TvgBinBlock
+{
+ TvgBinTag type;
+ TvgBinCounter length;
+ const char* data;
+ const char* end;
+};
+
+static Paint* _parsePaint(TvgBinBlock baseBlock);
+
+
+static TvgBinBlock _readBlock(const char *ptr)
+{
+ TvgBinBlock block;
+ block.type = *ptr;
+ READ_UI32(&block.length, ptr + SIZE(TvgBinTag));
+ block.data = ptr + SIZE(TvgBinTag) + SIZE(TvgBinCounter);
+ block.end = block.data + block.length;
+ return block;
+}
+
+
+static bool _parseCmpTarget(const char *ptr, const char *end, Paint *paint)
+{
+ auto block = _readBlock(ptr);
+ if (block.end > end) return false;
+
+ if (block.type != TVG_TAG_PAINT_CMP_METHOD) return false;
+ if (block.length != SIZE(TvgBinFlag)) return false;
+
+ auto cmpMethod = static_cast<CompositeMethod>(*block.data);
+
+ ptr = block.end;
+
+ auto cmpBlock = _readBlock(ptr);
+ if (cmpBlock.end > end) return false;
+
+ paint->composite(unique_ptr<Paint>(_parsePaint(cmpBlock)), cmpMethod);
+
+ return true;
+}
+
+
+static bool _parsePaintProperty(TvgBinBlock block, Paint *paint)
+{
+ switch (block.type) {
+ case TVG_TAG_PAINT_OPACITY: {
+ if (block.length != SIZE(uint8_t)) return false;
+ paint->opacity(*block.data);
+ return true;
+ }
+ case TVG_TAG_PAINT_TRANSFORM: {
+ if (block.length != SIZE(Matrix)) return false;
+ Matrix matrix;
+ memcpy(&matrix, block.data, SIZE(Matrix));
+ paint->transform(matrix);
+ return true;
+ }
+ case TVG_TAG_PAINT_CMP_TARGET: {
+ if (block.length < SIZE(TvgBinTag) + SIZE(TvgBinCounter)) return false;
+ return _parseCmpTarget(block.data, block.end, paint);
+ }
+ }
+ return false;
+}
+
+
+static bool _parseScene(TvgBinBlock block, Paint *paint)
+{
+ auto scene = static_cast<Scene*>(paint);
+
+ //Case1: scene reserve count
+ if (block.type == TVG_TAG_SCENE_RESERVEDCNT) {
+ if (block.length != SIZE(uint32_t)) return false;
+ uint32_t reservedCnt;
+ READ_UI32(&reservedCnt, block.data);
+ scene->reserve(reservedCnt);
+ return true;
+ }
+
+ //Case2: Base Paint Properties
+ if (_parsePaintProperty(block, scene)) return true;
+
+ //Case3: A Child paint
+ if (auto paint = _parsePaint(block)) {
+ scene->push(unique_ptr<Paint>(paint));
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool _parseShapePath(const char *ptr, const char *end, Shape *shape)
+{
+ uint32_t cmdCnt, ptsCnt;
+
+ READ_UI32(&cmdCnt, ptr);
+ ptr += SIZE(cmdCnt);
+
+ READ_UI32(&ptsCnt, ptr);
+ ptr += SIZE(ptsCnt);
+
+ auto cmds = (TvgBinFlag*) ptr;
+ ptr += SIZE(TvgBinFlag) * cmdCnt;
+
+ auto pts = (Point*) ptr;
+ ptr += SIZE(Point) * ptsCnt;
+
+ if (ptr > end) return false;
+
+ /* Recover to PathCommand(4 bytes) from TvgBinFlag(1 byte) */
+ PathCommand* inCmds = (PathCommand*)alloca(sizeof(PathCommand) * cmdCnt);
+ for (uint32_t i = 0; i < cmdCnt; ++i) {
+ inCmds[i] = static_cast<PathCommand>(cmds[i]);
+ }
+
+ shape->appendPath(inCmds, cmdCnt, pts, ptsCnt);
+
+ return true;
+}
+
+
+static unique_ptr<Fill> _parseShapeFill(const char *ptr, const char *end)
+{
+ unique_ptr<Fill> fillGrad;
+
+ while (ptr < end) {
+ auto block = _readBlock(ptr);
+ if (block.end > end) return nullptr;
+
+ switch (block.type) {
+ case TVG_TAG_FILL_RADIAL_GRADIENT: {
+ if (block.length != 3 * SIZE(float)) return nullptr;
+
+ auto ptr = block.data;
+ float x, y, radius;
+
+ READ_FLOAT(&x, ptr);
+ ptr += SIZE(float);
+ READ_FLOAT(&y, ptr);
+ ptr += SIZE(float);
+ READ_FLOAT(&radius, ptr);
+
+ auto fillGradRadial = RadialGradient::gen();
+ fillGradRadial->radial(x, y, radius);
+ fillGrad = move(fillGradRadial);
+ break;
+ }
+ case TVG_TAG_FILL_LINEAR_GRADIENT: {
+ if (block.length != 4 * SIZE(float)) return nullptr;
+
+ auto ptr = block.data;
+ float x1, y1, x2, y2;
+
+ READ_FLOAT(&x1, ptr);
+ ptr += SIZE(float);
+ READ_FLOAT(&y1, ptr);
+ ptr += SIZE(float);
+ READ_FLOAT(&x2, ptr);
+ ptr += SIZE(float);
+ READ_FLOAT(&y2, ptr);
+
+ auto fillGradLinear = LinearGradient::gen();
+ fillGradLinear->linear(x1, y1, x2, y2);
+ fillGrad = move(fillGradLinear);
+ break;
+ }
+ case TVG_TAG_FILL_FILLSPREAD: {
+ if (!fillGrad) return nullptr;
+ if (block.length != SIZE(TvgBinFlag)) return nullptr;
+ fillGrad->spread((FillSpread) *block.data);
+ break;
+ }
+ case TVG_TAG_FILL_COLORSTOPS: {
+ if (!fillGrad) return nullptr;
+ if (block.length == 0 || block.length & 0x07) return nullptr;
+ uint32_t stopsCnt = block.length >> 3; // 8 bytes per ColorStop
+ if (stopsCnt > 1023) return nullptr;
+ Fill::ColorStop* stops = (Fill::ColorStop*)alloca(sizeof(Fill::ColorStop) * stopsCnt);
+ auto p = block.data;
+ for (uint32_t i = 0; i < stopsCnt; i++, p += 8) {
+ READ_FLOAT(&stops[i].offset, p);
+ stops[i].r = p[4];
+ stops[i].g = p[5];
+ stops[i].b = p[6];
+ stops[i].a = p[7];
+ }
+ fillGrad->colorStops(stops, stopsCnt);
+ break;
+ }
+ case TVG_TAG_FILL_TRANSFORM: {
+ if (!fillGrad || block.length != SIZE(Matrix)) return nullptr;
+ Matrix gradTransform;
+ memcpy(&gradTransform, block.data, SIZE(Matrix));
+ fillGrad->transform(gradTransform);
+ break;
+ }
+ default: {
+ TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of the fill properties, %d bytes skipped", block.type, block.type, block.length);
+ break;
+ }
+ }
+ ptr = block.end;
+ }
+ return fillGrad;
+}
+
+
+static bool _parseShapeStrokeDashPattern(const char *ptr, const char *end, Shape *shape)
+{
+ uint32_t dashPatternCnt;
+ READ_UI32(&dashPatternCnt, ptr);
+ ptr += SIZE(uint32_t);
+ if (dashPatternCnt > 0) {
+ float* dashPattern = static_cast<float*>(malloc(sizeof(float) * dashPatternCnt));
+ if (!dashPattern) return false;
+ memcpy(dashPattern, ptr, sizeof(float) * dashPatternCnt);
+ ptr += SIZE(float) * dashPatternCnt;
+
+ if (ptr > end) {
+ free(dashPattern);
+ return false;
+ }
+
+ shape->stroke(dashPattern, dashPatternCnt);
+ free(dashPattern);
+ }
+ return true;
+}
+
+
+static bool _parseShapeStroke(const char *ptr, const char *end, Shape *shape)
+{
+ while (ptr < end) {
+ auto block = _readBlock(ptr);
+ if (block.end > end) return false;
+
+ switch (block.type) {
+ case TVG_TAG_SHAPE_STROKE_CAP: {
+ if (block.length != SIZE(TvgBinFlag)) return false;
+ shape->stroke((StrokeCap) *block.data);
+ break;
+ }
+ case TVG_TAG_SHAPE_STROKE_JOIN: {
+ if (block.length != SIZE(TvgBinFlag)) return false;
+ shape->stroke((StrokeJoin) *block.data);
+ break;
+ }
+ case TVG_TAG_SHAPE_STROKE_WIDTH: {
+ if (block.length != SIZE(float)) return false;
+ float width;
+ READ_FLOAT(&width, block.data);
+ shape->stroke(width);
+ break;
+ }
+ case TVG_TAG_SHAPE_STROKE_COLOR: {
+ if (block.length != 4) return false;
+ shape->stroke(block.data[0], block.data[1], block.data[2], block.data[3]);
+ break;
+ }
+ case TVG_TAG_SHAPE_STROKE_FILL: {
+ auto fill = _parseShapeFill(block.data, block.end);
+ if (!fill) return false;
+ shape->stroke(move(move(fill)));
+ break;
+ }
+ case TVG_TAG_SHAPE_STROKE_DASHPTRN: {
+ if (!_parseShapeStrokeDashPattern(block.data, block.end, shape)) return false;
+ break;
+ }
+ default: {
+ TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of stroke properties, %d bytes skipped", block.type, block.type, block.length);
+ break;
+ }
+ }
+ ptr = block.end;
+ }
+ return true;
+}
+
+
+static bool _parseShape(TvgBinBlock block, Paint* paint)
+{
+ auto shape = static_cast<Shape*>(paint);
+
+ //Case1: Shape specific properties
+ switch (block.type) {
+ case TVG_TAG_SHAPE_PATH: {
+ return _parseShapePath(block.data, block.end, shape);
+ }
+ case TVG_TAG_SHAPE_STROKE: {
+ return _parseShapeStroke(block.data, block.end, shape);
+ }
+ case TVG_TAG_SHAPE_FILL: {
+ auto fill = _parseShapeFill(block.data, block.end);
+ if (!fill) return false;
+ shape->fill(move(fill));
+ return true;
+ }
+ case TVG_TAG_SHAPE_COLOR: {
+ if (block.length != 4) return false;
+ shape->fill(block.data[0], block.data[1], block.data[2], block.data[3]);
+ return true;
+ }
+ case TVG_TAG_SHAPE_FILLRULE: {
+ if (block.length != SIZE(TvgBinFlag)) return false;
+ shape->fill((FillRule)*block.data);
+ return true;
+ }
+ }
+
+ //Case2: Base Paint Properties
+ return _parsePaintProperty(block, shape);
+}
+
+
+static bool _parsePicture(TvgBinBlock block, Paint* paint)
+{
+ auto picture = static_cast<Picture*>(paint);
+
+ //Case1: Image Picture
+ if (block.type == TVG_TAG_PICTURE_RAW_IMAGE) {
+ if (block.length < 2 * SIZE(uint32_t)) return false;
+
+ auto ptr = block.data;
+ uint32_t w, h;
+
+ READ_UI32(&w, ptr);
+ ptr += SIZE(uint32_t);
+ READ_UI32(&h, ptr);
+ ptr += SIZE(uint32_t);
+
+ auto size = w * h * SIZE(uint32_t);
+ if (block.length != 2 * SIZE(uint32_t) + size) return false;
+
+ picture->load((uint32_t*) ptr, w, h, true);
+ return true;
+ }
+
+ //Case2: Base Paint Properties
+ if (_parsePaintProperty(block, picture)) return true;
+
+ //Vector Picture won't be requested since Saver replaces it with the Scene
+ return false;
+}
+
+
+static Paint* _parsePaint(TvgBinBlock baseBlock)
+{
+ bool (*parser)(TvgBinBlock, Paint*);
+ Paint *paint;
+
+ //1. Decide the type of paint.
+ switch (baseBlock.type) {
+ case TVG_TAG_CLASS_SCENE: {
+ paint = Scene::gen().release();
+ parser = _parseScene;
+ break;
+ }
+ case TVG_TAG_CLASS_SHAPE: {
+ paint = Shape::gen().release();
+ parser = _parseShape;
+ break;
+ }
+ case TVG_TAG_CLASS_PICTURE: {
+ paint = Picture::gen().release();
+ parser = _parsePicture;
+ break;
+ }
+ default: {
+ TVGERR("TVG", "Invalid Paint Type %d (0x%x)", baseBlock.type, baseBlock.type);
+ return nullptr;
+ }
+ }
+
+ auto ptr = baseBlock.data;
+
+ //2. Read Subsquent properties of the current paint.
+ while (ptr < baseBlock.end) {
+ auto block = _readBlock(ptr);
+ if (block.end > baseBlock.end) return paint;
+ if (!parser(block, paint)) {
+ TVGERR("TVG", "Encountered the wrong paint properties... Paint Class %d (0x%x)", baseBlock.type, baseBlock.type);
+ return paint;
+ }
+ ptr = block.end;
+ }
+ return paint;
+}
+
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+unique_ptr<Scene> TvgBinInterpreter::run(const char *ptr, const char* end)
+{
+ auto scene = Scene::gen();
+ if (!scene) return nullptr;
+
+ while (ptr < end) {
+ auto block = _readBlock(ptr);
+ if (block.end > end) {
+ TVGERR("TVG", "Corrupted tvg file.");
+ return nullptr;
+ }
+ scene->push(unique_ptr<Paint>(_parsePaint(block)));
+ ptr = block.end;
+ }
+
+ return scene;
+}
diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h b/thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h
new file mode 100644
index 0000000000..e7c3eba488
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_TVG_COMMON_H_
+#define _TVG_TVG_COMMON_H_
+
+#include "tvgCommon.h"
+#include "tvgBinaryDesc.h"
+
+#define SIZE(A) sizeof(A)
+#define READ_UI32(dst, src) memcpy(dst, (src), sizeof(uint32_t))
+#define READ_FLOAT(dst, src) memcpy(dst, (src), sizeof(float))
+
+
+/* Interface for Tvg Binary Interpreter */
+class TvgBinInterpreterBase
+{
+public:
+ virtual ~TvgBinInterpreterBase() {}
+
+ /* ptr: points the tvg binary body (after header)
+ end: end of the tvg binary data */
+ virtual unique_ptr<Scene> run(const char* ptr, const char* end) = 0;
+};
+
+
+/* Version 0 */
+class TvgBinInterpreter : public TvgBinInterpreterBase
+{
+public:
+ unique_ptr<Scene> run(const char* ptr, const char* end) override;
+};
+
+
+#endif //_TVG_TVG_COMMON_H_ \ No newline at end of file
diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp
new file mode 100644
index 0000000000..d7f3184435
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include <memory.h>
+#include <fstream>
+#include "tvgLoader.h"
+#include "tvgTvgLoader.h"
+#include "tvgLzw.h"
+
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+
+void TvgLoader::clear()
+{
+ if (copy) free((char*)data);
+ ptr = data = nullptr;
+ size = 0;
+ copy = false;
+
+ if (interpreter) {
+ delete(interpreter);
+ interpreter = nullptr;
+ }
+}
+
+
+/* WARNING: Header format shall not change! */
+bool TvgLoader::readHeader()
+{
+ if (!ptr) return false;
+
+ //Make sure the size is large enough to hold the header
+ if (size < TVG_HEADER_SIZE) return false;
+
+ //1. Signature
+ if (memcmp(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH)) return false;
+ ptr += TVG_HEADER_SIGNATURE_LENGTH;
+
+ //2. Version
+ char version[TVG_HEADER_VERSION_LENGTH + 1];
+ memcpy(version, ptr, TVG_HEADER_VERSION_LENGTH);
+ version[TVG_HEADER_VERSION_LENGTH - 1] = '\0';
+ ptr += TVG_HEADER_VERSION_LENGTH;
+ this->version = atoi(version);
+ if (this->version > THORVG_VERSION_NUMBER()) {
+ TVGLOG("TVG", "This TVG file expects a higher version(%d) of ThorVG symbol(%d)", this->version, THORVG_VERSION_NUMBER());
+ }
+
+ //3. View Size
+ READ_FLOAT(&w, ptr);
+ ptr += SIZE(float);
+ READ_FLOAT(&h, ptr);
+ ptr += SIZE(float);
+
+ //4. Reserved
+ if (*ptr & TVG_HEAD_FLAG_COMPRESSED) compressed = true;
+ ptr += TVG_HEADER_RESERVED_LENGTH;
+
+ //5. Compressed Size if any
+ if (compressed) {
+ auto p = ptr;
+
+ //TVG_HEADER_UNCOMPRESSED_SIZE
+ memcpy(&uncompressedSize, p, sizeof(uint32_t));
+ p += SIZE(uint32_t);
+
+ //TVG_HEADER_COMPRESSED_SIZE
+ memcpy(&compressedSize, p, sizeof(uint32_t));
+ p += SIZE(uint32_t);
+
+ //TVG_HEADER_COMPRESSED_SIZE_BITS
+ memcpy(&compressedSizeBits, p, sizeof(uint32_t));
+ }
+
+ ptr += TVG_HEADER_COMPRESS_SIZE;
+
+ //Decide the proper Tvg Binary Interpreter based on the current file version
+ if (this->version >= 0) interpreter = new TvgBinInterpreter;
+
+ return true;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+TvgLoader::~TvgLoader()
+{
+ close();
+}
+
+
+bool TvgLoader::open(const string &path)
+{
+ clear();
+
+ ifstream f;
+ f.open(path, ifstream::in | ifstream::binary | ifstream::ate);
+
+ if (!f.is_open()) return false;
+
+ size = f.tellg();
+ f.seekg(0, ifstream::beg);
+
+ copy = true;
+ data = (char*)malloc(size);
+ if (!data) {
+ clear();
+ f.close();
+ return false;
+ }
+
+ if (!f.read((char*)data, size))
+ {
+ clear();
+ f.close();
+ return false;
+ }
+
+ f.close();
+
+ ptr = data;
+
+ return readHeader();
+}
+
+
+bool TvgLoader::open(const char *data, uint32_t size, bool copy)
+{
+ clear();
+
+ if (copy) {
+ this->data = (char*)malloc(size);
+ if (!this->data) return false;
+ memcpy((char*)this->data, data, size);
+ } else this->data = data;
+
+ this->ptr = this->data;
+ this->size = size;
+ this->copy = copy;
+
+ return readHeader();
+}
+
+
+bool TvgLoader::resize(Paint* paint, float w, float h)
+{
+ if (!paint) return false;
+
+ auto sx = w / this->w;
+ auto sy = h / this->h;
+
+ //Scale
+ auto scale = sx < sy ? sx : sy;
+ paint->scale(scale);
+
+ //Align
+ float tx = 0, ty = 0;
+ auto sw = this->w * scale;
+ auto sh = this->h * scale;
+ if (sw > sh) ty -= (h - sh) * 0.5f;
+ else tx -= (w - sw) * 0.5f;
+ paint->translate(-tx, -ty);
+
+ return true;
+}
+
+
+bool TvgLoader::read()
+{
+ if (!ptr || size == 0) return false;
+
+ TaskScheduler::request(this);
+
+ return true;
+}
+
+
+bool TvgLoader::close()
+{
+ this->done();
+ clear();
+ return true;
+}
+
+
+void TvgLoader::run(unsigned tid)
+{
+ if (root) root.reset();
+
+ auto data = const_cast<char*>(ptr);
+
+ if (compressed) {
+ data = (char*) lzwDecode((uint8_t*) data, compressedSize, compressedSizeBits, uncompressedSize);
+ root = interpreter->run(data, data + uncompressedSize);
+ free(data);
+ } else {
+ root = interpreter->run(data, this->data + size);
+ }
+
+ if (!root) clear();
+}
+
+
+unique_ptr<Paint> TvgLoader::paint()
+{
+ this->done();
+ if (root) return move(root);
+ return nullptr;
+}
diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h
new file mode 100644
index 0000000000..d276ded33a
--- /dev/null
+++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _TVG_TVG_LOADER_H_
+#define _TVG_TVG_LOADER_H_
+
+#include "tvgTaskScheduler.h"
+#include "tvgTvgCommon.h"
+
+
+class TvgLoader : public LoadModule, public Task
+{
+public:
+ const char* data = nullptr;
+ const char* ptr = nullptr;
+ uint32_t size = 0;
+ uint16_t version = 0;
+ unique_ptr<Scene> root = nullptr;
+ TvgBinInterpreterBase* interpreter = nullptr;
+ uint32_t uncompressedSize = 0;
+ uint32_t compressedSize = 0;
+ uint32_t compressedSizeBits = 0;
+ bool copy = false;
+ bool compressed = false;
+
+ ~TvgLoader();
+
+ using LoadModule::open;
+ bool open(const string &path) override;
+ bool open(const char *data, uint32_t size, bool copy) override;
+ bool read() override;
+ bool close() override;
+ bool resize(Paint* paint, float w, float h) override;
+ unique_ptr<Paint> paint() override;
+
+private:
+ bool readHeader();
+ void run(unsigned tid) override;
+ void clear();
+};
+
+#endif //_TVG_TVG_LOADER_H_
diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
new file mode 100644
index 0000000000..9450d80e88
--- /dev/null
+++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "tvgMath.h"
+#include "tvgSaveModule.h"
+#include "tvgTvgSaver.h"
+#include "tvgLzw.h"
+
+#ifdef _WIN32
+ #include <malloc.h>
+#else
+ #include <alloca.h>
+#endif
+
+static FILE* _fopen(const char* filename, const char* mode)
+{
+#if defined(_MSC_VER) && defined(__clang__)
+ FILE *fp;
+ auto err = fopen_s(&fp, filename, mode);
+ if (err != 0) return nullptr;
+ return fp;
+#else
+ auto fp = fopen(filename, mode);
+ if (!fp) return nullptr;
+ return fp;
+#endif
+}
+
+#define SIZE(A) sizeof(A)
+
+/************************************************************************/
+/* Internal Class Implementation */
+/************************************************************************/
+
+static inline TvgBinCounter SERIAL_DONE(TvgBinCounter cnt)
+{
+ return SIZE(TvgBinTag) + SIZE(TvgBinCounter) + cnt;
+}
+
+
+/* if the properties are identical, we can merge the shapes. */
+static bool _merge(Shape* from, Shape* to)
+{
+ uint8_t r, g, b, a;
+ uint8_t r2, g2, b2, a2;
+
+ //fill
+ if (from->fill() || to->fill()) return false;
+
+ r = g = b = a = r2 = g2 = b2 = a2 = 0;
+
+ from->fillColor(&r, &g, &b, &a);
+ to->fillColor(&r2, &g2, &b2, &a2);
+
+ if (r != r2 || g != g2 || b != b2 || a != a2) return false;
+
+ //composition
+ if (from->composite(nullptr) != CompositeMethod::None) return false;
+ if (to->composite(nullptr) != CompositeMethod::None) return false;
+
+ //opacity
+ if (from->opacity() != to->opacity()) return false;
+
+ //transform
+ auto t1 = from->transform();
+ auto t2 = to->transform();
+
+ if (!mathEqual(t1.e11, t2.e11) || !mathEqual(t1.e12, t2.e12) || !mathEqual(t1.e13, t2.e13) ||
+ !mathEqual(t1.e21, t2.e21) || !mathEqual(t1.e22, t2.e22) || !mathEqual(t1.e23, t2.e23) ||
+ !mathEqual(t1.e31, t2.e31) || !mathEqual(t1.e32, t2.e32) || !mathEqual(t1.e33, t2.e33)) {
+ return false;
+ }
+
+ //stroke
+ r = g = b = a = r2 = g2 = b2 = a2 = 0;
+
+ from->strokeColor(&r, &g, &b, &a);
+ to->strokeColor(&r2, &g2, &b2, &a2);
+
+ if (r != r2 || g != g2 || b != b2 || a != a2) return false;
+
+ if (fabs(from->strokeWidth() - to->strokeWidth()) > FLT_EPSILON) return false;
+
+ //OPTIMIZE: Yet we can't merge outlining shapes unless we can support merging shapes feature.
+ if (from->strokeWidth() > 0 || to->strokeWidth() > 0) return false;
+
+ if (from->strokeCap() != to->strokeCap()) return false;
+ if (from->strokeJoin() != to->strokeJoin()) return false;
+ if (from->strokeDash(nullptr) > 0 || to->strokeDash(nullptr) > 0) return false;
+ if (from->strokeFill() || to->strokeFill()) return false;
+
+ //fill rule
+ if (from->fillRule() != to->fillRule()) return false;
+
+ //Good, identical shapes, we can merge them.
+ const PathCommand* cmds = nullptr;
+ auto cmdCnt = from->pathCommands(&cmds);
+
+ const Point* pts = nullptr;
+ auto ptsCnt = from->pathCoords(&pts);
+
+ to->appendPath(cmds, cmdCnt, pts, ptsCnt);
+
+ return true;
+}
+
+
+bool TvgSaver::saveEncoding(const std::string& path)
+{
+ if (!compress) return flushTo(path);
+
+ //Try encoding
+ auto uncompressed = buffer.data + headerSize;
+ auto uncompressedSize = buffer.count - headerSize;
+
+ uint32_t compressedSize, compressedSizeBits;
+
+ auto compressed = lzwEncode(uncompressed, uncompressedSize, &compressedSize, &compressedSizeBits);
+
+ //Failed compression.
+ if (!compressed) return flushTo(path);
+
+ //Optimization is ineffective.
+ if (compressedSize >= uncompressedSize) {
+ free(compressed);
+ return flushTo(path);
+ }
+
+ TVGLOG("TVG_SAVER", "%s, compressed: %d -> %d, saved rate: %3.2f%%", path.c_str(), uncompressedSize, compressedSize, (1 - ((float) compressedSize / (float) uncompressedSize)) * 100);
+
+ //Update compress size in the header.
+ uncompressed -= (TVG_HEADER_COMPRESS_SIZE + TVG_HEADER_RESERVED_LENGTH);
+
+ //Compression Flag
+ *uncompressed |= TVG_HEAD_FLAG_COMPRESSED;
+ uncompressed += TVG_HEADER_RESERVED_LENGTH;
+
+ //Uncompressed Size
+ memcpy(uncompressed, &uncompressedSize, TVG_HEADER_UNCOMPRESSED_SIZE);
+ uncompressed += TVG_HEADER_UNCOMPRESSED_SIZE;
+
+ //Comprssed Size
+ memcpy(uncompressed, &compressedSize, TVG_HEADER_COMPRESSED_SIZE);
+ uncompressed += TVG_HEADER_COMPRESSED_SIZE;
+
+ //Compressed Size Bits
+ memcpy(uncompressed, &compressedSizeBits, TVG_HEADER_COMPRESSED_SIZE_BITS);
+
+ //Good optimization, flush to file.
+ auto fp = _fopen(path.c_str(), "w+");
+ if (!fp) goto fail;
+
+ //write header
+ if (fwrite(buffer.data, SIZE(uint8_t), headerSize, fp) == 0) goto fail;
+
+ //write compressed data
+ if (fwrite(compressed, SIZE(uint8_t), compressedSize, fp) == 0) goto fail;
+
+ fclose(fp);
+ free(compressed);
+
+ return true;
+
+fail:
+ if (fp) fclose(fp);
+ if (compressed) free(compressed);
+ return false;
+}
+
+
+bool TvgSaver::flushTo(const std::string& path)
+{
+ auto fp = _fopen(path.c_str(), "w+");
+ if (!fp) return false;
+
+ if (fwrite(buffer.data, SIZE(uint8_t), buffer.count, fp) == 0) {
+ fclose(fp);
+ return false;
+ }
+ fclose(fp);
+
+ return true;
+}
+
+
+/* WARNING: Header format shall not changed! */
+bool TvgSaver::writeHeader()
+{
+ headerSize = TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + SIZE(vsize) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE;
+
+ buffer.grow(headerSize);
+
+ //1. Signature
+ auto ptr = buffer.ptr();
+ memcpy(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH);
+ ptr += TVG_HEADER_SIGNATURE_LENGTH;
+
+ //2. Version
+ memcpy(ptr, TVG_HEADER_VERSION, TVG_HEADER_VERSION_LENGTH);
+ ptr += TVG_HEADER_VERSION_LENGTH;
+
+ buffer.count += (TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH);
+
+ //3. View Size
+ writeData(vsize, SIZE(vsize));
+ ptr += SIZE(vsize);
+
+ //4. Reserved data + Compress size
+ memset(ptr, 0x00, TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE);
+ buffer.count += (TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE);
+
+ return true;
+}
+
+
+void TvgSaver::writeTag(TvgBinTag tag)
+{
+ buffer.grow(SIZE(TvgBinTag));
+ memcpy(buffer.ptr(), &tag, SIZE(TvgBinTag));
+ buffer.count += SIZE(TvgBinTag);
+}
+
+
+void TvgSaver::writeCount(TvgBinCounter cnt)
+{
+ buffer.grow(SIZE(TvgBinCounter));
+ memcpy(buffer.ptr(), &cnt, SIZE(TvgBinCounter));
+ buffer.count += SIZE(TvgBinCounter);
+}
+
+
+void TvgSaver::writeReservedCount(TvgBinCounter cnt)
+{
+ memcpy(buffer.ptr() - cnt - SIZE(TvgBinCounter), &cnt, SIZE(TvgBinCounter));
+}
+
+
+void TvgSaver::reserveCount()
+{
+ buffer.grow(SIZE(TvgBinCounter));
+ buffer.count += SIZE(TvgBinCounter);
+}
+
+
+TvgBinCounter TvgSaver::writeData(const void* data, TvgBinCounter cnt)
+{
+ buffer.grow(cnt);
+ memcpy(buffer.ptr(), data, cnt);
+ buffer.count += cnt;
+
+ return cnt;
+}
+
+
+TvgBinCounter TvgSaver::writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data)
+{
+ auto growCnt = SERIAL_DONE(cnt);
+
+ buffer.grow(growCnt);
+
+ auto ptr = buffer.ptr();
+
+ *ptr = tag;
+ ++ptr;
+
+ memcpy(ptr, &cnt, SIZE(TvgBinCounter));
+ ptr += SIZE(TvgBinCounter);
+
+ memcpy(ptr, data, cnt);
+ ptr += cnt;
+
+ buffer.count += growCnt;
+
+ return growCnt;
+}
+
+
+TvgBinCounter TvgSaver::writeTransform(const Matrix* transform, TvgBinTag tag)
+{
+ if (!mathIdentity(transform)) return writeTagProperty(tag, SIZE(Matrix), transform);
+ return 0;
+}
+
+
+TvgBinCounter TvgSaver::serializePaint(const Paint* paint, const Matrix* pTransform)
+{
+ TvgBinCounter cnt = 0;
+
+ //opacity
+ auto opacity = paint->opacity();
+ if (opacity < 255) {
+ cnt += writeTagProperty(TVG_TAG_PAINT_OPACITY, SIZE(opacity), &opacity);
+ }
+
+ //composite
+ const Paint* cmpTarget = nullptr;
+ auto cmpMethod = paint->composite(&cmpTarget);
+ if (cmpMethod != CompositeMethod::None && cmpTarget) {
+ cnt += serializeComposite(cmpTarget, cmpMethod, pTransform);
+ }
+
+ return cnt;
+}
+
+
+/* Propagate parents properties to the child so that we can skip saving the parent. */
+TvgBinCounter TvgSaver::serializeChild(const Paint* parent, const Paint* child, const Matrix* transform)
+{
+ const Paint* compTarget = nullptr;
+ auto compMethod = parent->composite(&compTarget);
+
+ /* If the parent & the only child have composition, we can't skip the parent...
+ Or if the parent has the transform and composition, we can't skip the parent... */
+ if (compMethod != CompositeMethod::None) {
+ if (transform || child->composite(nullptr) != CompositeMethod::None) return 0;
+ }
+
+ //propagate opacity
+ uint32_t opacity = parent->opacity();
+
+ if (opacity < 255) {
+ uint32_t tmp = (child->opacity() * opacity);
+ if (tmp > 0) tmp /= 255;
+ const_cast<Paint*>(child)->opacity(tmp);
+ }
+
+ //propagate composition
+ if (compTarget) const_cast<Paint*>(child)->composite(unique_ptr<Paint>(compTarget->duplicate()), compMethod);
+
+ return serialize(child, transform);
+}
+
+
+TvgBinCounter TvgSaver::serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform)
+{
+ auto it = this->iterator(scene);
+ if (it->count() == 0) {
+ delete(it);
+ return 0;
+ }
+
+ //Case - Only Child: Skip saving this scene.
+ if (it->count() == 1) {
+ auto cnt = serializeChild(scene, it->next(), cTransform);
+ if (cnt > 0) {
+ delete(it);
+ return cnt;
+ }
+ }
+
+ it->begin();
+
+ //Case - Delegator Scene: This scene is just a delegator, we can skip this:
+ if (scene->composite(nullptr) == CompositeMethod::None && scene->opacity() == 255) {
+ auto ret = serializeChildren(it, cTransform, false);
+ delete(it);
+ return ret;
+ }
+
+ //Case - Serialize Scene & its children
+ writeTag(TVG_TAG_CLASS_SCENE);
+ reserveCount();
+
+ auto cnt = serializeChildren(it, cTransform, true) + serializePaint(scene, pTransform);
+
+ delete(it);
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+TvgBinCounter TvgSaver::serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform)
+{
+ const Fill::ColorStop* stops = nullptr;
+ auto stopsCnt = fill->colorStops(&stops);
+ if (!stops || stopsCnt == 0) return 0;
+
+ writeTag(tag);
+ reserveCount();
+
+ TvgBinCounter cnt = 0;
+
+ //radial fill
+ if (fill->identifier() == TVG_CLASS_ID_RADIAL) {
+ float args[3];
+ static_cast<const RadialGradient*>(fill)->radial(args, args + 1, args + 2);
+ cnt += writeTagProperty(TVG_TAG_FILL_RADIAL_GRADIENT, SIZE(args), args);
+ //linear fill
+ } else {
+ float args[4];
+ static_cast<const LinearGradient*>(fill)->linear(args, args + 1, args + 2, args + 3);
+ cnt += writeTagProperty(TVG_TAG_FILL_LINEAR_GRADIENT, SIZE(args), args);
+ }
+
+ if (auto flag = static_cast<TvgBinFlag>(fill->spread()))
+ cnt += writeTagProperty(TVG_TAG_FILL_FILLSPREAD, SIZE(TvgBinFlag), &flag);
+ cnt += writeTagProperty(TVG_TAG_FILL_COLORSTOPS, stopsCnt * SIZE(Fill::ColorStop), stops);
+
+ auto gTransform = fill->transform();
+ if (pTransform) gTransform = mathMultiply(pTransform, &gTransform);
+
+ cnt += writeTransform(&gTransform, TVG_TAG_FILL_TRANSFORM);
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+TvgBinCounter TvgSaver::serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform)
+{
+ writeTag(TVG_TAG_SHAPE_STROKE);
+ reserveCount();
+
+ //width
+ auto width = shape->strokeWidth();
+ if (preTransform) width *= sqrtf(powf(pTransform->e11, 2.0f) + powf(pTransform->e21, 2.0f)); //we know x/y scaling factors are same.
+ auto cnt = writeTagProperty(TVG_TAG_SHAPE_STROKE_WIDTH, SIZE(width), &width);
+
+ //cap
+ if (auto flag = static_cast<TvgBinFlag>(shape->strokeCap()))
+ cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_CAP, SIZE(TvgBinFlag), &flag);
+
+ //join
+ if (auto flag = static_cast<TvgBinFlag>(shape->strokeJoin()))
+ cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_JOIN, SIZE(TvgBinFlag), &flag);
+
+ //fill
+ if (auto fill = shape->strokeFill()) {
+ cnt += serializeFill(fill, TVG_TAG_SHAPE_STROKE_FILL, (preTransform ? pTransform : nullptr));
+ } else {
+ uint8_t color[4] = {0, 0, 0, 0};
+ shape->strokeColor(color, color + 1, color + 2, color + 3);
+ cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_COLOR, SIZE(color), &color);
+ }
+
+ //dash
+ const float* dashPattern = nullptr;
+ auto dashCnt = shape->strokeDash(&dashPattern);
+ if (dashPattern && dashCnt > 0) {
+ TvgBinCounter dashCntSize = SIZE(dashCnt);
+ TvgBinCounter dashPtrnSize = dashCnt * SIZE(dashPattern[0]);
+
+ writeTag(TVG_TAG_SHAPE_STROKE_DASHPTRN);
+ writeCount(dashCntSize + dashPtrnSize);
+ cnt += writeData(&dashCnt, dashCntSize);
+ cnt += writeData(dashPattern, dashPtrnSize);
+ cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
+ }
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+TvgBinCounter TvgSaver::serializePath(const Shape* shape, const Matrix* transform, bool preTransform)
+{
+ const PathCommand* cmds = nullptr;
+ auto cmdCnt = shape->pathCommands(&cmds);
+ const Point* pts = nullptr;
+ auto ptsCnt = shape->pathCoords(&pts);
+
+ if (!cmds || !pts || cmdCnt == 0 || ptsCnt == 0) return 0;
+
+ writeTag(TVG_TAG_SHAPE_PATH);
+ reserveCount();
+
+ /* Reduce the binary size.
+ Convert PathCommand(4 bytes) to TvgBinFlag(1 byte) */
+ TvgBinFlag* outCmds = (TvgBinFlag*)alloca(SIZE(TvgBinFlag) * cmdCnt);
+ for (uint32_t i = 0; i < cmdCnt; ++i) {
+ outCmds[i] = static_cast<TvgBinFlag>(cmds[i]);
+ }
+
+ auto cnt = writeData(&cmdCnt, SIZE(cmdCnt));
+ cnt += writeData(&ptsCnt, SIZE(ptsCnt));
+ cnt += writeData(outCmds, SIZE(TvgBinFlag) * cmdCnt);
+
+ //transform?
+ if (preTransform) {
+ if (!mathEqual(transform->e11, 1.0f) || !mathZero(transform->e12) || !mathZero(transform->e13) ||
+ !mathZero(transform->e21) || !mathEqual(transform->e22, 1.0f) || !mathZero(transform->e23) ||
+ !mathZero(transform->e31) || !mathZero(transform->e32) || !mathEqual(transform->e33, 1.0f)) {
+ auto p = const_cast<Point*>(pts);
+ for (uint32_t i = 0; i < ptsCnt; ++i) mathMultiply(p++, transform);
+ }
+ }
+
+ cnt += writeData(pts, ptsCnt * SIZE(pts[0]));
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+TvgBinCounter TvgSaver::serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform)
+{
+ writeTag(TVG_TAG_CLASS_SHAPE);
+ reserveCount();
+ TvgBinCounter cnt = 0;
+
+ //fill rule
+ if (auto flag = static_cast<TvgBinFlag>(shape->fillRule())) {
+ cnt = writeTagProperty(TVG_TAG_SHAPE_FILLRULE, SIZE(TvgBinFlag), &flag);
+ }
+
+ //the pre-transformation can't be applied in the case when the stroke is dashed or irregulary scaled
+ bool preTransform = true;
+
+ //stroke
+ if (shape->strokeWidth() > 0) {
+ uint8_t color[4] = {0, 0, 0, 0};
+ shape->strokeColor(color, color + 1, color + 2, color + 3);
+ auto fill = shape->strokeFill();
+ if (fill || color[3] > 0) {
+ if (!mathEqual(cTransform->e11, cTransform->e22) || (mathZero(cTransform->e11) && !mathEqual(cTransform->e12, cTransform->e21)) || shape->strokeDash(nullptr) > 0) preTransform = false;
+ cnt += serializeStroke(shape, cTransform, preTransform);
+ }
+ }
+
+ //fill
+ if (auto fill = shape->fill()) {
+ cnt += serializeFill(fill, TVG_TAG_SHAPE_FILL, (preTransform ? cTransform : nullptr));
+ } else {
+ uint8_t color[4] = {0, 0, 0, 0};
+ shape->fillColor(color, color + 1, color + 2, color + 3);
+ if (color[3] > 0) cnt += writeTagProperty(TVG_TAG_SHAPE_COLOR, SIZE(color), color);
+ }
+
+ cnt += serializePath(shape, cTransform, preTransform);
+
+ if (!preTransform) cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM);
+ cnt += serializePaint(shape, pTransform);
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+/* Picture has either a vector scene or a bitmap. */
+TvgBinCounter TvgSaver::serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform)
+{
+ auto it = this->iterator(picture);
+
+ //Case - Vector Scene:
+ if (it->count() == 1) {
+ auto cnt = serializeChild(picture, it->next(), cTransform);
+ //Only child, Skip to save Picture...
+ if (cnt > 0) {
+ delete(it);
+ return cnt;
+ /* Unfortunately, we can't skip the Picture because it might have a compositor,
+ Serialize Scene(instead of the Picture) & its scene. */
+ } else {
+ writeTag(TVG_TAG_CLASS_SCENE);
+ reserveCount();
+ auto cnt = serializeChildren(it, cTransform, true) + serializePaint(picture, pTransform);
+ writeReservedCount(cnt);
+ delete(it);
+ return SERIAL_DONE(cnt);
+ }
+ }
+ delete(it);
+
+ //Case - Bitmap Image:
+ uint32_t w, h;
+ auto pixels = picture->data(&w, &h);
+ if (!pixels) return 0;
+
+ writeTag(TVG_TAG_CLASS_PICTURE);
+ reserveCount();
+
+ TvgBinCounter cnt = 0;
+ TvgBinCounter sizeCnt = SIZE(w);
+ TvgBinCounter imgSize = w * h * SIZE(pixels[0]);
+
+ writeTag(TVG_TAG_PICTURE_RAW_IMAGE);
+ writeCount(2 * sizeCnt + imgSize);
+
+ cnt += writeData(&w, sizeCnt);
+ cnt += writeData(&h, sizeCnt);
+ cnt += writeData(pixels, imgSize);
+ cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
+
+ //Bitmap picture needs the transform info.
+ cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM);
+
+ cnt += serializePaint(picture, pTransform);
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+TvgBinCounter TvgSaver::serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform)
+{
+ writeTag(TVG_TAG_PAINT_CMP_TARGET);
+ reserveCount();
+
+ auto flag = static_cast<TvgBinFlag>(cmpMethod);
+ auto cnt = writeTagProperty(TVG_TAG_PAINT_CMP_METHOD, SIZE(TvgBinFlag), &flag);
+
+ cnt += serialize(cmpTarget, pTransform, true);
+
+ writeReservedCount(cnt);
+
+ return SERIAL_DONE(cnt);
+}
+
+
+TvgBinCounter TvgSaver::serializeChildren(Iterator* it, const Matrix* pTransform, bool reserved)
+{
+ TvgBinCounter cnt = 0;
+
+ //Merging shapes. the result is written in the children.
+ Array<const Paint*> children;
+ children.reserve(it->count());
+ children.push(it->next());
+
+ while (auto child = it->next()) {
+ if (child->identifier() == TVG_CLASS_ID_SHAPE) {
+ //only dosable if the previous child is a shape.
+ auto target = children.ptr() - 1;
+ if ((*target)->identifier() == TVG_CLASS_ID_SHAPE) {
+ if (_merge((Shape*)child, (Shape*)*target)) {
+ continue;
+ }
+ }
+ }
+ children.push(child);
+ }
+
+ //The children of a reserved scene
+ if (reserved && children.count > 1) {
+ cnt += writeTagProperty(TVG_TAG_SCENE_RESERVEDCNT, SIZE(children.count), &children.count);
+ }
+
+ //Serialize merged children.
+ auto child = children.data;
+ for (uint32_t i = 0; i < children.count; ++i, ++child) {
+ cnt += serialize(*child, pTransform);
+ }
+
+ return cnt;
+}
+
+
+TvgBinCounter TvgSaver::serialize(const Paint* paint, const Matrix* pTransform, bool compTarget)
+{
+ if (!paint) return 0;
+
+ //Invisible paint, no point to save it if the paint is not the composition target...
+ if (!compTarget && paint->opacity() == 0) return 0;
+
+ auto transform = const_cast<Paint*>(paint)->transform();
+ if (pTransform) transform = mathMultiply(pTransform, &transform);
+
+ switch (paint->identifier()) {
+ case TVG_CLASS_ID_SHAPE: return serializeShape(static_cast<const Shape*>(paint), pTransform, &transform);
+ case TVG_CLASS_ID_SCENE: return serializeScene(static_cast<const Scene*>(paint), pTransform, &transform);
+ case TVG_CLASS_ID_PICTURE: return serializePicture(static_cast<const Picture*>(paint), pTransform, &transform);
+ }
+
+ return 0;
+}
+
+
+void TvgSaver::run(unsigned tid)
+{
+ if (!writeHeader()) return;
+
+ //Serialize Root Paint, without its transform.
+ Matrix transform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+
+ if (paint->opacity() > 0) {
+ switch (paint->identifier()) {
+ case TVG_CLASS_ID_SHAPE: {
+ serializeShape(static_cast<const Shape*>(paint), nullptr, &transform);
+ break;
+ }
+ case TVG_CLASS_ID_SCENE: {
+ serializeScene(static_cast<const Scene*>(paint), nullptr, &transform);
+ break;
+ }
+ case TVG_CLASS_ID_PICTURE: {
+ serializePicture(static_cast<const Picture*>(paint), nullptr, &transform);
+ break;
+ }
+ }
+ }
+
+ if (!saveEncoding(path)) return;
+}
+
+
+/************************************************************************/
+/* External Class Implementation */
+/************************************************************************/
+
+TvgSaver::~TvgSaver()
+{
+ close();
+}
+
+
+bool TvgSaver::close()
+{
+ this->done();
+
+ if (paint) {
+ delete(paint);
+ paint = nullptr;
+ }
+ if (path) {
+ free(path);
+ path = nullptr;
+ }
+ buffer.reset();
+ return true;
+}
+
+
+bool TvgSaver::save(Paint* paint, const string& path, bool compress)
+{
+ close();
+
+ float x, y;
+ x = y = 0;
+ paint->bounds(&x, &y, &vsize[0], &vsize[1], false);
+
+ //cut off the negative space
+ if (x < 0) vsize[0] += x;
+ if (y < 0) vsize[1] += y;
+
+ if (vsize[0] < FLT_EPSILON || vsize[1] < FLT_EPSILON) {
+ TVGLOG("TVG_SAVER", "Saving paint(%p) has zero view size.", paint);
+ return false;
+ }
+
+ this->path = strdup(path.c_str());
+ if (!this->path) return false;
+
+ this->paint = paint;
+ this->compress = compress;
+
+ TaskScheduler::request(this);
+
+ return true;
+}
diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h
new file mode 100644
index 0000000000..27186b5d4a
--- /dev/null
+++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef _TVG_TVGSAVER_H_
+#define _TVG_TVGSAVER_H_
+
+#include "tvgArray.h"
+#include "tvgBinaryDesc.h"
+#include "tvgTaskScheduler.h"
+
+namespace tvg
+{
+
+class TvgSaver : public SaveModule, public Task
+{
+private:
+ Array<TvgBinByte> buffer;
+ Paint* paint = nullptr;
+ char *path = nullptr;
+ uint32_t headerSize;
+ float vsize[2] = {0.0f, 0.0f};
+ bool compress;
+
+ bool flushTo(const std::string& path);
+ bool saveEncoding(const std::string& path);
+ void reserveCount();
+
+ bool writeHeader();
+ bool writeViewSize();
+ void writeTag(TvgBinTag tag);
+ void writeCount(TvgBinCounter cnt);
+ void writeReservedCount(TvgBinCounter cnt);
+ TvgBinCounter writeData(const void* data, TvgBinCounter cnt);
+ TvgBinCounter writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data);
+ TvgBinCounter writeTransform(const Matrix* transform, TvgBinTag tag);
+
+ TvgBinCounter serialize(const Paint* paint, const Matrix* pTransform, bool compTarget = false);
+ TvgBinCounter serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform);
+ TvgBinCounter serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform);
+ TvgBinCounter serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform);
+ TvgBinCounter serializePaint(const Paint* paint, const Matrix* pTransform);
+ TvgBinCounter serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform);
+ TvgBinCounter serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform);
+ TvgBinCounter serializePath(const Shape* shape, const Matrix* transform, bool preTransform);
+ TvgBinCounter serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform);
+ TvgBinCounter serializeChildren(Iterator* it, const Matrix* transform, bool reserved);
+ TvgBinCounter serializeChild(const Paint* parent, const Paint* child, const Matrix* pTransform);
+
+public:
+ ~TvgSaver();
+
+ bool save(Paint* paint, const string& path, bool compress) override;
+ bool close() override;
+ void run(unsigned tid) override;
+};
+
+}
+
+#endif //_TVG_SAVE_MODULE_H_