mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-26 01:41:30 +00:00

Saver tries to pre-transfom to skip the matrix data, but it missed the case - transformed stroking, we skip it also only when xy scaling factors are same excluding the dash properties, because scaled of the stroking is depent on the engines, we have no idea of the proper input data in advance. @Issues: https://github.com/Samsung/thorvg/issues/773
766 lines
23 KiB
C++
766 lines
23 KiB
C++
/*
|
|
* 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 <float.h>
|
|
#include <math.h>
|
|
#include "tvgSaveModule.h"
|
|
#include "tvgTvgSaver.h"
|
|
#include "tvgLzw.h"
|
|
|
|
#define SIZE(A) sizeof(A)
|
|
|
|
/************************************************************************/
|
|
/* Internal Class Implementation */
|
|
/************************************************************************/
|
|
|
|
static inline TvgBinCounter SERIAL_DONE(TvgBinCounter cnt)
|
|
{
|
|
return SIZE(TvgBinTag) + SIZE(TvgBinCounter) + cnt;
|
|
}
|
|
|
|
|
|
static Matrix _multiply(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;
|
|
}
|
|
|
|
|
|
static void _multiply(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;
|
|
}
|
|
|
|
|
|
/* 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 (fabs(t1.e11 - t2.e11) > FLT_EPSILON || fabs(t1.e12 - t2.e12) > FLT_EPSILON || fabs(t1.e13 - t2.e13) > FLT_EPSILON ||
|
|
fabs(t1.e21 - t2.e21) > FLT_EPSILON || fabs(t1.e22 - t2.e22) > FLT_EPSILON || fabs(t1.e23 - t2.e23) > FLT_EPSILON ||
|
|
fabs(t1.e31 - t2.e31) > FLT_EPSILON || fabs(t1.e32 - t2.e32) > FLT_EPSILON || fabs(t1.e33 - t2.e33) > FLT_EPSILON) {
|
|
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;
|
|
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
|
|
*reinterpret_cast<TvgBinCounter*>(uncompressed) = uncompressedSize;
|
|
uncompressed += TVG_HEADER_UNCOMPRESSED_SIZE;
|
|
|
|
//Comprssed Size
|
|
*reinterpret_cast<TvgBinCounter*>(uncompressed) = compressedSize;
|
|
uncompressed += TVG_HEADER_COMPRESSED_SIZE;
|
|
|
|
//Compressed Size Bits
|
|
*reinterpret_cast<TvgBinCounter*>(uncompressed) = compressedSizeBits;
|
|
|
|
//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)
|
|
{
|
|
if (fabs(transform.e11 - 1) > FLT_EPSILON || fabs(transform.e12) > FLT_EPSILON || fabs(transform.e13) > FLT_EPSILON ||
|
|
fabs(transform.e21) > FLT_EPSILON || fabs(transform.e22 - 1) > FLT_EPSILON || fabs(transform.e23) > FLT_EPSILON ||
|
|
fabs(transform.e31) > FLT_EPSILON || fabs(transform.e32) > FLT_EPSILON || fabs(transform.e33 - 1) > FLT_EPSILON) {
|
|
return writeTagProperty(TVG_TAG_PAINT_TRANSFORM, 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)
|
|
{
|
|
auto it = this->iterator(scene);
|
|
if (it->count() == 0) return 0;
|
|
|
|
auto transform = const_cast<Scene*>(scene)->transform();
|
|
if (pTransform) transform = _multiply(pTransform, &transform);
|
|
|
|
//Case - Only Child: Skip saving this scene.
|
|
if (it->count() == 1) {
|
|
auto cnt = serializeChild(scene, it->next(), &transform);
|
|
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) {
|
|
return serializeChildren(it, &transform, false);
|
|
}
|
|
|
|
//Case - Serialize Scene & its children
|
|
writeTag(TVG_TAG_CLASS_SCENE);
|
|
reserveCount();
|
|
|
|
auto cnt = serializeChildren(it, &transform, 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->id() == TVG_CLASS_ID_RADIAL) {
|
|
float args[3];
|
|
static_cast<const RadialGradient*>(fill)->radial(args, args + 1, args + 2);
|
|
if (pTransform) {
|
|
auto cx = args[0] * pTransform->e11 + args[1] * pTransform->e12 + pTransform->e13;
|
|
args[1] = args[0] * pTransform->e21 + args[1] * pTransform->e22 + pTransform->e23;
|
|
args[0] = cx;
|
|
args[2] *= sqrt(pow(pTransform->e11, 2) + pow(pTransform->e21, 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);
|
|
if (pTransform) {
|
|
auto x1 = args[0];
|
|
args[0] = x1 * pTransform->e11 + args[1] * pTransform->e12 + pTransform->e13;
|
|
args[1] = x1 * pTransform->e21 + args[1] * pTransform->e22 + pTransform->e23;
|
|
auto x2 = args[2];
|
|
args[2] = x2 * pTransform->e11 + args[3] * pTransform->e12 + pTransform->e13;
|
|
args[3] = x2 * pTransform->e21 + args[3] * pTransform->e22 + pTransform->e23;
|
|
}
|
|
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);
|
|
|
|
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 *= pTransform->e11; //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, pTransform);
|
|
} 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[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(outCmds));
|
|
|
|
//transform?
|
|
if (preTransform) {
|
|
if (fabs(transform.e11 - 1) > FLT_EPSILON || fabs(transform.e12) > FLT_EPSILON || fabs(transform.e13) > FLT_EPSILON ||
|
|
fabs(transform.e21) > FLT_EPSILON || fabs(transform.e22 - 1) > FLT_EPSILON || fabs(transform.e23) > FLT_EPSILON ||
|
|
fabs(transform.e31) > FLT_EPSILON || fabs(transform.e32) > FLT_EPSILON || fabs(transform.e33 - 1) > FLT_EPSILON) {
|
|
auto p = const_cast<Point*>(pts);
|
|
for (uint32_t i = 0; i < ptsCnt; ++i) _multiply(p++, transform);
|
|
}
|
|
}
|
|
|
|
cnt += writeData(pts, ptsCnt * SIZE(pts[0]));
|
|
|
|
writeReservedCount(cnt);
|
|
|
|
return SERIAL_DONE(cnt);
|
|
}
|
|
|
|
|
|
TvgBinCounter TvgSaver::serializeShape(const Shape* shape, const Matrix* pTransform)
|
|
{
|
|
writeTag(TVG_TAG_CLASS_SHAPE);
|
|
reserveCount();
|
|
TvgBinCounter cnt = 0;
|
|
|
|
auto transform = const_cast<Shape*>(shape)->transform();
|
|
if (pTransform) transform = _multiply(pTransform, &transform);
|
|
|
|
//fill rule
|
|
if (auto flag = static_cast<TvgBinFlag>(shape->fillRule()))
|
|
cnt = writeTagProperty(TVG_TAG_SHAPE_FILLRULE, SIZE(TvgBinFlag), &flag);
|
|
|
|
|
|
bool preTransform = true;
|
|
|
|
//stroke
|
|
if (shape->strokeWidth() > 0) {
|
|
//We can't apply pre-transformation if the stroke has the irregular scaling per directions or it has dash.
|
|
if (abs(transform.e11 - transform.e22) > FLT_EPSILON || shape->strokeDash(nullptr) > 0) preTransform = false;
|
|
|
|
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) cnt += serializeStroke(shape, &transform, preTransform);
|
|
}
|
|
|
|
//fill
|
|
if (auto fill = shape->fill()) {
|
|
cnt += serializeFill(fill, TVG_TAG_SHAPE_FILL, &transform);
|
|
} 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, transform, preTransform);
|
|
|
|
if (!preTransform) cnt += writeTransform(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)
|
|
{
|
|
//transform
|
|
auto transform = const_cast<Picture*>(picture)->transform();
|
|
if (pTransform) transform = _multiply(pTransform, &transform);
|
|
|
|
auto it = this->iterator(picture);
|
|
|
|
//Case - Vector Scene:
|
|
if (it->count() == 1) {
|
|
auto cnt = serializeChild(picture, it->next(), &transform);
|
|
//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, &transform, 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(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->id() == TVG_CLASS_ID_SHAPE) {
|
|
//only dosable if the previous child is a shape.
|
|
auto target = children.ptr() - 1;
|
|
if ((*target)->id() == 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;
|
|
|
|
switch (paint->id()) {
|
|
case TVG_CLASS_ID_SHAPE: return serializeShape(static_cast<const Shape*>(paint), pTransform);
|
|
case TVG_CLASS_ID_SCENE: return serializeScene(static_cast<const Scene*>(paint), pTransform);
|
|
case TVG_CLASS_ID_PICTURE: return serializePicture(static_cast<const Picture*>(paint), pTransform);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void TvgSaver::run(unsigned tid)
|
|
{
|
|
if (!writeHeader()) return;
|
|
if (serialize(paint, nullptr) == 0) return;
|
|
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();
|
|
|
|
this->path = strdup(path.c_str());
|
|
if (!this->path) return false;
|
|
|
|
paint->bounds(nullptr, nullptr, &vsize[0], &vsize[1]);
|
|
if (vsize[0] <= FLT_EPSILON || vsize[1] <= FLT_EPSILON) {
|
|
TVGLOG("TVG_SAVER", "Saving paint(%p) has zero view size.", paint);
|
|
return false;
|
|
}
|
|
|
|
this->paint = paint;
|
|
this->compress = compress;
|
|
|
|
TaskScheduler::request(this);
|
|
|
|
return true;
|
|
}
|