summaryrefslogtreecommitdiff
path: root/thirdparty/thorvg/src/savers
diff options
context:
space:
mode:
authorK. S. Ernest (iFire) Lee <ernest.lee@chibifire.com>2022-01-13 13:54:19 +0100
committerRémi Verschelde <rverschelde@gmail.com>2022-01-14 15:49:39 +0100
commit8d02759c720c3a91663e56979273feabad1dc051 (patch)
treeb939f28feb3224d4c4d2e39d12ef191157e2664b /thirdparty/thorvg/src/savers
parent9b3535a33a1dda541a3a39e7786b8428fadbff6c (diff)
Use ThorVG instead of NanoSVG for importing SVGs
ThorVG is a platform-independent portable library for drawing vector-based scene and animation. Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
Diffstat (limited to 'thirdparty/thorvg/src/savers')
-rw-r--r--thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp773
-rw-r--r--thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h77
2 files changed, 850 insertions, 0 deletions
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_