mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-23 16:35:59 +00:00
834 lines
24 KiB
C++
834 lines
24 KiB
C++
/*
|
|
* Copyright (c) 2021 - 2024 the ThorVG project. 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 <cstring>
|
|
|
|
#include "tvgSaveModule.h"
|
|
#include "tvgTvgSaver.h"
|
|
#include "tvgCompressor.h"
|
|
#include "tvgShape.h"
|
|
#include "tvgFill.h"
|
|
#include "tvgPicture.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <malloc.h>
|
|
#elif defined(__linux__)
|
|
#include <alloca.h>
|
|
#else
|
|
#include <stdlib.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 || a < 255) return false;
|
|
|
|
auto fromRule = from->fillRule();
|
|
if (fromRule == FillRule::EvenOdd || fromRule != to->fillRule()) 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 (t1 != t2) return false;
|
|
|
|
//stroke
|
|
if (P(from)->strokeFirst() != P(to)->strokeFirst()) return false;
|
|
|
|
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()) > FLOAT_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;
|
|
|
|
if (fabsf(from->strokeMiterlimit() - to->strokeMiterlimit()) > FLOAT_EPSILON) 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;
|
|
|
|
//Compressed 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(), "wb+");
|
|
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(), "wb+");
|
|
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.end();
|
|
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.end(), &tag, SIZE(TvgBinTag));
|
|
buffer.count += SIZE(TvgBinTag);
|
|
}
|
|
|
|
|
|
void TvgSaver::writeCount(TvgBinCounter cnt)
|
|
{
|
|
buffer.grow(SIZE(TvgBinCounter));
|
|
memcpy(buffer.end(), &cnt, SIZE(TvgBinCounter));
|
|
buffer.count += SIZE(TvgBinCounter);
|
|
}
|
|
|
|
|
|
void TvgSaver::writeReservedCount(TvgBinCounter cnt)
|
|
{
|
|
memcpy(buffer.end() - 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.end(), 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.end();
|
|
|
|
*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 (!identity(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(cast<Paint>(compTarget->duplicate()), compMethod);
|
|
|
|
return serialize(child, transform);
|
|
}
|
|
|
|
|
|
TvgBinCounter TvgSaver::serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform)
|
|
{
|
|
auto it = IteratorAccessor::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 - 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->type() == Type::RadialGradient) {
|
|
const RadialGradient* radial = static_cast<const RadialGradient*>(fill);
|
|
float args[3];
|
|
radial->radial(args, args + 1, args + 2);
|
|
cnt += writeTagProperty(TVG_TAG_FILL_RADIAL_GRADIENT, SIZE(args), args);
|
|
//focal
|
|
if (!tvg::zero(P(radial)->fx)|| !tvg::zero(P(radial)->fy) || P(radial)->fr > 0.0f) {
|
|
args[0] = P(radial)->fx;
|
|
args[1] = P(radial)->fy;
|
|
args[2] = P(radial)->fr;
|
|
cnt += writeTagProperty(TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL, 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 = *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);
|
|
|
|
//order
|
|
if (auto flag = static_cast<TvgBinFlag>(P(shape)->strokeFirst()))
|
|
cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_ORDER, 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;
|
|
float offset = 0.0f;
|
|
auto dashCnt = P(shape)->rs.strokeDash(&dashPattern, &offset);
|
|
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);
|
|
}
|
|
|
|
//miterlimit (the default value is 4)
|
|
auto miterlimit = shape->strokeMiterlimit();
|
|
if (fabsf(miterlimit - 4.0f) > FLOAT_EPSILON) {
|
|
cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_MITERLIMIT, SIZE(miterlimit), &miterlimit);
|
|
}
|
|
|
|
//dash offset
|
|
if (!tvg::zero(offset)) {
|
|
cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_DASH_OFFSET, SIZE(offset), &offset);
|
|
}
|
|
|
|
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 (!tvg::equal(transform->e11, 1.0f) || !tvg::zero(transform->e12) || !tvg::zero(transform->e13) ||
|
|
!tvg::zero(transform->e21) || !tvg::equal(transform->e22, 1.0f) || !tvg::zero(transform->e23) ||
|
|
!tvg::zero(transform->e31) || !tvg::zero(transform->e32) || !tvg::equal(transform->e33, 1.0f)) {
|
|
auto p = const_cast<Point*>(pts);
|
|
for (uint32_t i = 0; i < ptsCnt; ++i) {
|
|
*p *= *transform;
|
|
++p;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 irregularly 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 (!tvg::equal(cTransform->e11, cTransform->e22) || (tvg::zero(cTransform->e11) && !tvg::equal(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 = IteratorAccessor::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 = P(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);
|
|
|
|
//mesh: currently only available in bitmap image.
|
|
const Polygon* triangles = nullptr;
|
|
auto triangleCnt = picture->mesh(&triangles);
|
|
if (triangles && triangleCnt > 0) {
|
|
TvgBinCounter triangleCntSize = SIZE(triangleCnt);
|
|
TvgBinCounter trianglesSize = triangleCnt * SIZE(triangles[0]);
|
|
|
|
writeTag(TVG_TAG_PICTURE_MESH);
|
|
writeCount(triangleCntSize + trianglesSize);
|
|
cnt += writeData(&triangleCnt, triangleCntSize);
|
|
cnt += writeData(triangles, trianglesSize);
|
|
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(it->count());
|
|
children.push(it->next());
|
|
|
|
while (auto child = it->next()) {
|
|
if (child->type() == Type::Shape) {
|
|
//only dosable if the previous child is a shape.
|
|
auto target = children.last();
|
|
if (target->type() == Type::Shape) {
|
|
if (_merge((Shape*)child, (Shape*)target)) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
children.push(child);
|
|
}
|
|
|
|
//TODO: Keep this for the compatibility, Remove in TVG 1.0 release
|
|
//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 = *pTransform * transform;
|
|
|
|
switch (paint->type()) {
|
|
case Type::Shape: return serializeShape(static_cast<const Shape*>(paint), pTransform, &transform);
|
|
case Type::Scene: return serializeScene(static_cast<const Scene*>(paint), pTransform, &transform);
|
|
case Type::Picture: return serializePicture(static_cast<const Picture*>(paint), pTransform, &transform);
|
|
case Type::Text: {
|
|
TVGERR("TVG", "TODO: Text Serialization!");
|
|
return 0;
|
|
}
|
|
default: return 0;
|
|
}
|
|
|
|
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->type()) {
|
|
case Type::Shape: {
|
|
serializeShape(static_cast<const Shape*>(paint), nullptr, &transform);
|
|
break;
|
|
}
|
|
case Type::Scene: {
|
|
serializeScene(static_cast<const Scene*>(paint), nullptr, &transform);
|
|
break;
|
|
}
|
|
case Type::Picture: {
|
|
serializePicture(static_cast<const Picture*>(paint), nullptr, &transform);
|
|
break;
|
|
}
|
|
case Type::Text: {
|
|
TVGERR("TVG", "TODO: Text Serialization!");
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
if (!saveEncoding(path)) return;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* External Class Implementation */
|
|
/************************************************************************/
|
|
|
|
TvgSaver::~TvgSaver()
|
|
{
|
|
close();
|
|
}
|
|
|
|
|
|
bool TvgSaver::close()
|
|
{
|
|
this->done();
|
|
|
|
if (paint && P(paint)->refCnt == 0) delete(paint);
|
|
paint = nullptr;
|
|
|
|
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] < FLOAT_EPSILON || vsize[1] < FLOAT_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;
|
|
}
|
|
|
|
|
|
bool TvgSaver::save(TVG_UNUSED Animation* animation, TVG_UNUSED Paint* bg, TVG_UNUSED const string& path, TVG_UNUSED uint32_t quality, TVG_UNUSED uint32_t fps)
|
|
{
|
|
TVGLOG("TVG_SAVER", "Animation is not supported.");
|
|
return false;
|
|
}
|
|
|