spec out tvg binary format

issue: https://github.com/thorvg/thorvg/issues/2721
This commit is contained in:
Hermet Park 2024-10-12 15:15:55 +09:00 committed by Hermet Park
parent 366cbfbc6b
commit 0e45fabb3d
104 changed files with 30 additions and 3133 deletions

View file

@ -8,7 +8,6 @@
/src/renderer/wg_engine @SergeyLebedkin
/src/loaders/external_webp/ @JSUYA
/src/loaders/raw/ @JSUYA
/src/loaders/tvg/ @mgrudzinska
/src/loaders/svg/ @JSUYA @mgrudzinska
/src/loaders/webp/ @JSUYA
/src/bindings/capi @mgrudzinska

View file

@ -27,7 +27,7 @@ The following list shows primitives that are supported by ThorVG: <br />
- **Scene Management**: retainable scene graph and object transformations
- **Composition**: various blending and masking
- **Text**: unicode characters with horizontal text layout using scalable fonts (TTF)
- **Images**: TVG, SVG, JPG, PNG, WebP, and raw bitmaps
- **Images**: SVG, JPG, PNG, WebP, and raw bitmaps
- **Animations**: Lottie
<p align="center">
@ -64,7 +64,6 @@ The task scheduler has been meticulously crafted to conceal complexity, streamli
- [Quick Start](#quick-start)
- [SVG](#svg)
- [Lottie](#lottie)
- [TVG Picture](#tvg-picture)
- [In Practice](#in-practice)
- [Canva iOS](#canva-ios)
- [dotLottie](#dotlottie)
@ -77,7 +76,6 @@ The task scheduler has been meticulously crafted to conceal complexity, streamli
- [ThorVG Viewer](#thorvg-viewer)
- [Lottie to GIF](#lottie-to-gif)
- [SVG to PNG](#svg-to-png)
- [SVG to TVG](#svg-to-tvg)
- [API Bindings](#api-bindings)
- [Dependencies](#dependencies)
- [Contributors](#contributors)
@ -325,32 +323,6 @@ Let's suppose the progress variable determines the position of the animation, ra
Please check out the [ThorVG Test App](https://thorvg-perf-test.vercel.app/) to see the performance of various Lottie animations powered by ThorVG.
[Back to contents](#contents)
<br />
<br />
## TVG Picture
ThorVG introduces the dedicated vector data format, known as TVG Picture, designed to efficiently store Paint node properties within a scene in binary form. This format is meticulously optimized in advance, ensuring compact file sizes and swift data loading processes. </br>
</br>
To leverage the TVG Picture format, ThorVG employs a specialized module called TVG Saver. This module is responsible for optimizing the data associated with all scene-tree nodes and storing them in binary form. During the optimization phase, TVG Saver intelligently eliminates unused information, eliminates duplicated properties, consolidates overlapping shapes, and employs data compression where feasible. Remarkably, these optimizations maintain compatibility with future versions of ThorVG libraries, with data compression utilizing the [Lempel-Ziv-Welchi](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch) algorithm when applicable.</br>
</br>
As a result of these efforts, the final data size is notably smaller than other text-based vector data formats, such as SVG. This reduction in data size not only minimizes I/O operations but also mitigates memory bandwidth requirements during data loading. This aspect proves particularly beneficial for programs reliant on substantial vector resources. </br>
</br>
Furthermore, TVG Picture substantially streamlines resource loading tasks by circumventing the need for data interpretation, resulting in reduced runtime memory demands and rendering tasks that subsequently enhance performance. </br>
</br>
By adopting TVG Picture, you can achieve an average reduction of over 30% in data size and loading times (for more details, refer to "[See More](https://github.com/thorvg/thorvg/wiki/TVG-Picture-Binary-Size)"). Notably, the extent of performance improvement is contingent on resource size and complexity. </br>
</br>
<p align="center">
<img width="909" height="auto" src="https://github.com/thorvg/thorvg/blob/main/res/example_tvgsize.png">
</p>
As TVG Saver facilitates the export of the scene-tree into TVG Picture data files (TVG), the subsequent task of importing and restoring this data to programmable instances is efficiently handled by the TVG Loader. For seamless conversion from SVG to TVG, the ThorVG Viewer provides a swift solution.
<p align="center">
<img width="710" height="auto" src="https://github.com/thorvg/thorvg/blob/main/res/example_tvgmodule.png">
</p>
[Back to contents](#contents)
<br />
<br />
@ -423,7 +395,7 @@ The ThorVG API documentation can be accessed at [thorvg.org/apis](https://www.th
<br />
## Tools
### ThorVG Viewer
ThorVG provides the resource verification tool for the ThorVG Engine. [ThorVG viewer](https://thorvg.github.io/thorvg.viewer/) does immediate rendering via web browser running on the ThorVG web-assembly binary, allowing real-time editing of the vector elements on it. It doesn't upload your resources to any external server while allowing to export to supported formats such as TVG, so the designer resource copyright is protected.</br>
ThorVG provides the resource verification tool for the ThorVG Engine. [ThorVG viewer](https://thorvg.github.io/thorvg.viewer/) does immediate rendering via web browser running on the ThorVG web-assembly binary, allowing real-time editing of the vector elements on it. It doesn't upload your resources to any external server while allowing to export to supported formats such as GIF, so the designer resource copyright is protected.</br>
</br>
<p align="center">
@ -494,24 +466,6 @@ Examples:
$ svg2png . -r 200x200
```
### SVG to TVG
ThorVG provides an executable `svg2tvg` converter that generates a TVG file from an SVG file.
To use `svg2tvg`, you need to activate this feature in the build option:
```
meson setup builddir -Dtools=svg2tvg -Dsavers=tvg
```
Examples of the usage of the `svg2tvg`:
```
Usage:
svg2tvg [SVG file] or [SVG folder]
Examples:
$ svg2tvg input.svg
$ svg2tvg svgfolder
```
[Back to contents](#contents)
<br />
<br />

View file

@ -1,62 +0,0 @@
/*
* 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 "Example.h"
/************************************************************************/
/* ThorVG Drawing Contents */
/************************************************************************/
struct UserExample : tvgexam::Example
{
bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override
{
if (!canvas) return false;
//load the tvg file
auto picture = tvg::Picture::gen();
if (!tvgexam::verify(picture->load(EXAMPLE_DIR"/tvg/test.tvg"), "You might need to run \"TvgSaver\" example first to generate the \"test.tvg\".")) {
return false;
}
float w2, h2;
picture->size(&w2, &h2);
cout << "default tvg view size = " << w2 << " x " << h2 << endl;
picture->translate(w2 * 0.1f, h2 * 0.1f);
picture->size(w2 * 0.8f, h2 * 0.8f);
canvas->push(std::move(picture));
return true;
}
};
/************************************************************************/
/* Entry Point */
/************************************************************************/
int main(int argc, char **argv)
{
return tvgexam::main(new UserExample, argc, argv);
}

View file

@ -1,115 +0,0 @@
/*
* Copyright (c) 2020 - 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 "Example.h"
/************************************************************************/
/* ThorVG Drawing Contents */
/************************************************************************/
#define NUM_PER_ROW 9
#define NUM_PER_COL 9
struct UserExample : tvgexam::Example
{
std::vector<unique_ptr<tvg::Picture>> pictures;
uint32_t w, h;
uint32_t size;
int counter = 0;
void populate(const char* path) override
{
if (counter >= NUM_PER_ROW * NUM_PER_COL) return;
//ignore if not tvg.
const char *ext = path + strlen(path) - 3;
if (strcmp(ext, "tvg")) return;
auto picture = tvg::Picture::gen();
if (!tvgexam::verify(picture->load(path))) return;
//image scaling preserving its aspect ratio
float scale;
float shiftX = 0.0f, shiftY = 0.0f;
float w, h;
picture->size(&w, &h);
if (w > h) {
scale = size / w;
shiftY = (size - h * scale) * 0.5f;
} else {
scale = size / h;
shiftX = (size - w * scale) * 0.5f;
}
picture->scale(scale);
picture->translate((counter % NUM_PER_ROW) * size + shiftX, (counter / NUM_PER_ROW) * (this->h / NUM_PER_COL) + shiftY);
pictures.push_back(std::move(picture));
cout << "TVG: " << path << endl;
counter++;
}
bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override
{
if (!canvas) return false;
//Background
auto shape = tvg::Shape::gen();
shape->appendRect(0, 0, w, h);
shape->fill(255, 255, 255);
canvas->push(std::move(shape));
this->w = w;
this->h = h;
this->size = w / NUM_PER_ROW;
this->scandir(EXAMPLE_DIR"/tvg");
/* This showcase demonstrates the asynchronous loading of tvg.
For this, pictures are pushed at a certain sync time.
This allows time for the tvg resources to finish loading;
otherwise, you can push pictures immediately. */
for (auto& paint : pictures) {
canvas->push(std::move(paint));
}
pictures.clear();
return true;
}
};
/************************************************************************/
/* Entry Point */
/************************************************************************/
int main(int argc, char **argv)
{
return tvgexam::main(new UserExample, argc, argv, 1280, 1280);
}

View file

@ -1,233 +0,0 @@
/*
* 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 "Example.h"
using namespace std;
/************************************************************************/
/* ThorVG Saving Contents */
/************************************************************************/
unique_ptr<tvg::Paint> tvgClippedImage(uint32_t * data, int width, int height)
{
auto image = tvg::Picture::gen();
if (!tvgexam::verify(image->load(data, width, height, true, true))) return nullptr;
image->translate(400, 0);
image->scale(2);
auto imageClip = tvg::Shape::gen();
imageClip->appendCircle(400, 200, 80, 180);
imageClip->translate(200, 0);
image->clip(std::move(imageClip));
return image;
}
unique_ptr<tvg::Paint> tvgMaskedSvg()
{
auto svg = tvg::Picture::gen();
if (!tvgexam::verify(svg->load(EXAMPLE_DIR"/svg/tiger.svg"))) return nullptr;
svg->opacity(200);
svg->scale(0.3);
svg->translate(50, 450);
auto svgMask = tvg::Shape::gen();
//star
svgMask->moveTo(199, 34);
svgMask->lineTo(253, 143);
svgMask->lineTo(374, 160);
svgMask->lineTo(287, 244);
svgMask->lineTo(307, 365);
svgMask->lineTo(199, 309);
svgMask->lineTo(97, 365);
svgMask->lineTo(112, 245);
svgMask->lineTo(26, 161);
svgMask->lineTo(146, 143);
svgMask->close();
svgMask->fill(0, 0, 0);
svgMask->translate(30, 440);
svgMask->opacity(200);
svgMask->scale(0.7);
svg->composite(std::move(svgMask), tvg::CompositeMethod::AlphaMask);
return svg;
}
unique_ptr<tvg::Paint> tvgNestedPaints(tvg::Fill::ColorStop* colorStops, int colorStopsCnt)
{
auto scene = tvg::Scene::gen();
scene->translate(100, 100);
auto scene2 = tvg::Scene::gen();
scene2->rotate(10);
scene2->scale(2);
scene2->translate(400,400);
auto shape = tvg::Shape::gen();
shape->appendRect(50, 0, 50, 100, 10, 40);
shape->fill(0, 0, 255, 125);
scene2->push(std::move(shape));
scene->push(std::move(scene2));
auto shape2 = tvg::Shape::gen();
shape2->appendRect(0, 0, 50, 100, 10, 40);
auto fillShape = tvg::RadialGradient::gen();
fillShape->radial(25, 50, 25);
fillShape->colorStops(colorStops, colorStopsCnt);
shape2->fill(std::move(fillShape));
shape2->scale(2);
shape2->opacity(200);
shape2->translate(400, 400);
scene->push(std::move(shape2));
return scene;
}
unique_ptr<tvg::Paint> tvgGradientShape(tvg::Fill::ColorStop* colorStops, int colorStopsCnt)
{
float dashPattern[2] = {30, 40};
//gradient shape + dashed stroke
auto fillStroke = tvg::LinearGradient::gen();
fillStroke->linear(20, 120, 380, 280);
fillStroke->colorStops(colorStops, colorStopsCnt);
auto fillShape = tvg::LinearGradient::gen();
fillShape->linear(20, 120, 380, 280);
fillShape->colorStops(colorStops, colorStopsCnt);
auto shape = tvg::Shape::gen();
shape->appendCircle(200, 200, 180, 80);
shape->fill(std::move(fillShape));
shape->strokeWidth(20);
shape->strokeDash(dashPattern, 2);
shape->strokeFill(std::move(fillStroke));
return shape;
}
unique_ptr<tvg::Paint> tvgCircle1(tvg::Fill::ColorStop* colorStops, int colorStopsCnt)
{
auto circ = tvg::Shape::gen();
circ->appendCircle(400, 375, 50, 50);
circ->fill(0, 255, 0, 155);
return circ;
}
unique_ptr<tvg::Paint> tvgCircle2(tvg::Fill::ColorStop* colorStops, int colorStopsCnt)
{
auto circ = tvg::Shape::gen();
circ->appendCircle(400, 425, 50, 50);
auto fill = tvg::RadialGradient::gen();
fill->radial(400, 425, 50);
fill->colorStops(colorStops, colorStopsCnt);
circ->fill(std::move(fill));
return circ;
}
void exportTvg()
{
//prepare the main scene
auto scene = tvg::Scene::gen();
//prepare image source
const int width = 200;
const int height = 300;
ifstream file(EXAMPLE_DIR"/image/rawimage_200x300.raw", ios::binary);
if (!file.is_open()) return;
uint32_t *data = (uint32_t*) malloc(sizeof(uint32_t) * width * height);
file.read(reinterpret_cast<char*>(data), sizeof(uint32_t) * width * height);
file.close();
//clipped image
auto image = tvgClippedImage(data, width, height);
scene->push(std::move(image));
free(data);
//prepare gradient common data
tvg::Fill::ColorStop colorStops1[3];
colorStops1[0] = {0, 255, 0, 0, 255};
colorStops1[1] = {0.5, 0, 0, 255, 127};
colorStops1[2] = {1, 127, 127, 127, 127};
tvg::Fill::ColorStop colorStops2[2];
colorStops2[0] = {0, 255, 0, 0, 255};
colorStops2[1] = {1, 50, 0, 255, 255};
tvg::Fill::ColorStop colorStops3[2];
colorStops3[0] = {0, 0, 0, 255, 155};
colorStops3[1] = {1, 0, 255, 0, 155};
//gradient shape + dashed stroke
auto shape1 = tvgGradientShape(colorStops1, 3);
scene->push(std::move(shape1));
//nested paints
auto scene2 = tvgNestedPaints(colorStops2, 2);
scene->push(std::move(scene2));
//masked svg file
auto svg = tvgMaskedSvg();
scene->push(std::move(svg));
//solid top circle and gradient bottom circle
auto circ1 = tvgCircle1(colorStops3, 2);
scene->push(std::move(circ1));
auto circ2 = tvgCircle2(colorStops3, 2);
scene->push(std::move(circ2));
//inv mask applied to the main scene
auto mask = tvg::Shape::gen();
mask->appendCircle(400, 400, 15, 15);
mask->fill(0, 0, 0);
scene->composite(std::move(mask), tvg::CompositeMethod::InvAlphaMask);
//save the tvg file
auto saver = tvg::Saver::gen();
if (!tvgexam::verify(saver->save(std::move(scene), EXAMPLE_DIR"/tvg/test.tvg"))) return;
saver->sync();
cout << "Successfully exported to test.tvg, Please check the result using PictureTvg!" << endl;
}
/************************************************************************/
/* Entry Point */
/************************************************************************/
int main(int argc, char **argv)
{
if (tvgexam::verify(tvg::Initializer::init(0))) {
exportTvg();
tvg::Initializer::term();
}
return 0;
}

View file

@ -52,7 +52,6 @@ source_file = [
'PicturePng.cpp',
'PictureRaw.cpp',
'PictureSvg.cpp',
'PictureTvg.cpp',
'PictureWebp.cpp',
'RadialGradient.cpp',
'Retaining.cpp',
@ -68,8 +67,6 @@ source_file = [
'Svg.cpp',
'Text.cpp',
'Transform.cpp',
'Tvg.cpp',
'TvgSaver.cpp',
'Update.cpp',
'Viewport.cpp'
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -41,14 +41,12 @@ endif
#Tools
all_tools = get_option('tools').contains('all')
svg2tvg = all_tools or get_option('tools').contains('svg2tvg')
lottie2gif = all_tools or get_option('tools').contains('lottie2gif')
svg2png = all_tools or get_option('tools').contains('svg2png')
#Loaders
all_loaders = get_option('loaders').contains('all')
svg_loader = all_loaders or get_option('loaders').contains('svg') or svg2tvg or svg2png
tvg_loader = all_loaders or get_option('loaders').contains('tvg')
svg_loader = all_loaders or get_option('loaders').contains('svg') or svg2png
png_loader = all_loaders or get_option('loaders').contains('png')
jpg_loader = all_loaders or get_option('loaders').contains('jpg')
lottie_loader = all_loaders or get_option('loaders').contains('lottie') or lottie2gif
@ -57,7 +55,6 @@ webp_loader = all_loaders or get_option('loaders').contains('webp')
#Savers
all_savers = get_option('savers').contains('all')
tvg_saver = all_savers or get_option('savers').contains('tvg') or svg2tvg
gif_saver = all_savers or get_option('savers').contains('gif') or lottie2gif
#Loaders/savers/tools config
@ -65,10 +62,6 @@ if svg_loader
config_h.set10('THORVG_SVG_LOADER_SUPPORT', true)
endif
if tvg_loader
config_h.set10('THORVG_TVG_LOADER_SUPPORT', true)
endif
if png_loader
config_h.set10('THORVG_PNG_LOADER_SUPPORT', true)
endif
@ -89,10 +82,6 @@ if webp_loader
config_h.set10('THORVG_WEBP_LOADER_SUPPORT', true)
endif
if tvg_saver
config_h.set10('THORVG_TVG_SAVER_SUPPORT', true)
endif
if gif_saver
config_h.set10('THORVG_GIF_SAVER_SUPPORT', true)
endif
@ -170,24 +159,21 @@ Summary:
Raster Engine (SW): @5@
Raster Engine (GL): @6@
Raster Engine (WG): @7@
Loader (TVG): @8@
Loader (SVG): @9@
Loader (TTF): @10@
Loader (LOTTIE): @11@
Loader (PNG): @12@
Loader (JPG): @13@
Loader (WEBP): @14@
Saver (TVG): @15@
Saver (GIF): @16@
Binding (CAPI): @17@
Binding (WASM_BETA): @18@
Log Message: @19@
Tests: @20@
Examples: @21@
Tool (Svg2Tvg): @22@
Tool (Svg2Png): @23@
Tool (Lottie2Gif): @24@
Extra (Lottie Expressions): @25@
Loader (SVG): @8@
Loader (TTF): @9@
Loader (LOTTIE): @10@
Loader (PNG): @11@
Loader (JPG): @12@
Loader (WEBP): @13@
Saver (GIF): @14@
Binding (CAPI): @15@
Binding (WASM_BETA): @16@
Log Message: @17@
Tests: @18@
Examples: @19@
Tool (Svg2Png): @20@
Tool (Lottie2Gif): @21@
Extra (Lottie Expressions): @22@
'''.format(
meson.project_version(),
@ -198,21 +184,18 @@ Summary:
sw_engine,
gl_engine,
wg_engine,
tvg_loader,
svg_loader,
ttf_loader,
lottie_loader,
png_loader,
jpg_loader,
webp_loader,
tvg_saver,
gif_saver,
get_option('bindings').contains('capi'),
get_option('bindings').contains('wasm_beta'),
get_option('log'),
get_option('tests'),
get_option('examples'),
svg2tvg,
svg2png,
lottie2gif,
lottie_expressions

View file

@ -6,13 +6,13 @@ option('engines',
option('loaders',
type: 'array',
choices: ['', 'tvg', 'svg', 'png', 'jpg', 'lottie', 'ttf', 'webp', 'all'],
value: ['svg', 'tvg', 'lottie', 'ttf'],
choices: ['', 'svg', 'png', 'jpg', 'lottie', 'ttf', 'webp', 'all'],
value: ['svg', 'lottie', 'ttf'],
description: 'Enable File Loaders in thorvg')
option('savers',
type: 'array',
choices: ['', 'tvg', 'gif', 'all'],
choices: ['', 'gif', 'all'],
value: [''],
description: 'Enable File Savers in thorvg')
@ -34,7 +34,7 @@ option('bindings',
option('tools',
type: 'array',
choices: ['', 'svg2tvg', 'svg2png', 'lottie2gif', 'all'],
choices: ['', 'svg2png', 'lottie2gif', 'all'],
value: [''],
description: 'Enable building thorvg tools')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -348,11 +348,7 @@ public:
// Saver methods
bool save(string mimetype)
{
if (mimetype == "gif") {
return save2Gif();
} else if (mimetype == "tvg") {
return save2Tvg();
}
if (mimetype == "gif") return save2Gif();
errorMsg = "Invalid mimetype";
return false;
@ -420,34 +416,6 @@ public:
return true;
}
bool save2Tvg()
{
errorMsg = NoError;
if (!animation) return false;
auto saver = Saver::gen();
if (!saver) {
errorMsg = "Invalid saver";
return false;
}
//preserve the picture using the reference counting
PP(animation->picture())->ref();
if (saver->save(tvg::cast<Picture>(animation->picture()), "output.tvg") != tvg::Result::Success) {
PP(animation->picture())->unref();
errorMsg = "save(), fail";
return false;
}
saver->sync();
PP(animation->picture())->unref();
return true;
}
// TODO: Advanced APIs wrt Interactivity & theme methods...
private:

View file

@ -1,7 +1,6 @@
source_file = [
'tvgArray.h',
'tvgCompressor.h',
'tvgFormat.h',
'tvgInlist.h',
'tvgLock.h',
'tvgMath.h',

View file

@ -21,7 +21,7 @@
*/
/*
* LempelZivWelch (LZW) encoder/decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com)
* LempelZivWelch (LZW) decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com)
* This is the compression scheme used by the GIF image format and the Unix 'compress' tool.
* Main differences from this implementation is that End Of Input (EOI) and Clear Codes (CC)
@ -55,8 +55,6 @@
*/
#include "config.h"
#include <string>
#include <memory.h>
#include "tvgCompressor.h"
@ -68,7 +66,6 @@ namespace tvg {
/* LZW Implementation */
/************************************************************************/
//LZW Dictionary helper:
constexpr int Nil = -1;
constexpr int MaxDictBits = 12;
@ -76,128 +73,6 @@ constexpr int StartBits = 9;
constexpr int FirstCode = (1 << (StartBits - 1)); // 256
constexpr int MaxDictEntries = (1 << MaxDictBits); // 4096
//Round up to the next power-of-two number, e.g. 37 => 64
static int nextPowerOfTwo(int num)
{
--num;
for (size_t i = 1; i < sizeof(num) * 8; i <<= 1) {
num = num | num >> i;
}
return ++num;
}
struct BitStreamWriter
{
uint8_t* stream; //Growable buffer to store our bits. Heap allocated & owned by the class instance.
int bytesAllocated; //Current size of heap-allocated stream buffer *in bytes*.
int granularity; //Amount bytesAllocated multiplies by when auto-resizing in appendBit().
int currBytePos; //Current byte being written to, from 0 to bytesAllocated-1.
int nextBitPos; //Bit position within the current byte to access next. 0 to 7.
int numBitsWritten; //Number of bits in use from the stream buffer, not including byte-rounding padding.
void internalInit()
{
stream = nullptr;
bytesAllocated = 0;
granularity = 2;
currBytePos = 0;
nextBitPos = 0;
numBitsWritten = 0;
}
uint8_t* allocBytes(const int bytesWanted, uint8_t * oldPtr, const int oldSize)
{
auto newMemory = static_cast<uint8_t *>(malloc(bytesWanted));
memset(newMemory, 0, bytesWanted);
if (oldPtr) {
memcpy(newMemory, oldPtr, oldSize);
free(oldPtr);
}
return newMemory;
}
BitStreamWriter()
{
/* 8192 bits for a start (1024 bytes). It will resize if needed.
Default granularity is 2. */
internalInit();
allocate(8192);
}
BitStreamWriter(const int initialSizeInBits, const int growthGranularity = 2)
{
internalInit();
setGranularity(growthGranularity);
allocate(initialSizeInBits);
}
~BitStreamWriter()
{
free(stream);
}
void allocate(int bitsWanted)
{
//Require at least a byte.
if (bitsWanted <= 0) bitsWanted = 8;
//Round upwards if needed:
if ((bitsWanted % 8) != 0) bitsWanted = nextPowerOfTwo(bitsWanted);
//We might already have the required count.
const int sizeInBytes = bitsWanted / 8;
if (sizeInBytes <= bytesAllocated) return;
stream = allocBytes(sizeInBytes, stream, bytesAllocated);
bytesAllocated = sizeInBytes;
}
void appendBit(const int bit)
{
const uint32_t mask = uint32_t(1) << nextBitPos;
stream[currBytePos] = (stream[currBytePos] & ~mask) | (-bit & mask);
++numBitsWritten;
if (++nextBitPos == 8) {
nextBitPos = 0;
if (++currBytePos == bytesAllocated) allocate(bytesAllocated * granularity * 8);
}
}
void appendBitsU64(const uint64_t num, const int bitCount)
{
for (int b = 0; b < bitCount; ++b) {
const uint64_t mask = uint64_t(1) << b;
const int bit = !!(num & mask);
appendBit(bit);
}
}
uint8_t* release()
{
auto oldPtr = stream;
internalInit();
return oldPtr;
}
void setGranularity(const int growthGranularity)
{
granularity = (growthGranularity >= 2) ? growthGranularity : 2;
}
int getByteCount() const
{
int usedBytes = numBitsWritten / 8;
int leftovers = numBitsWritten % 8;
if (leftovers != 0) ++usedBytes;
return usedBytes;
}
};
struct BitStreamReader
{
const uint8_t* stream; // Pointer to the external bit stream. Not owned by the reader.
@ -272,18 +147,6 @@ struct Dictionary
}
}
int findIndex(const int code, const int value) const
{
if (code == Nil) return value;
//Linear search for now.
//TODO: Worth optimizing with a proper hash-table?
for (int i = 0; i < size; ++i) {
if (entries[i].code == code && entries[i].value == value) return i;
}
return Nil;
}
bool add(const int code, const int value)
{
if (size == MaxDictEntries) return false;
@ -379,52 +242,10 @@ uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint
}
uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits)
{
//LZW encoding context:
int code = Nil;
int codeBitsWidth = StartBits;
Dictionary dictionary;
//Output bit stream we write to. This will allocate memory as needed to accommodate the encoded data.
BitStreamWriter bitStream;
for (; uncompressedSizeBytes > 0; --uncompressedSizeBytes, ++uncompressed) {
const int value = *uncompressed;
const int index = dictionary.findIndex(code, value);
if (index != Nil) {
code = index;
continue;
}
//Write the dictionary code using the minimum bit-with:
bitStream.appendBitsU64(code, codeBitsWidth);
//Flush it when full so we can restart the sequences.
if (!dictionary.flush(codeBitsWidth)) {
//There's still space for this sequence.
dictionary.add(code, value);
}
code = value;
}
//Residual code at the end:
if (code != Nil) bitStream.appendBitsU64(code, codeBitsWidth);
//Pass ownership of the compressed data buffer to the user pointer:
*compressedSizeBytes = bitStream.getByteCount();
*compressedSizeBits = bitStream.numBitsWritten;
return bitStream.release();
}
/************************************************************************/
/* B64 Implementation */
/************************************************************************/
size_t b64Decode(const char* encoded, const size_t len, char** decoded)
{
static constexpr const char B64_INDEX[256] =
@ -473,7 +294,7 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded)
/************************************************************************/
/* DJB2 Implementation */
/* DJB2 Implementation */
/************************************************************************/
unsigned long djb2Encode(const char* str)

View file

@ -27,7 +27,6 @@
namespace tvg
{
uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits);
uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes);
size_t b64Decode(const char* encoded, const size_t len, char** decoded);
unsigned long djb2Encode(const char* str);

View file

@ -1,95 +0,0 @@
/*
* 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.
*/
#ifndef _TVG_FORMAT_H_
#define _TVG_FORMAT_H_
/* TODO: Need to consider whether uin8_t is enough size for extension...
Rather than optimal data, we can use enough size and data compress? */
using TvgBinByte = uint8_t;
using TvgBinCounter = uint32_t;
using TvgBinTag = TvgBinByte;
using TvgBinFlag = TvgBinByte;
//Header
#define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE
#define TVG_HEADER_SIGNATURE "ThorVG"
#define TVG_HEADER_SIGNATURE_LENGTH 6
#define TVG_HEADER_VERSION "010000" //Major 01, Minor 00, Micro 00
#define TVG_HEADER_VERSION_LENGTH 6
#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions
#define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS
//Compress Size
#define TVG_HEADER_UNCOMPRESSED_SIZE 4 //SIZE (TvgBinCounter)
#define TVG_HEADER_COMPRESSED_SIZE 4 //SIZE (TvgBinCounter)
#define TVG_HEADER_COMPRESSED_SIZE_BITS 4 //SIZE (TvgBinCounter)
//Reserved Flag
#define TVG_HEAD_FLAG_COMPRESSED 0x01
//Paint Type
#define TVG_TAG_CLASS_PICTURE (TvgBinTag)0xfc
#define TVG_TAG_CLASS_SHAPE (TvgBinTag)0xfd
#define TVG_TAG_CLASS_SCENE (TvgBinTag)0xfe
//Paint
#define TVG_TAG_PAINT_OPACITY (TvgBinTag)0x10
#define TVG_TAG_PAINT_TRANSFORM (TvgBinTag)0x11
#define TVG_TAG_PAINT_CMP_TARGET (TvgBinTag)0x01
#define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20
//Shape
#define TVG_TAG_SHAPE_PATH (TvgBinTag)0x40
#define TVG_TAG_SHAPE_STROKE (TvgBinTag)0x41
#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42
#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43
#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44
//Stroke
#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50
#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51
#define TVG_TAG_SHAPE_STROKE_WIDTH (TvgBinTag)0x52
#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53
#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54
#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55
#define TVG_TAG_SHAPE_STROKE_MITERLIMIT (TvgBinTag)0x56
#define TVG_TAG_SHAPE_STROKE_ORDER (TvgBinTag)0x57
#define TVG_TAG_SHAPE_STROKE_DASH_OFFSET (TvgBinTag)0x58
//Fill
#define TVG_TAG_FILL_LINEAR_GRADIENT (TvgBinTag)0x60
#define TVG_TAG_FILL_RADIAL_GRADIENT (TvgBinTag)0x61
#define TVG_TAG_FILL_COLORSTOPS (TvgBinTag)0x62
#define TVG_TAG_FILL_FILLSPREAD (TvgBinTag)0x63
#define TVG_TAG_FILL_TRANSFORM (TvgBinTag)0x64
#define TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL (TvgBinTag)0x65
//Picture
#define TVG_TAG_PICTURE_RAW_IMAGE (TvgBinTag)0x70
#define TVG_TAG_PICTURE_MESH (TvgBinTag)0x71
#endif //_TVG_FORMAT_H_

View file

@ -1,9 +1,5 @@
subloader_dep = []
if tvg_loader
subdir('tvg')
endif
if svg_loader
subdir('svg')
endif

View file

@ -1,11 +0,0 @@
source_file = [
'tvgTvgLoader.h',
'tvgTvgCommon.h',
'tvgTvgLoader.cpp',
'tvgTvgBinInterpreter.cpp',
]
subloader_dep += [declare_dependency(
include_directories : include_directories('.'),
sources : source_file
)]

View file

@ -1,487 +0,0 @@
/*
* 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 <memory.h>
#ifdef _WIN32
#include <malloc.h>
#elif defined(__linux__)
#include <alloca.h>
#else
#include <stdlib.h>
#endif
#include "tvgTvgCommon.h"
#include "tvgShape.h"
#include "tvgFill.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct TvgBinBlock
{
TvgBinTag type;
TvgBinCounter length;
const char* data;
const char* end;
};
static Paint* _parsePaint(TvgBinBlock baseBlock);
static TvgBinBlock _readBlock(const char *ptr)
{
TvgBinBlock block;
block.type = *ptr;
READ_UI32(&block.length, ptr + SIZE(TvgBinTag));
block.data = ptr + SIZE(TvgBinTag) + SIZE(TvgBinCounter);
block.end = block.data + block.length;
return block;
}
static bool _parseCmpTarget(const char *ptr, const char *end, Paint *paint)
{
auto block = _readBlock(ptr);
if (block.end > end) return false;
if (block.type != TVG_TAG_PAINT_CMP_METHOD) return false;
if (block.length != SIZE(TvgBinFlag)) return false;
auto cmpMethod = static_cast<CompositeMethod>(*block.data);
ptr = block.end;
auto cmpBlock = _readBlock(ptr);
if (cmpBlock.end > end) return false;
paint->composite(unique_ptr<Paint>(_parsePaint(cmpBlock)), cmpMethod);
return true;
}
static bool _parsePaintProperty(TvgBinBlock block, Paint *paint)
{
switch (block.type) {
case TVG_TAG_PAINT_OPACITY: {
if (block.length != SIZE(uint8_t)) return false;
paint->opacity(*block.data);
return true;
}
case TVG_TAG_PAINT_TRANSFORM: {
if (block.length != SIZE(Matrix)) return false;
Matrix matrix;
memcpy(&matrix, block.data, SIZE(Matrix));
paint->transform(matrix);
return true;
}
case TVG_TAG_PAINT_CMP_TARGET: {
if (block.length < SIZE(TvgBinTag) + SIZE(TvgBinCounter)) return false;
return _parseCmpTarget(block.data, block.end, paint);
}
}
return false;
}
static bool _parseScene(TvgBinBlock block, Paint *paint)
{
auto scene = static_cast<Scene*>(paint);
//Case2: Base Paint Properties
if (_parsePaintProperty(block, scene)) return true;
//Case3: A Child paint
if (auto paint = _parsePaint(block)) {
scene->push(unique_ptr<Paint>(paint));
return true;
}
return false;
}
static bool _parseShapePath(const char *ptr, const char *end, Shape *shape)
{
uint32_t cmdCnt, ptsCnt;
READ_UI32(&cmdCnt, ptr);
ptr += SIZE(cmdCnt);
READ_UI32(&ptsCnt, ptr);
ptr += SIZE(ptsCnt);
auto cmds = (TvgBinFlag*) ptr;
ptr += SIZE(TvgBinFlag) * cmdCnt;
auto pts = (Point*) ptr;
ptr += SIZE(Point) * ptsCnt;
if (ptr > end) return false;
/* Recover to PathCommand(4 bytes) from TvgBinFlag(1 byte) */
PathCommand* inCmds = (PathCommand*)alloca(sizeof(PathCommand) * cmdCnt);
for (uint32_t i = 0; i < cmdCnt; ++i) {
inCmds[i] = static_cast<PathCommand>(cmds[i]);
}
shape->appendPath(inCmds, cmdCnt, pts, ptsCnt);
return true;
}
static unique_ptr<Fill> _parseShapeFill(const char *ptr, const char *end)
{
unique_ptr<Fill> fillGrad;
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) return nullptr;
switch (block.type) {
case TVG_TAG_FILL_RADIAL_GRADIENT: {
if (block.length != 3 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x, y, radius;
READ_FLOAT(&x, ptr);
ptr += SIZE(float);
READ_FLOAT(&y, ptr);
ptr += SIZE(float);
READ_FLOAT(&radius, ptr);
auto fillGradRadial = RadialGradient::gen();
fillGradRadial->radial(x, y, radius);
fillGrad = std::move(fillGradRadial);
break;
}
case TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL: {
if (block.length != 3 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x, y, radius;
READ_FLOAT(&x, ptr);
ptr += SIZE(float);
READ_FLOAT(&y, ptr);
ptr += SIZE(float);
READ_FLOAT(&radius, ptr);
if (auto fillGradRadial = static_cast<RadialGradient*>(fillGrad.get())) {
P(fillGradRadial)->fx = x;
P(fillGradRadial)->fy = y;
P(fillGradRadial)->fr = radius;
}
break;
}
case TVG_TAG_FILL_LINEAR_GRADIENT: {
if (block.length != 4 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x1, y1, x2, y2;
READ_FLOAT(&x1, ptr);
ptr += SIZE(float);
READ_FLOAT(&y1, ptr);
ptr += SIZE(float);
READ_FLOAT(&x2, ptr);
ptr += SIZE(float);
READ_FLOAT(&y2, ptr);
auto fillGradLinear = LinearGradient::gen();
fillGradLinear->linear(x1, y1, x2, y2);
fillGrad = std::move(fillGradLinear);
break;
}
case TVG_TAG_FILL_FILLSPREAD: {
if (!fillGrad) return nullptr;
if (block.length != SIZE(TvgBinFlag)) return nullptr;
fillGrad->spread((FillSpread) *block.data);
break;
}
case TVG_TAG_FILL_COLORSTOPS: {
if (!fillGrad) return nullptr;
if (block.length == 0 || block.length & 0x07) return nullptr;
uint32_t stopsCnt = block.length >> 3; // 8 bytes per ColorStop
if (stopsCnt > 1023) return nullptr;
Fill::ColorStop* stops = (Fill::ColorStop*)alloca(sizeof(Fill::ColorStop) * stopsCnt);
auto p = block.data;
for (uint32_t i = 0; i < stopsCnt; i++, p += 8) {
READ_FLOAT(&stops[i].offset, p);
stops[i].r = p[4];
stops[i].g = p[5];
stops[i].b = p[6];
stops[i].a = p[7];
}
fillGrad->colorStops(stops, stopsCnt);
break;
}
case TVG_TAG_FILL_TRANSFORM: {
if (!fillGrad || block.length != SIZE(Matrix)) return nullptr;
Matrix gradTransform;
memcpy(&gradTransform, block.data, SIZE(Matrix));
fillGrad->transform(gradTransform);
break;
}
default: {
TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of the fill properties, %d bytes skipped", block.type, block.type, block.length);
break;
}
}
ptr = block.end;
}
return fillGrad;
}
static bool _parseShapeStrokeDashPattern(const char *ptr, const char *end, Shape *shape)
{
uint32_t dashPatternCnt;
READ_UI32(&dashPatternCnt, ptr);
ptr += SIZE(uint32_t);
if (dashPatternCnt > 0) {
float* dashPattern = static_cast<float*>(malloc(sizeof(float) * dashPatternCnt));
if (!dashPattern) return false;
memcpy(dashPattern, ptr, sizeof(float) * dashPatternCnt);
ptr += SIZE(float) * dashPatternCnt;
if (ptr > end) {
free(dashPattern);
return false;
}
shape->strokeDash(dashPattern, dashPatternCnt);
free(dashPattern);
}
return true;
}
static bool _parseShapeStroke(const char *ptr, const char *end, Shape *shape)
{
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) return false;
switch (block.type) {
case TVG_TAG_SHAPE_STROKE_CAP: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->strokeCap((StrokeCap) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_JOIN: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->strokeJoin((StrokeJoin) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_ORDER: {
if (block.length != SIZE(TvgBinFlag)) return false;
P(shape)->strokeFirst((bool) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_WIDTH: {
if (block.length != SIZE(float)) return false;
float width;
READ_FLOAT(&width, block.data);
shape->strokeWidth(width);
break;
}
case TVG_TAG_SHAPE_STROKE_COLOR: {
if (block.length != 4) return false;
shape->strokeFill(block.data[0], block.data[1], block.data[2], block.data[3]);
break;
}
case TVG_TAG_SHAPE_STROKE_FILL: {
auto fill = _parseShapeFill(block.data, block.end);
if (!fill) return false;
shape->strokeFill(std::move(fill));
break;
}
case TVG_TAG_SHAPE_STROKE_DASHPTRN: {
if (!_parseShapeStrokeDashPattern(block.data, block.end, shape)) return false;
break;
}
case TVG_TAG_SHAPE_STROKE_MITERLIMIT: {
if (block.length != SIZE(float)) return false;
float miterlimit;
READ_FLOAT(&miterlimit, block.data);
shape->strokeMiterlimit(miterlimit);
break;
}
case TVG_TAG_SHAPE_STROKE_DASH_OFFSET: {
if (block.length != SIZE(float)) return false;
float offset;
READ_FLOAT(&offset, block.data);
P(shape)->rs.stroke->dashOffset = offset;
break;
}
default: {
TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of stroke properties, %d bytes skipped", block.type, block.type, block.length);
break;
}
}
ptr = block.end;
}
return true;
}
static bool _parseShape(TvgBinBlock block, Paint* paint)
{
auto shape = static_cast<Shape*>(paint);
//Case1: Shape specific properties
switch (block.type) {
case TVG_TAG_SHAPE_PATH: {
return _parseShapePath(block.data, block.end, shape);
}
case TVG_TAG_SHAPE_STROKE: {
return _parseShapeStroke(block.data, block.end, shape);
}
case TVG_TAG_SHAPE_FILL: {
auto fill = _parseShapeFill(block.data, block.end);
if (!fill) return false;
shape->fill(std::move(fill));
return true;
}
case TVG_TAG_SHAPE_COLOR: {
if (block.length != 4) return false;
shape->fill(block.data[0], block.data[1], block.data[2], block.data[3]);
return true;
}
case TVG_TAG_SHAPE_FILLRULE: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->fill((FillRule)*block.data);
return true;
}
}
//Case2: Base Paint Properties
return _parsePaintProperty(block, shape);
}
static bool _parsePicture(TvgBinBlock block, Paint* paint)
{
auto picture = static_cast<Picture*>(paint);
switch (block.type) {
case TVG_TAG_PICTURE_RAW_IMAGE: {
if (block.length < 2 * SIZE(uint32_t)) return false;
auto ptr = block.data;
uint32_t w, h;
READ_UI32(&w, ptr);
ptr += SIZE(uint32_t);
READ_UI32(&h, ptr);
ptr += SIZE(uint32_t);
auto size = w * h * SIZE(uint32_t);
if (block.length != 2 * SIZE(uint32_t) + size) return false;
picture->load((uint32_t*) ptr, w, h, true, true);
return true;
}
//Base Paint Properties
default: {
if (_parsePaintProperty(block, picture)) return true;
}
}
//Vector Picture won't be requested since Saver replaces it with the Scene
return false;
}
static Paint* _parsePaint(TvgBinBlock baseBlock)
{
bool (*parser)(TvgBinBlock, Paint*);
Paint *paint;
//1. Decide the type of paint.
switch (baseBlock.type) {
case TVG_TAG_CLASS_SCENE: {
paint = Scene::gen().release();
parser = _parseScene;
break;
}
case TVG_TAG_CLASS_SHAPE: {
paint = Shape::gen().release();
parser = _parseShape;
break;
}
case TVG_TAG_CLASS_PICTURE: {
paint = Picture::gen().release();
parser = _parsePicture;
break;
}
default: {
TVGERR("TVG", "Invalid Paint Type %d (0x%x)", baseBlock.type, baseBlock.type);
return nullptr;
}
}
auto ptr = baseBlock.data;
//2. Read Subsequent properties of the current paint.
while (ptr < baseBlock.end) {
auto block = _readBlock(ptr);
if (block.end > baseBlock.end) return paint;
if (!parser(block, paint)) {
TVGERR("TVG", "Encountered the wrong paint properties... Paint Class %d (0x%x)", baseBlock.type, baseBlock.type);
return paint;
}
ptr = block.end;
}
return paint;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Scene* TvgBinInterpreter::run(const char *ptr, const char* end)
{
auto scene = Scene::gen();
if (!scene) return nullptr;
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) {
TVGERR("TVG", "Corrupted tvg file.");
return nullptr;
}
scene->push(unique_ptr<Paint>(_parsePaint(block)));
ptr = block.end;
}
return scene.release();
}

View file

@ -1,54 +0,0 @@
/*
* 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.
*/
#ifndef _TVG_TVG_COMMON_H_
#define _TVG_TVG_COMMON_H_
#include "tvgCommon.h"
#include "tvgFormat.h"
#define SIZE(A) sizeof(A)
#define READ_UI32(dst, src) memcpy(dst, (src), sizeof(uint32_t))
#define READ_FLOAT(dst, src) memcpy(dst, (src), sizeof(float))
/* Interface for Tvg Binary Interpreter */
class TvgBinInterpreterBase
{
public:
virtual ~TvgBinInterpreterBase() {}
/* ptr: points the tvg binary body (after header)
end: end of the tvg binary data */
virtual Scene* run(const char* ptr, const char* end) = 0;
};
/* Version 0 */
class TvgBinInterpreter : public TvgBinInterpreterBase
{
public:
Scene* run(const char* ptr, const char* end) override;
};
#endif //_TVG_TVG_COMMON_H_

View file

@ -1,233 +0,0 @@
/*
* 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 <memory.h>
#include <fstream>
#include "tvgLoader.h"
#include "tvgTvgLoader.h"
#include "tvgCompressor.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void TvgLoader::clear(bool all)
{
if (copy) free((char*)data);
ptr = data = nullptr;
size = 0;
copy = false;
delete(interpreter);
interpreter = nullptr;
if (all) delete(root);
}
/* WARNING: Header format shall not change! */
bool TvgLoader::readHeader()
{
if (!ptr) return false;
//Make sure the size is large enough to hold the header
if (size < TVG_HEADER_SIZE) return false;
//1. Signature
if (memcmp(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH)) return false;
ptr += TVG_HEADER_SIGNATURE_LENGTH;
//2. Version
char version[TVG_HEADER_VERSION_LENGTH + 1];
memcpy(version, ptr, TVG_HEADER_VERSION_LENGTH);
version[TVG_HEADER_VERSION_LENGTH - 1] = '\0';
ptr += TVG_HEADER_VERSION_LENGTH;
this->version = atoi(version);
if (this->version > THORVG_VERSION_NUMBER()) {
TVGLOG("TVG", "This TVG file expects a higher version(%d) of ThorVG symbol(%d)", this->version, THORVG_VERSION_NUMBER());
}
//3. View Size
READ_FLOAT(&w, ptr);
ptr += SIZE(float);
READ_FLOAT(&h, ptr);
ptr += SIZE(float);
//4. Reserved
if (*ptr & TVG_HEAD_FLAG_COMPRESSED) compressed = true;
ptr += TVG_HEADER_RESERVED_LENGTH;
//5. Compressed Size if any
if (compressed) {
auto p = ptr;
//TVG_HEADER_UNCOMPRESSED_SIZE
memcpy(&uncompressedSize, p, sizeof(uint32_t));
p += SIZE(uint32_t);
//TVG_HEADER_COMPRESSED_SIZE
memcpy(&compressedSize, p, sizeof(uint32_t));
p += SIZE(uint32_t);
//TVG_HEADER_COMPRESSED_SIZE_BITS
memcpy(&compressedSizeBits, p, sizeof(uint32_t));
}
ptr += TVG_HEADER_COMPRESS_SIZE;
//Decide the proper Tvg Binary Interpreter based on the current file version
if (this->version >= 0) interpreter = new TvgBinInterpreter;
return true;
}
void TvgLoader::run(unsigned tid)
{
auto data = const_cast<char*>(ptr);
if (compressed) {
data = (char*) lzwDecode((uint8_t*) data, compressedSize, compressedSizeBits, uncompressedSize);
root = interpreter->run(data, data + uncompressedSize);
free(data);
} else {
root = interpreter->run(data, this->data + size);
}
clear(false);
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
TvgLoader::TvgLoader() : ImageLoader(FileType::Tvg)
{
}
TvgLoader::~TvgLoader()
{
this->done();
clear();
}
bool TvgLoader::open(const string &path)
{
clear();
ifstream f;
f.open(path, ifstream::in | ifstream::binary | ifstream::ate);
if (!f.is_open()) return false;
size = f.tellg();
f.seekg(0, ifstream::beg);
copy = true;
data = (char*)malloc(size);
if (!data) {
clear();
f.close();
return false;
}
if (!f.read((char*)data, size))
{
clear();
f.close();
return false;
}
f.close();
ptr = data;
return readHeader();
}
bool TvgLoader::open(const char *data, uint32_t size, TVG_UNUSED const string& rpath, bool copy)
{
clear();
if (copy) {
this->data = (char*)malloc(size);
if (!this->data) return false;
memcpy((char*)this->data, data, size);
} else this->data = data;
this->ptr = this->data;
this->size = size;
this->copy = copy;
return readHeader();
}
bool TvgLoader::resize(Paint* paint, float w, float h)
{
if (!paint) return false;
auto sx = w / this->w;
auto sy = h / this->h;
//Scale
auto scale = sx < sy ? sx : sy;
paint->scale(scale);
//Align
float tx = 0, ty = 0;
auto sw = this->w * scale;
auto sh = this->h * scale;
if (sw > sh) ty -= (h - sh) * 0.5f;
else tx -= (w - sw) * 0.5f;
paint->translate(-tx, -ty);
return true;
}
bool TvgLoader::read()
{
if (!ptr || size == 0) return false;
//the loading has been already completed
if (root || !LoadModule::read()) return true;
TaskScheduler::request(this);
return true;
}
Paint* TvgLoader::paint()
{
this->done();
auto ret = root;
root = nullptr;
return ret;
}

View file

@ -1,61 +0,0 @@
/*
* 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.
*/
#ifndef _TVG_TVG_LOADER_H_
#define _TVG_TVG_LOADER_H_
#include "tvgTaskScheduler.h"
#include "tvgTvgCommon.h"
class TvgLoader : public ImageLoader, public Task
{
public:
const char* data = nullptr;
const char* ptr = nullptr;
uint32_t size = 0;
uint16_t version = 0;
Scene* root = nullptr;
TvgBinInterpreterBase* interpreter = nullptr;
uint32_t uncompressedSize = 0;
uint32_t compressedSize = 0;
uint32_t compressedSizeBits = 0;
bool copy = false;
bool compressed = false;
TvgLoader();
~TvgLoader();
bool open(const string &path) override;
bool open(const char *data, uint32_t size, const string& rpath, bool copy) override;
bool read() override;
bool resize(Paint* paint, float w, float h) override;
Paint* paint() override;
private:
bool readHeader();
void run(unsigned tid) override;
void clear(bool all = true);
};
#endif //_TVG_TVG_LOADER_H_

View file

@ -1639,7 +1639,7 @@ bool rasterCompositor(SwSurface* surface)
surface->alphas[2] = _argbLuma;
surface->alphas[3] = _argbInvLuma;
} else {
TVGERR("SW_ENGINE", "Unsupported Colorspace(%d) is expected!", surface->cs);
TVGERR("SW_ENGINE", "Unsupported Colorspace(%d) is expected!", (int)surface->cs);
return false;
}
return true;

View file

@ -54,7 +54,7 @@ using namespace tvg;
#define strdup _strdup
#endif
enum class FileType { Png = 0, Jpg, Webp, Tvg, Svg, Lottie, Ttf, Raw, Gif, Unknown };
enum class FileType { Png = 0, Jpg, Webp, Svg, Lottie, Ttf, Raw, Gif, Unknown };
using Size = Point;

View file

@ -34,10 +34,6 @@
#include "tvgPngLoader.h"
#endif
#ifdef THORVG_TVG_LOADER_SUPPORT
#include "tvgTvgLoader.h"
#endif
#ifdef THORVG_JPG_LOADER_SUPPORT
#include "tvgJpgLoader.h"
#endif
@ -90,12 +86,6 @@ static LoadModule* _find(FileType type)
case FileType::Webp: {
#ifdef THORVG_WEBP_LOADER_SUPPORT
return new WebpLoader;
#endif
break;
}
case FileType::Tvg: {
#ifdef THORVG_TVG_LOADER_SUPPORT
return new TvgLoader;
#endif
break;
}
@ -129,10 +119,6 @@ static LoadModule* _find(FileType type)
#ifdef THORVG_LOG_ENABLED
const char *format;
switch(type) {
case FileType::Tvg: {
format = "TVG";
break;
}
case FileType::Svg: {
format = "SVG";
break;
@ -175,7 +161,6 @@ static LoadModule* _find(FileType type)
static LoadModule* _findByPath(const string& path)
{
auto ext = path.substr(path.find_last_of(".") + 1);
if (!ext.compare("tvg")) return _find(FileType::Tvg);
if (!ext.compare("svg")) return _find(FileType::Svg);
if (!ext.compare("json")) return _find(FileType::Lottie);
if (!ext.compare("png")) return _find(FileType::Png);
@ -191,8 +176,7 @@ static FileType _convert(const string& mimeType)
{
auto type = FileType::Unknown;
if (mimeType == "tvg") type = FileType::Tvg;
else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg;
if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg;
else if (mimeType == "ttf" || mimeType == "otf") type = FileType::Ttf;
else if (mimeType == "lottie") type = FileType::Lottie;
else if (mimeType == "raw") type = FileType::Raw;

View file

@ -24,9 +24,6 @@
#include "tvgSaveModule.h"
#include "tvgPaint.h"
#ifdef THORVG_TVG_SAVER_SUPPORT
#include "tvgTvgSaver.h"
#endif
#ifdef THORVG_GIF_SAVER_SUPPORT
#include "tvgGifSaver.h"
#endif
@ -51,12 +48,6 @@ struct Saver::Impl
static SaveModule* _find(FileType type)
{
switch(type) {
case FileType::Tvg: {
#ifdef THORVG_TVG_SAVER_SUPPORT
return new TvgSaver;
#endif
break;
}
case FileType::Gif: {
#ifdef THORVG_GIF_SAVER_SUPPORT
return new GifSaver;
@ -71,10 +62,6 @@ static SaveModule* _find(FileType type)
#ifdef THORVG_LOG_ENABLED
const char *format;
switch(type) {
case FileType::Tvg: {
format = "TVG";
break;
}
case FileType::Gif: {
format = "GIF";
break;
@ -93,11 +80,7 @@ static SaveModule* _find(FileType type)
static SaveModule* _find(const string& path)
{
auto ext = path.substr(path.find_last_of(".") + 1);
if (!ext.compare("tvg")) {
return _find(FileType::Tvg);
} else if (!ext.compare("gif")) {
return _find(FileType::Gif);
}
if (!ext.compare("gif")) return _find(FileType::Gif);
return nullptr;
}

View file

@ -1,9 +1,5 @@
subsaver_dep = []
if tvg_saver
subdir('tvg')
endif
if gif_saver
subdir('gif')
endif

View file

@ -1,9 +0,0 @@
source_file = [
'tvgTvgSaver.h',
'tvgTvgSaver.cpp',
]
subsaver_dep += [declare_dependency(
include_directories : include_directories('.'),
sources : source_file
)]

View file

@ -1,818 +0,0 @@
/*
* 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->strokeFill(&r, &g, &b, &a);
to->strokeFill(&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)
{
//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) + 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->strokeFill(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 = shape->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->strokeFill(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) + 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);
//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)
{
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);
}
//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, Paint* bg, const string& path, TVG_UNUSED uint32_t quality)
{
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;
if (bg) {
auto scene = Scene::gen();
scene->push(cast(bg->duplicate()));
scene->push(cast(paint));
this->paint = scene.release();
} else {
this->paint = paint;
}
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;
}

View file

@ -1,80 +0,0 @@
/*
* 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.
*/
#ifndef _TVG_TVGSAVER_H_
#define _TVG_TVGSAVER_H_
#include "tvgArray.h"
#include "tvgFormat.h"
#include "tvgTaskScheduler.h"
namespace tvg
{
class TvgSaver : public SaveModule, public Task
{
private:
Array<TvgBinByte> buffer;
Paint* paint = nullptr;
Paint* bg = nullptr;
char *path = nullptr;
uint32_t headerSize;
float vsize[2] = {0.0f, 0.0f};
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);
TvgBinCounter serializeChild(const Paint* parent, const Paint* child, const Matrix* pTransform);
void run(unsigned tid) override;
public:
~TvgSaver();
bool save(Paint* paint, Paint* bg, const string& path, uint32_t quality) override;
bool save(Animation* animation, Paint* bg, const string& path, uint32_t quality, uint32_t fps) override;
bool close() override;
};
}
#endif //_TVG_SAVE_MODULE_H_

View file

@ -180,32 +180,6 @@ TEST_CASE("Load Jpg file in Picture", "[capiPicture]")
#endif
#ifdef THORVG_TVG_LOADER_SUPPORT
TEST_CASE("Load Tvg file in Picture", "[capiPicture]")
{
Tvg_Paint* picture = tvg_picture_new();
REQUIRE(picture);
//Invalid file
REQUIRE(tvg_picture_load(picture, "invalid.tvg") == TVG_RESULT_INVALID_ARGUMENT);
//Load Png file
REQUIRE(tvg_picture_load(picture, TEST_DIR"/test.tvg") == TVG_RESULT_SUCCESS);
//Verify Size
float wNew = 500.0f, hNew = 500.0f;
float w = 0.0f, h = 0.0f;
REQUIRE(tvg_picture_set_size(picture, wNew, hNew) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_picture_get_size(picture, &w, &h) == TVG_RESULT_SUCCESS);
REQUIRE(w == Approx(wNew).epsilon(0.0000001));
REQUIRE(h == Approx(hNew).epsilon(0.0000001));
REQUIRE(tvg_paint_del(picture) == TVG_RESULT_SUCCESS);
}
#endif
#ifdef THORVG_WEBP_LOADER_SUPPORT
TEST_CASE("Load Webp file in Picture", "[capiPicture]")

View file

@ -34,121 +34,6 @@ TEST_CASE("Create and delete a Saver", "[capiSaver]")
REQUIRE(tvg_saver_del(saver) == TVG_RESULT_SUCCESS);
}
#ifdef THORVG_TVG_SAVER_SUPPORT
TEST_CASE("Save a paint into tvg", "[capiSaver]")
{
Tvg_Saver* saver = tvg_saver_new();
REQUIRE(saver);
Tvg_Paint* paint_empty = tvg_shape_new();
REQUIRE(paint_empty);
Tvg_Paint* paint1 = tvg_shape_new();
REQUIRE(paint1);
REQUIRE(tvg_shape_append_rect(paint1, 11.1f, 22.2f, 33.3f, 44.4f, 5.5f, 6.6f) == TVG_RESULT_SUCCESS);
Tvg_Paint* paint2 = tvg_paint_duplicate(paint1);
REQUIRE(paint2);
Tvg_Paint* paint3 = tvg_paint_duplicate(paint1);
REQUIRE(paint3);
//An invalid argument
REQUIRE(tvg_saver_save(nullptr, paint_empty, TEST_DIR"/test.tvg", 50) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_saver_save(saver, nullptr, TEST_DIR"/test.tvg", 999999) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_saver_save(saver, paint_empty, nullptr, 100) == TVG_RESULT_INVALID_ARGUMENT);
//Save an empty paint
REQUIRE(tvg_saver_save(saver, paint_empty, TEST_DIR"/test.tvg", 0) == TVG_RESULT_UNKNOWN);
//Unsupported target file format
REQUIRE(tvg_saver_save(saver, paint1, TEST_DIR"/test.err", 0) == TVG_RESULT_NOT_SUPPORTED);
//Correct call
REQUIRE(tvg_saver_save(saver, paint2, TEST_DIR"/test.tvg", 100) == TVG_RESULT_SUCCESS);
//Busy - saving some resources
REQUIRE(tvg_saver_save(saver, paint3, TEST_DIR"/test.tvg", 100) == TVG_RESULT_INSUFFICIENT_CONDITION);
REQUIRE(tvg_saver_del(saver) == TVG_RESULT_SUCCESS);
}
TEST_CASE("Synchronize a Saver", "[capiSaver]")
{
Tvg_Saver* saver = tvg_saver_new();
REQUIRE(saver);
Tvg_Paint* paint1 = tvg_shape_new();
REQUIRE(paint1);
REQUIRE(tvg_shape_append_rect(paint1, 11.1f, 22.2f, 33.3f, 44.4f, 5.5f, 6.6f) == TVG_RESULT_SUCCESS);
Tvg_Paint* paint2 = tvg_paint_duplicate(paint1);
REQUIRE(paint2);
//An invalid argument
REQUIRE(tvg_saver_sync(nullptr) == TVG_RESULT_INVALID_ARGUMENT);
//Nothing to be synced
REQUIRE(tvg_saver_sync(saver) == TVG_RESULT_INSUFFICIENT_CONDITION);
REQUIRE(tvg_saver_save(saver, paint1, TEST_DIR"/test.tvg", 100) == TVG_RESULT_SUCCESS);
//Releasing the saving task
REQUIRE(tvg_saver_sync(saver) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_saver_save(saver, paint2, TEST_DIR"/test.tvg", 100) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_saver_del(saver) == TVG_RESULT_SUCCESS);
}
TEST_CASE("Save scene into tvg", "[capiSaver]")
{
Tvg_Paint* picture = tvg_picture_new();
REQUIRE(picture);
Tvg_Saver* saver = tvg_saver_new();
REQUIRE(saver);
FILE* file = fopen(TEST_DIR"/rawimage_200x300.raw", "r");
REQUIRE(file);
uint32_t* data = (uint32_t*)malloc(sizeof(uint32_t) * (200*300));
if (data && fread(data, sizeof(uint32_t), 200 * 300, file) > 0) {
REQUIRE(tvg_picture_load_raw(picture, data, 200, 300, true, true) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_saver_save(saver, picture, TEST_DIR"/test.tvg", 88) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_saver_sync(saver) == TVG_RESULT_SUCCESS);
}
REQUIRE(tvg_saver_del(saver) == TVG_RESULT_SUCCESS);
fclose(file);
free(data);
}
#ifdef THORVG_SVG_LOADER_SUPPORT
TEST_CASE("Save svg into tvg", "[capiSaver]")
{
Tvg_Paint* picture = tvg_picture_new();
REQUIRE(picture);
REQUIRE(tvg_picture_load(picture, TEST_DIR"/logo.svg") == TVG_RESULT_SUCCESS);
REQUIRE(tvg_picture_set_size(picture, 222, 333) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_paint_translate(picture, 123.45f, 54.321f) == TVG_RESULT_SUCCESS);
Tvg_Saver* saver = tvg_saver_new();
REQUIRE(saver);
REQUIRE(tvg_saver_save(saver, picture, TEST_DIR"/test.tvg", 100) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_saver_sync(saver) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_saver_del(saver) == TVG_RESULT_SUCCESS);
}
#endif
#endif
#if defined(THORVG_GIF_SAVER_SUPPORT) && defined(THORVG_LOTTIE_LOADER_SUPPORT)
TEST_CASE("Save a lottie into gif", "[capiSavers]")

View file

@ -365,81 +365,6 @@ TEST_CASE("Load JPG file and render", "[tvgPicture]")
#endif
#ifdef THORVG_TVG_LOADER_SUPPORT
TEST_CASE("Load TVG file from path", "[tvgPicture]")
{
auto picture = Picture::gen();
REQUIRE(picture);
//Invalid file
REQUIRE(picture->load("invalid.tvg") == Result::InvalidArguments);
REQUIRE(picture->load(TEST_DIR"/tag.tvg") == Result::Success);
float w, h;
REQUIRE(picture->size(&w, &h) == Result::Success);
REQUIRE(w == 1000);
REQUIRE(h == 1000);
}
TEST_CASE("Load TVG file from data", "[tvgPicture]")
{
auto picture = Picture::gen();
REQUIRE(picture);
//Open file
ifstream file(TEST_DIR"/tag.tvg", ios::in | ios::binary);
REQUIRE(file.is_open());
auto begin = file.tellg();
file.seekg(0, ios::end);
auto size = file.tellg() - begin;
auto data = (char*)malloc(size);
file.seekg(0, ios::beg);
file.read(data, size);
file.close();
REQUIRE(picture->load(data, size, "") == Result::Success);
REQUIRE(picture->load(data, size, "tvg", "", true) == Result::Success);
float w, h;
REQUIRE(picture->size(&w, &h) == Result::Success);
REQUIRE(w == 1000);
REQUIRE(h == 1000);
free(data);
}
TEST_CASE("Load TVG file and render", "[tvgPicture]")
{
REQUIRE(Initializer::init(0) == Result::Success);
auto canvas = SwCanvas::gen();
REQUIRE(canvas);
auto buffer = new uint32_t[1000*1000];
if (!buffer) return;
REQUIRE(canvas->target(buffer, 1000, 1000, 1000, ColorSpace::ABGR8888) == Result::Success);
auto pictureTag = Picture::gen();
REQUIRE(pictureTag);
REQUIRE(pictureTag->load(TEST_DIR"/tag.tvg") == Result::Success);
REQUIRE(canvas->push(std::move(pictureTag)) == Result::Success);
auto pictureTest = Picture::gen();
REQUIRE(pictureTest);
REQUIRE(pictureTest->load(TEST_DIR"/test.tvg") == Result::Success);
REQUIRE(canvas->push(std::move(pictureTest)) == Result::Success);
REQUIRE(Initializer::term() == Result::Success);
delete[] buffer;
}
#endif
#ifdef THORVG_WEBP_LOADER_SUPPORT
TEST_CASE("Load WEBP file from path", "[tvgPicture]")

Some files were not shown because too many files have changed in this diff Show more