2 * Copyright (c) 2021 - 2022 Samsung Electronics Co., Ltd. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 #include "tvgSaveModule.h"
24 #include "tvgTvgSaver.h"
31 #elif defined(__linux__)
37 static FILE* _fopen(const char* filename, const char* mode)
39 #if defined(_MSC_VER) && defined(__clang__)
41 auto err = fopen_s(&fp, filename, mode);
42 if (err != 0) return nullptr;
45 auto fp = fopen(filename, mode);
46 if (!fp) return nullptr;
51 #define SIZE(A) sizeof(A)
53 /************************************************************************/
54 /* Internal Class Implementation */
55 /************************************************************************/
57 static inline TvgBinCounter SERIAL_DONE(TvgBinCounter cnt)
59 return SIZE(TvgBinTag) + SIZE(TvgBinCounter) + cnt;
63 /* if the properties are identical, we can merge the shapes. */
64 static bool _merge(Shape* from, Shape* to)
67 uint8_t r2, g2, b2, a2;
70 if (from->fill() || to->fill()) return false;
72 r = g = b = a = r2 = g2 = b2 = a2 = 0;
74 from->fillColor(&r, &g, &b, &a);
75 to->fillColor(&r2, &g2, &b2, &a2);
77 if (r != r2 || g != g2 || b != b2 || a != a2) return false;
80 if (from->composite(nullptr) != CompositeMethod::None) return false;
81 if (to->composite(nullptr) != CompositeMethod::None) return false;
84 if (from->opacity() != to->opacity()) return false;
87 auto t1 = from->transform();
88 auto t2 = to->transform();
90 if (!mathEqual(t1.e11, t2.e11) || !mathEqual(t1.e12, t2.e12) || !mathEqual(t1.e13, t2.e13) ||
91 !mathEqual(t1.e21, t2.e21) || !mathEqual(t1.e22, t2.e22) || !mathEqual(t1.e23, t2.e23) ||
92 !mathEqual(t1.e31, t2.e31) || !mathEqual(t1.e32, t2.e32) || !mathEqual(t1.e33, t2.e33)) {
97 r = g = b = a = r2 = g2 = b2 = a2 = 0;
99 from->strokeColor(&r, &g, &b, &a);
100 to->strokeColor(&r2, &g2, &b2, &a2);
102 if (r != r2 || g != g2 || b != b2 || a != a2) return false;
104 if (fabs(from->strokeWidth() - to->strokeWidth()) > FLT_EPSILON) return false;
106 //OPTIMIZE: Yet we can't merge outlining shapes unless we can support merging shapes feature.
107 if (from->strokeWidth() > 0 || to->strokeWidth() > 0) return false;
109 if (from->strokeCap() != to->strokeCap()) return false;
110 if (from->strokeJoin() != to->strokeJoin()) return false;
111 if (from->strokeDash(nullptr) > 0 || to->strokeDash(nullptr) > 0) return false;
112 if (from->strokeFill() || to->strokeFill()) return false;
115 if (from->fillRule() != to->fillRule()) return false;
117 //Good, identical shapes, we can merge them.
118 const PathCommand* cmds = nullptr;
119 auto cmdCnt = from->pathCommands(&cmds);
121 const Point* pts = nullptr;
122 auto ptsCnt = from->pathCoords(&pts);
124 to->appendPath(cmds, cmdCnt, pts, ptsCnt);
130 bool TvgSaver::saveEncoding(const std::string& path)
132 if (!compress) return flushTo(path);
135 auto uncompressed = buffer.data + headerSize;
136 auto uncompressedSize = buffer.count - headerSize;
138 uint32_t compressedSize, compressedSizeBits;
140 auto compressed = lzwEncode(uncompressed, uncompressedSize, &compressedSize, &compressedSizeBits);
142 //Failed compression.
143 if (!compressed) return flushTo(path);
145 //Optimization is ineffective.
146 if (compressedSize >= uncompressedSize) {
148 return flushTo(path);
151 TVGLOG("TVG_SAVER", "%s, compressed: %d -> %d, saved rate: %3.2f%%", path.c_str(), uncompressedSize, compressedSize, (1 - ((float) compressedSize / (float) uncompressedSize)) * 100);
153 //Update compress size in the header.
154 uncompressed -= (TVG_HEADER_COMPRESS_SIZE + TVG_HEADER_RESERVED_LENGTH);
157 *uncompressed |= TVG_HEAD_FLAG_COMPRESSED;
158 uncompressed += TVG_HEADER_RESERVED_LENGTH;
161 memcpy(uncompressed, &uncompressedSize, TVG_HEADER_UNCOMPRESSED_SIZE);
162 uncompressed += TVG_HEADER_UNCOMPRESSED_SIZE;
165 memcpy(uncompressed, &compressedSize, TVG_HEADER_COMPRESSED_SIZE);
166 uncompressed += TVG_HEADER_COMPRESSED_SIZE;
168 //Compressed Size Bits
169 memcpy(uncompressed, &compressedSizeBits, TVG_HEADER_COMPRESSED_SIZE_BITS);
171 //Good optimization, flush to file.
172 auto fp = _fopen(path.c_str(), "w+");
176 if (fwrite(buffer.data, SIZE(uint8_t), headerSize, fp) == 0) goto fail;
178 //write compressed data
179 if (fwrite(compressed, SIZE(uint8_t), compressedSize, fp) == 0) goto fail;
188 if (compressed) free(compressed);
193 bool TvgSaver::flushTo(const std::string& path)
195 auto fp = _fopen(path.c_str(), "w+");
196 if (!fp) return false;
198 if (fwrite(buffer.data, SIZE(uint8_t), buffer.count, fp) == 0) {
208 /* WARNING: Header format shall not changed! */
209 bool TvgSaver::writeHeader()
211 headerSize = TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + SIZE(vsize) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE;
213 buffer.grow(headerSize);
216 auto ptr = buffer.ptr();
217 memcpy(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH);
218 ptr += TVG_HEADER_SIGNATURE_LENGTH;
221 memcpy(ptr, TVG_HEADER_VERSION, TVG_HEADER_VERSION_LENGTH);
222 ptr += TVG_HEADER_VERSION_LENGTH;
224 buffer.count += (TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH);
227 writeData(vsize, SIZE(vsize));
230 //4. Reserved data + Compress size
231 memset(ptr, 0x00, TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE);
232 buffer.count += (TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE);
238 void TvgSaver::writeTag(TvgBinTag tag)
240 buffer.grow(SIZE(TvgBinTag));
241 memcpy(buffer.ptr(), &tag, SIZE(TvgBinTag));
242 buffer.count += SIZE(TvgBinTag);
246 void TvgSaver::writeCount(TvgBinCounter cnt)
248 buffer.grow(SIZE(TvgBinCounter));
249 memcpy(buffer.ptr(), &cnt, SIZE(TvgBinCounter));
250 buffer.count += SIZE(TvgBinCounter);
254 void TvgSaver::writeReservedCount(TvgBinCounter cnt)
256 memcpy(buffer.ptr() - cnt - SIZE(TvgBinCounter), &cnt, SIZE(TvgBinCounter));
260 void TvgSaver::reserveCount()
262 buffer.grow(SIZE(TvgBinCounter));
263 buffer.count += SIZE(TvgBinCounter);
267 TvgBinCounter TvgSaver::writeData(const void* data, TvgBinCounter cnt)
270 memcpy(buffer.ptr(), data, cnt);
277 TvgBinCounter TvgSaver::writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data)
279 auto growCnt = SERIAL_DONE(cnt);
281 buffer.grow(growCnt);
283 auto ptr = buffer.ptr();
288 memcpy(ptr, &cnt, SIZE(TvgBinCounter));
289 ptr += SIZE(TvgBinCounter);
291 memcpy(ptr, data, cnt);
294 buffer.count += growCnt;
300 TvgBinCounter TvgSaver::writeTransform(const Matrix* transform, TvgBinTag tag)
302 if (!mathIdentity(transform)) return writeTagProperty(tag, SIZE(Matrix), transform);
307 TvgBinCounter TvgSaver::serializePaint(const Paint* paint, const Matrix* pTransform)
309 TvgBinCounter cnt = 0;
312 auto opacity = paint->opacity();
314 cnt += writeTagProperty(TVG_TAG_PAINT_OPACITY, SIZE(opacity), &opacity);
318 const Paint* cmpTarget = nullptr;
319 auto cmpMethod = paint->composite(&cmpTarget);
320 if (cmpMethod != CompositeMethod::None && cmpTarget) {
321 cnt += serializeComposite(cmpTarget, cmpMethod, pTransform);
328 /* Propagate parents properties to the child so that we can skip saving the parent. */
329 TvgBinCounter TvgSaver::serializeChild(const Paint* parent, const Paint* child, const Matrix* transform)
331 const Paint* compTarget = nullptr;
332 auto compMethod = parent->composite(&compTarget);
334 /* If the parent & the only child have composition, we can't skip the parent...
335 Or if the parent has the transform and composition, we can't skip the parent... */
336 if (compMethod != CompositeMethod::None) {
337 if (transform || child->composite(nullptr) != CompositeMethod::None) return 0;
341 uint32_t opacity = parent->opacity();
344 uint32_t tmp = (child->opacity() * opacity);
345 if (tmp > 0) tmp /= 255;
346 const_cast<Paint*>(child)->opacity(tmp);
349 //propagate composition
350 if (compTarget) const_cast<Paint*>(child)->composite(unique_ptr<Paint>(compTarget->duplicate()), compMethod);
352 return serialize(child, transform);
356 TvgBinCounter TvgSaver::serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform)
358 auto it = this->iterator(scene);
359 if (it->count() == 0) {
364 //Case - Only Child: Skip saving this scene.
365 if (it->count() == 1) {
366 auto cnt = serializeChild(scene, it->next(), cTransform);
375 //Case - Delegator Scene: This scene is just a delegator, we can skip this:
376 if (scene->composite(nullptr) == CompositeMethod::None && scene->opacity() == 255) {
377 auto ret = serializeChildren(it, cTransform, false);
382 //Case - Serialize Scene & its children
383 writeTag(TVG_TAG_CLASS_SCENE);
386 auto cnt = serializeChildren(it, cTransform, true) + serializePaint(scene, pTransform);
390 writeReservedCount(cnt);
392 return SERIAL_DONE(cnt);
396 TvgBinCounter TvgSaver::serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform)
398 const Fill::ColorStop* stops = nullptr;
399 auto stopsCnt = fill->colorStops(&stops);
400 if (!stops || stopsCnt == 0) return 0;
405 TvgBinCounter cnt = 0;
408 if (fill->identifier() == TVG_CLASS_ID_RADIAL) {
410 static_cast<const RadialGradient*>(fill)->radial(args, args + 1, args + 2);
411 cnt += writeTagProperty(TVG_TAG_FILL_RADIAL_GRADIENT, SIZE(args), args);
415 static_cast<const LinearGradient*>(fill)->linear(args, args + 1, args + 2, args + 3);
416 cnt += writeTagProperty(TVG_TAG_FILL_LINEAR_GRADIENT, SIZE(args), args);
419 if (auto flag = static_cast<TvgBinFlag>(fill->spread()))
420 cnt += writeTagProperty(TVG_TAG_FILL_FILLSPREAD, SIZE(TvgBinFlag), &flag);
421 cnt += writeTagProperty(TVG_TAG_FILL_COLORSTOPS, stopsCnt * SIZE(Fill::ColorStop), stops);
423 auto gTransform = fill->transform();
424 if (pTransform) gTransform = mathMultiply(pTransform, &gTransform);
426 cnt += writeTransform(&gTransform, TVG_TAG_FILL_TRANSFORM);
428 writeReservedCount(cnt);
430 return SERIAL_DONE(cnt);
434 TvgBinCounter TvgSaver::serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform)
436 writeTag(TVG_TAG_SHAPE_STROKE);
440 auto width = shape->strokeWidth();
441 if (preTransform) width *= sqrtf(powf(pTransform->e11, 2.0f) + powf(pTransform->e21, 2.0f)); //we know x/y scaling factors are same.
442 auto cnt = writeTagProperty(TVG_TAG_SHAPE_STROKE_WIDTH, SIZE(width), &width);
445 if (auto flag = static_cast<TvgBinFlag>(shape->strokeCap()))
446 cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_CAP, SIZE(TvgBinFlag), &flag);
449 if (auto flag = static_cast<TvgBinFlag>(shape->strokeJoin()))
450 cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_JOIN, SIZE(TvgBinFlag), &flag);
453 if (auto fill = shape->strokeFill()) {
454 cnt += serializeFill(fill, TVG_TAG_SHAPE_STROKE_FILL, (preTransform ? pTransform : nullptr));
456 uint8_t color[4] = {0, 0, 0, 0};
457 shape->strokeColor(color, color + 1, color + 2, color + 3);
458 cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_COLOR, SIZE(color), &color);
462 const float* dashPattern = nullptr;
463 auto dashCnt = shape->strokeDash(&dashPattern);
464 if (dashPattern && dashCnt > 0) {
465 TvgBinCounter dashCntSize = SIZE(dashCnt);
466 TvgBinCounter dashPtrnSize = dashCnt * SIZE(dashPattern[0]);
468 writeTag(TVG_TAG_SHAPE_STROKE_DASHPTRN);
469 writeCount(dashCntSize + dashPtrnSize);
470 cnt += writeData(&dashCnt, dashCntSize);
471 cnt += writeData(dashPattern, dashPtrnSize);
472 cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
475 writeReservedCount(cnt);
477 return SERIAL_DONE(cnt);
481 TvgBinCounter TvgSaver::serializePath(const Shape* shape, const Matrix* transform, bool preTransform)
483 const PathCommand* cmds = nullptr;
484 auto cmdCnt = shape->pathCommands(&cmds);
485 const Point* pts = nullptr;
486 auto ptsCnt = shape->pathCoords(&pts);
488 if (!cmds || !pts || cmdCnt == 0 || ptsCnt == 0) return 0;
490 writeTag(TVG_TAG_SHAPE_PATH);
493 /* Reduce the binary size.
494 Convert PathCommand(4 bytes) to TvgBinFlag(1 byte) */
495 TvgBinFlag* outCmds = (TvgBinFlag*)alloca(SIZE(TvgBinFlag) * cmdCnt);
496 for (uint32_t i = 0; i < cmdCnt; ++i) {
497 outCmds[i] = static_cast<TvgBinFlag>(cmds[i]);
500 auto cnt = writeData(&cmdCnt, SIZE(cmdCnt));
501 cnt += writeData(&ptsCnt, SIZE(ptsCnt));
502 cnt += writeData(outCmds, SIZE(TvgBinFlag) * cmdCnt);
506 if (!mathEqual(transform->e11, 1.0f) || !mathZero(transform->e12) || !mathZero(transform->e13) ||
507 !mathZero(transform->e21) || !mathEqual(transform->e22, 1.0f) || !mathZero(transform->e23) ||
508 !mathZero(transform->e31) || !mathZero(transform->e32) || !mathEqual(transform->e33, 1.0f)) {
509 auto p = const_cast<Point*>(pts);
510 for (uint32_t i = 0; i < ptsCnt; ++i) mathMultiply(p++, transform);
514 cnt += writeData(pts, ptsCnt * SIZE(pts[0]));
516 writeReservedCount(cnt);
518 return SERIAL_DONE(cnt);
522 TvgBinCounter TvgSaver::serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform)
524 writeTag(TVG_TAG_CLASS_SHAPE);
526 TvgBinCounter cnt = 0;
529 if (auto flag = static_cast<TvgBinFlag>(shape->fillRule())) {
530 cnt = writeTagProperty(TVG_TAG_SHAPE_FILLRULE, SIZE(TvgBinFlag), &flag);
533 //the pre-transformation can't be applied in the case when the stroke is dashed or irregulary scaled
534 bool preTransform = true;
537 if (shape->strokeWidth() > 0) {
538 uint8_t color[4] = {0, 0, 0, 0};
539 shape->strokeColor(color, color + 1, color + 2, color + 3);
540 auto fill = shape->strokeFill();
541 if (fill || color[3] > 0) {
542 if (!mathEqual(cTransform->e11, cTransform->e22) || (mathZero(cTransform->e11) && !mathEqual(cTransform->e12, cTransform->e21)) || shape->strokeDash(nullptr) > 0) preTransform = false;
543 cnt += serializeStroke(shape, cTransform, preTransform);
548 if (auto fill = shape->fill()) {
549 cnt += serializeFill(fill, TVG_TAG_SHAPE_FILL, (preTransform ? cTransform : nullptr));
551 uint8_t color[4] = {0, 0, 0, 0};
552 shape->fillColor(color, color + 1, color + 2, color + 3);
553 if (color[3] > 0) cnt += writeTagProperty(TVG_TAG_SHAPE_COLOR, SIZE(color), color);
556 cnt += serializePath(shape, cTransform, preTransform);
558 if (!preTransform) cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM);
559 cnt += serializePaint(shape, pTransform);
561 writeReservedCount(cnt);
563 return SERIAL_DONE(cnt);
567 /* Picture has either a vector scene or a bitmap. */
568 TvgBinCounter TvgSaver::serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform)
570 auto it = this->iterator(picture);
572 //Case - Vector Scene:
573 if (it->count() == 1) {
574 auto cnt = serializeChild(picture, it->next(), cTransform);
575 //Only child, Skip to save Picture...
579 /* Unfortunately, we can't skip the Picture because it might have a compositor,
580 Serialize Scene(instead of the Picture) & its scene. */
582 writeTag(TVG_TAG_CLASS_SCENE);
584 auto cnt = serializeChildren(it, cTransform, true) + serializePaint(picture, pTransform);
585 writeReservedCount(cnt);
587 return SERIAL_DONE(cnt);
592 //Case - Bitmap Image:
594 auto pixels = picture->data(&w, &h);
595 if (!pixels) return 0;
597 writeTag(TVG_TAG_CLASS_PICTURE);
600 TvgBinCounter cnt = 0;
601 TvgBinCounter sizeCnt = SIZE(w);
602 TvgBinCounter imgSize = w * h * SIZE(pixels[0]);
604 writeTag(TVG_TAG_PICTURE_RAW_IMAGE);
605 writeCount(2 * sizeCnt + imgSize);
607 cnt += writeData(&w, sizeCnt);
608 cnt += writeData(&h, sizeCnt);
609 cnt += writeData(pixels, imgSize);
610 cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
612 //Bitmap picture needs the transform info.
613 cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM);
615 cnt += serializePaint(picture, pTransform);
617 writeReservedCount(cnt);
619 return SERIAL_DONE(cnt);
623 TvgBinCounter TvgSaver::serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform)
625 writeTag(TVG_TAG_PAINT_CMP_TARGET);
628 auto flag = static_cast<TvgBinFlag>(cmpMethod);
629 auto cnt = writeTagProperty(TVG_TAG_PAINT_CMP_METHOD, SIZE(TvgBinFlag), &flag);
631 cnt += serialize(cmpTarget, pTransform, true);
633 writeReservedCount(cnt);
635 return SERIAL_DONE(cnt);
639 TvgBinCounter TvgSaver::serializeChildren(Iterator* it, const Matrix* pTransform, bool reserved)
641 TvgBinCounter cnt = 0;
643 //Merging shapes. the result is written in the children.
644 Array<const Paint*> children;
645 children.reserve(it->count());
646 children.push(it->next());
648 while (auto child = it->next()) {
649 if (child->identifier() == TVG_CLASS_ID_SHAPE) {
650 //only dosable if the previous child is a shape.
651 auto target = children.ptr() - 1;
652 if ((*target)->identifier() == TVG_CLASS_ID_SHAPE) {
653 if (_merge((Shape*)child, (Shape*)*target)) {
658 children.push(child);
661 //The children of a reserved scene
662 if (reserved && children.count > 1) {
663 cnt += writeTagProperty(TVG_TAG_SCENE_RESERVEDCNT, SIZE(children.count), &children.count);
666 //Serialize merged children.
667 auto child = children.data;
668 for (uint32_t i = 0; i < children.count; ++i, ++child) {
669 cnt += serialize(*child, pTransform);
676 TvgBinCounter TvgSaver::serialize(const Paint* paint, const Matrix* pTransform, bool compTarget)
678 if (!paint) return 0;
680 //Invisible paint, no point to save it if the paint is not the composition target...
681 if (!compTarget && paint->opacity() == 0) return 0;
683 auto transform = const_cast<Paint*>(paint)->transform();
684 if (pTransform) transform = mathMultiply(pTransform, &transform);
686 switch (paint->identifier()) {
687 case TVG_CLASS_ID_SHAPE: return serializeShape(static_cast<const Shape*>(paint), pTransform, &transform);
688 case TVG_CLASS_ID_SCENE: return serializeScene(static_cast<const Scene*>(paint), pTransform, &transform);
689 case TVG_CLASS_ID_PICTURE: return serializePicture(static_cast<const Picture*>(paint), pTransform, &transform);
696 void TvgSaver::run(unsigned tid)
698 if (!writeHeader()) return;
700 //Serialize Root Paint, without its transform.
701 Matrix transform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
703 if (paint->opacity() > 0) {
704 switch (paint->identifier()) {
705 case TVG_CLASS_ID_SHAPE: {
706 serializeShape(static_cast<const Shape*>(paint), nullptr, &transform);
709 case TVG_CLASS_ID_SCENE: {
710 serializeScene(static_cast<const Scene*>(paint), nullptr, &transform);
713 case TVG_CLASS_ID_PICTURE: {
714 serializePicture(static_cast<const Picture*>(paint), nullptr, &transform);
720 if (!saveEncoding(path)) return;
724 /************************************************************************/
725 /* External Class Implementation */
726 /************************************************************************/
728 TvgSaver::~TvgSaver()
734 bool TvgSaver::close()
751 bool TvgSaver::save(Paint* paint, const string& path, bool compress)
757 paint->bounds(&x, &y, &vsize[0], &vsize[1], false);
759 //cut off the negative space
760 if (x < 0) vsize[0] += x;
761 if (y < 0) vsize[1] += y;
763 if (vsize[0] < FLT_EPSILON || vsize[1] < FLT_EPSILON) {
764 TVGLOG("TVG_SAVER", "Saving paint(%p) has zero view size.", paint);
768 this->path = strdup(path.c_str());
769 if (!this->path) return false;
772 this->compress = compress;
774 TaskScheduler::request(this);