From 8d02759c720c3a91663e56979273feabad1dc051 Mon Sep 17 00:00:00 2001 From: "K. S. Ernest (iFire) Lee" Date: Thu, 13 Jan 2022 13:54:19 +0100 Subject: Use ThorVG instead of NanoSVG for importing SVGs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ThorVG is a platform-independent portable library for drawing vector-based scene and animation. Co-authored-by: RĂ©mi Verschelde --- thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp | 773 +++++++++++++++++++++++ thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h | 77 +++ 2 files changed, 850 insertions(+) create mode 100644 thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp create mode 100644 thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h (limited to 'thirdparty/thorvg/src/savers') 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 +#else + #include +#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(child)->opacity(tmp); + } + + //propagate composition + if (compTarget) const_cast(child)->composite(unique_ptr(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(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(fill)->linear(args, args + 1, args + 2, args + 3); + cnt += writeTagProperty(TVG_TAG_FILL_LINEAR_GRADIENT, SIZE(args), args); + } + + if (auto flag = static_cast(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(shape->strokeCap())) + cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_CAP, SIZE(TvgBinFlag), &flag); + + //join + if (auto flag = static_cast(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(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(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(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(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 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)->transform(); + if (pTransform) transform = mathMultiply(pTransform, &transform); + + switch (paint->identifier()) { + case TVG_CLASS_ID_SHAPE: return serializeShape(static_cast(paint), pTransform, &transform); + case TVG_CLASS_ID_SCENE: return serializeScene(static_cast(paint), pTransform, &transform); + case TVG_CLASS_ID_PICTURE: return serializePicture(static_cast(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(paint), nullptr, &transform); + break; + } + case TVG_CLASS_ID_SCENE: { + serializeScene(static_cast(paint), nullptr, &transform); + break; + } + case TVG_CLASS_ID_PICTURE: { + serializePicture(static_cast(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 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_ -- cgit v1.2.3