diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index ca7f065bd6..21ed315097 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -357,10 +357,6 @@ Comment: Multi-channel signed distance field generator Copyright: 2016, Viktor Chlumsky License: MIT -Files: ./thirdparty/nanosvg/ -Comment: NanoSVG -Copyright: 2013-2014, Mikko Mononen -License: Zlib Files: ./thirdparty/oidn/ Comment: Intel Open Image Denoise @@ -398,6 +394,11 @@ Comment: libSquish Copyright: 2006, Simon Brown License: Expat +Files: ./thirdparty/thorvg/ +Comment: ThorVG +Copyright: 2020-2021, Samsung Electronics Co., Ltd. +License: Expat + Files: ./thirdparty/tinyexr/ Comment: TinyEXR Copyright: 2014-2020, Syoyo Fujita diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 8e8437f1b2..1dd762875f 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -30,6 +30,7 @@ #include "editor_themes.h" +#include "core/error/error_macros.h" #include "core/io/resource_loader.h" #include "editor_fonts.h" #include "editor_icons.gen.h" @@ -95,6 +96,7 @@ static Ref flip_icon(Ref p_texture, bool p_flip_y = false, Ref texture(memnew(ImageTexture)); Ref img = p_texture->get_image(); + ERR_FAIL_NULL_V(img, Ref()); img = img->duplicate(); if (p_flip_y) { diff --git a/modules/svg/SCsub b/modules/svg/SCsub index c7228a8d0b..235024f893 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -9,16 +9,64 @@ env_svg = env_modules.Clone() thirdparty_obj = [] -thirdparty_dir = "#thirdparty/nanosvg/" +thirdparty_dir = "#thirdparty/thorvg/" thirdparty_sources = [ - "nanosvg.cc", + "src/lib/tvgBezier.cpp", + "src/lib/tvgCanvas.cpp", + "src/lib/tvgFill.cpp", + "src/lib/tvgGlCanvas.cpp", + "src/lib/tvgInitializer.cpp", + "src/lib/tvgLinearGradient.cpp", + "src/lib/tvgLoader.cpp", + "src/lib/tvgLzw.cpp", + "src/lib/tvgPaint.cpp", + "src/lib/tvgPicture.cpp", + "src/lib/tvgRadialGradient.cpp", + "src/lib/tvgRender.cpp", + "src/lib/tvgSaver.cpp", + "src/lib/tvgScene.cpp", + "src/lib/tvgShape.cpp", + "src/lib/tvgSwCanvas.cpp", + "src/lib/tvgTaskScheduler.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/svg/tvgSvgUtil.cpp", + "src/loaders/svg/tvgSvgSceneBuilder.cpp", + "src/loaders/svg/tvgSvgPath.cpp", + "src/loaders/svg/tvgSvgLoader.cpp", + "src/loaders/tvg/tvgTvgBinInterpreter.cpp", + "src/loaders/tvg/tvgTvgLoader.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/png/tvgPngLoader.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/lib/sw_engine/tvgSwFill.cpp", + "src/lib/sw_engine/tvgSwImage.cpp", + "src/lib/sw_engine/tvgSwMath.cpp", + "src/lib/sw_engine/tvgSwMemPool.cpp", + "src/lib/sw_engine/tvgSwRaster.cpp", + "src/lib/sw_engine/tvgSwRenderer.cpp", + "src/lib/sw_engine/tvgSwRle.cpp", + "src/lib/sw_engine/tvgSwShape.cpp", + "src/lib/sw_engine/tvgSwStroke.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -env_svg.Prepend(CPPPATH=[thirdparty_dir]) +env_svg.Prepend(CPPPATH=[thirdparty_dir + "/inc"]) env_thirdparty = env_svg.Clone() env_thirdparty.disable_warnings() +env_thirdparty.Prepend( + CPPPATH=[ + thirdparty_dir + "/src/lib", + thirdparty_dir + "/src/lib/sw_engine", + thirdparty_dir + "/src/loaders/raw", + thirdparty_dir + "/src/loaders/svg", + thirdparty_dir + "/src/loaders/jpg", + thirdparty_dir + "/src/loaders/png", + thirdparty_dir + "/src/loaders/tvg", + ] +) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) env.modules_sources += thirdparty_obj diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index 4911346b96..72b42ca6eb 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -30,132 +30,178 @@ #include "image_loader_svg.h" -#include -#include +#include "core/error/error_macros.h" +#include "core/io/image_loader.h" +#include "core/templates/local_vector.h" -void SVGRasterizer::rasterize(NSVGimage *p_image, float p_tx, float p_ty, float p_scale, unsigned char *p_dst, int p_w, int p_h, int p_stride) { - nsvgRasterize(rasterizer, p_image, p_tx, p_ty, p_scale, p_dst, p_w, p_h, p_stride); -} +#include +#include +#include -SVGRasterizer::SVGRasterizer() { - rasterizer = nsvgCreateRasterizer(); -} +#include "thirdparty/thorvg/inc/thorvg.h" -SVGRasterizer::~SVGRasterizer() { - nsvgDeleteRasterizer(rasterizer); -} - -SVGRasterizer ImageLoaderSVG::rasterizer; - -inline void change_nsvg_paint_color(NSVGpaint *p_paint, const uint32_t p_old, const uint32_t p_new) { - if (p_paint->type == NSVG_PAINT_COLOR) { - if (p_paint->color << 8 == p_old << 8) { - p_paint->color = (p_paint->color & 0xFF000000) | (p_new & 0x00FFFFFF); - } - } - - if (p_paint->type == NSVG_PAINT_LINEAR_GRADIENT || p_paint->type == NSVG_PAINT_RADIAL_GRADIENT) { - for (int stop_index = 0; stop_index < p_paint->gradient->nstops; stop_index++) { - if (p_paint->gradient->stops[stop_index].color << 8 == p_old << 8) { - p_paint->gradient->stops[stop_index].color = p_new; - } - } - } -} - -void ImageLoaderSVG::_convert_colors(NSVGimage *p_svg_image) { - for (NSVGshape *shape = p_svg_image->shapes; shape != nullptr; shape = shape->next) { - for (int i = 0; i < replace_colors.old_colors.size(); i++) { - change_nsvg_paint_color(&(shape->stroke), replace_colors.old_colors[i], replace_colors.new_colors[i]); - change_nsvg_paint_color(&(shape->fill), replace_colors.old_colors[i], replace_colors.new_colors[i]); - } - } -} +ImageLoaderSVG::ConvertTVGColorsFunc ImageLoaderSVG::convert_tvg_color_func = ImageLoaderSVG::_convert_tvg_paints; void ImageLoaderSVG::set_convert_colors(Dictionary *p_replace_color) { - if (p_replace_color) { - Dictionary replace_color = *p_replace_color; - for (int i = 0; i < replace_color.keys().size(); i++) { - Variant o_c = replace_color.keys()[i]; - Variant n_c = replace_color[replace_color.keys()[i]]; - if (o_c.get_type() == Variant::COLOR && n_c.get_type() == Variant::COLOR) { - Color old_color = o_c; - Color new_color = n_c; - replace_colors.old_colors.push_back(old_color.to_abgr32()); - replace_colors.new_colors.push_back(new_color.to_abgr32()); - } + // if (p_replace_color) { + // Dictionary replace_color = *p_replace_color; + // for (int i = 0; i < replace_color.keys().size(); i++) { + // Variant o_c = replace_color.keys()[i]; + // Variant n_c = replace_color[replace_color.keys()[i]]; + // if (o_c.get_type() == Variant::COLOR && n_c.get_type() == Variant::COLOR) { + // Color old_color = o_c; + // Color new_color = n_c; + // replace_colors.old_colors.push_back(old_color.to_abgr32()); + // replace_colors.new_colors.push_back(new_color.to_abgr32()); + // } + // } + // } else { + // replace_colors.old_colors.clear(); + // replace_colors.new_colors.clear(); + // } +} + +void ImageLoaderSVG::create_image_from_string(Ref p_image, String p_string, float p_scale, bool p_upsample, bool p_convert_color) { + ERR_FAIL_COND(Math::is_zero_approx(p_scale)); + uint32_t bgColor = 0xffffffff; + if (tvg::Initializer::init(tvg::CanvasEngine::Sw, 1) != tvg::Result::Success) { + return; + } + std::unique_ptr picture = tvg::Picture::gen(); + float fw, fh; + CharString char_string = p_string.utf8(); + tvg::Result result = picture->load(char_string.get_data(), char_string.size(), "svg") ; + if (result != tvg::Result::Success) { + return; + } + picture->viewbox(nullptr, nullptr, &fw, &fh); + + uint32_t width = MIN(fw * p_scale, 16 * 1024); + uint32_t height = MIN(fh * p_scale, 16 * 1024); + picture->size(width, height); + + std::unique_ptr sw_canvas = tvg::SwCanvas::gen(); + uint32_t *buffer = (uint32_t *)malloc(sizeof(uint32_t) * width * height); + tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888_STRAIGHT); + if (res != tvg::Result::Success) { + return; + } + + if (bgColor != 0xffffffff) { + uint8_t bgColorR = (uint8_t)((bgColor & 0xff0000) >> 16); + uint8_t bgColorG = (uint8_t)((bgColor & 0x00ff00) >> 8); + uint8_t bgColorB = (uint8_t)((bgColor & 0x0000ff)); + + std::unique_ptr shape = tvg::Shape::gen(); + shape->appendRect(0, 0, width, height, 0, 0); //x, y, w, h, rx, ry + shape->fill(bgColorR, bgColorG, bgColorB, 255); //r, g, b, a + + if (sw_canvas->push(move(shape)) != tvg::Result::Success) { + return; } - } else { - replace_colors.old_colors.clear(); - replace_colors.new_colors.clear(); - } -} - -Error ImageLoaderSVG::_create_image(Ref p_image, const Vector *p_data, float p_scale, bool upsample, bool convert_colors) { - NSVGimage *svg_image; - const uint8_t *src_r = p_data->ptr(); - svg_image = nsvgParse((char *)src_r, "px", 96); - if (svg_image == nullptr) { - ERR_PRINT("SVG Corrupted"); - return ERR_FILE_CORRUPT; } - if (convert_colors) { - _convert_colors(svg_image); + // if (p_convert_color) { + // std::unique_ptr + // accessor = tvg::Accessor::gen(); + // picture = accessor->access(move(picture), convert_tvg_color_func); + // } + sw_canvas->push(move(picture)); + res = sw_canvas->draw(); + if (res != tvg::Result::Success) { + ERR_PRINT("ImageLoaderSVG can't create image."); + } + res = sw_canvas->sync(); + if (res != tvg::Result::Success) { + ERR_PRINT("ImageLoaderSVG can't create image."); + } + Vector image; + image.resize(width * height * sizeof(uint32_t)); + + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + uint32_t n = buffer[y * width + x]; + image.write[sizeof(uint32_t) * width * y + sizeof(uint32_t) * x + 0] = (n >> 16) & 0xff; + image.write[sizeof(uint32_t) * width * y + sizeof(uint32_t) * x + 1] = (n >> 8) & 0xff; + image.write[sizeof(uint32_t) * width * y + sizeof(uint32_t) * x + 2] = n & 0xff; + image.write[sizeof(uint32_t) * width * y + sizeof(uint32_t) * x + 3] = (n >> 24) & 0xff; + } } - const float upscale = upsample ? 2.0 : 1.0; - - const int w = (int)(svg_image->width * p_scale * upscale); - ERR_FAIL_COND_V_MSG(w > Image::MAX_WIDTH, ERR_PARAMETER_RANGE_ERROR, vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max width.", rtos(p_scale))); - - const int h = (int)(svg_image->height * p_scale * upscale); - ERR_FAIL_COND_V_MSG(h > Image::MAX_HEIGHT, ERR_PARAMETER_RANGE_ERROR, vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max height.", rtos(p_scale))); - - Vector dst_image; - dst_image.resize(w * h * 4); - - uint8_t *dw = dst_image.ptrw(); - - rasterizer.rasterize(svg_image, 0, 0, p_scale * upscale, (unsigned char *)dw, w, h, w * 4); - - p_image->create(w, h, false, Image::FORMAT_RGBA8, dst_image); - if (upsample) { - p_image->shrink_x2(); - } - - nsvgDelete(svg_image); - - return OK; -} - -Error ImageLoaderSVG::create_image_from_string(Ref p_image, const char *p_svg_str, float p_scale, bool upsample, bool convert_colors) { - size_t str_len = strlen(p_svg_str); - Vector src_data; - src_data.resize(str_len + 1); - uint8_t *src_w = src_data.ptrw(); - memcpy(src_w, p_svg_str, str_len + 1); - - return _create_image(p_image, &src_data, p_scale, upsample, convert_colors); -} - -Error ImageLoaderSVG::load_image(Ref p_image, FileAccess *f, bool p_force_linear, float p_scale) { - uint64_t size = f->get_length(); - Vector src_image; - src_image.resize(size + 1); - uint8_t *src_w = src_image.ptrw(); - f->get_buffer(src_w, size); - src_w[size] = '\0'; - - return _create_image(p_image, &src_image, p_scale, 1.0); + free(buffer); + p_image->create(width, height, false, Image::FORMAT_RGBA8, image); } void ImageLoaderSVG::get_recognized_extensions(List *p_extensions) const { p_extensions->push_back("svg"); - p_extensions->push_back("svgz"); } -ImageLoaderSVG::ImageLoaderSVG() { +Error ImageLoaderSVG::load_image(Ref p_image, FileAccess *p_fileaccess, bool p_force_linear, float p_scale) { + String svg = p_fileaccess->get_as_utf8_string(); + create_image_from_string(p_image, svg, p_scale, false, false); + ERR_FAIL_COND_V(p_image->is_empty(), FAILED); + if (p_force_linear) { + p_image->srgb_to_linear(); + } + return OK; } -ImageLoaderSVG::ReplaceColors ImageLoaderSVG::replace_colors; +bool ImageLoaderSVG::_convert_tvg_paints(const tvg::Paint *p_paint) { + // union ByteColor { + // uint32_t i = 0; + // uint8_t b[sizeof(float)]; + // }; + // if (p_paint->identifier() == tvg::Shape::identifier()) { + // tvg::Shape *shape = (tvg::Shape *)p_paint; + // uint8_t r = 0; + // uint8_t g = 0; + // uint8_t b = 0; + // uint8_t a = 0; + // shape->fillColor(&r, &g, &b, &a); + // for (int i = 0; i < replace_colors.old_colors.size(); i++) { + // ByteColor old_color = {}; + // old_color.b[0] = r; + // old_color.b[1] = g; + // old_color.b[2] = b; + // old_color.b[3] = a; + + // ByteColor new_color = {}; + // new_color.i = replace_colors.new_colors[i]; + // ByteColor replace_color = {}; + // replace_color.i = replace_colors.new_colors[i]; + // if (new_color.i == old_color.i) { + // shape->fill(replace_color.b[0], replace_color.b[1], replace_color.b[2], + // replace_color.b[3]); + // return true; + // } + // } + // } else if (p_paint->identifier() == tvg::LinearGradient::identifier() || p_paint->identifier() == tvg::RadialGradient::identifier()) { + // tvg::Shape *shape = (tvg::Shape *)p_paint; + // if (tvg::Fill *fill = shape->fill()) { + // tvg::Fill::ColorStop *colorStop; + // uint32_t count = fill->colorStops(&colorStop); + // for (int count_i = 0; count_i < count; ++count_i) { + // tvg::Fill::ColorStop &p = colorStop[count_i]; + // for (int i = 0; i < replace_colors.old_colors.size(); i++) { + // ByteColor old_color = {}; + // old_color.b[0] = p.r; + // old_color.b[1] = p.g; + // old_color.b[2] = p.b; + // old_color.b[3] = p.a; + + // ByteColor new_color = {}; + // new_color.i = replace_colors.new_colors[i]; + // ByteColor replace_color = {}; + // replace_color.i = replace_colors.new_colors[i]; + // if (new_color.i == old_color.i) { + // p.r = replace_color.b[0]; + // p.g = replace_color.b[1]; + // p.b = replace_color.b[2]; + // p.a = replace_color.b[3]; + // } + // } + // } + // } + // } + return true; +}; diff --git a/modules/svg/image_loader_svg.h b/modules/svg/image_loader_svg.h index e64175b172..a63ac075b3 100644 --- a/modules/svg/image_loader_svg.h +++ b/modules/svg/image_loader_svg.h @@ -32,42 +32,25 @@ #define IMAGE_LOADER_SVG_H #include "core/io/image_loader.h" -#include "core/string/ustring.h" -/** - @author Daniel Ramirez -*/ - -// Forward declare and include thirdparty headers in .cpp. -struct NSVGrasterizer; -struct NSVGimage; - -class SVGRasterizer { - NSVGrasterizer *rasterizer; - -public: - void rasterize(NSVGimage *p_image, float p_tx, float p_ty, float p_scale, unsigned char *p_dst, int p_w, int p_h, int p_stride); - - SVGRasterizer(); - ~SVGRasterizer(); -}; +#include "thirdparty/thorvg/inc/thorvg.h" class ImageLoaderSVG : public ImageFormatLoader { - static struct ReplaceColors { - List old_colors; - List new_colors; - } replace_colors; - static SVGRasterizer rasterizer; - static void _convert_colors(NSVGimage *p_svg_image); - static Error _create_image(Ref p_image, const Vector *p_data, float p_scale, bool upsample, bool convert_colors = false); + // static struct ReplaceColors { + // List old_colors; + // List new_colors; + // } replace_colors; public: - static void set_convert_colors(Dictionary *p_replace_color = nullptr); - static Error create_image_from_string(Ref p_image, const char *p_svg_str, float p_scale, bool upsample, bool convert_colors = false); + typedef bool (*ConvertTVGColorsFunc)(const tvg::Paint *); + static ConvertTVGColorsFunc convert_tvg_color_func; + static bool _convert_tvg_paints(const tvg::Paint *p_paint); - virtual Error load_image(Ref p_image, FileAccess *f, bool p_force_linear, float p_scale); - virtual void get_recognized_extensions(List *p_extensions) const; - ImageLoaderSVG(); + static void set_convert_colors(Dictionary *p_replace_color = nullptr); + static void create_image_from_string(Ref p_image, String p_string, float p_scale, bool p_upsample, bool p_convert_color); + + virtual Error load_image(Ref p_image, FileAccess *p_fileaccess, bool p_force_linear, float p_scale) override; + virtual void get_recognized_extensions(List *p_extensions) const override; }; -#endif +#endif // IMAGE_LOADER_SVG_H diff --git a/modules/svg/register_types.cpp b/modules/svg/register_types.cpp index 1a611184d2..f4dab7f6e1 100644 --- a/modules/svg/register_types.cpp +++ b/modules/svg/register_types.cpp @@ -32,6 +32,9 @@ #include "image_loader_svg.h" +#include +#include "thirdparty/thorvg/inc/config.h" + static ImageLoaderSVG *image_loader_svg = nullptr; void register_svg_types() { @@ -40,5 +43,8 @@ void register_svg_types() { } void unregister_svg_types() { + if (!image_loader_svg) { + return; + } memdelete(image_loader_svg); } diff --git a/thirdparty/README.md b/thirdparty/README.md index 25d2e1cfe3..edc799fe28 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -459,19 +459,6 @@ Files extracted from the upstream source: - Files in `core/` folder. - `LICENSE.txt` and `CHANGELOG.md` - -## nanosvg - -- Upstream: https://github.com/memononen/nanosvg -- Version: git (ccdb1995134d340a93fb20e3a3d323ccb3838dd0, 2021) -- License: zlib - -Files extracted from the upstream source: - -- All .h files in `src/` -- LICENSE.txt - - ## oidn - Upstream: https://github.com/OpenImageDenoise/oidn @@ -607,6 +594,17 @@ Files extracted from upstream source: - `tinyexr.{cc,h}` +## thorvg + +- Upstream: https://github.com/Samsung/thorvg +- Version: 0.4.2 (88928070e796a3a4ac44eb8eb2f1b815b99ac900, 2021) +- License: MIT + +Files extracted from upstream source: + +See `thorvg/update-thorvg.sh` for extraction instructions. + + ## vhacd - Upstream: https://github.com/kmammou/v-hacd diff --git a/thirdparty/nanosvg/LICENSE.txt b/thirdparty/nanosvg/LICENSE.txt deleted file mode 100644 index f896f2eb0f..0000000000 --- a/thirdparty/nanosvg/LICENSE.txt +++ /dev/null @@ -1,18 +0,0 @@ -Copyright (c) 2013-14 Mikko Mononen memon@inside.org - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not -claim that you wrote the original software. If you use this software -in a product, an acknowledgment in the product documentation would be -appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be -misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - diff --git a/thirdparty/nanosvg/nanosvg.cc b/thirdparty/nanosvg/nanosvg.cc deleted file mode 100644 index 3e8e86c792..0000000000 --- a/thirdparty/nanosvg/nanosvg.cc +++ /dev/null @@ -1,8 +0,0 @@ -#include "stdio.h" -#include "string.h" -#include "math.h" -#define NANOSVG_ALL_COLOR_KEYWORDS -#define NANOSVG_IMPLEMENTATION -#include "nanosvg.h" -#define NANOSVGRAST_IMPLEMENTATION -#include "nanosvgrast.h" diff --git a/thirdparty/nanosvg/nanosvg.h b/thirdparty/nanosvg/nanosvg.h deleted file mode 100644 index f5058b179a..0000000000 --- a/thirdparty/nanosvg/nanosvg.h +++ /dev/null @@ -1,3008 +0,0 @@ -/* - * Copyright (c) 2013-14 Mikko Mononen memon@inside.org - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example - * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) - * - * Arc calculation code based on canvg (https://code.google.com/p/canvg/) - * - * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - * - */ - -#ifndef NANOSVG_H -#define NANOSVG_H - -#ifndef NANOSVG_CPLUSPLUS -#ifdef __cplusplus -extern "C" { -#endif -#endif - -// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. -// -// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. -// -// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! -// -// The shapes in the SVG images are transformed by the viewBox and converted to specified units. -// That is, you should get the same looking data as your designed in your favorite app. -// -// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose -// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. -// -// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. -// DPI (dots-per-inch) controls how the unit conversion is done. -// -// If you don't know or care about the units stuff, "px" and 96 should get you going. - - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - printf("size: %f x %f\n", image->width, image->height); - // Use... - for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { - for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { - for (int i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); - } - } - } - // Delete - nsvgDelete(image); -*/ - -enum NSVGpaintType { - NSVG_PAINT_NONE = 0, - NSVG_PAINT_COLOR = 1, - NSVG_PAINT_LINEAR_GRADIENT = 2, - NSVG_PAINT_RADIAL_GRADIENT = 3 -}; - -enum NSVGspreadType { - NSVG_SPREAD_PAD = 0, - NSVG_SPREAD_REFLECT = 1, - NSVG_SPREAD_REPEAT = 2 -}; - -enum NSVGlineJoin { - NSVG_JOIN_MITER = 0, - NSVG_JOIN_ROUND = 1, - NSVG_JOIN_BEVEL = 2 -}; - -enum NSVGlineCap { - NSVG_CAP_BUTT = 0, - NSVG_CAP_ROUND = 1, - NSVG_CAP_SQUARE = 2 -}; - -enum NSVGfillRule { - NSVG_FILLRULE_NONZERO = 0, - NSVG_FILLRULE_EVENODD = 1 -}; - -enum NSVGflags { - NSVG_FLAGS_VISIBLE = 0x01 -}; - -typedef struct NSVGgradientStop { - unsigned int color; - float offset; -} NSVGgradientStop; - -typedef struct NSVGgradient { - float xform[6]; - char spread; - float fx, fy; - int nstops; - NSVGgradientStop stops[1]; -} NSVGgradient; - -typedef struct NSVGpaint { - char type; - union { - unsigned int color; - NSVGgradient* gradient; - }; -} NSVGpaint; - -typedef struct NSVGpath -{ - float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... - int npts; // Total number of bezier points. - char closed; // Flag indicating if shapes should be treated as closed. - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - struct NSVGpath* next; // Pointer to next path, or NULL if last element. -} NSVGpath; - -typedef struct NSVGshape -{ - char id[64]; // Optional 'id' attr of the shape or its group - NSVGpaint fill; // Fill paint - NSVGpaint stroke; // Stroke paint - float opacity; // Opacity of the shape. - float strokeWidth; // Stroke width (scaled). - float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. - char strokeLineJoin; // Stroke join type. - char strokeLineCap; // Stroke cap type. - float miterLimit; // Miter limit - char fillRule; // Fill rule, see NSVGfillRule. - unsigned char flags; // Logical or of NSVG_FLAGS_* flags - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - NSVGpath* paths; // Linked list of paths in the image. - struct NSVGshape* next; // Pointer to next shape, or NULL if last element. -} NSVGshape; - -typedef struct NSVGimage -{ - float width; // Width of the image. - float height; // Height of the image. - NSVGshape* shapes; // Linked list of shapes in the image. -} NSVGimage; - -// Parses SVG file from a file, returns SVG image as paths. -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); - -// Parses SVG file from a null terminated string, returns SVG image as paths. -// Important note: changes the string. -NSVGimage* nsvgParse(char* input, const char* units, float dpi); - -// Duplicates a path. -NSVGpath* nsvgDuplicatePath(NSVGpath* p); - -// Deletes an image. -void nsvgDelete(NSVGimage* image); - -#ifndef NANOSVG_CPLUSPLUS -#ifdef __cplusplus -} -#endif -#endif - -#endif // NANOSVG_H - -#ifdef NANOSVG_IMPLEMENTATION - -#include -#include -#include - -#define NSVG_PI (3.14159265358979323846264338327f) -#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. - -#define NSVG_ALIGN_MIN 0 -#define NSVG_ALIGN_MID 1 -#define NSVG_ALIGN_MAX 2 -#define NSVG_ALIGN_NONE 0 -#define NSVG_ALIGN_MEET 1 -#define NSVG_ALIGN_SLICE 2 - -#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) -#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) - -#ifdef _MSC_VER - #pragma warning (disable: 4996) // Switch off security warnings - #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings - #ifdef __cplusplus - #define NSVG_INLINE inline - #else - #define NSVG_INLINE - #endif -#else - #define NSVG_INLINE inline -#endif - - -static int nsvg__isspace(char c) -{ - return strchr(" \t\n\v\f\r", c) != 0; -} - -static int nsvg__isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } -static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } - - -// Simple XML parser - -#define NSVG_XML_TAG 1 -#define NSVG_XML_CONTENT 2 -#define NSVG_XML_MAX_ATTRIBS 256 - -static void nsvg__parseContent(char* s, - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - // Trim start white spaces - while (*s && nsvg__isspace(*s)) s++; - if (!*s) return; - - if (contentCb) - (*contentCb)(ud, s); -} - -static void nsvg__parseElement(char* s, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void* ud) -{ - const char* attr[NSVG_XML_MAX_ATTRIBS]; - int nattr = 0; - char* name; - int start = 0; - int end = 0; - char quote; - - // Skip white space after the '<' - while (*s && nsvg__isspace(*s)) s++; - - // Check if the tag is end tag - if (*s == '/') { - s++; - end = 1; - } else { - start = 1; - } - - // Skip comments, data and preprocessor stuff. - if (!*s || *s == '?' || *s == '!') - return; - - // Get tag name - name = s; - while (*s && !nsvg__isspace(*s)) s++; - if (*s) { *s++ = '\0'; } - - // Get attribs - while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { - char* name = NULL; - char* value = NULL; - - // Skip white space before the attrib name - while (*s && nsvg__isspace(*s)) s++; - if (!*s) break; - if (*s == '/') { - end = 1; - break; - } - name = s; - // Find end of the attrib name. - while (*s && !nsvg__isspace(*s) && *s != '=') s++; - if (*s) { *s++ = '\0'; } - // Skip until the beginning of the value. - while (*s && *s != '\"' && *s != '\'') s++; - if (!*s) break; - quote = *s; - s++; - // Store value and find the end of it. - value = s; - while (*s && *s != quote) s++; - if (*s) { *s++ = '\0'; } - - // Store only well formed attributes - if (name && value) { - attr[nattr++] = name; - attr[nattr++] = value; - } - } - - // List terminator - attr[nattr++] = 0; - attr[nattr++] = 0; - - // Call callbacks. - if (start && startelCb) - (*startelCb)(ud, name, attr); - if (end && endelCb) - (*endelCb)(ud, name); -} - -int nsvg__parseXML(char* input, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - char* s = input; - char* mark = s; - int state = NSVG_XML_CONTENT; - while (*s) { - if (*s == '<' && state == NSVG_XML_CONTENT) { - // Start of a tag - *s++ = '\0'; - nsvg__parseContent(mark, contentCb, ud); - mark = s; - state = NSVG_XML_TAG; - } else if (*s == '>' && state == NSVG_XML_TAG) { - // Start of a content or new tag. - *s++ = '\0'; - nsvg__parseElement(mark, startelCb, endelCb, ud); - mark = s; - state = NSVG_XML_CONTENT; - } else { - s++; - } - } - - return 1; -} - - -/* Simple SVG parser. */ - -#define NSVG_MAX_ATTR 128 - -enum NSVGgradientUnits { - NSVG_USER_SPACE = 0, - NSVG_OBJECT_SPACE = 1 -}; - -#define NSVG_MAX_DASHES 8 - -enum NSVGunits { - NSVG_UNITS_USER, - NSVG_UNITS_PX, - NSVG_UNITS_PT, - NSVG_UNITS_PC, - NSVG_UNITS_MM, - NSVG_UNITS_CM, - NSVG_UNITS_IN, - NSVG_UNITS_PERCENT, - NSVG_UNITS_EM, - NSVG_UNITS_EX -}; - -typedef struct NSVGcoordinate { - float value; - int units; -} NSVGcoordinate; - -typedef struct NSVGlinearData { - NSVGcoordinate x1, y1, x2, y2; -} NSVGlinearData; - -typedef struct NSVGradialData { - NSVGcoordinate cx, cy, r, fx, fy; -} NSVGradialData; - -typedef struct NSVGgradientData -{ - char id[64]; - char ref[64]; - char type; - union { - NSVGlinearData linear; - NSVGradialData radial; - }; - char spread; - char units; - float xform[6]; - int nstops; - NSVGgradientStop* stops; - struct NSVGgradientData* next; -} NSVGgradientData; - -typedef struct NSVGattrib -{ - char id[64]; - float xform[6]; - unsigned int fillColor; - unsigned int strokeColor; - float opacity; - float fillOpacity; - float strokeOpacity; - char fillGradient[64]; - char strokeGradient[64]; - float strokeWidth; - float strokeDashOffset; - float strokeDashArray[NSVG_MAX_DASHES]; - int strokeDashCount; - char strokeLineJoin; - char strokeLineCap; - float miterLimit; - char fillRule; - float fontSize; - unsigned int stopColor; - float stopOpacity; - float stopOffset; - char hasFill; - char hasStroke; - char visible; -} NSVGattrib; - -typedef struct NSVGparser -{ - NSVGattrib attr[NSVG_MAX_ATTR]; - int attrHead; - float* pts; - int npts; - int cpts; - NSVGpath* plist; - NSVGimage* image; - NSVGgradientData* gradients; - NSVGshape* shapesTail; - float viewMinx, viewMiny, viewWidth, viewHeight; - int alignX, alignY, alignType; - float dpi; - char pathFlag; - char defsFlag; -} NSVGparser; - -static void nsvg__xformIdentity(float* t) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetTranslation(float* t, float tx, float ty) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = tx; t[5] = ty; -} - -static void nsvg__xformSetScale(float* t, float sx, float sy) -{ - t[0] = sx; t[1] = 0.0f; - t[2] = 0.0f; t[3] = sy; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewX(float* t, float a) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = tanf(a); t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewY(float* t, float a) -{ - t[0] = 1.0f; t[1] = tanf(a); - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetRotation(float* t, float a) -{ - float cs = cosf(a), sn = sinf(a); - t[0] = cs; t[1] = sn; - t[2] = -sn; t[3] = cs; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformMultiply(float* t, float* s) -{ - float t0 = t[0] * s[0] + t[1] * s[2]; - float t2 = t[2] * s[0] + t[3] * s[2]; - float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; - t[1] = t[0] * s[1] + t[1] * s[3]; - t[3] = t[2] * s[1] + t[3] * s[3]; - t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; - t[0] = t0; - t[2] = t2; - t[4] = t4; -} - -static void nsvg__xformInverse(float* inv, float* t) -{ - double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; - if (det > -1e-6 && det < 1e-6) { - nsvg__xformIdentity(t); - return; - } - invdet = 1.0 / det; - inv[0] = (float)(t[3] * invdet); - inv[2] = (float)(-t[2] * invdet); - inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); - inv[1] = (float)(-t[1] * invdet); - inv[3] = (float)(t[0] * invdet); - inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); -} - -static void nsvg__xformPremultiply(float* t, float* s) -{ - float s2[6]; - memcpy(s2, s, sizeof(float)*6); - nsvg__xformMultiply(s2, t); - memcpy(t, s2, sizeof(float)*6); -} - -static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2] + t[4]; - *dy = x*t[1] + y*t[3] + t[5]; -} - -static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2]; - *dy = x*t[1] + y*t[3]; -} - -#define NSVG_EPSILON (1e-12) - -static int nsvg__ptInBounds(float* pt, float* bounds) -{ - return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; -} - - -static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) -{ - double it = 1.0-t; - return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; -} - -static void nsvg__curveBounds(float* bounds, float* curve) -{ - int i, j, count; - double roots[2], a, b, c, b2ac, t, v; - float* v0 = &curve[0]; - float* v1 = &curve[2]; - float* v2 = &curve[4]; - float* v3 = &curve[6]; - - // Start the bounding box by end points - bounds[0] = nsvg__minf(v0[0], v3[0]); - bounds[1] = nsvg__minf(v0[1], v3[1]); - bounds[2] = nsvg__maxf(v0[0], v3[0]); - bounds[3] = nsvg__maxf(v0[1], v3[1]); - - // Bezier curve fits inside the convex hull of it's control points. - // If control points are inside the bounds, we're done. - if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) - return; - - // Add bezier curve inflection points in X and Y. - for (i = 0; i < 2; i++) { - a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; - b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; - c = 3.0 * v1[i] - 3.0 * v0[i]; - count = 0; - if (fabs(a) < NSVG_EPSILON) { - if (fabs(b) > NSVG_EPSILON) { - t = -c / b; - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } else { - b2ac = b*b - 4.0*c*a; - if (b2ac > NSVG_EPSILON) { - t = (-b + sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - t = (-b - sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } - for (j = 0; j < count; j++) { - v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); - bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); - bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); - } - } -} - -static NSVGparser* nsvg__createParser() -{ - NSVGparser* p; - p = (NSVGparser*)malloc(sizeof(NSVGparser)); - if (p == NULL) goto error; - memset(p, 0, sizeof(NSVGparser)); - - p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); - if (p->image == NULL) goto error; - memset(p->image, 0, sizeof(NSVGimage)); - - // Init style - nsvg__xformIdentity(p->attr[0].xform); - memset(p->attr[0].id, 0, sizeof p->attr[0].id); - p->attr[0].fillColor = NSVG_RGB(0,0,0); - p->attr[0].strokeColor = NSVG_RGB(0,0,0); - p->attr[0].opacity = 1; - p->attr[0].fillOpacity = 1; - p->attr[0].strokeOpacity = 1; - p->attr[0].stopOpacity = 1; - p->attr[0].strokeWidth = 1; - p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; - p->attr[0].strokeLineCap = NSVG_CAP_BUTT; - p->attr[0].miterLimit = 4; - p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; - p->attr[0].hasFill = 1; - p->attr[0].visible = 1; - - return p; - -error: - if (p) { - if (p->image) free(p->image); - free(p); - } - return NULL; -} - -static void nsvg__deletePaths(NSVGpath* path) -{ - while (path) { - NSVGpath *next = path->next; - if (path->pts != NULL) - free(path->pts); - free(path); - path = next; - } -} - -static void nsvg__deletePaint(NSVGpaint* paint) -{ - if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) - free(paint->gradient); -} - -static void nsvg__deleteGradientData(NSVGgradientData* grad) -{ - NSVGgradientData* next; - while (grad != NULL) { - next = grad->next; - free(grad->stops); - free(grad); - grad = next; - } -} - -static void nsvg__deleteParser(NSVGparser* p) -{ - if (p != NULL) { - nsvg__deletePaths(p->plist); - nsvg__deleteGradientData(p->gradients); - nsvgDelete(p->image); - free(p->pts); - free(p); - } -} - -static void nsvg__resetPath(NSVGparser* p) -{ - p->npts = 0; -} - -static void nsvg__addPoint(NSVGparser* p, float x, float y) -{ - if (p->npts+1 > p->cpts) { - p->cpts = p->cpts ? p->cpts*2 : 8; - p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); - if (!p->pts) return; - } - p->pts[p->npts*2+0] = x; - p->pts[p->npts*2+1] = y; - p->npts++; -} - -static void nsvg__moveTo(NSVGparser* p, float x, float y) -{ - if (p->npts > 0) { - p->pts[(p->npts-1)*2+0] = x; - p->pts[(p->npts-1)*2+1] = y; - } else { - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__lineTo(NSVGparser* p, float x, float y) -{ - float px,py, dx,dy; - if (p->npts > 0) { - px = p->pts[(p->npts-1)*2+0]; - py = p->pts[(p->npts-1)*2+1]; - dx = x - px; - dy = y - py; - nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); - nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) -{ - if (p->npts > 0) { - nsvg__addPoint(p, cpx1, cpy1); - nsvg__addPoint(p, cpx2, cpy2); - nsvg__addPoint(p, x, y); - } -} - -static NSVGattrib* nsvg__getAttr(NSVGparser* p) -{ - return &p->attr[p->attrHead]; -} - -static void nsvg__pushAttr(NSVGparser* p) -{ - if (p->attrHead < NSVG_MAX_ATTR-1) { - p->attrHead++; - memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); - } -} - -static void nsvg__popAttr(NSVGparser* p) -{ - if (p->attrHead > 0) - p->attrHead--; -} - -static float nsvg__actualOrigX(NSVGparser* p) -{ - return p->viewMinx; -} - -static float nsvg__actualOrigY(NSVGparser* p) -{ - return p->viewMiny; -} - -static float nsvg__actualWidth(NSVGparser* p) -{ - return p->viewWidth; -} - -static float nsvg__actualHeight(NSVGparser* p) -{ - return p->viewHeight; -} - -static float nsvg__actualLength(NSVGparser* p) -{ - float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); - return sqrtf(w*w + h*h) / sqrtf(2.0f); -} - -static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) -{ - NSVGattrib* attr = nsvg__getAttr(p); - switch (c.units) { - case NSVG_UNITS_USER: return c.value; - case NSVG_UNITS_PX: return c.value; - case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; - case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; - case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; - case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; - case NSVG_UNITS_IN: return c.value * p->dpi; - case NSVG_UNITS_EM: return c.value * attr->fontSize; - case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. - case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; - default: return c.value; - } - return c.value; -} - -static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) -{ - NSVGgradientData* grad = p->gradients; - if (id == NULL || *id == '\0') - return NULL; - while (grad != NULL) { - if (strcmp(grad->id, id) == 0) - return grad; - grad = grad->next; - } - return NULL; -} - -static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGgradientData* data = NULL; - NSVGgradientData* ref = NULL; - NSVGgradientStop* stops = NULL; - NSVGgradient* grad; - float ox, oy, sw, sh, sl; - int nstops = 0; - int refIter; - - data = nsvg__findGradientData(p, id); - if (data == NULL) return NULL; - - // TODO: use ref to fill in all unset values too. - ref = data; - refIter = 0; - while (ref != NULL) { - NSVGgradientData* nextRef = NULL; - if (stops == NULL && ref->stops != NULL) { - stops = ref->stops; - nstops = ref->nstops; - break; - } - nextRef = nsvg__findGradientData(p, ref->ref); - if (nextRef == ref) break; // prevent infite loops on malformed data - ref = nextRef; - refIter++; - if (refIter > 32) break; // prevent infite loops on malformed data - } - if (stops == NULL) return NULL; - - grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); - if (grad == NULL) return NULL; - - // The shape width and height. - if (data->units == NSVG_OBJECT_SPACE) { - ox = localBounds[0]; - oy = localBounds[1]; - sw = localBounds[2] - localBounds[0]; - sh = localBounds[3] - localBounds[1]; - } else { - ox = nsvg__actualOrigX(p); - oy = nsvg__actualOrigY(p); - sw = nsvg__actualWidth(p); - sh = nsvg__actualHeight(p); - } - sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); - - if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { - float x1, y1, x2, y2, dx, dy; - x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); - y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); - x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); - y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); - // Calculate transform aligned to the line - dx = x2 - x1; - dy = y2 - y1; - grad->xform[0] = dy; grad->xform[1] = -dx; - grad->xform[2] = dx; grad->xform[3] = dy; - grad->xform[4] = x1; grad->xform[5] = y1; - } else { - float cx, cy, fx, fy, r; - cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); - cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); - fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); - fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); - r = nsvg__convertToPixels(p, data->radial.r, 0, sl); - // Calculate transform aligned to the circle - grad->xform[0] = r; grad->xform[1] = 0; - grad->xform[2] = 0; grad->xform[3] = r; - grad->xform[4] = cx; grad->xform[5] = cy; - grad->fx = fx / r; - grad->fy = fy / r; - } - - nsvg__xformMultiply(grad->xform, data->xform); - nsvg__xformMultiply(grad->xform, attr->xform); - - grad->spread = data->spread; - memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); - grad->nstops = nstops; - - *paintType = data->type; - - return grad; -} - -static float nsvg__getAverageScale(float* t) -{ - float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); - float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); - return (sx + sy) * 0.5f; -} - -static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) -{ - NSVGpath* path; - float curve[4*2], curveBounds[4]; - int i, first = 1; - for (path = shape->paths; path != NULL; path = path->next) { - nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); - for (i = 0; i < path->npts-1; i += 3) { - nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); - nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); - nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); - nsvg__curveBounds(curveBounds, curve); - if (first) { - bounds[0] = curveBounds[0]; - bounds[1] = curveBounds[1]; - bounds[2] = curveBounds[2]; - bounds[3] = curveBounds[3]; - first = 0; - } else { - bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); - bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); - bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); - bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); - } - curve[0] = curve[6]; - curve[1] = curve[7]; - } - } -} - -static void nsvg__addShape(NSVGparser* p) -{ - NSVGattrib* attr = nsvg__getAttr(p); - float scale = 1.0f; - NSVGshape* shape; - NSVGpath* path; - int i; - - if (p->plist == NULL) - return; - - shape = (NSVGshape*)malloc(sizeof(NSVGshape)); - if (shape == NULL) goto error; - memset(shape, 0, sizeof(NSVGshape)); - - memcpy(shape->id, attr->id, sizeof shape->id); - scale = nsvg__getAverageScale(attr->xform); - shape->strokeWidth = attr->strokeWidth * scale; - shape->strokeDashOffset = attr->strokeDashOffset * scale; - shape->strokeDashCount = (char)attr->strokeDashCount; - for (i = 0; i < attr->strokeDashCount; i++) - shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; - shape->strokeLineJoin = attr->strokeLineJoin; - shape->strokeLineCap = attr->strokeLineCap; - shape->miterLimit = attr->miterLimit; - shape->fillRule = attr->fillRule; - shape->opacity = attr->opacity; - - shape->paths = p->plist; - p->plist = NULL; - - // Calculate shape bounds - shape->bounds[0] = shape->paths->bounds[0]; - shape->bounds[1] = shape->paths->bounds[1]; - shape->bounds[2] = shape->paths->bounds[2]; - shape->bounds[3] = shape->paths->bounds[3]; - for (path = shape->paths->next; path != NULL; path = path->next) { - shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); - shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); - shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); - shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); - } - - // Set fill - if (attr->hasFill == 0) { - shape->fill.type = NSVG_PAINT_NONE; - } else if (attr->hasFill == 1) { - shape->fill.type = NSVG_PAINT_COLOR; - shape->fill.color = attr->fillColor; - shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; - } else if (attr->hasFill == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); - if (shape->fill.gradient == NULL) { - shape->fill.type = NSVG_PAINT_NONE; - } - } - - // Set stroke - if (attr->hasStroke == 0) { - shape->stroke.type = NSVG_PAINT_NONE; - } else if (attr->hasStroke == 1) { - shape->stroke.type = NSVG_PAINT_COLOR; - shape->stroke.color = attr->strokeColor; - shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; - } else if (attr->hasStroke == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); - if (shape->stroke.gradient == NULL) - shape->stroke.type = NSVG_PAINT_NONE; - } - - // Set flags - shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); - - // Add to tail - if (p->image->shapes == NULL) - p->image->shapes = shape; - else - p->shapesTail->next = shape; - p->shapesTail = shape; - - return; - -error: - if (shape) free(shape); -} - -static void nsvg__addPath(NSVGparser* p, char closed) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGpath* path = NULL; - float bounds[4]; - float* curve; - int i; - - if (p->npts < 4) - return; - - if (closed) - nsvg__lineTo(p, p->pts[0], p->pts[1]); - - // Expect 1 + N*3 points (N = number of cubic bezier segments). - if ((p->npts % 3) != 1) - return; - - path = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (path == NULL) goto error; - memset(path, 0, sizeof(NSVGpath)); - - path->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (path->pts == NULL) goto error; - path->closed = closed; - path->npts = p->npts; - - // Transform path. - for (i = 0; i < p->npts; ++i) - nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); - - // Find bounds - for (i = 0; i < path->npts-1; i += 3) { - curve = &path->pts[i*2]; - nsvg__curveBounds(bounds, curve); - if (i == 0) { - path->bounds[0] = bounds[0]; - path->bounds[1] = bounds[1]; - path->bounds[2] = bounds[2]; - path->bounds[3] = bounds[3]; - } else { - path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); - path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); - path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); - path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); - } - } - - path->next = p->plist; - p->plist = path; - - return; - -error: - if (path != NULL) { - if (path->pts != NULL) free(path->pts); - free(path); - } -} - -// We roll our own string to float because the std library one uses locale and messes things up. -static double nsvg__atof(const char* s) -{ - char* cur = (char*)s; - char* end = NULL; - double res = 0.0, sign = 1.0; - long long intPart = 0, fracPart = 0; - char hasIntPart = 0, hasFracPart = 0; - - // Parse optional sign - if (*cur == '+') { - cur++; - } else if (*cur == '-') { - sign = -1; - cur++; - } - - // Parse integer part - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - intPart = strtoll(cur, &end, 10); - if (cur != end) { - res = (double)intPart; - hasIntPart = 1; - cur = end; - } - } - - // Parse fractional part. - if (*cur == '.') { - cur++; // Skip '.' - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - fracPart = strtoll(cur, &end, 10); - if (cur != end) { - res += (double)fracPart / pow(10.0, (double)(end - cur)); - hasFracPart = 1; - cur = end; - } - } - } - - // A valid number should have integer or fractional part. - if (!hasIntPart && !hasFracPart) - return 0.0; - - // Parse optional exponent - if (*cur == 'e' || *cur == 'E') { - long expPart = 0; - cur++; // skip 'E' - expPart = strtol(cur, &end, 10); // Parse digit sequence with sign - if (cur != end) { - res *= pow(10.0, (double)expPart); - } - } - - return res * sign; -} - - -static const char* nsvg__parseNumber(const char* s, char* it, const int size) -{ - const int last = size-1; - int i = 0; - - // sign - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - // integer part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - if (*s == '.') { - // decimal point - if (i < last) it[i++] = *s; - s++; - // fraction part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - // exponent - if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { - if (i < last) it[i++] = *s; - s++; - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - it[i] = '\0'; - - return s; -} - -static const char* nsvg__getNextPathItem(const char* s, char* it) -{ - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - if (!*s) return s; - if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { - s = nsvg__parseNumber(s, it, 64); - } else { - // Parse command - it[0] = *s++; - it[1] = '\0'; - return s; - } - - return s; -} - -static unsigned int nsvg__parseColorHex(const char* str) -{ - unsigned int r=0, g=0, b=0; - if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 ) // 2 digit hex - return NSVG_RGB(r, g, b); - if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 ) // 1 digit hex, e.g. #abc -> 0xccbbaa - return NSVG_RGB(r*17, g*17, b*17); // same effect as (r<<4|r), (g<<4|g), .. - return NSVG_RGB(128, 128, 128); -} - -static unsigned int nsvg__parseColorRGB(const char* str) -{ - unsigned int r=0, g=0, b=0; - if (sscanf(str, "rgb(%u, %u, %u)", &r, &g, &b) == 3) // decimal integers - return NSVG_RGB(r, g, b); - if (sscanf(str, "rgb(%u%%, %u%%, %u%%)", &r, &g, &b) == 3) // decimal integer percentage - return NSVG_RGB(r*255/100, g*255/100, b*255/100); - return NSVG_RGB(128, 128, 128); -} - -typedef struct NSVGNamedColor { - const char* name; - unsigned int color; -} NSVGNamedColor; - -NSVGNamedColor nsvg__colors[] = { - - { "red", NSVG_RGB(255, 0, 0) }, - { "green", NSVG_RGB( 0, 128, 0) }, - { "blue", NSVG_RGB( 0, 0, 255) }, - { "yellow", NSVG_RGB(255, 255, 0) }, - { "cyan", NSVG_RGB( 0, 255, 255) }, - { "magenta", NSVG_RGB(255, 0, 255) }, - { "black", NSVG_RGB( 0, 0, 0) }, - { "grey", NSVG_RGB(128, 128, 128) }, - { "gray", NSVG_RGB(128, 128, 128) }, - { "white", NSVG_RGB(255, 255, 255) }, - -#ifdef NANOSVG_ALL_COLOR_KEYWORDS - { "aliceblue", NSVG_RGB(240, 248, 255) }, - { "antiquewhite", NSVG_RGB(250, 235, 215) }, - { "aqua", NSVG_RGB( 0, 255, 255) }, - { "aquamarine", NSVG_RGB(127, 255, 212) }, - { "azure", NSVG_RGB(240, 255, 255) }, - { "beige", NSVG_RGB(245, 245, 220) }, - { "bisque", NSVG_RGB(255, 228, 196) }, - { "blanchedalmond", NSVG_RGB(255, 235, 205) }, - { "blueviolet", NSVG_RGB(138, 43, 226) }, - { "brown", NSVG_RGB(165, 42, 42) }, - { "burlywood", NSVG_RGB(222, 184, 135) }, - { "cadetblue", NSVG_RGB( 95, 158, 160) }, - { "chartreuse", NSVG_RGB(127, 255, 0) }, - { "chocolate", NSVG_RGB(210, 105, 30) }, - { "coral", NSVG_RGB(255, 127, 80) }, - { "cornflowerblue", NSVG_RGB(100, 149, 237) }, - { "cornsilk", NSVG_RGB(255, 248, 220) }, - { "crimson", NSVG_RGB(220, 20, 60) }, - { "darkblue", NSVG_RGB( 0, 0, 139) }, - { "darkcyan", NSVG_RGB( 0, 139, 139) }, - { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, - { "darkgray", NSVG_RGB(169, 169, 169) }, - { "darkgreen", NSVG_RGB( 0, 100, 0) }, - { "darkgrey", NSVG_RGB(169, 169, 169) }, - { "darkkhaki", NSVG_RGB(189, 183, 107) }, - { "darkmagenta", NSVG_RGB(139, 0, 139) }, - { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, - { "darkorange", NSVG_RGB(255, 140, 0) }, - { "darkorchid", NSVG_RGB(153, 50, 204) }, - { "darkred", NSVG_RGB(139, 0, 0) }, - { "darksalmon", NSVG_RGB(233, 150, 122) }, - { "darkseagreen", NSVG_RGB(143, 188, 143) }, - { "darkslateblue", NSVG_RGB( 72, 61, 139) }, - { "darkslategray", NSVG_RGB( 47, 79, 79) }, - { "darkslategrey", NSVG_RGB( 47, 79, 79) }, - { "darkturquoise", NSVG_RGB( 0, 206, 209) }, - { "darkviolet", NSVG_RGB(148, 0, 211) }, - { "deeppink", NSVG_RGB(255, 20, 147) }, - { "deepskyblue", NSVG_RGB( 0, 191, 255) }, - { "dimgray", NSVG_RGB(105, 105, 105) }, - { "dimgrey", NSVG_RGB(105, 105, 105) }, - { "dodgerblue", NSVG_RGB( 30, 144, 255) }, - { "firebrick", NSVG_RGB(178, 34, 34) }, - { "floralwhite", NSVG_RGB(255, 250, 240) }, - { "forestgreen", NSVG_RGB( 34, 139, 34) }, - { "fuchsia", NSVG_RGB(255, 0, 255) }, - { "gainsboro", NSVG_RGB(220, 220, 220) }, - { "ghostwhite", NSVG_RGB(248, 248, 255) }, - { "gold", NSVG_RGB(255, 215, 0) }, - { "goldenrod", NSVG_RGB(218, 165, 32) }, - { "greenyellow", NSVG_RGB(173, 255, 47) }, - { "honeydew", NSVG_RGB(240, 255, 240) }, - { "hotpink", NSVG_RGB(255, 105, 180) }, - { "indianred", NSVG_RGB(205, 92, 92) }, - { "indigo", NSVG_RGB( 75, 0, 130) }, - { "ivory", NSVG_RGB(255, 255, 240) }, - { "khaki", NSVG_RGB(240, 230, 140) }, - { "lavender", NSVG_RGB(230, 230, 250) }, - { "lavenderblush", NSVG_RGB(255, 240, 245) }, - { "lawngreen", NSVG_RGB(124, 252, 0) }, - { "lemonchiffon", NSVG_RGB(255, 250, 205) }, - { "lightblue", NSVG_RGB(173, 216, 230) }, - { "lightcoral", NSVG_RGB(240, 128, 128) }, - { "lightcyan", NSVG_RGB(224, 255, 255) }, - { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, - { "lightgray", NSVG_RGB(211, 211, 211) }, - { "lightgreen", NSVG_RGB(144, 238, 144) }, - { "lightgrey", NSVG_RGB(211, 211, 211) }, - { "lightpink", NSVG_RGB(255, 182, 193) }, - { "lightsalmon", NSVG_RGB(255, 160, 122) }, - { "lightseagreen", NSVG_RGB( 32, 178, 170) }, - { "lightskyblue", NSVG_RGB(135, 206, 250) }, - { "lightslategray", NSVG_RGB(119, 136, 153) }, - { "lightslategrey", NSVG_RGB(119, 136, 153) }, - { "lightsteelblue", NSVG_RGB(176, 196, 222) }, - { "lightyellow", NSVG_RGB(255, 255, 224) }, - { "lime", NSVG_RGB( 0, 255, 0) }, - { "limegreen", NSVG_RGB( 50, 205, 50) }, - { "linen", NSVG_RGB(250, 240, 230) }, - { "maroon", NSVG_RGB(128, 0, 0) }, - { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, - { "mediumblue", NSVG_RGB( 0, 0, 205) }, - { "mediumorchid", NSVG_RGB(186, 85, 211) }, - { "mediumpurple", NSVG_RGB(147, 112, 219) }, - { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, - { "mediumslateblue", NSVG_RGB(123, 104, 238) }, - { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, - { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, - { "mediumvioletred", NSVG_RGB(199, 21, 133) }, - { "midnightblue", NSVG_RGB( 25, 25, 112) }, - { "mintcream", NSVG_RGB(245, 255, 250) }, - { "mistyrose", NSVG_RGB(255, 228, 225) }, - { "moccasin", NSVG_RGB(255, 228, 181) }, - { "navajowhite", NSVG_RGB(255, 222, 173) }, - { "navy", NSVG_RGB( 0, 0, 128) }, - { "oldlace", NSVG_RGB(253, 245, 230) }, - { "olive", NSVG_RGB(128, 128, 0) }, - { "olivedrab", NSVG_RGB(107, 142, 35) }, - { "orange", NSVG_RGB(255, 165, 0) }, - { "orangered", NSVG_RGB(255, 69, 0) }, - { "orchid", NSVG_RGB(218, 112, 214) }, - { "palegoldenrod", NSVG_RGB(238, 232, 170) }, - { "palegreen", NSVG_RGB(152, 251, 152) }, - { "paleturquoise", NSVG_RGB(175, 238, 238) }, - { "palevioletred", NSVG_RGB(219, 112, 147) }, - { "papayawhip", NSVG_RGB(255, 239, 213) }, - { "peachpuff", NSVG_RGB(255, 218, 185) }, - { "peru", NSVG_RGB(205, 133, 63) }, - { "pink", NSVG_RGB(255, 192, 203) }, - { "plum", NSVG_RGB(221, 160, 221) }, - { "powderblue", NSVG_RGB(176, 224, 230) }, - { "purple", NSVG_RGB(128, 0, 128) }, - { "rosybrown", NSVG_RGB(188, 143, 143) }, - { "royalblue", NSVG_RGB( 65, 105, 225) }, - { "saddlebrown", NSVG_RGB(139, 69, 19) }, - { "salmon", NSVG_RGB(250, 128, 114) }, - { "sandybrown", NSVG_RGB(244, 164, 96) }, - { "seagreen", NSVG_RGB( 46, 139, 87) }, - { "seashell", NSVG_RGB(255, 245, 238) }, - { "sienna", NSVG_RGB(160, 82, 45) }, - { "silver", NSVG_RGB(192, 192, 192) }, - { "skyblue", NSVG_RGB(135, 206, 235) }, - { "slateblue", NSVG_RGB(106, 90, 205) }, - { "slategray", NSVG_RGB(112, 128, 144) }, - { "slategrey", NSVG_RGB(112, 128, 144) }, - { "snow", NSVG_RGB(255, 250, 250) }, - { "springgreen", NSVG_RGB( 0, 255, 127) }, - { "steelblue", NSVG_RGB( 70, 130, 180) }, - { "tan", NSVG_RGB(210, 180, 140) }, - { "teal", NSVG_RGB( 0, 128, 128) }, - { "thistle", NSVG_RGB(216, 191, 216) }, - { "tomato", NSVG_RGB(255, 99, 71) }, - { "turquoise", NSVG_RGB( 64, 224, 208) }, - { "violet", NSVG_RGB(238, 130, 238) }, - { "wheat", NSVG_RGB(245, 222, 179) }, - { "whitesmoke", NSVG_RGB(245, 245, 245) }, - { "yellowgreen", NSVG_RGB(154, 205, 50) }, -#endif -}; - -static unsigned int nsvg__parseColorName(const char* str) -{ - int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); - - for (i = 0; i < ncolors; i++) { - if (strcmp(nsvg__colors[i].name, str) == 0) { - return nsvg__colors[i].color; - } - } - - return NSVG_RGB(128, 128, 128); -} - -static unsigned int nsvg__parseColor(const char* str) -{ - size_t len = 0; - while(*str == ' ') ++str; - len = strlen(str); - if (len >= 1 && *str == '#') - return nsvg__parseColorHex(str); - else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') - return nsvg__parseColorRGB(str); - return nsvg__parseColorName(str); -} - -static float nsvg__parseOpacity(const char* str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) val = 0.0f; - if (val > 1.0f) val = 1.0f; - return val; -} - -static float nsvg__parseMiterLimit(const char* str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) val = 0.0f; - return val; -} - -static int nsvg__parseUnits(const char* units) -{ - if (units[0] == 'p' && units[1] == 'x') - return NSVG_UNITS_PX; - else if (units[0] == 'p' && units[1] == 't') - return NSVG_UNITS_PT; - else if (units[0] == 'p' && units[1] == 'c') - return NSVG_UNITS_PC; - else if (units[0] == 'm' && units[1] == 'm') - return NSVG_UNITS_MM; - else if (units[0] == 'c' && units[1] == 'm') - return NSVG_UNITS_CM; - else if (units[0] == 'i' && units[1] == 'n') - return NSVG_UNITS_IN; - else if (units[0] == '%') - return NSVG_UNITS_PERCENT; - else if (units[0] == 'e' && units[1] == 'm') - return NSVG_UNITS_EM; - else if (units[0] == 'e' && units[1] == 'x') - return NSVG_UNITS_EX; - return NSVG_UNITS_USER; -} - -static int nsvg__isCoordinate(const char* s) -{ - // optional sign - if (*s == '-' || *s == '+') - s++; - // must have at least one digit, or start by a dot - return (nsvg__isdigit(*s) || *s == '.'); -} - -static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) -{ - NSVGcoordinate coord = {0, NSVG_UNITS_USER}; - char buf[64]; - coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); - coord.value = nsvg__atof(buf); - return coord; -} - -static NSVGcoordinate nsvg__coord(float v, int units) -{ - NSVGcoordinate coord = {v, units}; - return coord; -} - -static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) -{ - NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); - return nsvg__convertToPixels(p, coord, orig, length); -} - -static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) -{ - const char* end; - const char* ptr; - char it[64]; - - *na = 0; - ptr = str; - while (*ptr && *ptr != '(') ++ptr; - if (*ptr == 0) - return 1; - end = ptr; - while (*end && *end != ')') ++end; - if (*end == 0) - return 1; - - while (ptr < end) { - if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { - if (*na >= maxNa) return 0; - ptr = nsvg__parseNumber(ptr, it, 64); - args[(*na)++] = (float)nsvg__atof(it); - } else { - ++ptr; - } - } - return (int)(end - str); -} - - -static int nsvg__parseMatrix(float* xform, const char* str) -{ - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, t, 6, &na); - if (na != 6) return len; - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseTranslate(float* xform, const char* str) -{ - float args[2]; - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = 0.0; - - nsvg__xformSetTranslation(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseScale(float* xform, const char* str) -{ - float args[2]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = args[0]; - nsvg__xformSetScale(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewX(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewY(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseRotate(float* xform, const char* str) -{ - float args[3]; - int na = 0; - float m[6]; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 3, &na); - if (na == 1) - args[1] = args[2] = 0.0f; - nsvg__xformIdentity(m); - - if (na > 1) { - nsvg__xformSetTranslation(t, -args[1], -args[2]); - nsvg__xformMultiply(m, t); - } - - nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); - nsvg__xformMultiply(m, t); - - if (na > 1) { - nsvg__xformSetTranslation(t, args[1], args[2]); - nsvg__xformMultiply(m, t); - } - - memcpy(xform, m, sizeof(float)*6); - - return len; -} - -static void nsvg__parseTransform(float* xform, const char* str) -{ - float t[6]; - int len; - nsvg__xformIdentity(xform); - while (*str) - { - if (strncmp(str, "matrix", 6) == 0) - len = nsvg__parseMatrix(t, str); - else if (strncmp(str, "translate", 9) == 0) - len = nsvg__parseTranslate(t, str); - else if (strncmp(str, "scale", 5) == 0) - len = nsvg__parseScale(t, str); - else if (strncmp(str, "rotate", 6) == 0) - len = nsvg__parseRotate(t, str); - else if (strncmp(str, "skewX", 5) == 0) - len = nsvg__parseSkewX(t, str); - else if (strncmp(str, "skewY", 5) == 0) - len = nsvg__parseSkewY(t, str); - else{ - ++str; - continue; - } - if (len != 0) { - str += len; - } else { - ++str; - continue; - } - - nsvg__xformPremultiply(xform, t); - } -} - -static void nsvg__parseUrl(char* id, const char* str) -{ - int i = 0; - str += 4; // "url("; - if (*str == '#') - str++; - while (i < 63 && *str != ')') { - id[i] = *str++; - i++; - } - id[i] = '\0'; -} - -static char nsvg__parseLineCap(const char* str) -{ - if (strcmp(str, "butt") == 0) - return NSVG_CAP_BUTT; - else if (strcmp(str, "round") == 0) - return NSVG_CAP_ROUND; - else if (strcmp(str, "square") == 0) - return NSVG_CAP_SQUARE; - // TODO: handle inherit. - return NSVG_CAP_BUTT; -} - -static char nsvg__parseLineJoin(const char* str) -{ - if (strcmp(str, "miter") == 0) - return NSVG_JOIN_MITER; - else if (strcmp(str, "round") == 0) - return NSVG_JOIN_ROUND; - else if (strcmp(str, "bevel") == 0) - return NSVG_JOIN_BEVEL; - // TODO: handle inherit. - return NSVG_JOIN_MITER; -} - -static char nsvg__parseFillRule(const char* str) -{ - if (strcmp(str, "nonzero") == 0) - return NSVG_FILLRULE_NONZERO; - else if (strcmp(str, "evenodd") == 0) - return NSVG_FILLRULE_EVENODD; - // TODO: handle inherit. - return NSVG_FILLRULE_NONZERO; -} - -static const char* nsvg__getNextDashItem(const char* s, char* it) -{ - int n = 0; - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - // Advance until whitespace, comma or end. - while (*s && (!nsvg__isspace(*s) && *s != ',')) { - if (n < 63) - it[n++] = *s; - s++; - } - it[n++] = '\0'; - return s; -} - -static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) -{ - char item[64]; - int count = 0, i; - float sum = 0.0f; - - // Handle "none" - if (str[0] == 'n') - return 0; - - // Parse dashes - while (*str) { - str = nsvg__getNextDashItem(str, item); - if (!*item) break; - if (count < NSVG_MAX_DASHES) - strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); - } - - for (i = 0; i < count; i++) - sum += strokeDashArray[i]; - if (sum <= 1e-6f) - count = 0; - - return count; -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str); - -static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) -{ - float xform[6]; - NSVGattrib* attr = nsvg__getAttr(p); - if (!attr) return 0; - - if (strcmp(name, "style") == 0) { - nsvg__parseStyle(p, value); - } else if (strcmp(name, "display") == 0) { - if (strcmp(value, "none") == 0) - attr->visible = 0; - // Don't reset ->visible on display:inline, one display:none hides the whole subtree - - } else if (strcmp(name, "fill") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasFill = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasFill = 2; - nsvg__parseUrl(attr->fillGradient, value); - } else { - attr->hasFill = 1; - attr->fillColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "opacity") == 0) { - attr->opacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "fill-opacity") == 0) { - attr->fillOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasStroke = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasStroke = 2; - nsvg__parseUrl(attr->strokeGradient, value); - } else { - attr->hasStroke = 1; - attr->strokeColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "stroke-width") == 0) { - attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-dasharray") == 0) { - attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); - } else if (strcmp(name, "stroke-dashoffset") == 0) { - attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-opacity") == 0) { - attr->strokeOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke-linecap") == 0) { - attr->strokeLineCap = nsvg__parseLineCap(value); - } else if (strcmp(name, "stroke-linejoin") == 0) { - attr->strokeLineJoin = nsvg__parseLineJoin(value); - } else if (strcmp(name, "stroke-miterlimit") == 0) { - attr->miterLimit = nsvg__parseMiterLimit(value); - } else if (strcmp(name, "fill-rule") == 0) { - attr->fillRule = nsvg__parseFillRule(value); - } else if (strcmp(name, "font-size") == 0) { - attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "transform") == 0) { - nsvg__parseTransform(xform, value); - nsvg__xformPremultiply(attr->xform, xform); - } else if (strcmp(name, "stop-color") == 0) { - attr->stopColor = nsvg__parseColor(value); - } else if (strcmp(name, "stop-opacity") == 0) { - attr->stopOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "offset") == 0) { - attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); - } else if (strcmp(name, "id") == 0) { - strncpy(attr->id, value, 63); - attr->id[63] = '\0'; - } else { - return 0; - } - return 1; -} - -static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) -{ - const char* str; - const char* val; - char name[512]; - char value[512]; - int n; - - str = start; - while (str < end && *str != ':') ++str; - - val = str; - - // Right Trim - while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; - ++str; - - n = (int)(str - start); - if (n > 511) n = 511; - if (n) memcpy(name, start, n); - name[n] = 0; - - while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; - - n = (int)(end - val); - if (n > 511) n = 511; - if (n) memcpy(value, val, n); - value[n] = 0; - - return nsvg__parseAttr(p, name, value); -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str) -{ - const char* start; - const char* end; - - while (*str) { - // Left Trim - while(*str && nsvg__isspace(*str)) ++str; - start = str; - while(*str && *str != ';') ++str; - end = str; - - // Right Trim - while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; - ++end; - - nsvg__parseNameValue(p, start, end); - if (*str) ++str; - } -} - -static void nsvg__parseAttribs(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) - { - if (strcmp(attr[i], "style") == 0) - nsvg__parseStyle(p, attr[i + 1]); - else - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } -} - -static int nsvg__getArgsPerElement(char cmd) -{ - switch (cmd) { - case 'v': - case 'V': - case 'h': - case 'H': - return 1; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - return 2; - case 'q': - case 'Q': - case 's': - case 'S': - return 4; - case 'c': - case 'C': - return 6; - case 'a': - case 'A': - return 7; - case 'z': - case 'Z': - return 0; - } - return -1; -} - -static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__moveTo(p, *cpx, *cpy); -} - -static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpx += args[0]; - else - *cpx = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpy += args[0]; - else - *cpy = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x2, y2, cx1, cy1, cx2, cy2; - - if (rel) { - cx1 = *cpx + args[0]; - cy1 = *cpy + args[1]; - cx2 = *cpx + args[2]; - cy2 = *cpy + args[3]; - x2 = *cpx + args[4]; - y2 = *cpy + args[5]; - } else { - cx1 = args[0]; - cy1 = args[1]; - cx2 = args[2]; - cy2 = args[3]; - x2 = args[4]; - y2 = args[5]; - } - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx2 = *cpx + args[0]; - cy2 = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx2 = args[0]; - cy2 = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - cx1 = 2*x1 - *cpx2; - cy1 = 2*y1 - *cpy2; - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx = *cpx + args[0]; - cy = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx = args[0]; - cy = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - // Convert to cubic bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - x2 = *cpx + args[0]; - y2 = *cpy + args[1]; - } else { - x2 = args[0]; - y2 = args[1]; - } - - cx = 2*x1 - *cpx2; - cy = 2*y1 - *cpy2; - - // Convert to cubix bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static float nsvg__sqr(float x) { return x*x; } -static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } - -static float nsvg__vecrat(float ux, float uy, float vx, float vy) -{ - return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); -} - -static float nsvg__vecang(float ux, float uy, float vx, float vy) -{ - float r = nsvg__vecrat(ux,uy, vx,vy); - if (r < -1.0f) r = -1.0f; - if (r > 1.0f) r = 1.0f; - return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); -} - -static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - // Ported from canvg (https://code.google.com/p/canvg/) - float rx, ry, rotx; - float x1, y1, x2, y2, cx, cy, dx, dy, d; - float x1p, y1p, cxp, cyp, s, sa, sb; - float ux, uy, vx, vy, a1, da; - float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; - float sinrx, cosrx; - int fa, fs; - int i, ndivs; - float hda, kappa; - - rx = fabsf(args[0]); // y radius - ry = fabsf(args[1]); // x radius - rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle - fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc - fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction - x1 = *cpx; // start point - y1 = *cpy; - if (rel) { // end point - x2 = *cpx + args[5]; - y2 = *cpy + args[6]; - } else { - x2 = args[5]; - y2 = args[6]; - } - - dx = x1 - x2; - dy = y1 - y2; - d = sqrtf(dx*dx + dy*dy); - if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { - // The arc degenerates to a line - nsvg__lineTo(p, x2, y2); - *cpx = x2; - *cpy = y2; - return; - } - - sinrx = sinf(rotx); - cosrx = cosf(rotx); - - // Convert to center point parameterization. - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - // 1) Compute x1', y1' - x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; - y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; - d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); - if (d > 1) { - d = sqrtf(d); - rx *= d; - ry *= d; - } - // 2) Compute cx', cy' - s = 0.0f; - sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); - sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); - if (sa < 0.0f) sa = 0.0f; - if (sb > 0.0f) - s = sqrtf(sa / sb); - if (fa == fs) - s = -s; - cxp = s * rx * y1p / ry; - cyp = s * -ry * x1p / rx; - - // 3) Compute cx,cy from cx',cy' - cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; - cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; - - // 4) Calculate theta1, and delta theta. - ux = (x1p - cxp) / rx; - uy = (y1p - cyp) / ry; - vx = (-x1p - cxp) / rx; - vy = (-y1p - cyp) / ry; - a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle - da = nsvg__vecang(ux,uy, vx,vy); // Delta angle - -// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; -// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; - - if (fs == 0 && da > 0) - da -= 2 * NSVG_PI; - else if (fs == 1 && da < 0) - da += 2 * NSVG_PI; - - // Approximate the arc using cubic spline segments. - t[0] = cosrx; t[1] = sinrx; - t[2] = -sinrx; t[3] = cosrx; - t[4] = cx; t[5] = cy; - - // Split arc into max 90 degree segments. - // The loop assumes an iteration per end point (including start and end), this +1. - ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); - hda = (da / (float)ndivs) / 2.0f; - // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) - if ((hda < 1e-3f) && (hda > -1e-3f)) - hda *= 0.5f; - else - hda = (1.0f - cosf(hda)) / sinf(hda); - kappa = fabsf(4.0f / 3.0f * hda); - if (da < 0.0f) - kappa = -kappa; - - for (i = 0; i <= ndivs; i++) { - a = a1 + da * ((float)i/(float)ndivs); - dx = cosf(a); - dy = sinf(a); - nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position - nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent - if (i > 0) - nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); - px = x; - py = y; - ptanx = tanx; - ptany = tany; - } - - *cpx = x2; - *cpy = y2; -} - -static void nsvg__parsePath(NSVGparser* p, const char** attr) -{ - const char* s = NULL; - char cmd = '\0'; - float args[10]; - int nargs; - int rargs = 0; - char initPoint; - float cpx, cpy, cpx2, cpy2; - const char* tmp[4]; - char closedFlag; - int i; - char item[64]; - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "d") == 0) { - s = attr[i + 1]; - } else { - tmp[0] = attr[i]; - tmp[1] = attr[i + 1]; - tmp[2] = 0; - tmp[3] = 0; - nsvg__parseAttribs(p, tmp); - } - } - - if (s) { - nsvg__resetPath(p); - cpx = 0; cpy = 0; - cpx2 = 0; cpy2 = 0; - initPoint = 0; - closedFlag = 0; - nargs = 0; - - while (*s) { - s = nsvg__getNextPathItem(s, item); - if (!*item) break; - if (cmd != '\0' && nsvg__isCoordinate(item)) { - if (nargs < 10) - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= rargs) { - switch (cmd) { - case 'm': - case 'M': - nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); - // Moveto can be followed by multiple coordinate pairs, - // which should be treated as linetos. - cmd = (cmd == 'm') ? 'l' : 'L'; - rargs = nsvg__getArgsPerElement(cmd); - cpx2 = cpx; cpy2 = cpy; - initPoint = 1; - break; - case 'l': - case 'L': - nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'H': - case 'h': - nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'V': - case 'v': - nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'C': - case 'c': - nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); - break; - case 'S': - case 's': - nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); - break; - case 'Q': - case 'q': - nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); - break; - case 'T': - case 't': - nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); - break; - case 'A': - case 'a': - nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - default: - if (nargs >= 2) { - cpx = args[nargs-2]; - cpy = args[nargs-1]; - cpx2 = cpx; cpy2 = cpy; - } - break; - } - nargs = 0; - } - } else { - cmd = item[0]; - if (cmd == 'M' || cmd == 'm') { - // Commit path. - if (p->npts > 0) - nsvg__addPath(p, closedFlag); - // Start new subpath. - nsvg__resetPath(p); - closedFlag = 0; - nargs = 0; - } else if (initPoint == 0) { - // Do not allow other commands until initial point has been set (moveTo called once). - cmd = '\0'; - } - if (cmd == 'Z' || cmd == 'z') { - closedFlag = 1; - // Commit path. - if (p->npts > 0) { - // Move current point to first point - cpx = p->pts[0]; - cpy = p->pts[1]; - cpx2 = cpx; cpy2 = cpy; - nsvg__addPath(p, closedFlag); - } - // Start new subpath. - nsvg__resetPath(p); - nsvg__moveTo(p, cpx, cpy); - closedFlag = 0; - nargs = 0; - } - rargs = nsvg__getArgsPerElement(cmd); - if (rargs == -1) { - // Command not recognized - cmd = '\0'; - rargs = 0; - } - } - } - // Commit path. - if (p->npts) - nsvg__addPath(p, closedFlag); - } - - nsvg__addShape(p); -} - -static void nsvg__parseRect(NSVGparser* p, const char** attr) -{ - float x = 0.0f; - float y = 0.0f; - float w = 0.0f; - float h = 0.0f; - float rx = -1.0f; // marks not set - float ry = -1.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); - if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx < 0.0f && ry > 0.0f) rx = ry; - if (ry < 0.0f && rx > 0.0f) ry = rx; - if (rx < 0.0f) rx = 0.0f; - if (ry < 0.0f) ry = 0.0f; - if (rx > w/2.0f) rx = w/2.0f; - if (ry > h/2.0f) ry = h/2.0f; - - if (w != 0.0f && h != 0.0f) { - nsvg__resetPath(p); - - if (rx < 0.00001f || ry < 0.0001f) { - nsvg__moveTo(p, x, y); - nsvg__lineTo(p, x+w, y); - nsvg__lineTo(p, x+w, y+h); - nsvg__lineTo(p, x, y+h); - } else { - // Rounded rectangle - nsvg__moveTo(p, x+rx, y); - nsvg__lineTo(p, x+w-rx, y); - nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); - nsvg__lineTo(p, x+w, y+h-ry); - nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); - nsvg__lineTo(p, x+rx, y+h); - nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); - nsvg__lineTo(p, x, y+ry); - nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); - } - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseCircle(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float r = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); - } - } - - if (r > 0.0f) { - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+r, cy); - nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); - nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); - nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); - nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseEllipse(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float rx = 0.0f; - float ry = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx > 0.0f && ry > 0.0f) { - - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+rx, cy); - nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); - nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); - nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); - nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseLine(NSVGparser* p, const char** attr) -{ - float x1 = 0.0; - float y1 = 0.0; - float x2 = 0.0; - float y2 = 0.0; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - } - } - - nsvg__resetPath(p); - - nsvg__moveTo(p, x1, y1); - nsvg__lineTo(p, x2, y2); - - nsvg__addPath(p, 0); - - nsvg__addShape(p); -} - -static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) -{ - int i; - const char* s; - float args[2]; - int nargs, npts = 0; - char item[64]; - - nsvg__resetPath(p); - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "points") == 0) { - s = attr[i + 1]; - nargs = 0; - while (*s) { - s = nsvg__getNextPathItem(s, item); - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= 2) { - if (npts == 0) - nsvg__moveTo(p, args[0], args[1]); - else - nsvg__lineTo(p, args[0], args[1]); - nargs = 0; - npts++; - } - } - } - } - } - - nsvg__addPath(p, (char)closeFlag); - - nsvg__addShape(p); -} - -static void nsvg__parseSVG(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "width") == 0) { - p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } else if (strcmp(attr[i], "height") == 0) { - p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } else if (strcmp(attr[i], "viewBox") == 0) { - const char *s = attr[i + 1]; - char buf[64]; - s = nsvg__parseNumber(s, buf, 64); - p->viewMinx = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewMiny = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewWidth = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewHeight = nsvg__atof(buf); - } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { - if (strstr(attr[i + 1], "none") != 0) { - // No uniform scaling - p->alignType = NSVG_ALIGN_NONE; - } else { - // Parse X align - if (strstr(attr[i + 1], "xMin") != 0) - p->alignX = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "xMid") != 0) - p->alignX = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "xMax") != 0) - p->alignX = NSVG_ALIGN_MAX; - // Parse X align - if (strstr(attr[i + 1], "yMin") != 0) - p->alignY = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "yMid") != 0) - p->alignY = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "yMax") != 0) - p->alignY = NSVG_ALIGN_MAX; - // Parse meet/slice - p->alignType = NSVG_ALIGN_MEET; - if (strstr(attr[i + 1], "slice") != 0) - p->alignType = NSVG_ALIGN_SLICE; - } - } - } - } -} - -static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) -{ - int i; - NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); - if (grad == NULL) return; - memset(grad, 0, sizeof(NSVGgradientData)); - grad->units = NSVG_OBJECT_SPACE; - grad->type = type; - if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { - grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); - grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { - grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - } - - nsvg__xformIdentity(grad->xform); - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "id") == 0) { - strncpy(grad->id, attr[i+1], 63); - grad->id[63] = '\0'; - } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "gradientUnits") == 0) { - if (strcmp(attr[i+1], "objectBoundingBox") == 0) - grad->units = NSVG_OBJECT_SPACE; - else - grad->units = NSVG_USER_SPACE; - } else if (strcmp(attr[i], "gradientTransform") == 0) { - nsvg__parseTransform(grad->xform, attr[i + 1]); - } else if (strcmp(attr[i], "cx") == 0) { - grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "cy") == 0) { - grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "r") == 0) { - grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fx") == 0) { - grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fy") == 0) { - grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x1") == 0) { - grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y1") == 0) { - grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x2") == 0) { - grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y2") == 0) { - grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "spreadMethod") == 0) { - if (strcmp(attr[i+1], "pad") == 0) - grad->spread = NSVG_SPREAD_PAD; - else if (strcmp(attr[i+1], "reflect") == 0) - grad->spread = NSVG_SPREAD_REFLECT; - else if (strcmp(attr[i+1], "repeat") == 0) - grad->spread = NSVG_SPREAD_REPEAT; - } else if (strcmp(attr[i], "xlink:href") == 0) { - const char *href = attr[i+1]; - strncpy(grad->ref, href+1, 62); - grad->ref[62] = '\0'; - } - } - } - - grad->next = p->gradients; - p->gradients = grad; -} - -static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) -{ - NSVGattrib* curAttr = nsvg__getAttr(p); - NSVGgradientData* grad; - NSVGgradientStop* stop; - int i, idx; - - curAttr->stopOffset = 0; - curAttr->stopColor = 0; - curAttr->stopOpacity = 1.0f; - - for (i = 0; attr[i]; i += 2) { - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } - - // Add stop to the last gradient. - grad = p->gradients; - if (grad == NULL) return; - - grad->nstops++; - grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); - if (grad->stops == NULL) return; - - // Insert - idx = grad->nstops-1; - for (i = 0; i < grad->nstops-1; i++) { - if (curAttr->stopOffset < grad->stops[i].offset) { - idx = i; - break; - } - } - if (idx != grad->nstops-1) { - for (i = grad->nstops-1; i > idx; i--) - grad->stops[i] = grad->stops[i-1]; - } - - stop = &grad->stops[idx]; - stop->color = curAttr->stopColor; - stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; - stop->offset = curAttr->stopOffset; -} - -static void nsvg__startElement(void* ud, const char* el, const char** attr) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (p->defsFlag) { - // Skip everything but gradients in defs - if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } - return; - } - - if (strcmp(el, "g") == 0) { - nsvg__pushAttr(p); - nsvg__parseAttribs(p, attr); - } else if (strcmp(el, "path") == 0) { - if (p->pathFlag) // Do not allow nested paths. - return; - nsvg__pushAttr(p); - nsvg__parsePath(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "rect") == 0) { - nsvg__pushAttr(p); - nsvg__parseRect(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "circle") == 0) { - nsvg__pushAttr(p); - nsvg__parseCircle(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "ellipse") == 0) { - nsvg__pushAttr(p); - nsvg__parseEllipse(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "line") == 0) { - nsvg__pushAttr(p); - nsvg__parseLine(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "polyline") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 0); - nsvg__popAttr(p); - } else if (strcmp(el, "polygon") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 1); - nsvg__popAttr(p); - } else if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 1; - } else if (strcmp(el, "svg") == 0) { - nsvg__parseSVG(p, attr); - } -} - -static void nsvg__endElement(void* ud, const char* el) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (strcmp(el, "g") == 0) { - nsvg__popAttr(p); - } else if (strcmp(el, "path") == 0) { - p->pathFlag = 0; - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 0; - } -} - -static void nsvg__content(void* ud, const char* s) -{ - NSVG_NOTUSED(ud); - NSVG_NOTUSED(s); - // empty -} - -static void nsvg__imageBounds(NSVGparser* p, float* bounds) -{ - NSVGshape* shape; - shape = p->image->shapes; - if (shape == NULL) { - bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; - return; - } - bounds[0] = shape->bounds[0]; - bounds[1] = shape->bounds[1]; - bounds[2] = shape->bounds[2]; - bounds[3] = shape->bounds[3]; - for (shape = shape->next; shape != NULL; shape = shape->next) { - bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); - bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); - bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); - bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); - } -} - -static float nsvg__viewAlign(float content, float container, int type) -{ - if (type == NSVG_ALIGN_MIN) - return 0; - else if (type == NSVG_ALIGN_MAX) - return container - content; - // mid - return (container - content) * 0.5f; -} - -static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) -{ - float t[6]; - nsvg__xformSetTranslation(t, tx, ty); - nsvg__xformMultiply (grad->xform, t); - - nsvg__xformSetScale(t, sx, sy); - nsvg__xformMultiply (grad->xform, t); -} - -static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) -{ - NSVGshape* shape; - NSVGpath* path; - float tx, ty, sx, sy, us, bounds[4], t[6], avgs; - int i; - float* pt; - - // Guess image size if not set completely. - nsvg__imageBounds(p, bounds); - - if (p->viewWidth == 0) { - if (p->image->width > 0) { - p->viewWidth = p->image->width; - } else { - p->viewMinx = bounds[0]; - p->viewWidth = bounds[2] - bounds[0]; - } - } - if (p->viewHeight == 0) { - if (p->image->height > 0) { - p->viewHeight = p->image->height; - } else { - p->viewMiny = bounds[1]; - p->viewHeight = bounds[3] - bounds[1]; - } - } - if (p->image->width == 0) - p->image->width = p->viewWidth; - if (p->image->height == 0) - p->image->height = p->viewHeight; - - tx = -p->viewMinx; - ty = -p->viewMiny; - sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; - sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; - // Unit scaling - us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); - - // Fix aspect ratio - if (p->alignType == NSVG_ALIGN_MEET) { - // fit whole image into viewbox - sx = sy = nsvg__minf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } else if (p->alignType == NSVG_ALIGN_SLICE) { - // fill whole viewbox with image - sx = sy = nsvg__maxf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } - - // Transform - sx *= us; - sy *= us; - avgs = (sx+sy) / 2.0f; - for (shape = p->image->shapes; shape != NULL; shape = shape->next) { - shape->bounds[0] = (shape->bounds[0] + tx) * sx; - shape->bounds[1] = (shape->bounds[1] + ty) * sy; - shape->bounds[2] = (shape->bounds[2] + tx) * sx; - shape->bounds[3] = (shape->bounds[3] + ty) * sy; - for (path = shape->paths; path != NULL; path = path->next) { - path->bounds[0] = (path->bounds[0] + tx) * sx; - path->bounds[1] = (path->bounds[1] + ty) * sy; - path->bounds[2] = (path->bounds[2] + tx) * sx; - path->bounds[3] = (path->bounds[3] + ty) * sy; - for (i =0; i < path->npts; i++) { - pt = &path->pts[i*2]; - pt[0] = (pt[0] + tx) * sx; - pt[1] = (pt[1] + ty) * sy; - } - } - - if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); - memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->fill.gradient->xform, t); - } - if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); - memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->stroke.gradient->xform, t); - } - - shape->strokeWidth *= avgs; - shape->strokeDashOffset *= avgs; - for (i = 0; i < shape->strokeDashCount; i++) - shape->strokeDashArray[i] *= avgs; - } -} - -NSVGimage* nsvgParse(char* input, const char* units, float dpi) -{ - NSVGparser* p; - NSVGimage* ret = 0; - - p = nsvg__createParser(); - if (p == NULL) { - return NULL; - } - p->dpi = dpi; - - nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); - - // Scale to viewBox - nsvg__scaleToViewbox(p, units); - - ret = p->image; - p->image = NULL; - - nsvg__deleteParser(p); - - return ret; -} - -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) -{ - FILE* fp = NULL; - size_t size; - char* data = NULL; - NSVGimage* image = NULL; - - fp = fopen(filename, "rb"); - if (!fp) goto error; - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - data = (char*)malloc(size+1); - if (data == NULL) goto error; - if (fread(data, 1, size, fp) != size) goto error; - data[size] = '\0'; // Must be null terminated. - fclose(fp); - image = nsvgParse(data, units, dpi); - free(data); - - return image; - -error: - if (fp) fclose(fp); - if (data) free(data); - if (image) nsvgDelete(image); - return NULL; -} - -NSVGpath* nsvgDuplicatePath(NSVGpath* p) -{ - NSVGpath* res = NULL; - - if (p == NULL) - return NULL; - - res = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (res == NULL) goto error; - memset(res, 0, sizeof(NSVGpath)); - - res->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (res->pts == NULL) goto error; - memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); - res->npts = p->npts; - - memcpy(res->bounds, p->bounds, sizeof(p->bounds)); - - res->closed = p->closed; - - return res; - -error: - if (res != NULL) { - free(res->pts); - free(res); - } - return NULL; -} - -void nsvgDelete(NSVGimage* image) -{ - NSVGshape *snext, *shape; - if (image == NULL) return; - shape = image->shapes; - while (shape != NULL) { - snext = shape->next; - nsvg__deletePaths(shape->paths); - nsvg__deletePaint(&shape->fill); - nsvg__deletePaint(&shape->stroke); - free(shape); - shape = snext; - } - free(image); -} - -#endif diff --git a/thirdparty/nanosvg/nanosvgrast.h b/thirdparty/nanosvg/nanosvgrast.h deleted file mode 100644 index b740c316ca..0000000000 --- a/thirdparty/nanosvg/nanosvgrast.h +++ /dev/null @@ -1,1452 +0,0 @@ -/* - * Copyright (c) 2013-14 Mikko Mononen memon@inside.org - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The polygon rasterization is heavily based on stb_truetype rasterizer - * by Sean Barrett - http://nothings.org/ - * - */ - -#ifndef NANOSVGRAST_H -#define NANOSVGRAST_H - -#ifndef NANOSVGRAST_CPLUSPLUS -#ifdef __cplusplus -extern "C" { -#endif -#endif - -typedef struct NSVGrasterizer NSVGrasterizer; - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - - // Create rasterizer (can be used to render multiple images). - struct NSVGrasterizer* rast = nsvgCreateRasterizer(); - // Allocate memory for image - unsigned char* img = malloc(w*h*4); - // Rasterize - nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); -*/ - -// Allocated rasterizer context. -NSVGrasterizer* nsvgCreateRasterizer(); - -// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) -// r - pointer to rasterizer context -// image - pointer to image to rasterize -// tx,ty - image offset (applied after scaling) -// scale - image scale -// dst - pointer to destination image data, 4 bytes per pixel (RGBA) -// w - width of the image to render -// h - height of the image to render -// stride - number of bytes per scaleline in the destination buffer -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride); - -// Deletes rasterizer context. -void nsvgDeleteRasterizer(NSVGrasterizer*); - - -#ifndef NANOSVGRAST_CPLUSPLUS -#ifdef __cplusplus -} -#endif -#endif - -#endif // NANOSVGRAST_H - -#ifdef NANOSVGRAST_IMPLEMENTATION - -#include - -#define NSVG__SUBSAMPLES 5 -#define NSVG__FIXSHIFT 10 -#define NSVG__FIX (1 << NSVG__FIXSHIFT) -#define NSVG__FIXMASK (NSVG__FIX-1) -#define NSVG__MEMPAGE_SIZE 1024 - -typedef struct NSVGedge { - float x0,y0, x1,y1; - int dir; - struct NSVGedge* next; -} NSVGedge; - -typedef struct NSVGpoint { - float x, y; - float dx, dy; - float len; - float dmx, dmy; - unsigned char flags; -} NSVGpoint; - -typedef struct NSVGactiveEdge { - int x,dx; - float ey; - int dir; - struct NSVGactiveEdge *next; -} NSVGactiveEdge; - -typedef struct NSVGmemPage { - unsigned char mem[NSVG__MEMPAGE_SIZE]; - int size; - struct NSVGmemPage* next; -} NSVGmemPage; - -typedef struct NSVGcachedPaint { - char type; - char spread; - float xform[6]; - unsigned int colors[256]; -} NSVGcachedPaint; - -struct NSVGrasterizer -{ - float px, py; - - float tessTol; - float distTol; - - NSVGedge* edges; - int nedges; - int cedges; - - NSVGpoint* points; - int npoints; - int cpoints; - - NSVGpoint* points2; - int npoints2; - int cpoints2; - - NSVGactiveEdge* freelist; - NSVGmemPage* pages; - NSVGmemPage* curpage; - - unsigned char* scanline; - int cscanline; - - unsigned char* bitmap; - int width, height, stride; -}; - -NSVGrasterizer* nsvgCreateRasterizer() -{ - NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); - if (r == NULL) goto error; - memset(r, 0, sizeof(NSVGrasterizer)); - - r->tessTol = 0.25f; - r->distTol = 0.01f; - - return r; - -error: - nsvgDeleteRasterizer(r); - return NULL; -} - -void nsvgDeleteRasterizer(NSVGrasterizer* r) -{ - NSVGmemPage* p; - - if (r == NULL) return; - - p = r->pages; - while (p != NULL) { - NSVGmemPage* next = p->next; - free(p); - p = next; - } - - if (r->edges) free(r->edges); - if (r->points) free(r->points); - if (r->points2) free(r->points2); - if (r->scanline) free(r->scanline); - - free(r); -} - -static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) -{ - NSVGmemPage *newp; - - // If using existing chain, return the next page in chain - if (cur != NULL && cur->next != NULL) { - return cur->next; - } - - // Alloc new page - newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); - if (newp == NULL) return NULL; - memset(newp, 0, sizeof(NSVGmemPage)); - - // Add to linked list - if (cur != NULL) - cur->next = newp; - else - r->pages = newp; - - return newp; -} - -static void nsvg__resetPool(NSVGrasterizer* r) -{ - NSVGmemPage* p = r->pages; - while (p != NULL) { - p->size = 0; - p = p->next; - } - r->curpage = r->pages; -} - -static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) -{ - unsigned char* buf; - if (size > NSVG__MEMPAGE_SIZE) return NULL; - if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { - r->curpage = nsvg__nextPage(r, r->curpage); - } - buf = &r->curpage->mem[r->curpage->size]; - r->curpage->size += size; - return buf; -} - -static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) -{ - float dx = x2 - x1; - float dy = y2 - y1; - return dx*dx + dy*dy < tol*tol; -} - -static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) -{ - NSVGpoint* pt; - - if (r->npoints > 0) { - pt = &r->points[r->npoints-1]; - if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { - pt->flags = (unsigned char)(pt->flags | flags); - return; - } - } - - if (r->npoints+1 > r->cpoints) { - r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; - r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); - if (r->points == NULL) return; - } - - pt = &r->points[r->npoints]; - pt->x = x; - pt->y = y; - pt->flags = (unsigned char)flags; - r->npoints++; -} - -static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) -{ - if (r->npoints+1 > r->cpoints) { - r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; - r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); - if (r->points == NULL) return; - } - r->points[r->npoints] = pt; - r->npoints++; -} - -static void nsvg__duplicatePoints(NSVGrasterizer* r) -{ - if (r->npoints > r->cpoints2) { - r->cpoints2 = r->npoints; - r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); - if (r->points2 == NULL) return; - } - - memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); - r->npoints2 = r->npoints; -} - -static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) -{ - NSVGedge* e; - - // Skip horizontal edges - if (y0 == y1) - return; - - if (r->nedges+1 > r->cedges) { - r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; - r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); - if (r->edges == NULL) return; - } - - e = &r->edges[r->nedges]; - r->nedges++; - - if (y0 < y1) { - e->x0 = x0; - e->y0 = y0; - e->x1 = x1; - e->y1 = y1; - e->dir = 1; - } else { - e->x0 = x1; - e->y0 = y1; - e->x1 = x0; - e->y1 = y0; - e->dir = -1; - } -} - -static float nsvg__normalize(float *x, float* y) -{ - float d = sqrtf((*x)*(*x) + (*y)*(*y)); - if (d > 1e-6f) { - float id = 1.0f / d; - *x *= id; - *y *= id; - } - return d; -} - -static float nsvg__absf(float x) { return x < 0 ? -x : x; } - -static void nsvg__flattenCubicBez(NSVGrasterizer* r, - float x1, float y1, float x2, float y2, - float x3, float y3, float x4, float y4, - int level, int type) -{ - float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; - float dx,dy,d2,d3; - - if (level > 10) return; - - x12 = (x1+x2)*0.5f; - y12 = (y1+y2)*0.5f; - x23 = (x2+x3)*0.5f; - y23 = (y2+y3)*0.5f; - x34 = (x3+x4)*0.5f; - y34 = (y3+y4)*0.5f; - x123 = (x12+x23)*0.5f; - y123 = (y12+y23)*0.5f; - - dx = x4 - x1; - dy = y4 - y1; - d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); - d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); - - if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { - nsvg__addPathPoint(r, x4, y4, type); - return; - } - - x234 = (x23+x34)*0.5f; - y234 = (y23+y34)*0.5f; - x1234 = (x123+x234)*0.5f; - y1234 = (y123+y234)*0.5f; - - nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); - nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); -} - -static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) -{ - int i, j; - NSVGpath* path; - - for (path = shape->paths; path != NULL; path = path->next) { - r->npoints = 0; - // Flatten path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); - } - // Close path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); - // Build edges - for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) - nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); - } -} - -enum NSVGpointFlags -{ - NSVG_PT_CORNER = 0x01, - NSVG_PT_BEVEL = 0x02, - NSVG_PT_LEFT = 0x04 -}; - -static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - float len = nsvg__normalize(&dx, &dy); - float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) -{ - float w = lineWidth * 0.5f; - float px = p->x, py = p->y; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - - nsvg__addEdge(r, lx, ly, rx, ry); - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) -{ - float w = lineWidth * 0.5f; - float px = p->x - dx*w, py = p->y - dy*w; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - - nsvg__addEdge(r, lx, ly, rx, ry); - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -#ifndef NSVG_PI -#define NSVG_PI (3.14159265358979323846264338327f) -#endif - -static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) -{ - int i; - float w = lineWidth * 0.5f; - float px = p->x, py = p->y; - float dlx = dy, dly = -dx; - float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; - - for (i = 0; i < ncap; i++) { - float a = (float)i/(float)(ncap-1)*NSVG_PI; - float ax = cosf(a) * w, ay = sinf(a) * w; - float x = px - dlx*ax - dx*ay; - float y = py - dly*ax - dy*ay; - - if (i > 0) - nsvg__addEdge(r, prevx, prevy, x, y); - - prevx = x; - prevy = y; - - if (i == 0) { - lx = x; ly = y; - } else if (i == ncap-1) { - rx = x; ry = y; - } - } - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); - float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); - float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); - float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); - - nsvg__addEdge(r, lx0, ly0, left->x, left->y); - nsvg__addEdge(r, lx1, ly1, lx0, ly0); - - nsvg__addEdge(r, right->x, right->y, rx0, ry0); - nsvg__addEdge(r, rx0, ry0, rx1, ry1); - - left->x = lx1; left->y = ly1; - right->x = rx1; right->y = ry1; -} - -static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float lx0, rx0, lx1, rx1; - float ly0, ry0, ly1, ry1; - - if (p1->flags & NSVG_PT_LEFT) { - lx0 = lx1 = p1->x - p1->dmx * w; - ly0 = ly1 = p1->y - p1->dmy * w; - nsvg__addEdge(r, lx1, ly1, left->x, left->y); - - rx0 = p1->x + (dlx0 * w); - ry0 = p1->y + (dly0 * w); - rx1 = p1->x + (dlx1 * w); - ry1 = p1->y + (dly1 * w); - nsvg__addEdge(r, right->x, right->y, rx0, ry0); - nsvg__addEdge(r, rx0, ry0, rx1, ry1); - } else { - lx0 = p1->x - (dlx0 * w); - ly0 = p1->y - (dly0 * w); - lx1 = p1->x - (dlx1 * w); - ly1 = p1->y - (dly1 * w); - nsvg__addEdge(r, lx0, ly0, left->x, left->y); - nsvg__addEdge(r, lx1, ly1, lx0, ly0); - - rx0 = rx1 = p1->x + p1->dmx * w; - ry0 = ry1 = p1->y + p1->dmy * w; - nsvg__addEdge(r, right->x, right->y, rx1, ry1); - } - - left->x = lx1; left->y = ly1; - right->x = rx1; right->y = ry1; -} - -static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) -{ - int i, n; - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float a0 = atan2f(dly0, dlx0); - float a1 = atan2f(dly1, dlx1); - float da = a1 - a0; - float lx, ly, rx, ry; - - if (da < NSVG_PI) da += NSVG_PI*2; - if (da > NSVG_PI) da -= NSVG_PI*2; - - n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); - if (n < 2) n = 2; - if (n > ncap) n = ncap; - - lx = left->x; - ly = left->y; - rx = right->x; - ry = right->y; - - for (i = 0; i < n; i++) { - float u = (float)i/(float)(n-1); - float a = a0 + u*da; - float ax = cosf(a) * w, ay = sinf(a) * w; - float lx1 = p1->x - ax, ly1 = p1->y - ay; - float rx1 = p1->x + ax, ry1 = p1->y + ay; - - nsvg__addEdge(r, lx1, ly1, lx, ly); - nsvg__addEdge(r, rx, ry, rx1, ry1); - - lx = lx1; ly = ly1; - rx = rx1; ry = ry1; - } - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); - float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); - - nsvg__addEdge(r, lx, ly, left->x, left->y); - nsvg__addEdge(r, right->x, right->y, rx, ry); - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static int nsvg__curveDivs(float r, float arc, float tol) -{ - float da = acosf(r / (r + tol)) * 2.0f; - int divs = (int)ceilf(arc / da); - if (divs < 2) divs = 2; - return divs; -} - -static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) -{ - int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. - NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; - NSVGpoint* p0, *p1; - int j, s, e; - - // Build stroke edges - if (closed) { - // Looping - p0 = &points[npoints-1]; - p1 = &points[0]; - s = 0; - e = npoints; - } else { - // Add cap - p0 = &points[0]; - p1 = &points[1]; - s = 1; - e = npoints-1; - } - - if (closed) { - nsvg__initClosed(&left, &right, p0, p1, lineWidth); - firstLeft = left; - firstRight = right; - } else { - // Add cap - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - nsvg__normalize(&dx, &dy); - if (lineCap == NSVG_CAP_BUTT) - nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); - else if (lineCap == NSVG_CAP_SQUARE) - nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); - else if (lineCap == NSVG_CAP_ROUND) - nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); - } - - for (j = s; j < e; ++j) { - if (p1->flags & NSVG_PT_CORNER) { - if (lineJoin == NSVG_JOIN_ROUND) - nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); - else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) - nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); - else - nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); - } else { - nsvg__straightJoin(r, &left, &right, p1, lineWidth); - } - p0 = p1++; - } - - if (closed) { - // Loop it - nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); - nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); - } else { - // Add cap - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - nsvg__normalize(&dx, &dy); - if (lineCap == NSVG_CAP_BUTT) - nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); - else if (lineCap == NSVG_CAP_SQUARE) - nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); - else if (lineCap == NSVG_CAP_ROUND) - nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); - } -} - -static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) -{ - int i, j; - NSVGpoint* p0, *p1; - - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - for (i = 0; i < r->npoints; i++) { - // Calculate segment direction and length - p0->dx = p1->x - p0->x; - p0->dy = p1->y - p0->y; - p0->len = nsvg__normalize(&p0->dx, &p0->dy); - // Advance - p0 = p1++; - } - - // calculate joins - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - for (j = 0; j < r->npoints; j++) { - float dlx0, dly0, dlx1, dly1, dmr2, cross; - dlx0 = p0->dy; - dly0 = -p0->dx; - dlx1 = p1->dy; - dly1 = -p1->dx; - // Calculate extrusions - p1->dmx = (dlx0 + dlx1) * 0.5f; - p1->dmy = (dly0 + dly1) * 0.5f; - dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; - if (dmr2 > 0.000001f) { - float s2 = 1.0f / dmr2; - if (s2 > 600.0f) { - s2 = 600.0f; - } - p1->dmx *= s2; - p1->dmy *= s2; - } - - // Clear flags, but keep the corner. - p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; - - // Keep track of left turns. - cross = p1->dx * p0->dy - p0->dx * p1->dy; - if (cross > 0.0f) - p1->flags |= NSVG_PT_LEFT; - - // Check to see if the corner needs to be beveled. - if (p1->flags & NSVG_PT_CORNER) { - if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { - p1->flags |= NSVG_PT_BEVEL; - } - } - - p0 = p1++; - } -} - -static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) -{ - int i, j, closed; - NSVGpath* path; - NSVGpoint* p0, *p1; - float miterLimit = shape->miterLimit; - int lineJoin = shape->strokeLineJoin; - int lineCap = shape->strokeLineCap; - float lineWidth = shape->strokeWidth * scale; - - for (path = shape->paths; path != NULL; path = path->next) { - // Flatten path - r->npoints = 0; - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); - } - if (r->npoints < 2) - continue; - - closed = path->closed; - - // If the first and last points are the same, remove the last, mark as closed path. - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { - r->npoints--; - p0 = &r->points[r->npoints-1]; - closed = 1; - } - - if (shape->strokeDashCount > 0) { - int idash = 0, dashState = 1; - float totalDist = 0, dashLen, allDashLen, dashOffset; - NSVGpoint cur; - - if (closed) - nsvg__appendPathPoint(r, r->points[0]); - - // Duplicate points -> points2. - nsvg__duplicatePoints(r); - - r->npoints = 0; - cur = r->points2[0]; - nsvg__appendPathPoint(r, cur); - - // Figure out dash offset. - allDashLen = 0; - for (j = 0; j < shape->strokeDashCount; j++) - allDashLen += shape->strokeDashArray[j]; - if (shape->strokeDashCount & 1) - allDashLen *= 2.0f; - // Find location inside pattern - dashOffset = fmodf(shape->strokeDashOffset, allDashLen); - if (dashOffset < 0.0f) - dashOffset += allDashLen; - - while (dashOffset > shape->strokeDashArray[idash]) { - dashOffset -= shape->strokeDashArray[idash]; - idash = (idash + 1) % shape->strokeDashCount; - } - dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; - - for (j = 1; j < r->npoints2; ) { - float dx = r->points2[j].x - cur.x; - float dy = r->points2[j].y - cur.y; - float dist = sqrtf(dx*dx + dy*dy); - - if ((totalDist + dist) > dashLen) { - // Calculate intermediate point - float d = (dashLen - totalDist) / dist; - float x = cur.x + dx * d; - float y = cur.y + dy * d; - nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); - - // Stroke - if (r->npoints > 1 && dashState) { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); - } - // Advance dash pattern - dashState = !dashState; - idash = (idash+1) % shape->strokeDashCount; - dashLen = shape->strokeDashArray[idash] * scale; - // Restart - cur.x = x; - cur.y = y; - cur.flags = NSVG_PT_CORNER; - totalDist = 0.0f; - r->npoints = 0; - nsvg__appendPathPoint(r, cur); - } else { - totalDist += dist; - cur = r->points2[j]; - nsvg__appendPathPoint(r, cur); - j++; - } - } - // Stroke any leftover path - if (r->npoints > 1 && dashState) - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); - } else { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); - } - } -} - -static int nsvg__cmpEdge(const void *p, const void *q) -{ - const NSVGedge* a = (const NSVGedge*)p; - const NSVGedge* b = (const NSVGedge*)q; - - if (a->y0 < b->y0) return -1; - if (a->y0 > b->y0) return 1; - return 0; -} - - -static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) -{ - NSVGactiveEdge* z; - - if (r->freelist != NULL) { - // Restore from freelist. - z = r->freelist; - r->freelist = z->next; - } else { - // Alloc new edge. - z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); - if (z == NULL) return NULL; - } - - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); -// STBTT_assert(e->y0 <= start_point); - // round dx down to avoid going too far - if (dxdy < 0) - z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); - else - z->dx = (int)floorf(NSVG__FIX * dxdy); - z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); -// z->x -= off_x * FIX; - z->ey = e->y1; - z->next = 0; - z->dir = e->dir; - - return z; -} - -static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) -{ - z->next = r->freelist; - r->freelist = z; -} - -static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) -{ - int i = x0 >> NSVG__FIXSHIFT; - int j = x1 >> NSVG__FIXSHIFT; - if (i < *xmin) *xmin = i; - if (j > *xmax) *xmax = j; - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = (unsigned char)(scanline[i] + maxWeight); - } - } -} - -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) -{ - // non-zero winding fill - int x0 = 0, w = 0; - - if (fillRule == NSVG_FILLRULE_NONZERO) { - // Non-zero - while (e != NULL) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->dir; - } else { - int x1 = e->x; w += e->dir; - // if we went to zero, we need to draw - if (w == 0) - nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); - } - e = e->next; - } - } else if (fillRule == NSVG_FILLRULE_EVENODD) { - // Even-odd - while (e != NULL) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w = 1; - } else { - int x1 = e->x; w = 0; - nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); - } - e = e->next; - } - } -} - -static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } - -static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return (r) | (g << 8) | (b << 16) | (a << 24); -} - -static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) -{ - int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); - int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; - int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; - int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; - int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; - return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); -} - -static unsigned int nsvg__applyOpacity(unsigned int c, float u) -{ - int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); - int r = (c) & 0xff; - int g = (c>>8) & 0xff; - int b = (c>>16) & 0xff; - int a = (((c>>24) & 0xff)*iu) >> 8; - return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); -} - -static inline int nsvg__div255(int x) -{ - return ((x+1) * 257) >> 16; -} - -static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, - float tx, float ty, float scale, NSVGcachedPaint* cache) -{ - - if (cache->type == NSVG_PAINT_COLOR) { - int i, cr, cg, cb, ca; - cr = cache->colors[0] & 0xff; - cg = (cache->colors[0] >> 8) & 0xff; - cb = (cache->colors[0] >> 16) & 0xff; - ca = (cache->colors[0] >> 24) & 0xff; - - for (i = 0; i < count; i++) { - int r,g,b; - int a = nsvg__div255((int)cover[0] * ca); - int ia = 255 - a; - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - } - } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { - // TODO: spread modes. - // TODO: plenty of opportunities to optimize. - float fx, fy, dx, gy; - float* t = cache->xform; - int i, cr, cg, cb, ca; - unsigned int c; - - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; - - for (i = 0; i < count; i++) { - int r,g,b,a,ia; - gy = fx*t[1] + fy*t[3] + t[5]; - c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; - cr = (c) & 0xff; - cg = (c >> 8) & 0xff; - cb = (c >> 16) & 0xff; - ca = (c >> 24) & 0xff; - - a = nsvg__div255((int)cover[0] * ca); - ia = 255 - a; - - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - fx += dx; - } - } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { - // TODO: spread modes. - // TODO: plenty of opportunities to optimize. - // TODO: focus (fx,fy) - float fx, fy, dx, gx, gy, gd; - float* t = cache->xform; - int i, cr, cg, cb, ca; - unsigned int c; - - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; - - for (i = 0; i < count; i++) { - int r,g,b,a,ia; - gx = fx*t[0] + fy*t[2] + t[4]; - gy = fx*t[1] + fy*t[3] + t[5]; - gd = sqrtf(gx*gx + gy*gy); - c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; - cr = (c) & 0xff; - cg = (c >> 8) & 0xff; - cb = (c >> 16) & 0xff; - ca = (c >> 24) & 0xff; - - a = nsvg__div255((int)cover[0] * ca); - ia = 255 - a; - - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - fx += dx; - } - } -} - -static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) -{ - NSVGactiveEdge *active = NULL; - int y, s; - int e = 0; - int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline - int xmin, xmax; - - for (y = 0; y < r->height; y++) { - memset(r->scanline, 0, r->width); - xmin = r->width; - xmax = 0; - for (s = 0; s < NSVG__SUBSAMPLES; ++s) { - // find center of pixel for this scanline - float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; - NSVGactiveEdge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - NSVGactiveEdge *z = *step; - if (z->ey <= scany) { - *step = z->next; // delete from list -// NSVG__assert(z->valid); - nsvg__freeActive(r, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for (;;) { - int changed = 0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - NSVGactiveEdge* t = *step; - NSVGactiveEdge* q = t->next; - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e < r->nedges && r->edges[e].y0 <= scany) { - if (r->edges[e].y1 > scany) { - NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); - if (z == NULL) break; - // find insertion point - if (active == NULL) { - active = z; - } else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - NSVGactiveEdge* p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - e++; - } - - // now process all active edges in non-zero fashion - if (active != NULL) - nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); - } - // Blit - if (xmin < 0) xmin = 0; - if (xmax > r->width-1) xmax = r->width-1; - if (xmin <= xmax) { - nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); - } - } - -} - -static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) -{ - int x,y; - - // Unpremultiply - for (y = 0; y < h; y++) { - unsigned char *row = &image[y*stride]; - for (x = 0; x < w; x++) { - int r = row[0], g = row[1], b = row[2], a = row[3]; - if (a != 0) { - row[0] = (unsigned char)(r*255/a); - row[1] = (unsigned char)(g*255/a); - row[2] = (unsigned char)(b*255/a); - } - row += 4; - } - } - - // Defringe - for (y = 0; y < h; y++) { - unsigned char *row = &image[y*stride]; - for (x = 0; x < w; x++) { - int r = 0, g = 0, b = 0, a = row[3], n = 0; - if (a == 0) { - if (x-1 > 0 && row[-1] != 0) { - r += row[-4]; - g += row[-3]; - b += row[-2]; - n++; - } - if (x+1 < w && row[7] != 0) { - r += row[4]; - g += row[5]; - b += row[6]; - n++; - } - if (y-1 > 0 && row[-stride+3] != 0) { - r += row[-stride]; - g += row[-stride+1]; - b += row[-stride+2]; - n++; - } - if (y+1 < h && row[stride+3] != 0) { - r += row[stride]; - g += row[stride+1]; - b += row[stride+2]; - n++; - } - if (n > 0) { - row[0] = (unsigned char)(r/n); - row[1] = (unsigned char)(g/n); - row[2] = (unsigned char)(b/n); - } - } - row += 4; - } - } -} - - -static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) -{ - int i, j; - NSVGgradient* grad; - - cache->type = paint->type; - - if (paint->type == NSVG_PAINT_COLOR) { - cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); - return; - } - - grad = paint->gradient; - - cache->spread = grad->spread; - memcpy(cache->xform, grad->xform, sizeof(float)*6); - - if (grad->nstops == 0) { - for (i = 0; i < 256; i++) - cache->colors[i] = 0; - } if (grad->nstops == 1) { - for (i = 0; i < 256; i++) - cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); - } else { - unsigned int ca, cb = 0; - float ua, ub, du, u; - int ia, ib, count; - - ca = nsvg__applyOpacity(grad->stops[0].color, opacity); - ua = nsvg__clampf(grad->stops[0].offset, 0, 1); - ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - for (i = 0; i < ia; i++) { - cache->colors[i] = ca; - } - - for (i = 0; i < grad->nstops-1; i++) { - ca = nsvg__applyOpacity(grad->stops[i].color, opacity); - cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); - ua = nsvg__clampf(grad->stops[i].offset, 0, 1); - ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - count = ib - ia; - if (count <= 0) continue; - u = 0; - du = 1.0f / (float)count; - for (j = 0; j < count; j++) { - cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); - u += du; - } - } - - for (i = ib; i < 256; i++) - cache->colors[i] = cb; - } - -} - -/* -static void dumpEdges(NSVGrasterizer* r, const char* name) -{ - float xmin = 0, xmax = 0, ymin = 0, ymax = 0; - NSVGedge *e = NULL; - int i; - if (r->nedges == 0) return; - FILE* fp = fopen(name, "w"); - if (fp == NULL) return; - - xmin = xmax = r->edges[0].x0; - ymin = ymax = r->edges[0].y0; - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - xmin = nsvg__minf(xmin, e->x0); - xmin = nsvg__minf(xmin, e->x1); - xmax = nsvg__maxf(xmax, e->x0); - xmax = nsvg__maxf(xmax, e->x1); - ymin = nsvg__minf(ymin, e->y0); - ymin = nsvg__minf(ymin, e->y1); - ymax = nsvg__maxf(ymax, e->y0); - ymax = nsvg__maxf(ymax, e->y1); - } - - fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); - - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); - } - - for (i = 0; i < r->npoints; i++) { - if (i+1 < r->npoints) - fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); - fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); - } - - fprintf(fp, ""); - fclose(fp); -} -*/ - -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride) -{ - NSVGshape *shape = NULL; - NSVGedge *e = NULL; - NSVGcachedPaint cache; - int i; - - r->bitmap = dst; - r->width = w; - r->height = h; - r->stride = stride; - - if (w > r->cscanline) { - r->cscanline = w; - r->scanline = (unsigned char*)realloc(r->scanline, w); - if (r->scanline == NULL) return; - } - - for (i = 0; i < h; i++) - memset(&dst[i*stride], 0, w*4); - - for (shape = image->shapes; shape != NULL; shape = shape->next) { - if (!(shape->flags & NSVG_FLAGS_VISIBLE)) - continue; - - if (shape->fill.type != NSVG_PAINT_NONE) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShape(r, shape, scale); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaint(&cache, &shape->fill, shape->opacity); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); - } - if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShapeStroke(r, shape, scale); - -// dumpEdges(r, "edge.svg"); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaint(&cache, &shape->stroke, shape->opacity); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); - } - } - - nsvg__unpremultiplyAlpha(dst, w, h, stride); - - r->bitmap = NULL; - r->width = 0; - r->height = 0; - r->stride = 0; -} - -#endif diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS new file mode 100644 index 0000000000..66057232b6 --- /dev/null +++ b/thirdparty/thorvg/AUTHORS @@ -0,0 +1,15 @@ +Hermet Park +Prudhvi Raj Vasireddi +Junsu Choi +Pranay Samanta +Mateusz Palkowski +Subhransu Mohanty +Mira Grudzinska +Michal Szczecinski +Shinwoo Kim +Piotr Kalota +Vincent Torri +Pankaj Kumar +Patryk Kaczmarek +Michal Maciola +Peter Vullings diff --git a/thirdparty/thorvg/LICENSE b/thirdparty/thorvg/LICENSE new file mode 100644 index 0000000000..b096b0888e --- /dev/null +++ b/thirdparty/thorvg/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2020 - 2021 notice for the ThorVG Project (see AUTHORS) + +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. diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h new file mode 100644 index 0000000000..441612281a --- /dev/null +++ b/thirdparty/thorvg/inc/config.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 SVG_CONFIG_H +#define SVG_CONFIG_H + +#define THORVG_SW_RASTER_SUPPORT 1 + +#define THORVG_SVG_LOADER_SUPPORT 1 + +#define THORVG_PNG_LOADER_SUPPORT 1 + +#define THORVG_TVG_LOADER_SUPPORT 1 + +#define THORVG_JPG_LOADER_SUPPORT 1 + +#define THORVG_LOG_ENABLED 1 + +#define THORVG_VERSION_STRING "master" +#endif \ No newline at end of file diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h new file mode 100644 index 0000000000..e5dcc66829 --- /dev/null +++ b/thirdparty/thorvg/inc/thorvg.h @@ -0,0 +1,1554 @@ +/*! + * @file thorvg.h + * + * The main APIs enabling the TVG initialization, preparation of the canvas and provisioning of its content: + * - drawing shapes such as line, curve, arc, rectangle, circle or user-defined + * - drawing pictures - SVG, PNG, JPG, RAW + * - solid or gradient filling + * - continuous and dashed stroking + * - clipping and masking + * and finally drawing the canvas and TVG termination. + */ + + +#ifndef _THORVG_H_ +#define _THORVG_H_ + +#include +#include + +#ifdef TVG_BUILD + #if defined(_MSC_VER) && !defined(__clang__) + #define TVG_EXPORT __declspec(dllexport) + #define TVG_DEPRECATED __declspec(deprecated) + #else + #define TVG_EXPORT __attribute__ ((visibility ("default"))) + #define TVG_DEPRECATED __attribute__ ((__deprecated__)) + #endif +#else + #define TVG_EXPORT + #define TVG_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define _TVG_DECLARE_PRIVATE(A) \ +protected: \ + struct Impl; \ + Impl* pImpl; \ + A(const A&) = delete; \ + const A& operator=(const A&) = delete; \ + A() + +#define _TVG_DISABLE_CTOR(A) \ + A() = delete; \ + ~A() = delete + +#define _TVG_DECLARE_ACCESSOR() \ + friend Canvas; \ + friend Scene; \ + friend Picture; \ + friend IteratorAccessor + + +#define _TVG_DECALRE_IDENTIFIER() \ +protected: \ + unsigned _id + +namespace tvg +{ + +class RenderMethod; +class IteratorAccessor; +class Scene; +class Picture; +class Canvas; + +/** + * @defgroup ThorVG ThorVG + * @brief ThorVG classes and enumerations providing C++ APIs. + */ + +/**@{*/ + +/** + * @brief Enumeration specifying the result from the APIs. + */ +enum class TVG_EXPORT Result +{ + Success = 0, ///< The value returned in case of a correct request execution. + InvalidArguments, ///< The value returned in the event of a problem with the arguments given to the API - e.g. empty paths or null pointers. + InsufficientCondition, ///< The value returned in case the request cannot be processed - e.g. asking for properties of an object, which does not exist. + FailedAllocation, ///< The value returned in case of unsuccessful memory allocation. + MemoryCorruption, ///< The value returned in the event of bad memory handling - e.g. failing in pointer releasing or casting + NonSupport, ///< The value returned in case of choosing unsupported options. + Unknown ///< The value returned in all other cases. +}; + +/** + * @brief Enumeration specifying the values of the path commands accepted by TVG. + * + * Not to be confused with the path commands from the svg path element (like M, L, Q, H and many others). + * TVG interprets all of them and translates to the ones from the PathCommand values. + */ +enum class TVG_EXPORT PathCommand +{ + Close = 0, ///< Ends the current sub-path and connects it with its initial point. This command doesn't expect any points. + MoveTo, ///< Sets a new initial point of the sub-path and a new current point. This command expects 1 point: the starting position. + LineTo, ///< Draws a line from the current point to the given point and sets a new value of the current point. This command expects 1 point: the end-position of the line. + CubicTo ///< Draws a cubic Bezier curve from the current point to the given point using two given control points and sets a new value of the current point. This command expects 3 points: the 1st control-point, the 2nd control-point, the end-point of the curve. +}; + +/** + * @brief Enumeration determining the ending type of a stroke in the open sub-paths. + */ +enum class TVG_EXPORT StrokeCap +{ + Square = 0, ///< The stroke is extended in both end-points of a sub-path by a rectangle, with the width equal to the stroke width and the length equal to the half of the stroke width. For zero length sub-paths the square is rendered with the size of the stroke width. + Round, ///< The stroke is extended in both end-points of a sub-path by a half circle, with a radius equal to the half of a stroke width. For zero length sub-paths a full circle is rendered. + Butt ///< The stroke ends exactly at each of the two end-points of a sub-path. For zero length sub-paths no stroke is rendered. +}; + +/** + * @brief Enumeration determining the style used at the corners of joined stroked path segments. + */ +enum class TVG_EXPORT StrokeJoin +{ + Bevel = 0, ///< The outer corner of the joined path segments is bevelled at the join point. The triangular region of the corner is enclosed by a straight line between the outer corners of each stroke. + Round, ///< The outer corner of the joined path segments is rounded. The circular region is centered at the join point. + Miter ///< The outer corner of the joined path segments is spiked. The spike is created by extension beyond the join point of the outer edges of the stroke until they intersect. In case the extension goes beyond the limit, the join style is converted to the Bevel style. +}; + +/** + * @brief Enumeration specifying how to fill the area outside the gradient bounds. + */ +enum class TVG_EXPORT FillSpread +{ + Pad = 0, ///< The remaining area is filled with the closest stop color. + Reflect, ///< The gradient pattern is reflected outside the gradient area until the expected region is filled. + Repeat ///< The gradient pattern is repeated continuously beyond the gradient area until the expected region is filled. +}; + +/** + * @brief Enumeration specifying the algorithm used to establish which parts of the shape are treated as the inside of the shape. + */ +enum class TVG_EXPORT FillRule +{ + Winding = 0, ///< A line from the point to a location outside the shape is drawn. The intersections of the line with the path segment of the shape are counted. Starting from zero, if the path segment of the shape crosses the line clockwise, one is added, otherwise one is subtracted. If the resulting sum is non zero, the point is inside the shape. + EvenOdd ///< A line from the point to a location outside the shape is drawn and its intersections with the path segments of the shape are counted. If the number of intersections is an odd number, the point is inside the shape. +}; + +/** + * @brief Enumeration indicating the method used in the composition of two objects - the target and the source. + */ +enum class TVG_EXPORT CompositeMethod +{ + None = 0, ///< No composition is applied. + ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. + AlphaMask, ///< The pixels of the source and the target are alpha blended. As a result, only the part of the source, which intersects with the target is visible. + InvAlphaMask ///< The pixels of the source and the complement to the target's pixels are alpha blended. As a result, only the part of the source which is not covered by the target is visible. +}; + +/** + * @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed. + */ +enum class TVG_EXPORT CanvasEngine +{ + Sw = (1 << 1), ///< CPU rasterizer. + Gl = (1 << 2) ///< OpenGL rasterizer. +}; + + +/** + * @brief A data structure representing a point in two-dimensional space. + */ +struct Point +{ + float x, y; +}; + + +/** + * @brief A data structure representing a three-dimensional matrix. + * + * The elements e11, e12, e21 and e22 represent the rotation matrix, including the scaling factor. + * The elements e13 and e23 determine the translation of the object along the x and y-axis, respectively. + * The elements e31 and e32 are set to 0, e33 is set to 1. + */ +struct Matrix +{ + float e11, e12, e13; + float e21, e22, e23; + float e31, e32, e33; +}; + + +/** + * @class Paint + * + * @brief An abstract class for managing graphical elements. + * + * A graphical element in TVG is any object composed into a Canvas. + * Paint represents such a graphical object and its behaviors such as duplication, transformation and composition. + * TVG recommends the user to regard a paint as a set of volatile commands. They can prepare a Paint and then request a Canvas to run them. + */ +class TVG_EXPORT Paint +{ +public: + virtual ~Paint(); + + /** + * @brief Sets the angle by which the object is rotated. + * + * The angle in measured clockwise from the horizontal axis. + * The rotational axis passes through the point on the object with zero coordinates. + * + * @param[in] degree The value of the angle in degrees. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result rotate(float degree) noexcept; + + /** + * @brief Sets the scale value of the object. + * + * @param[in] factor The value of the scaling factor. The default value is 1. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result scale(float factor) noexcept; + + /** + * @brief Sets the values by which the object is moved in a two-dimensional space. + * + * The origin of the coordinate system is in the upper left corner of the canvas. + * The horizontal and vertical axes point to the right and down, respectively. + * + * @param[in] x The value of the horizontal shift. + * @param[in] y The value of the vertical shift. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result translate(float x, float y) noexcept; + + /** + * @brief Sets the matrix of the affine transformation for the object. + * + * The augmented matrix of the transformation is expected to be given. + * + * @param[in] m The 3x3 augmented matrix. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result transform(const Matrix& m) noexcept; + + /** + * @brief Gets the matrix of the affine transformation of the object. + * + * The values of the matrix can be set by the transform() API, as well by the translate(), + * scale() and rotate(). In case no transformation was applied, the identity matrix is returned. + * + * @retval The augmented transformation matrix. + * + * @since 0.4 + */ + Matrix transform() noexcept; + + /** + * @brief Sets the opacity of the object. + * + * @param[in] o The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + * + * @return Result::Success when succeed. + * + * @note Setting the opacity with this API may require multiple render pass for composition. It is recommended to avoid changing the opacity if possible. + */ + Result opacity(uint8_t o) noexcept; + + /** + * @brief Sets the composition target object and the composition method. + * + * @param[in] target The paint of the target object. + * @param[in] method The method used to composite the source object with the target. + * + * @return Result::Success when succeed, Result::InvalidArguments otherwise. + */ + Result composite(std::unique_ptr target, CompositeMethod method) noexcept; + + /** + * @brief Gets the bounding box of the paint object before any transformation. + * + * @param[out] x The x coordinate of the upper left corner of the object. + * @param[out] y The y coordinate of the upper left corner of the object. + * @param[out] w The width of the object. + * @param[out] h The height of the object. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note The bounding box doesn't indicate the final rendered region. It's the smallest rectangle that encloses the object. + * @see Paint::bounds(float* x, float* y, float* w, float* h, bool transformed); + */ + TVG_DEPRECATED Result bounds(float* x, float* y, float* w, float* h) const noexcept; + + /** + * @brief Gets the axis-aligned bounding box of the paint object. + * + * In case @p transform is @c true, all object's transformations are applied first, and then the bounding box is established. Otherwise, the bounding box is determined before any transformations. + * + * @param[out] x The x coordinate of the upper left corner of the object. + * @param[out] y The y coordinate of the upper left corner of the object. + * @param[out] w The width of the object. + * @param[out] h The height of the object. + * @param[in] transformed If @c true, the paint's transformations are taken into account, otherwise they aren't. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note The bounding box doesn't indicate the actual drawing region. It's the smallest rectangle that encloses the object. + * + * @BETA_API + */ + Result bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept; + + /** + * @brief Duplicates the object. + * + * Creates a new object and sets its all properties as in the original object. + * + * @return The created object when succeed, @c nullptr otherwise. + */ + Paint* duplicate() const noexcept; + + /** + * @brief Gets the opacity value of the object. + * + * @return The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + */ + uint8_t opacity() const noexcept; + + /** + * @brief Gets the composition target object and the composition method. + * + * @param[out] target The paint of the target object. + * + * @return The method used to composite the source object with the target. + * + * @since 0.5 + */ + CompositeMethod composite(const Paint** target) const noexcept; + + /** + * @brief Return the unique id value of the paint instance. + * + * This method can be called for checking the current concrete instance type. + * + * @return The type id of the Paint instance. + * + * @BETA_API + */ + uint32_t identifier() const { return _id; } + + _TVG_DECLARE_ACCESSOR(); + _TVG_DECALRE_IDENTIFIER(); + _TVG_DECLARE_PRIVATE(Paint); +}; + + +/** + * @class Fill + * + * @brief An abstract class representing the gradient fill of the Shape object. + * + * It contains the information about the gradient colors and their arrangement + * inside the gradient bounds. The gradients bounds are defined in the LinearGradient + * or RadialGradient class, depending on the type of the gradient to be used. + * It specifies the gradient behavior in case the area defined by the gradient bounds + * is smaller than the area to be filled. + */ +class TVG_EXPORT Fill +{ +public: + /** + * @brief A data structure storing the information about the color and its relative position inside the gradient bounds. + */ + struct ColorStop + { + float offset; /**< The relative position of the color. */ + uint8_t r; /**< The red color channel value in the range [0 ~ 255]. */ + uint8_t g; /**< The green color channel value in the range [0 ~ 255]. */ + uint8_t b; /**< The blue color channel value in the range [0 ~ 255]. */ + uint8_t a; /**< The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. */ + }; + + virtual ~Fill(); + + /** + * @brief Sets the parameters of the colors of the gradient and their position. + * + * @param[in] colorStops An array of ColorStop data structure. + * @param[in] cnt The count of the @p colorStops array equal to the colors number used in the gradient. + * + * @return Result::Success when succeed. + */ + Result colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept; + + /** + * @brief Sets the FillSpread value, which specifies how to fill the area outside the gradient bounds. + * + * @param[in] s The FillSpread value. + * + * @return Result::Success when succeed. + */ + Result spread(FillSpread s) noexcept; + + /** + * @brief Sets the matrix of the affine transformation for the gradient fill. + * + * The augmented matrix of the transformation is expected to be given. + * + * @param[in] m The 3x3 augmented matrix. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + * + * @BETA_API + */ + Result transform(const Matrix& m) noexcept; + + /** + * @brief Gets the parameters of the colors of the gradient, their position and number. + * + * @param[out] colorStops A pointer to the memory location, where the array of the gradient's ColorStop is stored. + * + * @return The number of colors used in the gradient. This value corresponds to the length of the @p colorStops array. + */ + uint32_t colorStops(const ColorStop** colorStops) const noexcept; + + /** + * @brief Gets the FillSpread value of the fill. + * + * @return The FillSpread value of this Fill. + */ + FillSpread spread() const noexcept; + + /** + * @brief Gets the matrix of the affine transformation of the gradient fill. + * + * In case no transformation was applied, the identity matrix is returned. + * + * @retval The augmented transformation matrix. + * + * @BETA_API + */ + Matrix transform() const noexcept; + + /** + * @brief Creates a copy of the Fill object. + * + * Return a newly created Fill object with the properties copied from the original. + * + * @return A copied Fill object when succeed, @c nullptr otherwise. + */ + Fill* duplicate() const noexcept; + + /** + * @brief Return the unique id value of the Fill instance. + * + * This method can be called for checking the current concrete instance type. + * + * @return The type id of the Fill instance. + * + * @BETA_API + */ + uint32_t identifier() const { return _id; } + + _TVG_DECALRE_IDENTIFIER(); + _TVG_DECLARE_PRIVATE(Fill); +}; + + +/** + * @class Canvas + * + * @brief An abstract class for drawing graphical elements. + * + * A canvas is an entity responsible for drawing the target. It sets up the drawing engine and the buffer, which can be drawn on the screen. It also manages given Paint objects. + * + * @note A Canvas behavior depends on the raster engine though the final content of the buffer is expected to be identical. + * @warning The Paint objects belonging to one Canvas can't be shared among multiple Canvases. + */ +class TVG_EXPORT Canvas +{ +public: + Canvas(RenderMethod*); + virtual ~Canvas(); + + /** + * @brief Sets the size of the container, where all the paints pushed into the Canvas are stored. + * + * If the number of objects pushed into the Canvas is known in advance, calling the function + * prevents multiple memory reallocation, thus improving the performance. + * + * @param[in] n The number of objects for which the memory is to be reserved. + * + * @return Result::Success when succeed. + */ + Result reserve(uint32_t n) noexcept; + + /** + * @brief Passes drawing elements to the Canvas using Paint objects. + * + * Only pushed paints in the canvas will be drawing targets. + * They are retained by the canvas until you call Canvas::clear(). + * If you know the number of the pushed objects in advance, please call Canvas::reserve(). + * + * @param[in] paint A Paint object to be drawn. + * + * @retval Result::Success When succeed. + * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. + * @retval Result::InsufficientCondition An internal error. + * + * @note The rendering order of the paints is the same as the order as they were pushed into the canvas. Consider sorting the paints before pushing them if you intend to use layering. + * @see Canvas::reserve() + * @see Canvas::clear() + */ + virtual Result push(std::unique_ptr paint) noexcept; + + /** + * @brief Sets the total number of the paints pushed into the canvas to be zero. + * Depending on the value of the @p free argument, the paints are freed or not. + * + * @param[in] free If @c true, the memory occupied by paints is deallocated, otherwise it is not. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @warning If you don't free the paints they become dangled. They are supposed to be reused, otherwise you are responsible for their lives. Thus please use the @p free argument only when you know how it works, otherwise it's not recommended. + */ + virtual Result clear(bool free = true) noexcept; + + /** + * @brief Request the canvas to update the paint objects. + * + * If a @c nullptr is passed all paint objects retained by the Canvas are updated, + * otherwise only the paint to which the given @p paint points. + * + * @param[in] paint A pointer to the Paint object or @c nullptr. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note The Update behavior can be asynchronous if the assigned thread number is greater than zero. + */ + virtual Result update(Paint* paint = nullptr) noexcept; + + /** + * @brief Requests the canvas to draw the Paint objects. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + * + * @note Drawing can be asynchronous if the assigned thread number is greater than zero. To guarantee the drawing is done, call sync() afterwards. + * @see Canvas::sync() + */ + virtual Result draw() noexcept; + + /** + * @brief Guarantees that drawing task is finished. + * + * The Canvas rendering can be performed asynchronously. To make sure that rendering is finished, + * the sync() must be called after the draw() regardless of threading. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + * @see Canvas::draw() + */ + virtual Result sync() noexcept; + + _TVG_DECLARE_PRIVATE(Canvas); +}; + + +/** + * @class LinearGradient + * + * @brief A class representing the linear gradient fill of the Shape object. + * + * Besides the APIs inherited from the Fill class, it enables setting and getting the linear gradient bounds. + * The behavior outside the gradient bounds depends on the value specified in the spread API. + */ +class TVG_EXPORT LinearGradient final : public Fill +{ +public: + ~LinearGradient(); + + /** + * @brief Sets the linear gradient bounds. + * + * The bounds of the linear gradient are defined as a surface constrained by two parallel lines crossing + * the given points (@p x1, @p y1) and (@p x2, @p y2), respectively. Both lines are perpendicular to the line linking + * (@p x1, @p y1) and (@p x2, @p y2). + * + * @param[in] x1 The horizontal coordinate of the first point used to determine the gradient bounds. + * @param[in] y1 The vertical coordinate of the first point used to determine the gradient bounds. + * @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds. + * @param[in] y2 The vertical coordinate of the second point used to determine the gradient bounds. + * + * @return Result::Success when succeed. + * + * @note In case the first and the second points are equal, an object filled with such a gradient fill is not rendered. + */ + Result linear(float x1, float y1, float x2, float y2) noexcept; + + /** + * @brief Gets the linear gradient bounds. + * + * The bounds of the linear gradient are defined as a surface constrained by two parallel lines crossing + * the given points (@p x1, @p y1) and (@p x2, @p y2), respectively. Both lines are perpendicular to the line linking + * (@p x1, @p y1) and (@p x2, @p y2). + * + * @param[out] x1 The horizontal coordinate of the first point used to determine the gradient bounds. + * @param[out] y1 The vertical coordinate of the first point used to determine the gradient bounds. + * @param[out] x2 The horizontal coordinate of the second point used to determine the gradient bounds. + * @param[out] y2 The vertical coordinate of the second point used to determine the gradient bounds. + * + * @return Result::Success when succeed. + */ + Result linear(float* x1, float* y1, float* x2, float* y2) const noexcept; + + /** + * @brief Creates a new LinearGradient object. + * + * @return A new LinearGradient object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the LinearGradient class type. + * + * @return The type id of the LinearGradient class. + * + * @BETA_API + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(LinearGradient); +}; + + +/** + * @class RadialGradient + * + * @brief A class representing the radial gradient fill of the Shape object. + * + */ +class TVG_EXPORT RadialGradient final : public Fill +{ +public: + ~RadialGradient(); + + /** + * @brief Sets the radial gradient bounds. + * + * The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius. + * + * @param[in] cx The horizontal coordinate of the center of the bounding circle. + * @param[in] cy The vertical coordinate of the center of the bounding circle. + * @param[in] radius The radius of the bounding circle. + * + * @return Result::Success when succeed, Result::InvalidArguments in case the @p radius value is zero or less. + */ + Result radial(float cx, float cy, float radius) noexcept; + + /** + * @brief Gets the radial gradient bounds. + * + * The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius. + * + * @param[out] cx The horizontal coordinate of the center of the bounding circle. + * @param[out] cy The vertical coordinate of the center of the bounding circle. + * @param[out] radius The radius of the bounding circle. + * + * @return Result::Success when succeed. + */ + Result radial(float* cx, float* cy, float* radius) const noexcept; + + /** + * @brief Creates a new RadialGradient object. + * + * @return A new RadialGradient object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the RadialGradient class type. + * + * @return The type id of the RadialGradient class. + * + * @BETA_API + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(RadialGradient); +}; + + +/** + * @class Shape + * + * @brief A class representing two-dimensional figures and their properties. + * + * A shape has three major properties: shape outline, stroking, filling. The outline in the Shape is retained as the path. + * Path can be composed by accumulating primitive commands such as moveTo(), lineTo(), cubicTo(), or complete shape interfaces such as appendRect(), appendCircle(), etc. + * Path can consists of sub-paths. One sub-path is determined by a close command. + * + * The stroke of Shape is an optional property in case the Shape needs to be represented with/without the outline borders. + * It's efficient since the shape path and the stroking path can be shared with each other. It's also convenient when controlling both in one context. + */ +class TVG_EXPORT Shape final : public Paint +{ +public: + ~Shape(); + + /** + * @brief Resets the properties of the shape path. + * + * The color, the fill and the stroke properties are retained. + * + * @return Result::Success when succeed. + * + * @note The memory, where the path data is stored, is not deallocated at this stage for caching effect. + */ + Result reset() noexcept; + + /** + * @brief Sets the initial point of the sub-path. + * + * The value of the current point is set to the given point. + * + * @param[in] x The horizontal coordinate of the initial point of the sub-path. + * @param[in] y The vertical coordinate of the initial point of the sub-path. + * + * @return Result::Success when succeed. + */ + Result moveTo(float x, float y) noexcept; + + /** + * @brief Adds a new point to the sub-path, which results in drawing a line from the current point to the given end-point. + * + * The value of the current point is set to the given end-point. + * + * @param[in] x The horizontal coordinate of the end-point of the line. + * @param[in] y The vertical coordinate of the end-point of the line. + * + * @return Result::Success when succeed. + * + * @note In case this is the first command in the path, it corresponds to the moveTo() call. + */ + Result lineTo(float x, float y) noexcept; + + /** + * @brief Adds new points to the sub-path, which results in drawing a cubic Bezier curve starting + * at the current point and ending at the given end-point (@p x, @p y) using the control points (@p cx1, @p cy1) and (@p cx2, @p cy2). + * + * The value of the current point is set to the given end-point. + * + * @param[in] cx1 The horizontal coordinate of the 1st control point. + * @param[in] cy1 The vertical coordinate of the 1st control point. + * @param[in] cx2 The horizontal coordinate of the 2nd control point. + * @param[in] cy2 The vertical coordinate of the 2nd control point. + * @param[in] x The horizontal coordinate of the end-point of the curve. + * @param[in] y The vertical coordinate of the end-point of the curve. + * + * @return Result::Success when succeed. + * + * @note In case this is the first command in the path, no data from the path are rendered. + */ + Result cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept; + + /** + * @brief Closes the current sub-path by drawing a line from the current point to the initial point of the sub-path. + * + * The value of the current point is set to the initial point of the closed sub-path. + * + * @return Result::Success when succeed. + * + * @note In case the sub-path does not contain any points, this function has no effect. + */ + Result close() noexcept; + + /** + * @brief Appends a rectangle to the path. + * + * The rectangle with rounded corners can be achieved by setting non-zero values to @p rx and @p ry arguments. + * The @p rx and @p ry values specify the radii of the ellipse defining the rounding of the corners. + * + * The position of the rectangle is specified by the coordinates of its upper left corner - @p x and @p y arguments. + * + * The rectangle is treated as a new sub-path - it is not connected with the previous sub-path. + * + * The value of the current point is set to (@p x + @p rx, @p y) - in case @p rx is greater + * than @p w/2 the current point is set to (@p x + @p w/2, @p y) + * + * @param[in] x The horizontal coordinate of the upper left corner of the rectangle. + * @param[in] y The vertical coordinate of the upper left corner of the rectangle. + * @param[in] w The width of the rectangle. + * @param[in] h The height of the rectangle. + * @param[in] rx The x-axis radius of the ellipse defining the rounded corners of the rectangle. + * @param[in] ry The y-axis radius of the ellipse defining the rounded corners of the rectangle. + * + * @return Result::Success when succeed. + * + * @note For @p rx and @p ry greater than or equal to the half of @p w and the half of @p h, respectively, the shape become an ellipse. + */ + Result appendRect(float x, float y, float w, float h, float rx, float ry) noexcept; + + /** + * @brief Appends an ellipse to the path. + * + * The position of the ellipse is specified by the coordinates of its center - @p cx and @p cy arguments. + * + * The ellipse is treated as a new sub-path - it is not connected with the previous sub-path. + * + * The value of the current point is set to (@p cx, @p cy - @p ry). + * + * @param[in] cx The horizontal coordinate of the center of the ellipse. + * @param[in] cy The vertical coordinate of the center of the ellipse. + * @param[in] rx The x-axis radius of the ellipse. + * @param[in] ry The y-axis radius of the ellipse. + * + * @return Result::Success when succeed. + */ + Result appendCircle(float cx, float cy, float rx, float ry) noexcept; + + /** + * @brief Appends a circular arc to the path. + * + * The arc is treated as a new sub-path - it is not connected with the previous sub-path. + * The current point value is set to the end-point of the arc in case @p pie is @c false, and to the center of the arc otherwise. + * + * @param[in] cx The horizontal coordinate of the center of the arc. + * @param[in] cy The vertical coordinate of the center of the arc. + * @param[in] radius The radius of the arc. + * @param[in] startAngle The start angle of the arc given in degrees, measured counter-clockwise from the horizontal line. + * @param[in] sweep The central angle of the arc given in degrees, measured counter-clockwise from @p startAngle. + * @param[in] pie Specifies whether to draw radii from the arc's center to both of its end-point - drawn if @c true. + * + * @return Result::Success when succeed. + * + * @note Setting @p sweep value greater than 360 degrees, is equivalent to calling appendCircle(cx, cy, radius, radius). + */ + Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept; + + /** + * @brief Appends a given sub-path to the path. + * + * The current point value is set to the last point from the sub-path. + * For each command from the @p cmds array, an appropriate number of points in @p pts array should be specified. + * If the number of points in the @p pts array is different than the number required by the @p cmds array, the shape with this sub-path will not be displayed on the screen. + * + * @param[in] cmds The array of the commands in the sub-path. + * @param[in] cmdCnt The number of the sub-path's commands. + * @param[in] pts The array of the two-dimensional points. + * @param[in] ptsCnt The number of the points in the @p pts array. + * + * @return Result::Success when succeed, Result::InvalidArguments otherwise. + * + * @note The interface is designed for optimal path setting if the caller has a completed path commands already. + */ + Result appendPath(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept; + + /** + * @brief Sets the stroke width for all of the figures from the path. + * + * @param[in] width The width of the stroke. The default value is 0. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result stroke(float width) noexcept; + + /** + * @brief Sets the color of the stroke for all of the figures from the path. + * + * @param[in] r The red color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + + /** + * @brief Sets the gradient fill of the stroke for all of the figures from the path. + * + * @param[in] f The gradient fill. + * + * @retval Result::Success When succeed. + * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be filled. + * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. + */ + Result stroke(std::unique_ptr f) noexcept; + + /** + * @brief Sets the dash pattern of the stroke. + * + * @param[in] dashPattern The array of consecutive pair values of the dash length and the gap length. + * @param[in] cnt The length of the @p dashPattern array. + * + * @retval Result::Success When succeed. + * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be dashed. + * @retval Result::InvalidArguments In case @p dashPattern is @c nullptr and @p cnt > 0, @p cnt is zero, any of the dash pattern values is zero or less. + * + * @note To reset the stroke dash pattern, pass @c nullptr to @p dashPattern and zero to @p cnt. + * @warning @p cnt must be greater than 1 if the dash pattern is valid. + */ + Result stroke(const float* dashPattern, uint32_t cnt) noexcept; + + /** + * @brief Sets the cap style of the stroke in the open sub-paths. + * + * @param[in] cap The cap style value. The default value is @c StrokeCap::Square. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result stroke(StrokeCap cap) noexcept; + + /** + * @brief Sets the join style for stroked path segments. + * + * The join style is used for joining the two line segment while stroking the path. + * + * @param[in] join The join style value. The default value is @c StrokeJoin::Bevel. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result stroke(StrokeJoin join) noexcept; + + /** + * @brief Sets the solid color for all of the figures from the path. + * + * The parts of the shape defined as inner are colored. + * + * @param[in] r The red color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. + * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. + * + * @return Result::Success when succeed. + * + * @note Either a solid color or a gradient fill is applied, depending on what was set as last. + */ + Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + + /** + * @brief Sets the gradient fill for all of the figures from the path. + * + * The parts of the shape defined as inner are filled. + * + * @param[in] f The unique pointer to the gradient fill. + * + * @return Result::Success when succeed, Result::MemoryCorruption otherwise. + * + * @note Either a solid color or a gradient fill is applied, depending on what was set as last. + */ + Result fill(std::unique_ptr f) noexcept; + + /** + * @brief Sets the fill rule for the Shape object. + * + * @param[in] r The fill rule value. The default value is @c FillRule::Winding. + * + * @return Result::Success when succeed. + */ + Result fill(FillRule r) noexcept; + + /** + * @brief Gets the commands data of the path. + * + * @param[out] cmds The pointer to the array of the commands from the path. + * + * @return The length of the @p cmds array when succeed, zero otherwise. + */ + uint32_t pathCommands(const PathCommand** cmds) const noexcept; + + /** + * @brief Gets the points values of the path. + * + * @param[out] pts The pointer to the array of the two-dimensional points from the path. + * + * @return The length of the @p pts array when succeed, zero otherwise. + */ + uint32_t pathCoords(const Point** pts) const noexcept; + + /** + * @brief Gets the pointer to the gradient fill of the shape. + * + * @return The pointer to the gradient fill of the stroke when succeed, @c nullptr in case no fill was set. + */ + const Fill* fill() const noexcept; + + /** + * @brief Gets the solid color of the shape. + * + * @param[out] r The red color channel value in the range [0 ~ 255]. + * @param[out] g The green color channel value in the range [0 ~ 255]. + * @param[out] b The blue color channel value in the range [0 ~ 255]. + * @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + * + * @return Result::Success when succeed. + */ + Result fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; + + /** + * @brief Gets the fill rule value. + * + * @return The fill rule value of the shape. + */ + FillRule fillRule() const noexcept; + + /** + * @brief Gets the stroke width. + * + * @return The stroke width value when succeed, zero if no stroke was set. + */ + float strokeWidth() const noexcept; + + /** + * @brief Gets the color of the shape's stroke. + * + * @param[out] r The red color channel value in the range [0 ~ 255]. + * @param[out] g The green color channel value in the range [0 ~ 255]. + * @param[out] b The blue color channel value in the range [0 ~ 255]. + * @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + */ + Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; + + /** + * @brief Gets the pointer to the gradient fill of the stroke. + * + * @return The pointer to the gradient fill of the stroke when succeed, @c nullptr otherwise. + */ + const Fill* strokeFill() const noexcept; + + /** + * @brief Gets the dash pattern of the stroke. + * + * @param[out] dashPattern The pointer to the memory, where the dash pattern array is stored. + * + * @return The length of the @p dashPattern array. + */ + uint32_t strokeDash(const float** dashPattern) const noexcept; + + /** + * @brief Gets the cap style used for stroking the path. + * + * @return The cap style value of the stroke. + */ + StrokeCap strokeCap() const noexcept; + + /** + * @brief Gets the join style value used for stroking the path. + * + * @return The join style value of the stroke. + */ + StrokeJoin strokeJoin() const noexcept; + + /** + * @brief Creates a new Shape object. + * + * @return A new Shape object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Shape class type. + * + * @return The type id of the Shape class. + * + * @BETA_API + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(Shape); +}; + + +/** + * @class Picture + * + * @brief A class representing an image read in one of the supported formats: raw, svg, png, jpg and etc. + * Besides the methods inherited from the Paint, it provides methods to load & draw images on the canvas. + * + * @note Supported formats are depended on the available TVG loaders. + */ +class TVG_EXPORT Picture final : public Paint +{ +public: + ~Picture(); + + /** + * @brief Loads a picture data directly from a file. + * + * @param[in] path A path to the picture file. + * + * @retval Result::Success When succeed. + * @retval Result::InvalidArguments In case the @p path is invalid. + * @retval Result::NonSupport When trying to load a file with an unknown extension. + * @retval Result::Unknown If an error occurs at a later stage. + * + * @note The Load behavior can be asynchronous if the assigned thread number is greater than zero. + * @see Initializer::init() + */ + Result load(const std::string& path) noexcept; + + /** + * @brief Loads a picture data from a memory block of a given size. + * + * @param[in] data A pointer to a memory location where the content of the picture file is stored. + * @param[in] size The size in bytes of the memory occupied by the @p data. + * @param[in] copy Decides whether the data should be copied into the engine local buffer. + * + * @retval Result::Success When succeed. + * @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less. + * @retval Result::NonSupport When trying to load a file with an unknown extension. + * @retval Result::Unknown If an error occurs at a later stage. + * + * @warning: you have responsibility to release the @p data memory if the @p copy is true + * @deprecated Use load(const char* data, uint32_t size, const std::string& mimeType, bool copy) instead. + * @see Result load(const char* data, uint32_t size, const std::string& mimeType, bool copy = false) noexcept + */ + TVG_DEPRECATED Result load(const char* data, uint32_t size, bool copy = false) noexcept; + + /** + * @brief Loads a picture data from a memory block of a given size. + * + * @param[in] data A pointer to a memory location where the content of the picture file is stored. + * @param[in] size The size in bytes of the memory occupied by the @p data. + * @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one. + * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. + * + * @retval Result::Success When succeed. + * @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less. + * @retval Result::NonSupport When trying to load a file with an unknown extension. + * @retval Result::Unknown If an error occurs at a later stage. + * + * @warning: It's the user responsibility to release the @p data memory if the @p copy is @c true. + * + * @since 0.5 + */ + Result load(const char* data, uint32_t size, const std::string& mimeType, bool copy = false) noexcept; + + /** + * @brief Resizes the picture content to the given width and height. + * + * The picture content is resized while keeping the default size aspect ratio. + * The scaling factor is established for each of dimensions and the smaller value is applied to both of them. + * + * @param[in] w A new width of the image in pixels. + * @param[in] h A new height of the image in pixels. + * + * @return Result::Success when succeed, Result::InsufficientCondition otherwise. + */ + Result size(float w, float h) noexcept; + + /** + * @brief Gets the size of the image. + * + * @param[out] w The width of the image in pixels. + * @param[out] h The height of the image in pixels. + * + * @return Result::Success when succeed. + */ + Result size(float* w, float* h) const noexcept; + + /** + * @brief Gets the pixels information of the picture. + * + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @BETA_API + */ + const uint32_t* data(uint32_t* w, uint32_t* h) const noexcept; + + /** + * @brief Loads a raw data from a memory block with a given size. + * + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @BETA_API + */ + Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept; + + /** + * @brief Gets the position and the size of the loaded SVG picture. + * + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @BETA_API + */ + Result viewbox(float* x, float* y, float* w, float* h) const noexcept; + + /** + * @brief Creates a new Picture object. + * + * @return A new Picture object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Picture class type. + * + * @return The type id of the Picture class. + * + * @BETA_API + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(Picture); +}; + + +/** + * @class Scene + * + * @brief A class to composite children paints. + * + * As the traditional graphics rendering method, TVG also enables scene-graph mechanism. + * This feature supports an array function for managing the multiple paints as one group paint. + * + * As a group, the scene can be transformed, made translucent and composited with other target paints, + * its children will be affected by the scene world. + */ +class TVG_EXPORT Scene final : public Paint +{ +public: + ~Scene(); + + /** + * @brief Passes drawing elements to the Scene using Paint objects. + * + * Only the paints pushed into the scene will be the drawn targets. + * The paints are retained by the scene until Scene::clear() is called. + * If you know the number of the pushed objects in advance, please call Scene::reserve(). + * + * @param[in] paint A Paint object to be drawn. + * + * @return Result::Success when succeed, Result::MemoryCorruption otherwise. + * + * @note The rendering order of the paints is the same as the order as they were pushed. Consider sorting the paints before pushing them if you intend to use layering. + * @see Scene::reserve() + */ + Result push(std::unique_ptr paint) noexcept; + + /** + * @brief Sets the size of the container, where all the paints pushed into the Scene are stored. + * + * If the number of objects pushed into the scene is known in advance, calling the function + * prevents multiple memory reallocation, thus improving the performance. + * + * @param[in] size The number of objects for which the memory is to be reserved. + * + * @return Result::Success when succeed, Result::FailedAllocation otherwise. + */ + Result reserve(uint32_t size) noexcept; + + /** + * @brief Sets the total number of the paints pushed into the scene to be zero. + * Depending on the value of the @p free argument, the paints are freed or not. + * + * @param[in] free If @c true, the memory occupied by paints is deallocated, otherwise it is not. + * + * @return Result::Success when succeed + * + * @warning If you don't free the paints they become dangled. They are supposed to be reused, otherwise you are responsible for their lives. Thus please use the @p free argument only when you know how it works, otherwise it's not recommended. + * + * @since 0.2 + */ + Result clear(bool free = true) noexcept; + + /** + * @brief Creates a new Scene object. + * + * @return A new Scene object. + */ + static std::unique_ptr gen() noexcept; + + /** + * @brief Return the unique id value of this class. + * + * This method can be referred for identifying the Scene class type. + * + * @return The type id of the Scene class. + * + * @BETA_API + */ + static uint32_t identifier() noexcept; + + _TVG_DECLARE_PRIVATE(Scene); +}; + + +/** + * @class SwCanvas + * + * @brief A class for the rendering graphical elements with a software raster engine. + */ +class TVG_EXPORT SwCanvas final : public Canvas +{ +public: + ~SwCanvas(); + + /** + * @brief Enumeration specifying the methods of combining the 8-bit color channels into 32-bit color. + */ + enum Colorspace + { + ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. + ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. + ABGR8888_STRAIGHT, ///< @BETA_API The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. + ARGB8888_STRAIGHT, ///< @BETA_API The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. + }; + + /** + * @brief Enumeration specifying the methods of Memory Pool behavior policy. + * @since 0.4 + */ + enum MempoolPolicy + { + Default = 0, ///< Default behavior that ThorVG is designed to. + Shareable, ///< Memory Pool is shared among the SwCanvases. + Individual ///< Allocate designated memory pool that is only used by current instance. + }; + + /** + * @brief Sets the target buffer for the rasterization. + * + * The buffer of a desirable size should be allocated and owned by the caller. + * + * @param[in] buffer A pointer to a memory block of the size @p stride x @p h, where the raster data are stored. + * @param[in] stride The stride of the raster image - greater than or equal to @p w. + * @param[in] w The width of the raster image. + * @param[in] h The height of the raster image. + * @param[in] cs The value specifying the way the 32-bits colors should be read/written. + * + * @retval Result::Success When succeed. + * @retval Result::MemoryCorruption When casting in the internal function implementation failed. + * @retval Result::InvalidArguments In case no valid pointer is provided or the width, or the height or the stride is zero. + * @retval Result::NonSupport In case the software engine is not supported. + * + * @warning Do not access @p buffer during Canvas::draw() - Canvas::sync(). It should not be accessed while TVG is writing on it. + */ + Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept; + + /** + * @brief Set sw engine memory pool behavior policy. + * + * Basically ThorVG draws a lot of shapes, it allocates/deallocates a few chunk of memory + * while processing rendering. It internally uses one shared memory pool + * which can be reused among the canvases in order to avoid memory overhead. + * + * Thus ThorVG suggests using a memory pool policy to satisfy user demands, + * if it needs to guarantee the thread-safety of the internal data access. + * + * @param[in] policy The method specifying the Memory Pool behavior. The default value is @c MempoolPolicy::Default. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition If the canvas contains some paints already. + * @retval Result::NonSupport In case the software engine is not supported. + * + * @note When @c policy is set as @c MempoolPolicy::Individual, the current instance of canvas uses its own individual + * memory data, which is not shared with others. This is necessary when the canvas is accessed on a worker-thread. + * + * @warning It's not allowed after pushing any paints. + * + * @since 0.4 + */ + Result mempool(MempoolPolicy policy) noexcept; + + /** + * @brief Creates a new SwCanvas object. + * @return A new SwCanvas object. + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(SwCanvas); +}; + + +/** + * @class GlCanvas + * + * @brief A class for the rendering graphic elements with a GL raster engine. + * + * @warning Please do not use it. This class is not fully supported yet. + * + * @BETA_API + */ +class TVG_EXPORT GlCanvas final : public Canvas +{ +public: + ~GlCanvas(); + + /** + * @brief Sets the target buffer for the rasterization. + * + * @warning Please do not use it, this API is not official one. It could be modified in the next version. + * + * @BETA_API + */ + Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept; + + /** + * @brief Creates a new GlCanvas object. + * + * @return A new GlCanvas object. + * + * @BETA_API + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(GlCanvas); +}; + + +/** + * @class Initializer + * + * @brief A class that enables initialization and termination of the TVG engines. + */ +class TVG_EXPORT Initializer final +{ +public: + /** + * @brief Initializes TVG engines. + * + * TVG requires the running-engine environment. + * TVG runs its own task-scheduler for parallelizing rendering tasks efficiently. + * You can indicate the number of threads, the count of which is designated @p threads. + * In the initialization step, TVG will generate/spawn the threads as set by @p threads count. + * + * @param[in] engine The engine types to initialize. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed. + * @param[in] threads The number of additional threads. Zero indicates only the main thread is to be used. + * + * @retval Result::Success When succeed. + * @retval Result::FailedAllocation An internal error possibly with memory allocation. + * @retval Result::InvalidArguments If unknown engine type chosen. + * @retval Result::NonSupport In case the engine type is not supported on the system. + * @retval Result::Unknown Others. + * + * @note The Initializer keeps track of the number of times it was called. Threads count is fixed at the first init() call. + * @see Initializer::term() + */ + static Result init(CanvasEngine engine, uint32_t threads) noexcept; + + /** + * @brief Terminates TVG engines. + * + * @param[in] engine The engine types to terminate. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case there is nothing to be terminated. + * @retval Result::InvalidArguments If unknown engine type chosen. + * @retval Result::NonSupport In case the engine type is not supported on the system. + * @retval Result::Unknown Others. + * + * @note Initializer does own reference counting for multiple calls. + * @see Initializer::init() + */ + static Result term(CanvasEngine engine) noexcept; + + _TVG_DISABLE_CTOR(Initializer); +}; + + +/** + * @class Saver + * + * @brief A class for exporting a paint object into a specified file, from which to recover the paint data later. + * + * ThorVG provides a feature for exporting & importing paint data. The Saver role is to export the paint data to a file. + * It's useful when you need to save the composed scene or image from a paint object and recreate it later. + * + * The file format is decided by the extension name(i.e. "*.tvg") while the supported formats depend on the TVG packaging environment. + * If it doesn't support the file format, the save() method returns the @c Result::NonSuppport result. + * + * Once you export a paint to the file successfully, you can recreate it using the Picture class. + * + * @see Picture::load() + * + * @since 0.5 + */ +class TVG_EXPORT Saver final +{ +public: + ~Saver(); + + /** + * @brief Exports the given @p paint data to the given @p path + * + * If the saver module supports any compression mechanism, it will optimize the data size. + * This might affect the encoding/decoding time in some cases. You can turn off the compression + * if you wish to optimize for speed. + * + * @param[in] paint The paint to be saved with all its associated properties. + * @param[in] path A path to the file, in which the paint data is to be saved. + * @param[in] compress If @c true then compress data if possible. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition If currently saving other resources. + * @retval Result::NonSupport When trying to save a file with an unknown extension or in an unsupported format. + * @retval Result::MemoryCorruption An internal error. + * @retval Result::Unknown In case an empty paint is to be saved. + * + * @note Saving can be asynchronous if the assigned thread number is greater than zero. To guarantee the saving is done, call sync() afterwards. + * @see Saver::sync() + * + * @since 0.5 + */ + Result save(std::unique_ptr paint, const std::string& path, bool compress = true) noexcept; + + /** + * @brief Guarantees that the saving task is finished. + * + * The behavior of the Saver works on a sync/async basis, depending on the threading setting of the Initializer. + * Thus, if you wish to have a benefit of it, you must call sync() after the save() in the proper delayed time. + * Otherwise, you can call sync() immediately. + * + * @retval Result::Success when succeed. + * @retval Result::InsufficientCondition otherwise. + * + * @note The asynchronous tasking is dependent on the Saver module implementation. + * @see Saver::save() + * + * @since 0.5 + */ + Result sync() noexcept; + + /** + * @brief Creates a new Saver object. + * + * @return A new Saver object. + * + * @since 0.5 + */ + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Saver); +}; + +/** @}*/ + +} //namespace + +#ifdef __cplusplus +} +#endif + +#endif //_THORVG_H_ diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h new file mode 100644 index 0000000000..8a9dd5c830 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwCommon.h @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SW_COMMON_H_ +#define _TVG_SW_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgRender.h" + +#if 0 +#include +static double timeStamp() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec + tv.tv_usec / 1000000.0); +} +#endif + +#define SW_CURVE_TYPE_POINT 0 +#define SW_CURVE_TYPE_CUBIC 1 +#define SW_ANGLE_PI (180L << 16) +#define SW_ANGLE_2PI (SW_ANGLE_PI << 1) +#define SW_ANGLE_PI2 (SW_ANGLE_PI >> 1) +#define SW_ANGLE_PI4 (SW_ANGLE_PI >> 2) + +using SwCoord = signed long; +using SwFixed = signed long long; + +struct SwPoint +{ + SwCoord x, y; + + SwPoint& operator+=(const SwPoint& rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + + SwPoint operator+(const SwPoint& rhs) const + { + return {x + rhs.x, y + rhs.y}; + } + + SwPoint operator-(const SwPoint& rhs) const + { + return {x - rhs.x, y - rhs.y}; + } + + bool operator==(const SwPoint& rhs) const + { + return (x == rhs.x && y == rhs.y); + } + + bool operator!=(const SwPoint& rhs) const + { + return (x != rhs.x || y != rhs.y); + } + + bool zero() const + { + if (x == 0 && y == 0) return true; + else return false; + } + + bool small() const + { + //2 is epsilon... + if (abs(x) < 2 && abs(y) < 2) return true; + else return false; + } + +}; + +struct SwSize +{ + SwCoord w, h; +}; + +struct SwOutline +{ + SwPoint* pts; //the outline's points + uint16_t ptsCnt; //number of points in the glyph + uint16_t reservedPtsCnt; + uint16_t* cntrs; //the contour end points + uint16_t cntrsCnt; //number of contours in glyph + uint16_t reservedCntrsCnt; + uint8_t* types; //curve type + bool* closed; //opened or closed path? + FillRule fillRule; +}; + +struct SwSpan +{ + int16_t x, y; + uint16_t len; + uint8_t coverage; +}; + +struct SwRleData +{ + SwSpan *spans; + uint32_t alloc; + uint32_t size; +}; + +struct SwBBox +{ + SwPoint min, max; + + void reset() + { + min.x = min.y = max.x = max.y = 0; + } +}; + +struct SwFill +{ + struct SwLinear { + float dx, dy; + float len; + float offset; + }; + + struct SwRadial { + float a11, a12, shiftX; + float a21, a22, shiftY; + float detSecDeriv; + float a; + }; + + union { + SwLinear linear; + SwRadial radial; + }; + + uint32_t* ctable; + FillSpread spread; + + bool translucent; +}; + +struct SwStrokeBorder +{ + uint32_t ptsCnt; + uint32_t maxPts; + SwPoint* pts; + uint8_t* tags; + int32_t start; //index of current sub-path start point + bool movable; //true: for ends of lineto borders +}; + +struct SwStroke +{ + SwFixed angleIn; + SwFixed angleOut; + SwPoint center; + SwFixed lineLength; + SwFixed subPathAngle; + SwPoint ptStartSubPath; + SwFixed subPathLineLength; + SwFixed width; + + StrokeCap cap; + StrokeJoin join; + StrokeJoin joinSaved; + SwFill* fill = nullptr; + + SwStrokeBorder borders[2]; + + float sx, sy; + + bool firstPt; + bool closedSubPath; + bool handleWideStrokes; +}; + +struct SwDashStroke +{ + SwOutline* outline; + float curLen; + int32_t curIdx; + Point ptStart; + Point ptCur; + float* pattern; + uint32_t cnt; + bool curOpGap; +}; + +struct SwShape +{ + SwOutline* outline = nullptr; + SwStroke* stroke = nullptr; + SwFill* fill = nullptr; + SwRleData* rle = nullptr; + SwRleData* strokeRle = nullptr; + SwBBox bbox; //Keep it boundary without stroke region. Using for optimal filling. + + bool fastTrack = false; //Fast Track: axis-aligned rectangle without any clips? +}; + +struct SwImage +{ + SwOutline* outline = nullptr; + SwRleData* rle = nullptr; + uint32_t* data = nullptr; + uint32_t w, h; +}; + +struct SwBlender +{ + uint32_t (*join)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + uint32_t (*alpha)(uint32_t rgba); +}; + +struct SwCompositor; + +struct SwSurface : Surface +{ + SwBlender blender; //mandatory + SwCompositor* compositor = nullptr; //compositor (optional) +}; + +struct SwCompositor : Compositor +{ + SwSurface* recoverSfc; //Recover surface when composition is started + SwCompositor* recoverCmp; //Recover compositor when composition is done + SwImage image; + SwBBox bbox; + bool valid; +}; + +struct SwMpool +{ + SwOutline* outline = nullptr; + SwOutline* strokeOutline = nullptr; + unsigned allocSize = 0; +}; + +static inline SwCoord TO_SWCOORD(float val) +{ + return SwCoord(val * 64.0f); +} + +static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a) +{ + return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) + + ((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff)); +} + +static inline uint32_t COLOR_INTERPOLATE(uint32_t c1, uint32_t a1, uint32_t c2, uint32_t a2) +{ + auto t = (((c1 & 0xff00ff) * a1 + (c2 & 0xff00ff) * a2) >> 8) & 0xff00ff; + c1 = (((c1 >> 8) & 0xff00ff) * a1 + ((c2 >> 8) & 0xff00ff) * a2) & 0xff00ff00; + return (c1 |= t); +} + +static inline uint32_t ALPHA_MULTIPLY(uint32_t c, uint32_t a) +{ + return ((c * a + 0xff) >> 8); +} + +static inline SwCoord HALF_STROKE(float width) +{ + return TO_SWCOORD(width * 0.5f); +} + +int64_t mathMultiply(int64_t a, int64_t b); +int64_t mathDivide(int64_t a, int64_t b); +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c); +void mathRotate(SwPoint& pt, SwFixed angle); +SwFixed mathTan(SwFixed angle); +SwFixed mathAtan(const SwPoint& pt); +SwFixed mathCos(SwFixed angle); +SwFixed mathSin(SwFixed angle); +void mathSplitCubic(SwPoint* base); +SwFixed mathDiff(SwFixed angle1, SwFixed angle2); +SwFixed mathLength(const SwPoint& pt); +bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); +SwFixed mathMean(SwFixed angle1, SwFixed angle2); +SwPoint mathTransform(const Point* to, const Matrix* transform); +bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack); +bool mathInverse(const Matrix* m, Matrix* invM); +bool mathMultiply(const Matrix* lhs, Matrix* rhs); +bool mathIdentity(const Matrix* m); + +void shapeReset(SwShape* shape); +bool shapePrepare(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite); +bool shapePrepared(const SwShape* shape); +bool shapeGenRle(SwShape* shape, const Shape* sdata, bool antiAlias); +void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid); +void shapeResetStroke(SwShape* shape, const Shape* sdata, const Matrix* transform); +bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid); +void shapeFree(SwShape* shape); +void shapeDelStroke(SwShape* shape); +bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable); +bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable); +void shapeResetFill(SwShape* shape); +void shapeResetStrokeFill(SwShape* shape); +void shapeDelFill(SwShape* shape); +void shapeDelStrokeFill(SwShape* shape); + +void strokeReset(SwStroke* stroke, const Shape* shape, const Matrix* transform); +bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline); +SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid); +void strokeFree(SwStroke* stroke); + +bool imagePrepare(SwImage* image, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid); +bool imageGenRle(SwImage* image, TVG_UNUSED const Picture* pdata, const SwBBox& renderRegion, bool antiAlias); +void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid); +void imageReset(SwImage* image); +void imageFree(SwImage* image); + +bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable); +void fillReset(SwFill* fill); +void fillFree(SwFill* fill); +void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len); +void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len); + +SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias); +void rleFree(SwRleData* rle); +void rleReset(SwRleData* rle); +void rleClipPath(SwRleData *rle, const SwRleData *clip); +void rleClipRect(SwRleData *rle, const SwBBox* clip); + +SwMpool* mpoolInit(uint32_t threads); +bool mpoolTerm(SwMpool* mpool); +bool mpoolClear(SwMpool* mpool); +SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx); +void mpoolRetOutline(SwMpool* mpool, unsigned idx); +SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx); +void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx); + +bool rasterCompositor(SwSurface* surface); +bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterSolidShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity); +bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterClear(SwSurface* surface); +void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len); +void rasterUnpremultiply(SwSurface* surface); + +#endif /* _TVG_SW_COMMON_H_ */ diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp new file mode 100644 index 0000000000..20eeb3b84f --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwFill.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#define GRADIENT_STOP_SIZE 1024 +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1<ctable) { + fill->ctable = static_cast(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t))); + if (!fill->ctable) return false; + } + + const Fill::ColorStop* colors; + auto cnt = fdata->colorStops(&colors); + if (cnt == 0 || !colors) return false; + + auto pColors = colors; + + auto a = (pColors->a * opacity) / 255; + if (a < 255) fill->translucent = true; + + auto r = pColors->r; + auto g = pColors->g; + auto b = pColors->b; + auto rgba = surface->blender.join(r, g, b, a); + + auto inc = 1.0f / static_cast(GRADIENT_STOP_SIZE); + auto pos = 1.5f * inc; + uint32_t i = 0; + + fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a); + + while (pos <= pColors->offset) { + fill->ctable[i] = fill->ctable[i - 1]; + ++i; + pos += inc; + } + + for (uint32_t j = 0; j < cnt - 1; ++j) { + auto curr = colors + j; + auto next = curr + 1; + auto delta = 1.0f / (next->offset - curr->offset); + auto a2 = (next->a * opacity) / 255; + if (!fill->translucent && a2 < 255) fill->translucent = true; + + auto rgba2 = surface->blender.join(next->r, next->g, next->b, a2); + + while (pos < next->offset && i < GRADIENT_STOP_SIZE) { + auto t = (pos - curr->offset) * delta; + auto dist = static_cast(255 * t); + auto dist2 = 255 - dist; + + auto color = COLOR_INTERPOLATE(rgba, dist2, rgba2, dist); + fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24)); + + ++i; + pos += inc; + } + rgba = rgba2; + a = a2; + } + rgba = ALPHA_BLEND((rgba | 0xff000000), a); + + for (; i < GRADIENT_STOP_SIZE; ++i) + fill->ctable[i] = rgba; + + //Make sure the last color stop is represented at the end of the table + fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; + + return true; +} + + +bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform) +{ + float x1, x2, y1, y2; + if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false; + + fill->linear.dx = x2 - x1; + fill->linear.dy = y2 - y1; + fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; + + if (fill->linear.len < FLT_EPSILON) return true; + + fill->linear.dx /= fill->linear.len; + fill->linear.dy /= fill->linear.len; + fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1; + + auto gradTransform = linear->transform(); + bool isTransformation = !mathIdentity(&gradTransform); + + if (isTransformation) { + if (transform) mathMultiply(transform, &gradTransform); + } else if (transform) { + gradTransform = *transform; + isTransformation = true; + } + + if (isTransformation) { + Matrix invTransform; + if (!mathInverse(&gradTransform, &invTransform)) return false; + + fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23; + + auto dx = fill->linear.dx; + fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21; + fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22; + + fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; + if (fill->linear.len < FLT_EPSILON) return true; + } + + return true; +} + + +bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform) +{ + float radius, cx, cy; + if (radial->radial(&cx, &cy, &radius) != Result::Success) return false; + if (radius < FLT_EPSILON) return true; + + float invR = 1.0f / radius; + fill->radial.shiftX = -cx; + fill->radial.shiftY = -cy; + fill->radial.a = radius; + + auto gradTransform = radial->transform(); + bool isTransformation = !mathIdentity(&gradTransform); + + if (isTransformation) { + if (transform) mathMultiply(transform, &gradTransform); + } else if (transform) { + gradTransform = *transform; + isTransformation = true; + } + + if (isTransformation) { + Matrix invTransform; + if (!mathInverse(&gradTransform, &invTransform)) return false; + + fill->radial.a11 = invTransform.e11 * invR; + fill->radial.a12 = invTransform.e12 * invR; + fill->radial.shiftX += invTransform.e13; + fill->radial.a21 = invTransform.e21 * invR; + fill->radial.a22 = invTransform.e22 * invR; + fill->radial.shiftY += invTransform.e23; + fill->radial.detSecDeriv = 2.0f * fill->radial.a11 * fill->radial.a11 + 2 * fill->radial.a21 * fill->radial.a21; + + fill->radial.a *= sqrt(pow(invTransform.e11, 2) + pow(invTransform.e21, 2)); + } else { + fill->radial.a11 = fill->radial.a22 = invR; + fill->radial.a12 = fill->radial.a21 = 0.0f; + fill->radial.detSecDeriv = 2.0f * invR * invR; + } + fill->radial.shiftX *= invR; + fill->radial.shiftY *= invR; + + return true; +} + + +static inline uint32_t _clamp(const SwFill* fill, int32_t pos) +{ + switch (fill->spread) { + case FillSpread::Pad: { + if (pos >= GRADIENT_STOP_SIZE) pos = GRADIENT_STOP_SIZE - 1; + else if (pos < 0) pos = 0; + break; + } + case FillSpread::Repeat: { + pos = pos % GRADIENT_STOP_SIZE; + if (pos < 0) pos = GRADIENT_STOP_SIZE + pos; + break; + } + case FillSpread::Reflect: { + auto limit = GRADIENT_STOP_SIZE * 2; + pos = pos % limit; + if (pos < 0) pos = limit + pos; + if (pos >= GRADIENT_STOP_SIZE) pos = (limit - pos - 1); + break; + } + } + return pos; +} + + +static inline uint32_t _fixedPixel(const SwFill* fill, int32_t pos) +{ + int32_t i = (pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + return fill->ctable[_clamp(fill, i)]; +} + + +static inline uint32_t _pixel(const SwFill* fill, float pos) +{ + auto i = static_cast(pos * (GRADIENT_STOP_SIZE - 1) + 0.5f); + return fill->ctable[_clamp(fill, i)]; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len) +{ + auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX; + auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY; + + // detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx + auto detSecondDerivative = fill->radial.detSecDeriv; + // detFirstDerivative = d(det)/dx + auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative; + auto det = rx * rx + ry * ry; + + for (uint32_t i = 0 ; i < len ; ++i) { + *dst = _pixel(fill, sqrtf(det)); + ++dst; + det += detFirstDerivative; + detFirstDerivative += detSecondDerivative; + } +} + + +void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len) +{ + //Rotation + float rx = x + 0.5f; + float ry = y + 0.5f; + float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1); + float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1); + + if (fabsf(inc) < FLT_EPSILON) { + auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); + rasterRGBA32(dst, color, 0, len); + return; + } + + auto vMax = static_cast(INT32_MAX >> (FIXPT_BITS + 1)); + auto vMin = -vMax; + auto v = t + (inc * len); + + //we can use fixed point math + if (v < vMax && v > vMin) { + auto t2 = static_cast(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j) { + *dst = _fixedPixel(fill, t2); + ++dst; + t2 += inc2; + } + //we have to fallback to float math + } else { + uint32_t counter = 0; + while (counter++ < len) { + *dst = _pixel(fill, t / GRADIENT_STOP_SIZE); + ++dst; + t += inc; + } + } +} + + +bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable) +{ + if (!fill) return false; + + fill->spread = fdata->spread(); + + if (ctable) { + if (!_updateColorTable(fill, fdata, surface, opacity)) return false; + } + + if (fdata->identifier() == TVG_CLASS_ID_LINEAR) { + return _prepareLinear(fill, static_cast(fdata), transform); + } else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { + return _prepareRadial(fill, static_cast(fdata), transform); + } + + //LOG: What type of gradient?! + + return false; +} + + +void fillReset(SwFill* fill) +{ + if (fill->ctable) { + free(fill->ctable); + fill->ctable = nullptr; + } + fill->translucent = false; +} + + +void fillFree(SwFill* fill) +{ + if (!fill) return; + + if (fill->ctable) free(fill->ctable); + + free(fill); +} diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp new file mode 100644 index 0000000000..2ef024d15c --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static bool _genOutline(SwImage* image, const Matrix* transform, SwMpool* mpool, unsigned tid) +{ + image->outline = mpoolReqOutline(mpool, tid); + auto outline = image->outline; + + if (outline->reservedPtsCnt < 5) { + outline->reservedPtsCnt = 5; + outline->pts = static_cast(realloc(outline->pts, outline->reservedPtsCnt * sizeof(SwPoint))); + outline->types = static_cast(realloc(outline->types, outline->reservedPtsCnt * sizeof(uint8_t))); + } + + if (outline->reservedCntrsCnt < 1) { + outline->reservedCntrsCnt = 1; + outline->cntrs = static_cast(realloc(outline->cntrs, outline->reservedCntrsCnt * sizeof(uint16_t))); + outline->closed = static_cast(realloc(outline->closed, outline->reservedCntrsCnt * sizeof(bool))); + outline->closed[0] = true; + } + + auto w = static_cast(image->w); + auto h = static_cast(image->h); + + Point to[4] = {{0 ,0}, {w, 0}, {w, h}, {0, h}}; + for (int i = 0; i < 4; i++) { + outline->pts[outline->ptsCnt] = mathTransform(&to[i], transform); + outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline->ptsCnt; + } + + outline->pts[outline->ptsCnt] = outline->pts[0]; + outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline->ptsCnt; + + outline->cntrs[outline->cntrsCnt] = outline->ptsCnt - 1; + ++outline->cntrsCnt; + + image->outline = outline; + + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool imagePrepare(SwImage* image, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid) +{ + if (!_genOutline(image, transform, mpool, tid)) return false; + return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, false); +} + + +bool imageGenRle(SwImage* image, TVG_UNUSED const Picture* pdata, const SwBBox& renderRegion, bool antiAlias) +{ + if ((image->rle = rleRender(image->rle, image->outline, renderRegion, antiAlias))) return true; + + return false; +} + + +void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid) +{ + mpoolRetOutline(mpool, tid); + image->outline = nullptr; +} + + +void imageReset(SwImage* image) +{ + rleReset(image->rle); +} + + +void imageFree(SwImage* image) +{ + rleFree(image->rle); +} diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp new file mode 100644 index 0000000000..464ac381a0 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMath.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +//clz: count leading zero’s +#if defined(_MSC_VER) && !defined(__clang__) + #include + static uint32_t __inline _clz(uint32_t value) + { + unsigned long leadingZero = 0; + if (_BitScanReverse(&leadingZero, value)) return 31 - leadingZero; + else return 32; + } +#else + #define _clz(x) __builtin_clz((x)) +#endif + + +constexpr SwFixed CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 + +//this table was generated for SW_FT_PI = 180L << 16, i.e. degrees +constexpr static auto ATAN_MAX = 23; +constexpr static SwFixed ATAN_TBL[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, + 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, + 57L, 29L, 14L, 7L, 4L, 2L, 1L}; + +static inline SwCoord SATURATE(const SwCoord x) +{ + return (x >> (sizeof(SwCoord) * 8 - 1)); +} + + +static inline SwFixed PAD_ROUND(const SwFixed x, int32_t n) +{ + return (((x) + ((n)/2)) & ~((n)-1)); +} + + +static SwCoord _downscale(SwFixed x) +{ + //multiply a give value by the CORDIC shrink factor + auto s = abs(x); + int64_t t = (s * static_cast(CORDIC_FACTOR)) + 0x100000000UL; + s = static_cast(t >> 32); + if (x < 0) s = -s; + return s; +} + + +static int32_t _normalize(SwPoint& pt) +{ + /* the highest bit in overflow-safe vector components + MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ + constexpr auto SAFE_MSB = 29; + + auto v = pt; + + //High order bit(MSB) + int32_t shift = 31 - _clz(abs(v.x) | abs(v.y)); + + if (shift <= SAFE_MSB) { + shift = SAFE_MSB - shift; + pt.x = static_cast((unsigned long)v.x << shift); + pt.y = static_cast((unsigned long)v.y << shift); + } else { + shift -= SAFE_MSB; + pt.x = v.x >> shift; + pt.y = v.y >> shift; + shift = -shift; + } + return shift; +} + + +static void _polarize(SwPoint& pt) +{ + auto v = pt; + SwFixed theta; + + //Get the vector into [-PI/4, PI/4] sector + if (v.y > v.x) { + if (v.y > -v.x) { + auto tmp = v.y; + v.y = -v.x; + v.x = tmp; + theta = SW_ANGLE_PI2; + } else { + theta = v.y > 0 ? SW_ANGLE_PI : -SW_ANGLE_PI; + v.x = -v.x; + v.y = -v.y; + } + } else { + if (v.y < -v.x) { + theta = -SW_ANGLE_PI2; + auto tmp = -v.y; + v.y = v.x; + v.x = tmp; + } else { + theta = 0; + } + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + //Pseudorotations. with right shifts + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (v.y > 0) { + auto tmp = v.x + ((v.y + j) >> i); + v.y = v.y - ((v.x + j) >> i); + v.x = tmp; + theta += *atan++; + } else { + auto tmp = v.x - ((v.y + j) >> i); + v.y = v.y + ((v.x + j) >> i); + v.x = tmp; + theta -= *atan++; + } + } + + //round theta + if (theta >= 0) theta = PAD_ROUND(theta, 32); + else theta = -PAD_ROUND(-theta, 32); + + pt.x = v.x; + pt.y = theta; +} + + +static void _rotate(SwPoint& pt, SwFixed theta) +{ + SwFixed x = pt.x; + SwFixed y = pt.y; + + //Rotate inside [-PI/4, PI/4] sector + while (theta < -SW_ANGLE_PI4) { + auto tmp = y; + y = -x; + x = tmp; + theta += SW_ANGLE_PI2; + } + + while (theta > SW_ANGLE_PI4) { + auto tmp = -y; + y = x; + x = tmp; + theta -= SW_ANGLE_PI2; + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (theta < 0) { + auto tmp = x + ((y + j) >> i); + y = y - ((x + j) >> i); + x = tmp; + theta += *atan++; + } else { + auto tmp = x - ((y + j) >> i); + y = y + ((x + j) >> i); + x = tmp; + theta -= *atan++; + } + } + + pt.x = static_cast(x); + pt.y = static_cast(y); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwFixed mathMean(SwFixed angle1, SwFixed angle2) +{ + return angle1 + mathDiff(angle1, angle2) / 2; +} + + +bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) +{ + auto d1 = base[2] - base[3]; + auto d2 = base[1] - base[2]; + auto d3 = base[0] - base[1]; + + if (d1.small()) { + if (d2.small()) { + if (d3.small()) { + //basically a point. + //do nothing to retain original direction + } else { + angleIn = angleMid = angleOut = mathAtan(d3); + } + } else { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d2); + } else { + angleIn = angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } else { + if (d2.small()) { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d1); + } else { + angleIn = mathAtan(d1); + angleOut = mathAtan(d3); + angleMid = mathMean(angleIn, angleOut); + } + } else { + if (d3.small()) { + angleIn = mathAtan(d1); + angleMid = angleOut = mathAtan(d2); + } else { + angleIn = mathAtan(d1); + angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } + + auto theta1 = abs(mathDiff(angleIn, angleMid)); + auto theta2 = abs(mathDiff(angleMid, angleOut)); + + if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true; + return false; +} + + +int64_t mathMultiply(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t c = (a * b + 0x8000L) >> 16; + return (s > 0) ? c : -c; +} + + +int64_t mathDivide(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; + return (s < 0 ? -q : q); +} + + +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + if (c < 0) { + c = -c; + s = -s; + } + int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL; + + return (s > 0 ? d : -d); +} + + +void mathRotate(SwPoint& pt, SwFixed angle) +{ + if (angle == 0 || (pt.x == 0 && pt.y == 0)) return; + + auto v = pt; + auto shift = _normalize(v); + + auto theta = angle; + _rotate(v, theta); + + v.x = _downscale(v.x); + v.y = _downscale(v.y); + + if (shift > 0) { + auto half = static_cast(1L << (shift - 1)); + pt.x = (v.x + half + SATURATE(v.x)) >> shift; + pt.y = (v.y + half + SATURATE(v.y)) >> shift; + } else { + shift = -shift; + pt.x = static_cast((unsigned long)v.x << shift); + pt.y = static_cast((unsigned long)v.y << shift); + } +} + +SwFixed mathTan(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + _rotate(v, angle); + return mathDivide(v.y, v.x); +} + + +SwFixed mathAtan(const SwPoint& pt) +{ + if (pt.x == 0 && pt.y == 0) return 0; + + auto v = pt; + _normalize(v); + _polarize(v); + + return v.y; +} + + +SwFixed mathSin(SwFixed angle) +{ + return mathCos(SW_ANGLE_PI2 - angle); +} + + +SwFixed mathCos(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + _rotate(v, angle); + return (v.x + 0x80L) >> 8; +} + + +SwFixed mathLength(const SwPoint& pt) +{ + auto v = pt; + + //trivial case + if (v.x == 0) return abs(v.y); + if (v.y == 0) return abs(v.x); + + //general case + auto shift = _normalize(v); + _polarize(v); + v.x = _downscale(v.x); + + if (shift > 0) return (v.x + (static_cast(1) << (shift -1))) >> shift; + return static_cast((uint32_t)v.x << -shift); +} + + +void mathSplitCubic(SwPoint* base) +{ + SwCoord a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = (base[0].x + c) / 2; + base[5].x = b = (base[3].x + d) / 2; + c = (c + d) / 2; + base[2].x = a = (a + c) / 2; + base[4].x = b = (b + c) / 2; + base[3].x = (a + b) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = (base[0].y + c) / 2; + base[5].y = b = (base[3].y + d) / 2; + c = (c + d) / 2; + base[2].y = a = (a + c) / 2; + base[4].y = b = (b + c) / 2; + base[3].y = (a + b) / 2; +} + + +SwFixed mathDiff(SwFixed angle1, SwFixed angle2) +{ + auto delta = angle2 - angle1; + + delta %= SW_ANGLE_2PI; + if (delta < 0) delta += SW_ANGLE_2PI; + if (delta > SW_ANGLE_PI) delta -= SW_ANGLE_2PI; + + return delta; +} + + +SwPoint mathTransform(const Point* to, const Matrix* transform) +{ + if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)}; + + auto tx = to->x * transform->e11 + to->y * transform->e12 + transform->e13; + auto ty = to->x * transform->e21 + to->y * transform->e22 + transform->e23; + + return {TO_SWCOORD(tx), TO_SWCOORD(ty)}; +} + + +bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack) +{ + if (!outline) return false; + + auto pt = outline->pts; + + if (outline->ptsCnt == 0 || outline->cntrsCnt <= 0) { + renderRegion.reset(); + return false; + } + + auto xMin = pt->x; + auto xMax = pt->x; + auto yMin = pt->y; + auto yMax = pt->y; + + ++pt; + + for(uint32_t i = 1; i < outline->ptsCnt; ++i, ++pt) { + if (xMin > pt->x) xMin = pt->x; + if (xMax < pt->x) xMax = pt->x; + if (yMin > pt->y) yMin = pt->y; + if (yMax < pt->y) yMax = pt->y; + } + + //Since no antialiasing is applied in the Fast Track case, + //the rasterization region has to be rearranged. + //https://github.com/Samsung/thorvg/issues/916 + if (fastTrack) { + renderRegion.min.x = static_cast(round(xMin / 64.0f)); + renderRegion.max.x = static_cast(round(xMax / 64.0f)); + renderRegion.min.y = static_cast(round(yMin / 64.0f)); + renderRegion.max.y = static_cast(round(yMax / 64.0f)); + } else { + renderRegion.min.x = xMin >> 6; + renderRegion.max.x = (xMax + 63) >> 6; + renderRegion.min.y = yMin >> 6; + renderRegion.max.y = (yMax + 63) >> 6; + } + + renderRegion.max.x = (renderRegion.max.x < clipRegion.max.x) ? renderRegion.max.x : clipRegion.max.x; + renderRegion.max.y = (renderRegion.max.y < clipRegion.max.y) ? renderRegion.max.y : clipRegion.max.y; + renderRegion.min.x = (renderRegion.min.x > clipRegion.min.x) ? renderRegion.min.x : clipRegion.min.x; + renderRegion.min.y = (renderRegion.min.y > clipRegion.min.y) ? renderRegion.min.y : clipRegion.min.y; + + //Check valid region + if (renderRegion.max.x - renderRegion.min.x < 1 && renderRegion.max.y - renderRegion.min.y < 1) return false; + + //Check boundary + if (renderRegion.min.x >= clipRegion.max.x || renderRegion.min.y >= clipRegion.max.y || + renderRegion.max.x <= clipRegion.min.x || renderRegion.max.y <= clipRegion.min.y) return false; + + return true; +} + + +bool mathInverse(const Matrix* m, Matrix* invM) +{ + auto det = m->e11 * (m->e22 * m->e33 - m->e32 * m->e23) - + m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) + + m->e13 * (m->e21 * m->e32 - m->e22 * m->e31); + + if (fabsf(det) < FLT_EPSILON) return false; + + auto invDet = 1 / det; + + invM->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet; + invM->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet; + invM->e13 = (m->e12 * m->e23 - m->e13 * m->e22) * invDet; + invM->e21 = (m->e23 * m->e31 - m->e21 * m->e33) * invDet; + invM->e22 = (m->e11 * m->e33 - m->e13 * m->e31) * invDet; + invM->e23 = (m->e21 * m->e13 - m->e11 * m->e23) * invDet; + invM->e31 = (m->e21 * m->e32 - m->e31 * m->e22) * invDet; + invM->e32 = (m->e31 * m->e12 - m->e11 * m->e32) * invDet; + invM->e33 = (m->e11 * m->e22 - m->e21 * m->e12) * invDet; + + return true; +} + + +bool mathMultiply(const Matrix* lhs, Matrix* rhs) +{ + Matrix m; + + m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31; + m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32; + m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33; + + m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31; + m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32; + m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33; + + m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31; + m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32; + m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33; + + *rhs = m; + + return true; +} + + +bool mathIdentity(const Matrix* m) +{ + if (m) { + if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || + m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || + m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) { + return false; + } + } + + return true; +} diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp new file mode 100644 index 0000000000..5bf15d7f77 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwMemPool.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx) +{ + return &mpool->outline[idx]; +} + + +void mpoolRetOutline(SwMpool* mpool, unsigned idx) +{ + mpool->outline[idx].cntrsCnt = 0; + mpool->outline[idx].ptsCnt = 0; +} + + +SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx) +{ + return &mpool->strokeOutline[idx]; +} + + +void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx) +{ + mpool->strokeOutline[idx].cntrsCnt = 0; + mpool->strokeOutline[idx].ptsCnt = 0; +} + + +SwMpool* mpoolInit(unsigned threads) +{ + if (threads == 0) threads = 1; + + auto mpool = new SwMpool; + mpool->outline = static_cast(calloc(1, sizeof(SwOutline) * threads)); + if (!mpool->outline) goto err; + + mpool->strokeOutline = static_cast(calloc(1, sizeof(SwOutline) * threads)); + if (!mpool->strokeOutline) goto err; + + mpool->allocSize = threads; + + return mpool; + +err: + if (mpool->outline) { + free(mpool->outline); + mpool->outline = nullptr; + } + + if (mpool->strokeOutline) { + free(mpool->strokeOutline); + mpool->strokeOutline = nullptr; + } + delete(mpool); + return nullptr; +} + + +bool mpoolClear(SwMpool* mpool) +{ + SwOutline* p; + + for (unsigned i = 0; i < mpool->allocSize; ++i) { + + //Outline + p = &mpool->outline[i]; + + free(p->cntrs); + p->cntrs = nullptr; + + free(p->pts); + p->pts = nullptr; + + free(p->types); + p->types = nullptr; + + free(p->closed); + p->closed = nullptr; + + p->cntrsCnt = p->reservedCntrsCnt = 0; + p->ptsCnt = p->reservedPtsCnt = 0; + + //StrokeOutline + p = &mpool->strokeOutline[i]; + + free(p->cntrs); + p->cntrs = nullptr; + + free(p->pts); + p->pts = nullptr; + + free(p->types); + p->types = nullptr; + + free(p->closed); + p->closed = nullptr; + + p->cntrsCnt = p->reservedCntrsCnt = 0; + p->ptsCnt = p->reservedPtsCnt = 0; + } + + return true; +} + + +bool mpoolTerm(SwMpool* mpool) +{ + if (!mpool) return false; + + mpoolClear(mpool); + + if (mpool->outline) { + free(mpool->outline); + mpool->outline = nullptr; + } + + if (mpool->strokeOutline) { + free(mpool->strokeOutline); + mpool->strokeOutline = nullptr; + } + + delete(mpool); + + return true; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp new file mode 100644 index 0000000000..e376a6da5a --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp @@ -0,0 +1,1594 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgSwCommon.h" +#include "tvgRender.h" +#include "tvgSwRasterC.h" +#include "tvgSwRasterAvx.h" +#include "tvgSwRasterNeon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static uint32_t _colorAlpha(uint32_t c) +{ + return (c >> 24); +} + + +static uint32_t _abgrJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24 | b << 16 | g << 8 | r); +} + + +static uint32_t _argbJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24 | r << 16 | g << 8 | b); +} + + +static bool _translucent(const SwSurface* surface, uint8_t a) +{ + if (a < 255) return true; + if (!surface->compositor || surface->compositor->method == CompositeMethod::None) return false; + return true; +} + + +static uint32_t _applyBilinearInterpolation(const uint32_t *img, uint32_t w, uint32_t h, float fX, float fY) +{ + auto rX = static_cast(fX); + auto rY = static_cast(fY); + + auto dX = static_cast((fX - rX) * 255.0); + auto dY = static_cast((fY - rY) * 255.0); + + auto c1 = img[rX + (rY * w)]; + auto c2 = img[(rX + 1) + (rY * w)]; + auto c3 = img[(rX + 1) + ((rY + 1) * w)]; + auto c4 = img[rX + ((rY + 1) * w)]; + + if (c1 == c2 && c1 == c3 && c1 == c4) return img[rX + (rY * w)]; + return COLOR_INTERPOLATE(COLOR_INTERPOLATE(c1, 255 - dX, c2, dX), 255 - dY, COLOR_INTERPOLATE(c4, 255 - dX, c3, dX), dY); +} + +static uint32_t _average2Nx2NPixel(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t rX, uint32_t rY, uint32_t n) +{ + uint32_t c[4] = { 0 }; + auto n2 = n * n; + auto source = img + rX - n + (rY - n) * w; + for (auto y = rY - n; y < rY + n; ++y) { + auto src = source; + for (auto x = rX - n; x < rX + n; ++x, ++src) { + c[0] += *src >> 24; + c[1] += (*src >> 16) & 0xff; + c[2] += (*src >> 8) & 0xff; + c[3] += *src & 0xff; + } + source += w; + } + for (auto i = 0; i < 4; ++i) { + c[i] = (c[i] >> 2) / n2; + } + return (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; +} + +/************************************************************************/ +/* Rect */ +/************************************************************************/ + +static bool _translucentRectAlphaMask(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + TVGLOG("SW_ENGINE", "Rectangle Alpha Mask Composition"); + + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; //compositor buffer + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + auto cmp = &cbuffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x) { + auto tmp = ALPHA_BLEND(color, surface->blender.alpha(*cmp)); + dst[x] = tmp + ALPHA_BLEND(dst[x], 255 - surface->blender.alpha(tmp)); + ++cmp; + } + } + return true; +} + +static bool _translucentRectInvAlphaMask(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + TVGLOG("SW_ENGINE", "Rectangle Inverse Alpha Mask Composition"); + + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; //compositor buffer + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + auto cmp = &cbuffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x) { + auto tmp = ALPHA_BLEND(color, 255 - surface->blender.alpha(*cmp)); + dst[x] = tmp + ALPHA_BLEND(dst[x], 255 - surface->blender.alpha(tmp)); + ++cmp; + } + } + return true; +} + +static bool _rasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentRectAlphaMask(surface, region, color); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentRectInvAlphaMask(surface, region, color); + } + } + +#if defined(THORVG_AVX_VECTOR_SUPPORT) + return avxRasterTranslucentRect(surface, region, color); +#elif defined(THORVG_NEON_VECTOR_SUPPORT) + return neonRasterTranslucentRect(surface, region, color); +#else + return cRasterTranslucentRect(surface, region, color); +#endif +} + + +static bool _rasterSolidRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride); + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + + for (uint32_t y = 0; y < h; ++y) { + rasterRGBA32(buffer + y * surface->stride, color, region.min.x, w); + } + return true; +} + + +/************************************************************************/ +/* Rle */ +/************************************************************************/ + + +static bool _translucentRleAlphaMask(SwSurface* surface, const SwRleData* rle, uint32_t color) +{ + TVGLOG("SW_ENGINE", "Rle Alpha Mask Composition"); + + auto span = rle->spans; + uint32_t src; + auto cbuffer = surface->compositor->image.data; + + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[span->y * surface->stride + span->x]; + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + for (uint32_t x = 0; x < span->len; ++x) { + auto tmp = ALPHA_BLEND(src, surface->blender.alpha(*cmp)); + dst[x] = tmp + ALPHA_BLEND(dst[x], 255 - surface->blender.alpha(tmp)); + ++cmp; + } + ++span; + } + return true; +} + +static bool _translucentRleInvAlphaMask(SwSurface* surface, SwRleData* rle, uint32_t color) +{ + TVGLOG("SW_ENGINE", "Rle Inverse Alpha Mask Composition"); + + auto span = rle->spans; + uint32_t src; + auto cbuffer = surface->compositor->image.data; + + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[span->y * surface->stride + span->x]; + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + for (uint32_t x = 0; x < span->len; ++x) { + auto tmp = ALPHA_BLEND(src, 255 - surface->blender.alpha(*cmp)); + dst[x] = tmp + ALPHA_BLEND(dst[x], 255 - surface->blender.alpha(tmp)); + ++cmp; + } + ++span; + } + return true; +} + +static bool _rasterTranslucentRle(SwSurface* surface, SwRleData* rle, uint32_t color) +{ + if (!rle) return false; + + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentRleAlphaMask(surface, rle, color); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentRleInvAlphaMask(surface, rle, color); + } + } + +#if defined(THORVG_NEON_VECTOR_SUPPORT) + return neonRasterTranslucentRle(surface, rle, color); +#else + return cRasterTranslucentRle(surface, rle, color); +#endif +} + + +static bool _rasterSolidRle(SwSurface* surface, const SwRleData* rle, uint32_t color) +{ + if (!rle) return false; + + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i) { + if (span->coverage == 255) { + rasterRGBA32(surface->buffer + span->y * surface->stride, color, span->x, span->len); + } else { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto src = ALPHA_BLEND(color, span->coverage); + auto ialpha = 255 - span->coverage; + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = src + ALPHA_BLEND(dst[i], ialpha); + } + } + ++span; + } + return true; +} + + +/************************************************************************/ +/* Image */ +/************************************************************************/ + +static bool _rasterTranslucentImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity) +{ + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto src = img + span->x + span->y * w; //TODO: need to use image's stride + auto alpha = ALPHA_MULTIPLY(span->coverage, opacity); + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++src) { + *src = ALPHA_BLEND(*src, alpha); + *dst = *src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(*src)); + } + } + return true; +} + + +static bool _rasterTranslucentImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const Matrix* invTransform) +{ + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto ey1 = span->y * invTransform->e12 + invTransform->e13; + auto ey2 = span->y * invTransform->e22 + invTransform->e23; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto alpha = ALPHA_MULTIPLY(span->coverage, opacity); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto rX = static_cast(roundf((span->x + x) * invTransform->e11 + ey1)); + auto rY = static_cast(roundf((span->x + x) * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + auto src = ALPHA_BLEND(img[rY * w + rX], alpha); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterTranslucentUpScaleImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const Matrix* invTransform) +{ + auto span = rle->spans; + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto ey1 = span->y * invTransform->e12 + invTransform->e13; + auto ey2 = span->y * invTransform->e22 + invTransform->e23; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto alpha = ALPHA_MULTIPLY(span->coverage, opacity); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto fX = (span->x + x) * invTransform->e11 + ey1; + auto fY = (span->x + x) * invTransform->e21 + ey2; + auto rX = static_cast(roundf(fX)); + auto rY = static_cast(roundf(fY)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX == w - 1 || rY == h - 1) src = ALPHA_BLEND(img[rY * w + rX], alpha); //TODO: need to use image's stride + else src = ALPHA_BLEND(_applyBilinearInterpolation(img, w, h, fX, fY), alpha); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterTranslucentDownScaleImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const Matrix* invTransform, float scaling) +{ + uint32_t halfScaling = static_cast(0.5f / scaling); + if (halfScaling == 0) halfScaling = 1; + auto span = rle->spans; + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto ey1 = span->y * invTransform->e12 + invTransform->e13; + auto ey2 = span->y * invTransform->e22 + invTransform->e23; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto alpha = ALPHA_MULTIPLY(span->coverage, opacity); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto rX = static_cast(roundf((span->x + x) * invTransform->e11 + ey1)); + auto rY = static_cast(roundf((span->x + x) * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rY * w + rX], alpha); //TODO: need to use image's stride + else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), alpha); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h) +{ + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto src = img + span->x + span->y * w; //TODO: need to use image's stride + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++src) { + *src = ALPHA_BLEND(*src, span->coverage); + *dst = *src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(*src)); + } + } + return true; +} + + +static bool _rasterImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, const Matrix* invTransform) +{ + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto ey1 = span->y * invTransform->e12 + invTransform->e13; + auto ey2 = span->y * invTransform->e22 + invTransform->e23; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto rX = static_cast(roundf((span->x + x) * invTransform->e11 + ey1)); + auto rY = static_cast(roundf((span->x + x) * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + auto src = ALPHA_BLEND(img[rY * w + rX], span->coverage); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterUpScaleImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, const Matrix* invTransform) +{ + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto ey1 = span->y * invTransform->e12 + invTransform->e13; + auto ey2 = span->y * invTransform->e22 + invTransform->e23; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto fX = (span->x + x) * invTransform->e11 + ey1; + auto fY = (span->x + x) * invTransform->e21 + ey2; + auto rX = static_cast(roundf(fX)); + auto rY = static_cast(roundf(fY)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX == w - 1 || rY == h - 1) src = ALPHA_BLEND(img[rY * w + rX], span->coverage); //TODO: need to use image's stride + else src = ALPHA_BLEND(_applyBilinearInterpolation(img, w, h, fX, fY), span->coverage); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterDownScaleImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, const Matrix* invTransform, float scaling) +{ + uint32_t halfScaling = static_cast(0.5f / scaling); + if (halfScaling == 0) halfScaling = 1; + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto ey1 = span->y * invTransform->e12 + invTransform->e13; + auto ey2 = span->y * invTransform->e22 + invTransform->e23; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto rX = static_cast(roundf((span->x + x) * invTransform->e11 + ey1)); + auto rY = static_cast(roundf((span->x + x) * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rY * w + rX], span->coverage); //TODO: need to use image's stride + else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), span->coverage); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _translucentImage(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto ey1 = y * invTransform->e12 + invTransform->e13; + auto ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + auto src = ALPHA_BLEND(img[rX + (rY * w)], opacity); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + } + return true; +} + + +static bool _translucentImageAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + TVGLOG("SW_ENGINE", "Transformed Image Alpha Mask Composition"); + + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto cmp = cbuffer; + float ey1 = y * invTransform->e12 + invTransform->e13; + float ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + auto src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + +static bool _translucentImageInvAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + TVGLOG("SW_ENGINE", "Transformed Image Inverse Alpha Mask Composition"); + + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto cmp = cbuffer; + float ey1 = y * invTransform->e12 + invTransform->e13; + float ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + auto src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _rasterTranslucentImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentImageAlphaMask(surface, img, w, h, opacity, region, invTransform); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentImageInvAlphaMask(surface, img, w, h, opacity, region, invTransform); + } + } + return _translucentImage(surface, img, w, h, opacity, region, invTransform); +} + + +static bool _translucentUpScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto ey1 = y * invTransform->e12 + invTransform->e13; + auto ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + auto fX = x * invTransform->e11 + ey1; + auto fY = x * invTransform->e21 + ey2; + auto rX = static_cast(roundf(fX)); + auto rY = static_cast(roundf(fY)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX == w - 1 || rY == h - 1) src = ALPHA_BLEND(img[rX + (rY * w)], opacity); //TODO: need to use image's stride + else src = ALPHA_BLEND(_applyBilinearInterpolation(img, w, h, fX, fY), opacity); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + } + return true; +} + + +static bool _translucentUpScaleImageAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + TVGLOG("SW_ENGINE", "Transformed Image Alpha Mask Composition"); + + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto cmp = cbuffer; + float ey1 = y * invTransform->e12 + invTransform->e13; + float ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) { + auto fX = x * invTransform->e11 + ey1; + auto fY = x * invTransform->e21 + ey2; + auto rX = static_cast(roundf(fX)); + auto rY = static_cast(roundf(fY)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX == w - 1 || rY == h - 1) src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride + else src = ALPHA_BLEND(_applyBilinearInterpolation(img, w, h, fX, fY), ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _translucentUpScaleImageInvAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + TVGLOG("SW_ENGINE", "Transformed Image Inverse Alpha Mask Composition"); + + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto cmp = cbuffer; + float ey1 = y * invTransform->e12 + invTransform->e13; + float ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) { + auto fX = x * invTransform->e11 + ey1; + auto fY = x * invTransform->e21 + ey2; + auto rX = static_cast(roundf(fX)); + auto rY = static_cast(roundf(fY)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX == w - 1 || rY == h - 1) src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride + else src = ALPHA_BLEND(_applyBilinearInterpolation(img, w, h, fX, fY), ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _rasterTranslucentUpScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentUpScaleImageAlphaMask(surface, img, w, h, opacity, region, invTransform); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentUpScaleImageInvAlphaMask(surface, img, w, h, opacity, region, invTransform); + } + } + return _translucentUpScaleImage(surface, img, w, h, opacity, region, invTransform); +} + + +static bool _translucentDownScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling) +{ + uint32_t halfScaling = static_cast(0.5f / scaling); + if (halfScaling == 0) halfScaling = 1; + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto ey1 = y * invTransform->e12 + invTransform->e13; + auto ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rX + (rY * w)], opacity); + else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), opacity); + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + } + return true; +} + + +static bool _translucentDownScaleImageAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling) +{ + TVGLOG("SW_ENGINE", "Transformed Image Alpha Mask Composition"); + uint32_t halfScaling = static_cast(0.5f / scaling); + if (halfScaling == 0) halfScaling = 1; + + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto cmp = cbuffer; + float ey1 = y * invTransform->e12 + invTransform->e13; + float ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride + else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _translucentDownScaleImageInvAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling) +{ + TVGLOG("SW_ENGINE", "Transformed Image Inverse Alpha Mask Composition"); + uint32_t halfScaling = static_cast(0.5f / scaling); + if (halfScaling == 0) halfScaling = 1; + + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x]; + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto cmp = cbuffer; + float ey1 = y * invTransform->e12 + invTransform->e13; + float ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride + else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + dbuffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _rasterTranslucentDownScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentDownScaleImageAlphaMask(surface, img, w, h, opacity, region, invTransform, scaling); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentDownScaleImageInvAlphaMask(surface, img, w, h, opacity, region, invTransform, scaling); + } + } + return _translucentDownScaleImage(surface, img, w, h, opacity, region, invTransform, scaling); +} + + +static bool _translucentImage(SwSurface* surface, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region) +{ + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto sbuffer = img + region.min.x + region.min.y * w; //TODO: need to use image's stride + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { + auto p = ALPHA_BLEND(*src, opacity); + *dst = p + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(p)); + } + dbuffer += surface->stride; + sbuffer += w; //TODO: need to use image's stride + } + return true; +} + + +static bool _translucentImageAlphaMask(SwSurface* surface, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h2 = static_cast(region.max.y - region.min.y); + auto w2 = static_cast(region.max.x - region.min.x); + + TVGLOG("SW_ENGINE", "Image Alpha Mask Composition"); + + auto sbuffer = img + (region.min.y * w) + region.min.x; + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; //compositor buffer + + for (uint32_t y = 0; y < h2; ++y) { + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + for (uint32_t x = 0; x < w2; ++x, ++dst, ++src, ++cmp) { + auto tmp = ALPHA_BLEND(*src, ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + buffer += surface->stride; + cbuffer += surface->stride; + sbuffer += w; //TODO: need to use image's stride + } + return true; +} + + +static bool _translucentImageInvAlphaMask(SwSurface* surface, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h2 = static_cast(region.max.y - region.min.y); + auto w2 = static_cast(region.max.x - region.min.x); + + TVGLOG("SW_ENGINE", "Image Inverse Alpha Mask Composition"); + + auto sbuffer = img + (region.min.y * w) + region.min.x; + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; //compositor buffer + + for (uint32_t y = 0; y < h2; ++y) { + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + for (uint32_t x = 0; x < w2; ++x, ++dst, ++src, ++cmp) { + auto tmp = ALPHA_BLEND(*src, ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + buffer += surface->stride; + cbuffer += surface->stride; + sbuffer += w; //TODO: need to use image's stride + } + return true; +} + + +static bool _rasterTranslucentImage(SwSurface* surface, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentImageAlphaMask(surface, img, w, h, opacity, region); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentImageInvAlphaMask(surface, img, w, h, opacity, region); + } + } + return _translucentImage(surface, img, w, h, opacity, region); +} + + +static bool _rasterImage(SwSurface* surface, uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, const SwBBox& region) +{ + auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x]; + auto sbuffer = img + region.min.x + region.min.y * w; //TODO: need to use image's stride + + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = dbuffer; + auto src = sbuffer; + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { + *dst = *src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(*src)); + } + dbuffer += surface->stride; + sbuffer += w; //TODO: need to use image's stride + } + return true; +} + + +static bool _rasterImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, const SwBBox& region, const Matrix* invTransform) +{ + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = &surface->buffer[y * surface->stride + region.min.x]; + auto ey1 = y * invTransform->e12 + invTransform->e13; + auto ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + auto src = img[rX + (rY * w)]; //TODO: need to use image's stride + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterUpScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, const SwBBox& region, const Matrix* invTransform) +{ + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = &surface->buffer[y * surface->stride + region.min.x]; + auto ey1 = y * invTransform->e12 + invTransform->e13; + auto ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + auto fX = x * invTransform->e11 + ey1; + auto fY = x * invTransform->e21 + ey2; + auto rX = static_cast(roundf(fX)); + auto rY = static_cast(roundf(fY)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX == w - 1 || rY == h - 1) src = img[rX + (rY * w)]; + else src = _applyBilinearInterpolation(img, w, h, fX, fY); + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +static bool _rasterDownScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, const SwBBox& region, const Matrix* invTransform, float scaling) +{ + uint32_t halfScaling = static_cast(0.5f / scaling); + + if (halfScaling == 0) halfScaling = 1; + for (auto y = region.min.y; y < region.max.y; ++y) { + auto dst = &surface->buffer[y * surface->stride + region.min.x]; + auto ey1 = y * invTransform->e12 + invTransform->e13; + auto ey2 = y * invTransform->e22 + invTransform->e23; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + auto rX = static_cast(roundf(x * invTransform->e11 + ey1)); + auto rY = static_cast(roundf(x * invTransform->e21 + ey2)); + if (rX >= w || rY >= h) continue; + uint32_t src; + if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = img[rX + (rY * w)]; + else src = _average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling); + *dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src)); + } + } + return true; +} + + +/************************************************************************/ +/* Gradient */ +/************************************************************************/ + +static bool _translucentLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + auto sbuffer = static_cast(alloca(w * sizeof(uint32_t))); + if (!sbuffer) return false; + + auto dst = buffer; + for (uint32_t y = 0; y < h; ++y) { + fillFetchLinear(fill, sbuffer, region.min.y + y, region.min.x, w); + for (uint32_t x = 0; x < w; ++x) { + dst[x] = sbuffer[x] + ALPHA_BLEND(dst[x], 255 - surface->blender.alpha(sbuffer[x])); + } + dst += surface->stride; + } + return true; +} + + +static bool _translucentLinearGradientRectAlphaMask(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; + + auto sbuffer = static_cast(alloca(w * sizeof(uint32_t))); + if (!sbuffer) return false; + + for (uint32_t y = 0; y < h; ++y) { + fillFetchLinear(fill, sbuffer, region.min.y + y, region.min.x, w); + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + buffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _translucentLinearGradientRectInvAlphaMask(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; + + auto sbuffer = static_cast(alloca(w * sizeof(uint32_t))); + if (!sbuffer) return false; + + for (uint32_t y = 0; y < h; ++y) { + fillFetchLinear(fill, sbuffer, region.min.y + y, region.min.x, w); + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, 255 - surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + buffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _rasterTranslucentLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentLinearGradientRectAlphaMask(surface, region, fill); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentLinearGradientRectInvAlphaMask(surface, region, fill); + } + } + return _translucentLinearGradientRect(surface, region, fill); +} + + +static bool _rasterOpaqueLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + for (uint32_t y = 0; y < h; ++y) { + fillFetchLinear(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, w); + } + return true; +} + + +static bool _translucentRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + auto sbuffer = static_cast(alloca(w * sizeof(uint32_t))); + if (!sbuffer) return false; + + auto dst = buffer; + for (uint32_t y = 0; y < h; ++y) { + fillFetchRadial(fill, sbuffer, region.min.y + y, region.min.x, w); + for (uint32_t x = 0; x < w; ++x) { + dst[x] = sbuffer[x] + ALPHA_BLEND(dst[x], 255 - surface->blender.alpha(sbuffer[x])); + } + dst += surface->stride; + } + return true; +} + + +static bool _translucentRadialGradientRectAlphaMask(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; + + auto sbuffer = static_cast(alloca(w * sizeof(uint32_t))); + if (!sbuffer) return false; + + for (uint32_t y = 0; y < h; ++y) { + fillFetchRadial(fill, sbuffer, region.min.y + y, region.min.x, w); + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + buffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _translucentRadialGradientRectInvAlphaMask(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto cbuffer = surface->compositor->image.data + (region.min.y * surface->stride) + region.min.x; + + auto sbuffer = static_cast(alloca(w * sizeof(uint32_t))); + if (!sbuffer) return false; + + for (uint32_t y = 0; y < h; ++y) { + fillFetchRadial(fill, sbuffer, region.min.y + y, region.min.x, w); + auto dst = buffer; + auto cmp = cbuffer; + auto src = sbuffer; + for (uint32_t x = 0; x < w; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, 255 - surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + buffer += surface->stride; + cbuffer += surface->stride; + } + return true; +} + + +static bool _rasterTranslucentRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentRadialGradientRectAlphaMask(surface, region, fill); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentRadialGradientRectInvAlphaMask(surface, region, fill); + } + } + return _translucentRadialGradientRect(surface, region, fill); +} + + +static bool _rasterOpaqueRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + fillFetchRadial(fill, dst, region.min.y + y, region.min.x, w); + } + return true; +} + + +static bool _translucentLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto span = rle->spans; + auto buffer = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buffer) return false; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + fillFetchLinear(fill, buffer, span->y, span->x, span->len); + if (span->coverage == 255) { + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = buffer[i] + ALPHA_BLEND(dst[i], 255 - surface->blender.alpha(buffer[i])); + } + } else { + for (uint32_t i = 0; i < span->len; ++i) { + auto tmp = ALPHA_BLEND(buffer[i], span->coverage); + dst[i] = tmp + ALPHA_BLEND(dst[i], 255 - surface->blender.alpha(tmp)); + } + } + } + return true; +} + + +static bool _translucentLinearGradientRleAlphaMask(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto span = rle->spans; + auto cbuffer = surface->compositor->image.data; + auto buffer = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buffer) return false; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + fillFetchLinear(fill, buffer, span->y, span->x, span->len); + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[span->y * surface->stride + span->x]; + auto src = buffer; + if (span->coverage == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } else { + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, surface->blender.alpha(*cmp)); + tmp = ALPHA_BLEND(tmp, span->coverage) + ALPHA_BLEND(*dst, ialpha); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } + } + return true; +} + + +static bool _translucentLinearGradientRleInvAlphaMask(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto span = rle->spans; + auto cbuffer = surface->compositor->image.data; + auto buffer = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buffer) return false; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + fillFetchLinear(fill, buffer, span->y, span->x, span->len); + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[span->y * surface->stride + span->x]; + auto src = buffer; + if (span->coverage == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, 255 - surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } else { + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, 255 - surface->blender.alpha(*cmp)); + tmp = ALPHA_BLEND(tmp, span->coverage) + ALPHA_BLEND(*dst, ialpha); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } + } + return true; +} + + +static bool _rasterTranslucentLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (!rle) return false; + + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentLinearGradientRleAlphaMask(surface, rle, fill); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentLinearGradientRleInvAlphaMask(surface, rle, fill); + } + } + return _translucentLinearGradientRle(surface, rle, fill); +} + + +static bool _rasterOpaqueLinearGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->linear.len < FLT_EPSILON) return false; + + auto buf = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buf) return false; + + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + if (span->coverage == 255) { + fillFetchLinear(fill, surface->buffer + span->y * surface->stride + span->x, span->y, span->x, span->len); + } else { + fillFetchLinear(fill, buf, span->y, span->x, span->len); + auto ialpha = 255 - span->coverage; + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = ALPHA_BLEND(buf[i], span->coverage) + ALPHA_BLEND(dst[i], ialpha); + } + } + } + return true; +} + + +static bool _translucentRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto span = rle->spans; + auto buffer = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buffer) return false; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + fillFetchRadial(fill, buffer, span->y, span->x, span->len); + if (span->coverage == 255) { + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = buffer[i] + ALPHA_BLEND(dst[i], 255 - surface->blender.alpha(buffer[i])); + } + } else { + for (uint32_t i = 0; i < span->len; ++i) { + auto tmp = ALPHA_BLEND(buffer[i], span->coverage); + dst[i] = tmp + ALPHA_BLEND(dst[i], 255 - surface->blender.alpha(tmp)); + } + } + } + return true; +} + + +static bool _translucentRadialGradientRleAlphaMask(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto span = rle->spans; + auto cbuffer = surface->compositor->image.data; + auto buffer = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buffer) return false; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + fillFetchRadial(fill, buffer, span->y, span->x, span->len); + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[span->y * surface->stride + span->x]; + auto src = buffer; + if (span->coverage == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } else { + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, surface->blender.alpha(*cmp)); + tmp = ALPHA_BLEND(tmp, span->coverage) + ALPHA_BLEND(*dst, ialpha); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } + } + return true; +} + + +static bool _translucentRadialGradientRleInvAlphaMask(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto span = rle->spans; + auto cbuffer = surface->compositor->image.data; + auto buffer = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buffer) return false; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + fillFetchRadial(fill, buffer, span->y, span->x, span->len); + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto cmp = &cbuffer[span->y * surface->stride + span->x]; + auto src = buffer; + if (span->coverage == 255) { + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, 255 - surface->blender.alpha(*cmp)); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } else { + auto ialpha = 255 - span->coverage; + for (uint32_t x = 0; x < span->len; ++x, ++dst, ++cmp, ++src) { + auto tmp = ALPHA_BLEND(*src, 255 - surface->blender.alpha(*cmp)); + tmp = ALPHA_BLEND(tmp, span->coverage) + ALPHA_BLEND(*dst, ialpha); + *dst = tmp + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(tmp)); + } + } + } + return true; +} + + +static bool _rasterTranslucentRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (!rle) return false; + + if (surface->compositor) { + if (surface->compositor->method == CompositeMethod::AlphaMask) { + return _translucentRadialGradientRleAlphaMask(surface, rle, fill); + } + if (surface->compositor->method == CompositeMethod::InvAlphaMask) { + return _translucentRadialGradientRleInvAlphaMask(surface, rle, fill); + } + } + return _translucentRadialGradientRle(surface, rle, fill); +} + + +static bool _rasterOpaqueRadialGradientRle(SwSurface* surface, const SwRleData* rle, const SwFill* fill) +{ + if (fill->radial.a < FLT_EPSILON) return false; + + auto buf = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buf) return false; + + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i, ++span) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + if (span->coverage == 255) { + fillFetchRadial(fill, dst, span->y, span->x, span->len); + } else { + fillFetchRadial(fill, buf, span->y, span->x, span->len); + auto ialpha = 255 - span->coverage; + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = ALPHA_BLEND(buf[i], span->coverage) + ALPHA_BLEND(dst[i], ialpha); + } + } + } + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ +#if defined(THORVG_AVX_VECTOR_SUPPORT) + avxRasterRGBA32(dst, val, offset, len); +#elif defined(THORVG_NEON_VECTOR_SUPPORT) + neonRasterRGBA32(dst, val, offset, len); +#else + cRasterRGBA32(dst, val, offset, len); +#endif +} + + +bool rasterCompositor(SwSurface* surface) +{ + if (surface->cs == SwCanvas::ABGR8888 || surface->cs == SwCanvas::ABGR8888_STRAIGHT) { + surface->blender.alpha = _colorAlpha; + surface->blender.join = _abgrJoin; + } else if (surface->cs == SwCanvas::ARGB8888 || surface->cs == SwCanvas::ARGB8888_STRAIGHT) { + surface->blender.alpha = _colorAlpha; + surface->blender.join = _argbJoin; + } else { + //What Color Space ??? + return false; + } + + return true; +} + + +bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) +{ + if (!shape->fill) return false; + + auto translucent = shape->fill->translucent || (surface->compositor && surface->compositor->method != CompositeMethod::None); + + //Fast Track + if (shape->fastTrack) { + if (id == TVG_CLASS_ID_LINEAR) { + if (translucent) return _rasterTranslucentLinearGradientRect(surface, shape->bbox, shape->fill); + return _rasterOpaqueLinearGradientRect(surface, shape->bbox, shape->fill); + } else { + if (translucent) return _rasterTranslucentRadialGradientRect(surface, shape->bbox, shape->fill); + return _rasterOpaqueRadialGradientRect(surface, shape->bbox, shape->fill); + } + } else { + if (!shape->rle) return false; + if (id == TVG_CLASS_ID_LINEAR) { + if (translucent) return _rasterTranslucentLinearGradientRle(surface, shape->rle, shape->fill); + return _rasterOpaqueLinearGradientRle(surface, shape->rle, shape->fill); + } else { + if (translucent) return _rasterTranslucentRadialGradientRle(surface, shape->rle, shape->fill); + return _rasterOpaqueRadialGradientRle(surface, shape->rle, shape->fill); + } + } + return false; +} + + +bool rasterSolidShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (a < 255) { + r = ALPHA_MULTIPLY(r, a); + g = ALPHA_MULTIPLY(g, a); + b = ALPHA_MULTIPLY(b, a); + } + + auto color = surface->blender.join(r, g, b, a); + auto translucent = _translucent(surface, a); + + //Fast Track + if (shape->fastTrack) { + if (translucent) return _rasterTranslucentRect(surface, shape->bbox, color); + return _rasterSolidRect(surface, shape->bbox, color); + } + if (translucent) { + return _rasterTranslucentRle(surface, shape->rle, color); + } + return _rasterSolidRle(surface, shape->rle, color); +} + + +bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (a < 255) { + r = ALPHA_MULTIPLY(r, a); + g = ALPHA_MULTIPLY(g, a); + b = ALPHA_MULTIPLY(b, a); + } + + auto color = surface->blender.join(r, g, b, a); + auto translucent = _translucent(surface, a); + + if (translucent) return _rasterTranslucentRle(surface, shape->strokeRle, color); + return _rasterSolidRle(surface, shape->strokeRle, color); +} + + +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id) +{ + if (!shape->stroke || !shape->stroke->fill || !shape->strokeRle) return false; + + auto translucent = shape->stroke->fill->translucent || (surface->compositor && surface->compositor->method != CompositeMethod::None); + + if (id == TVG_CLASS_ID_LINEAR) { + if (translucent) return _rasterTranslucentLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); + return _rasterOpaqueLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); + } else { + if (translucent) return _rasterTranslucentRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); + return _rasterOpaqueRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); + } + + return false; +} + + +bool rasterClear(SwSurface* surface) +{ + if (!surface || !surface->buffer || surface->stride <= 0 || surface->w <= 0 || surface->h <= 0) return false; + + if (surface->w == surface->stride) { + rasterRGBA32(surface->buffer, 0x00000000, 0, surface->w * surface->h); + } else { + for (uint32_t i = 0; i < surface->h; i++) { + rasterRGBA32(surface->buffer + surface->stride * i, 0x00000000, 0, surface->w); + } + } + return true; +} + + +void rasterUnpremultiply(SwSurface* surface) +{ + //TODO: Create simd avx and neon version + for (uint32_t y = 0; y < surface->h; y++) { + auto buffer = surface->buffer + surface->stride * y; + for (uint32_t x = 0; x < surface->w; ++x) { + uint8_t a = buffer[x] >> 24; + if (a == 255) { + continue; + } else if (a == 0) { + buffer[x] = 0x00ffffff; + } else { + uint16_t r = ((buffer[x] >> 8) & 0xff00) / a; + uint16_t g = ((buffer[x]) & 0xff00) / a; + uint16_t b = ((buffer[x] << 8) & 0xff00) / a; + if (r > 0xff) r = 0xff; + if (g > 0xff) g = 0xff; + if (b > 0xff) b = 0xff; + buffer[x] = (a << 24) | (r << 16) | (g << 8) | (b); + } + } + } +} + + +bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity) +{ + Matrix invTransform; + float scaling = 1.0f; + + if (transform) { + if (!mathInverse(transform, &invTransform)) return false; + scaling = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21)); + auto scalingY = sqrtf((transform->e22 * transform->e22) + (transform->e12 * transform->e12)); + //TODO:If the x and y axis scaling is different, a separate algorithm for each axis should be applied. + if (scaling != scalingY) scaling = 1.0f; + } + else invTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + + auto translucent = _translucent(surface, opacity); + const float downScalingFactor = 0.5f; + + if (image->rle) { + //Fast track + if (mathIdentity(transform)) { + //OPTIMIZE ME: Support non transformed image. Only shifted image can use these routines. + if (translucent) return _rasterTranslucentImageRle(surface, image->rle, image->data, image->w, image->h, opacity); + return _rasterImageRle(surface, image->rle, image->data, image->w, image->h); + } else { + if (translucent) { + if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterTranslucentImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform); + else if (scaling < downScalingFactor) return _rasterTranslucentDownScaleImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform, scaling); + else return _rasterTranslucentUpScaleImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform); + } + if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform); + else if (scaling < downScalingFactor) return _rasterDownScaleImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform, scaling); + else return _rasterUpScaleImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform); + } + } + else { + //Fast track + if (mathIdentity(transform)) { + //OPTIMIZE ME: Support non transformed image. Only shifted image can use these routines. + if (translucent) return _rasterTranslucentImage(surface, image->data, image->w, image->h, opacity, bbox); + return _rasterImage(surface, image->data, image->w, image->h, bbox); + } else { + if (translucent) { + if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterTranslucentImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform); + else if (scaling < downScalingFactor) return _rasterTranslucentDownScaleImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform, scaling); + else return _rasterTranslucentUpScaleImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform); + } + if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterImage(surface, image->data, image->w, image->h, bbox, &invTransform); + else if (scaling < downScalingFactor) return _rasterDownScaleImage(surface, image->data, image->w, image->h, bbox, &invTransform, scaling); + else return _rasterUpScaleImage(surface, image->data, image->w, image->h, bbox, &invTransform); + } + } +} diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h new file mode 100644 index 0000000000..48d34f6bf5 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterAvx.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef THORVG_AVX_VECTOR_SUPPORT + +#include + +#define N_32BITS_IN_128REG 4 +#define N_32BITS_IN_256REG 8 + +static inline __m128i ALPHA_BLEND(__m128i c, __m128i a) +{ + //1. set the masks for the A/G and R/B channels + auto AG = _mm_set1_epi32(0xff00ff00); + auto RB = _mm_set1_epi32(0x00ff00ff); + + //2. mask the alpha vector - originally quartet [a, a, a, a] + auto aAG = _mm_and_si128(a, AG); + auto aRB = _mm_and_si128(a, RB); + + //3. calculate the alpha blending of the 2nd and 4th channel + //- mask the color vector + //- multiply it by the masked alpha vector + //- add the correction to compensate bit shifting used instead of dividing by 255 + //- shift bits - corresponding to division by 256 + auto even = _mm_and_si128(c, RB); + even = _mm_mullo_epi16(even, aRB); + even =_mm_add_epi16(even, RB); + even = _mm_srli_epi16(even, 8); + + //4. calculate the alpha blending of the 1st and 3rd channel: + //- mask the color vector + //- multiply it by the corresponding masked alpha vector and store the high bits of the result + //- add the correction to compensate division by 256 instead of by 255 (next step) + //- remove the low 8 bits to mimic the division by 256 + auto odd = _mm_and_si128(c, AG); + odd = _mm_mulhi_epu16(odd, aAG); + odd = _mm_add_epi16(odd, RB); + odd = _mm_and_si128(odd, AG); + + //5. the final result + return _mm_or_si128(odd, even); +} + + +static inline void avxRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ + //1. calculate how many iterations we need to cover the length + uint32_t iterations = len / N_32BITS_IN_256REG; + uint32_t avxFilled = iterations * N_32BITS_IN_256REG; + + //2. set the beginning of the array + dst += offset; + + //3. fill the octets + for (uint32_t i = 0; i < iterations; ++i, dst += N_32BITS_IN_256REG) { + _mm256_storeu_si256((__m256i*)dst, _mm256_set1_epi32(val)); + } + + //4. fill leftovers (in the first step we have to set the pointer to the place where the avx job is done) + int32_t leftovers = len - avxFilled; + while (leftovers--) *dst++ = val; +} + + +static inline bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + auto ialpha = 255 - static_cast(surface->blender.alpha(color)); + + auto avxColor = _mm_set1_epi32(color); + auto avxIalpha = _mm_set1_epi8(ialpha); + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + + //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required) + auto notAligned = ((uintptr_t)dst & 0xf) / 4; + if (notAligned) { + notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned); + for (uint32_t x = 0; x < notAligned; ++x, ++dst) { + *dst = color + ALPHA_BLEND(*dst, ialpha); + } + } + + //2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once + uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG; + uint32_t avxFilled = iterations * N_32BITS_IN_128REG; + auto avxDst = (__m128i*)dst; + for (uint32_t x = 0; x < iterations; ++x, ++avxDst) { + *avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha)); + } + + //3. fill the remaining pixels + int32_t leftovers = w - notAligned - avxFilled; + dst += avxFilled; + while (leftovers--) { + *dst = color + ALPHA_BLEND(*dst, ialpha); + dst++; + } + } + return true; +} + +#endif diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h new file mode 100644 index 0000000000..b634edfe83 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterC.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +static inline void cRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ + dst += offset; + while (len--) *dst++ = val; +} + + +static inline bool cRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint32_t color) +{ + auto span = rle->spans; + uint32_t src; + + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + + auto ialpha = 255 - surface->blender.alpha(src); + + for (uint32_t x = 0; x < span->len; ++x) + dst[x] = src + ALPHA_BLEND(dst[x], ialpha); + + ++span; + } + return true; +} + +static inline bool cRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto ialpha = 255 - surface->blender.alpha(color); + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x) { + dst[x] = color + ALPHA_BLEND(dst[x], ialpha); + } + } + return true; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h new file mode 100644 index 0000000000..fd391b972d --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterNeon.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef THORVG_NEON_VECTOR_SUPPORT + +#include + +static inline uint8x8_t ALPHA_BLEND_NEON(uint8x8_t c, uint8x8_t a) +{ + uint16x8_t t = vmull_u8(c, a); + return vshrn_n_u16(t, 8); +} + + +static inline void neonRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ + uint32_t iterations = len / 4; + uint32_t neonFilled = iterations * 4; + + dst += offset; + uint32x4_t vectorVal = {val, val, val, val}; + + for (uint32_t i = 0; i < iterations; ++i) { + vst1q_u32(dst, vectorVal); + dst += 4; + } + + int32_t leftovers = len - neonFilled; + while (leftovers--) *dst++ = val; +} + + +static inline bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint32_t color) +{ + auto span = rle->spans; + uint32_t src; + uint8x8_t *vDst = nullptr; + uint16_t align; + + for (uint32_t i = 0; i < rle->size; ++i) { + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto ialpha = 255 - surface->blender.alpha(src); + + if ((((uint32_t) dst) & 0x7) != 0) { + //fill not aligned byte + *dst = src + ALPHA_BLEND(*dst, ialpha); + vDst = (uint8x8_t*)(dst + 1); + align = 1; + } else { + vDst = (uint8x8_t*) dst; + align = 0; + } + + uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src); + uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha); + + for (uint32_t x = 0; x < (span->len - align) / 2; ++x) + vDst[x] = vadd_u8(vSrc, ALPHA_BLEND_NEON(vDst[x], vIalpha)); + + auto leftovers = (span->len - align) % 2; + if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha); + + ++span; + } + return true; +} + + +static inline bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto ialpha = 255 - surface->blender.alpha(color); + + auto vColor = vdup_n_u32(color); + auto vIalpha = vdup_n_u8((uint8_t) ialpha); + + uint8x8_t* vDst = nullptr; + uint32_t align; + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + + if ((((uint32_t) dst) & 0x7) != 0) { + //fill not aligned byte + *dst = color + ALPHA_BLEND(*dst, ialpha); + vDst = (uint8x8_t*) (dst + 1); + align = 1; + } else { + vDst = (uint8x8_t*) dst; + align = 0; + } + + for (uint32_t x = 0; x < (w - align) / 2; ++x) + vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND_NEON(vDst[x], vIalpha)); + + auto leftovers = (w - align) % 2; + if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha); + } + return true; +} + +#endif diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp new file mode 100644 index 0000000000..e425420f36 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp @@ -0,0 +1,658 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include "tvgSwCommon.h" +#include "tvgTaskScheduler.h" +#include "tvgSwRenderer.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +static int32_t initEngineCnt = false; +static int32_t rendererCnt = 0; +static SwMpool* globalMpool = nullptr; +static uint32_t threadsCnt = 0; + +struct SwTask : Task +{ + Matrix* transform = nullptr; + SwSurface* surface = nullptr; + SwMpool* mpool = nullptr; + RenderUpdateFlag flags = RenderUpdateFlag::None; + Array clips; + uint32_t opacity; + SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region + + RenderRegion bounds() const + { + RenderRegion region; + + //Range over? + region.x = bbox.min.x > 0 ? bbox.min.x : 0; + region.y = bbox.min.y > 0 ? bbox.min.y : 0; + region.w = bbox.max.x - region.x; + region.h = bbox.max.y - region.y; + + return region; + } + + virtual bool dispose() = 0; +}; + + +struct SwShapeTask : SwTask +{ + SwShape shape; + const Shape* sdata = nullptr; + bool cmpStroking = false; + + void run(unsigned tid) override + { + if (opacity == 0) return; //Invisible + + uint8_t strokeAlpha = 0; + auto visibleStroke = false; + bool visibleFill = false; + auto clipRegion = bbox; + + if (HALF_STROKE(sdata->strokeWidth()) > 0) { + sdata->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha); + visibleStroke = sdata->strokeFill() || (static_cast(strokeAlpha * opacity / 255) > 0); + } + + //This checks also for the case, if the invisible shape turned to visible by alpha. + auto prepareShape = false; + if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true; + + //Shape + if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) { + uint8_t alpha = 0; + sdata->fillColor(nullptr, nullptr, nullptr, &alpha); + alpha = static_cast(static_cast(alpha) * opacity / 255); + visibleFill = (alpha > 0 || sdata->fill()); + if (visibleFill || visibleStroke) { + shapeReset(&shape); + if (!shapePrepare(&shape, sdata, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) goto err; + } + } + + //Decide Stroking Composition + if (visibleStroke && visibleFill && opacity < 255) cmpStroking = true; + else cmpStroking = false; + + //Fill + if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) { + if (visibleFill) { + /* We assume that if stroke width is bigger than 2, + shape outline below stroke could be full covered by stroke drawing. + Thus it turns off antialising in that condition. + Also, it shouldn't be dash style. */ + auto antiAlias = (strokeAlpha == 255 && sdata->strokeWidth() > 2 && sdata->strokeDash(nullptr) == 0) ? false : true; + if (!shapeGenRle(&shape, sdata, antiAlias)) goto err; + } + if (auto fill = sdata->fill()) { + auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false; + if (ctable) shapeResetFill(&shape); + if (!shapeGenFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err; + } else { + shapeDelFill(&shape); + } + } + + //Stroke + if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { + if (visibleStroke) { + shapeResetStroke(&shape, sdata, transform); + if (!shapeGenStrokeRle(&shape, sdata, transform, clipRegion, bbox, mpool, tid)) goto err; + + if (auto fill = sdata->strokeFill()) { + auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; + if (ctable) shapeResetStrokeFill(&shape); + if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err; + } else { + shapeDelStrokeFill(&shape); + } + } else { + shapeDelStroke(&shape); + } + } + + //Clip Path + for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) { + auto clipper = &static_cast(*clip)->shape; + //Clip shape rle + if (shape.rle) { + if (clipper->fastTrack) rleClipRect(shape.rle, &clipper->bbox); + else if (clipper->rle) rleClipPath(shape.rle, clipper->rle); + else goto err; + } + //Clip stroke rle + if (shape.strokeRle) { + if (clipper->fastTrack) rleClipRect(shape.strokeRle, &clipper->bbox); + else if (clipper->rle) rleClipPath(shape.strokeRle, clipper->rle); + else goto err; + } + } + goto end; + + err: + shapeReset(&shape); + end: + shapeDelOutline(&shape, mpool, tid); + } + + bool dispose() override + { + shapeFree(&shape); + return true; + } +}; + + +struct SwImageTask : SwTask +{ + SwImage image; + const Picture* pdata = nullptr; + + void run(unsigned tid) override + { + auto clipRegion = bbox; + + //Invisible shape turned to visible by alpha. + if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) { + imageReset(&image); + + image.data = const_cast(pdata->data(&image.w, &image.h)); + if (!image.data || image.w == 0 || image.h == 0) goto end; + + if (!imagePrepare(&image, transform, clipRegion, bbox, mpool, tid)) goto end; + + //Clip Path? + if (clips.count > 0) { + if (!imageGenRle(&image, pdata, bbox, false)) goto end; + if (image.rle) { + for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) { + auto clipper = &static_cast(*clip)->shape; + if (clipper->fastTrack) rleClipRect(image.rle, &clipper->bbox); + else if (clipper->rle) rleClipPath(image.rle, clipper->rle); + else goto err; + } + } + } + } + goto end; + + err: + rleReset(image.rle); + end: + imageDelOutline(&image, mpool, tid); + } + + bool dispose() override + { + imageFree(&image); + return true; + } +}; + + +static void _termEngine() +{ + if (rendererCnt > 0) return; + + mpoolTerm(globalMpool); + globalMpool = nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwRenderer::~SwRenderer() +{ + clearCompositors(); + + if (surface) delete(surface); + + if (!sharedMpool) mpoolTerm(mpool); + + --rendererCnt; + + if (rendererCnt == 0 && initEngineCnt == 0) _termEngine(); +} + + +bool SwRenderer::clear() +{ + for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) (*task)->done(); + tasks.clear(); + + if (!sharedMpool) mpoolClear(mpool); + + if (surface) { + vport.x = vport.y = 0; + vport.w = surface->w; + vport.h = surface->h; + } + + return true; +} + + +bool SwRenderer::sync() +{ + return true; +} + + +RenderRegion SwRenderer::viewport() +{ + return vport; +} + + +bool SwRenderer::viewport(const RenderRegion& vp) +{ + this->vport = vp; + + return true; +} + + +bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs) +{ + if (!buffer || stride == 0 || w == 0 || h == 0 || w > stride) return false; + + if (!surface) surface = new SwSurface; + + surface->buffer = buffer; + surface->stride = stride; + surface->w = w; + surface->h = h; + surface->cs = cs; + + vport.x = vport.y = 0; + vport.w = surface->w; + vport.h = surface->h; + + return rasterCompositor(surface); +} + + +bool SwRenderer::preRender() +{ + return rasterClear(surface); +} + +void SwRenderer::clearCompositors() +{ + //Free Composite Caches + for (auto comp = compositors.data; comp < (compositors.data + compositors.count); ++comp) { + free((*comp)->compositor->image.data); + delete((*comp)->compositor); + delete(*comp); + } + compositors.reset(); +} + + +bool SwRenderer::postRender() +{ + //Unmultiply alpha if needed + if (surface->cs == SwCanvas::ABGR8888_STRAIGHT || surface->cs == SwCanvas::ARGB8888_STRAIGHT) { + rasterUnpremultiply(surface); + } + + tasks.clear(); + clearCompositors(); + return true; +} + + +bool SwRenderer::renderImage(RenderData data) +{ + auto task = static_cast(data); + task->done(); + + if (task->opacity == 0) return true; + + return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity); +} + + +bool SwRenderer::renderShape(RenderData data) +{ + auto task = static_cast(data); + if (!task) return false; + + task->done(); + + if (task->opacity == 0) return true; + + uint32_t opacity; + Compositor* cmp = nullptr; + + //Do Stroking Composition + if (task->cmpStroking) { + opacity = 255; + cmp = target(task->bounds()); + beginComposite(cmp, CompositeMethod::None, task->opacity); + //No Stroking Composition + } else { + opacity = task->opacity; + } + + //Main raster stage + uint8_t r, g, b, a; + + if (auto fill = task->sdata->fill()) { + rasterGradientShape(surface, &task->shape, fill->identifier()); + } else { + task->sdata->fillColor(&r, &g, &b, &a); + a = static_cast((opacity * (uint32_t) a) / 255); + if (a > 0) rasterSolidShape(surface, &task->shape, r, g, b, a); + } + + if (auto strokeFill = task->sdata->strokeFill()) { + rasterGradientStroke(surface, &task->shape, strokeFill->identifier()); + } else { + if (task->sdata->strokeColor(&r, &g, &b, &a) == Result::Success) { + a = static_cast((opacity * (uint32_t) a) / 255); + if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a); + } + } + + if (task->cmpStroking) endComposite(cmp); + + return true; +} + + +RenderRegion SwRenderer::region(RenderData data) +{ + return static_cast(data)->bounds(); +} + + +bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) +{ + if (!cmp) return false; + auto p = static_cast(cmp); + + p->method = method; + p->opacity = opacity; + + //Current Context? + if (p->method != CompositeMethod::None) { + surface = p->recoverSfc; + surface->compositor = p; + } + + return true; +} + + +bool SwRenderer::mempool(bool shared) +{ + if (shared == sharedMpool) return true; + + if (shared) { + if (!sharedMpool) { + if (!mpoolTerm(mpool)) return false; + mpool = globalMpool; + } + } else { + if (sharedMpool) mpool = mpoolInit(threadsCnt); + } + + sharedMpool = shared; + + if (mpool) return true; + return false; +} + + +Compositor* SwRenderer::target(const RenderRegion& region) +{ + auto x = region.x; + auto y = region.y; + auto w = region.w; + auto h = region.h; + + //Out of boundary + if (x > surface->w || y > surface->h) return nullptr; + + SwSurface* cmp = nullptr; + + //Use cached data + for (auto p = compositors.data; p < (compositors.data + compositors.count); ++p) { + if ((*p)->compositor->valid) { + cmp = *p; + break; + } + } + + //New Composition + if (!cmp) { + cmp = new SwSurface; + if (!cmp) goto err; + + //Inherits attributes from main surface + *cmp = *surface; + + cmp->compositor = new SwCompositor; + if (!cmp->compositor) goto err; + + //SwImage, Optimize Me: Surface size from MainSurface(WxH) to Parameter W x H + cmp->compositor->image.data = (uint32_t*) malloc(sizeof(uint32_t) * surface->stride * surface->h); + if (!cmp->compositor->image.data) goto err; + compositors.push(cmp); + } + + //Boundary Check + if (x + w > surface->w) w = (surface->w - x); + if (y + h > surface->h) h = (surface->h - y); + + TVGLOG("SW_ENGINE", "Using intermediate composition [Region: %d %d %d %d]", x, y, w, h); + + cmp->compositor->recoverSfc = surface; + cmp->compositor->recoverCmp = surface->compositor; + cmp->compositor->valid = false; + cmp->compositor->bbox.min.x = x; + cmp->compositor->bbox.min.y = y; + cmp->compositor->bbox.max.x = x + w; + cmp->compositor->bbox.max.y = y + h; + cmp->compositor->image.w = surface->stride; + cmp->compositor->image.h = surface->h; + + //We know partial clear region + cmp->buffer = cmp->compositor->image.data + (cmp->stride * y + x); + cmp->w = w; + cmp->h = h; + + rasterClear(cmp); + + //Recover context + cmp->buffer = cmp->compositor->image.data; + cmp->w = cmp->compositor->image.w; + cmp->h = cmp->compositor->image.h; + + //Switch render target + surface = cmp; + + return cmp->compositor; + +err: + if (cmp) { + if (cmp->compositor) delete(cmp->compositor); + delete(cmp); + } + + return nullptr; +} + + +bool SwRenderer::endComposite(Compositor* cmp) +{ + if (!cmp) return false; + + auto p = static_cast(cmp); + p->valid = true; + + //Recover Context + surface = p->recoverSfc; + surface->compositor = p->recoverCmp; + + //Default is alpha blending + if (p->method == CompositeMethod::None) { + return rasterImage(surface, &p->image, nullptr, p->bbox, p->opacity); + } + + return true; +} + + +bool SwRenderer::dispose(RenderData data) +{ + auto task = static_cast(data); + if (!task) return true; + + task->done(); + task->dispose(); + if (task->transform) free(task->transform); + delete(task); + + return true; +} + + +void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array& clips, RenderUpdateFlag flags) +{ + if (!surface) return task; + if (flags == RenderUpdateFlag::None) return task; + + //Finish previous task if it has duplicated request. + task->done(); + + if (clips.count > 0) { + //Guarantee composition targets get ready. + for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) { + static_cast(*clip)->done(); + } + task->clips = clips; + } + + if (transform) { + if (!task->transform) task->transform = static_cast(malloc(sizeof(Matrix))); + *task->transform = transform->m; + } else { + if (task->transform) free(task->transform); + task->transform = nullptr; + } + + task->opacity = opacity; + task->surface = surface; + task->mpool = mpool; + task->flags = flags; + task->bbox.min.x = max(static_cast(0), static_cast(vport.x)); + task->bbox.min.y = max(static_cast(0), static_cast(vport.y)); + task->bbox.max.x = min(static_cast(surface->w), static_cast(vport.x + vport.w)); + task->bbox.max.y = min(static_cast(surface->h), static_cast(vport.y + vport.h)); + + tasks.push(task); + TaskScheduler::request(task); + + return task; +} + + +RenderData SwRenderer::prepare(const Picture& pdata, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) +{ + //prepare task + auto task = static_cast(data); + if (!task) { + task = new SwImageTask; + if (!task) return nullptr; + task->pdata = &pdata; + } + return prepareCommon(task, transform, opacity, clips, flags); +} + + +RenderData SwRenderer::prepare(const Shape& sdata, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) +{ + //prepare task + auto task = static_cast(data); + if (!task) { + task = new SwShapeTask; + if (!task) return nullptr; + task->sdata = &sdata; + } + return prepareCommon(task, transform, opacity, clips, flags); +} + + +SwRenderer::SwRenderer():mpool(globalMpool) +{ +} + + +bool SwRenderer::init(uint32_t threads) +{ + if ((initEngineCnt++) > 0) return true; + + threadsCnt = threads; + + //Share the memory pool among the renderer + globalMpool = mpoolInit(threads); + if (!globalMpool) { + --initEngineCnt; + return false; + } + + return true; +} + + +int32_t SwRenderer::init() +{ + return initEngineCnt; +} + + +bool SwRenderer::term() +{ + if ((--initEngineCnt) > 0) return true; + + initEngineCnt = 0; + + _termEngine(); + + return true; +} + +SwRenderer* SwRenderer::gen() +{ + ++rendererCnt; + return new SwRenderer(); +} diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h new file mode 100644 index 0000000000..51eed5dbd1 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SW_RENDERER_H_ +#define _TVG_SW_RENDERER_H_ + +#include "tvgRender.h" + +struct SwSurface; +struct SwTask; +struct SwCompositor; +struct SwMpool; + +namespace tvg +{ + +class SwRenderer : public RenderMethod +{ +public: + RenderData prepare(const Shape& shape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) override; + RenderData prepare(const Picture& picture, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) override; + bool preRender() override; + bool renderShape(RenderData data) override; + bool renderImage(RenderData data) override; + bool postRender() override; + bool dispose(RenderData data) override; + RenderRegion region(RenderData data) override; + RenderRegion viewport() override; + bool viewport(const RenderRegion& vp) override; + + bool clear() override; + bool sync() override; + bool target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs); + bool mempool(bool shared); + + Compositor* target(const RenderRegion& region) override; + bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) override; + bool endComposite(Compositor* cmp) override; + void clearCompositors(); + + static SwRenderer* gen(); + static bool init(uint32_t threads); + static int32_t init(); + static bool term(); + +private: + SwSurface* surface = nullptr; //active surface + Array tasks; //async task list + Array compositors; //render targets cache list + SwMpool* mpool; //private memory pool + RenderRegion vport; //viewport + + bool sharedMpool = true; //memory-pool behavior policy + + SwRenderer(); + ~SwRenderer(); + + RenderData prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array& clips, RenderUpdateFlag flags); +}; + +} + +#endif /* _TVG_SW_RENDERER_H_ */ diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp new file mode 100644 index 0000000000..b41e48b7ca --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRle.cpp @@ -0,0 +1,1043 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * The FreeType Project LICENSE + * ---------------------------- + + * 2006-Jan-27 + + * Copyright 1996-2002, 2006 by + * David Turner, Robert Wilhelm, and Werner Lemberg + + + + * Introduction + * ============ + + * The FreeType Project is distributed in several archive packages; + * some of them may contain, in addition to the FreeType font engine, + * various tools and contributions which rely on, or relate to, the + * FreeType Project. + + * This license applies to all files found in such packages, and + * which do not fall under their own explicit license. The license + * affects thus the FreeType font engine, the test programs, + * documentation and makefiles, at the very least. + + * This license was inspired by the BSD, Artistic, and IJG + * (Independent JPEG Group) licenses, which all encourage inclusion + * and use of free software in commercial and freeware products + * alike. As a consequence, its main points are that: + + * o We don't promise that this software works. However, we will be + * interested in any kind of bug reports. (`as is' distribution) + + * o You can use this software for whatever you want, in parts or + * full form, without having to pay us. (`royalty-free' usage) + + * o You may not pretend that you wrote this software. If you use + * it, or only parts of it, in a program, you must acknowledge + * somewhere in your documentation that you have used the + * FreeType code. (`credits') + + * We specifically permit and encourage the inclusion of this + * software, with or without modifications, in commercial products. + * We disclaim all warranties covering The FreeType Project and + * assume no liability related to The FreeType Project. + + + * Finally, many people asked us for a preferred form for a + * credit/disclaimer to use in compliance with this license. We thus + * encourage you to use the following text: + + * """ + * Portions of this software are copyright � The FreeType + * Project (www.freetype.org). All rights reserved. + * """ + + * Please replace with the value from the FreeType version you + * actually use. + +* Legal Terms +* =========== + +* 0. Definitions +* -------------- + +* Throughout this license, the terms `package', `FreeType Project', +* and `FreeType archive' refer to the set of files originally +* distributed by the authors (David Turner, Robert Wilhelm, and +* Werner Lemberg) as the `FreeType Project', be they named as alpha, +* beta or final release. + +* `You' refers to the licensee, or person using the project, where +* `using' is a generic term including compiling the project's source +* code as well as linking it to form a `program' or `executable'. +* This program is referred to as `a program using the FreeType +* engine'. + +* This license applies to all files distributed in the original +* FreeType Project, including all source code, binaries and +* documentation, unless otherwise stated in the file in its +* original, unmodified form as distributed in the original archive. +* If you are unsure whether or not a particular file is covered by +* this license, you must contact us to verify this. + +* The FreeType Project is copyright (C) 1996-2000 by David Turner, +* Robert Wilhelm, and Werner Lemberg. All rights reserved except as +* specified below. + +* 1. No Warranty +* -------------- + +* THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY +* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS +* BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO +* USE, OF THE FREETYPE PROJECT. + +* 2. Redistribution +* ----------------- + +* This license grants a worldwide, royalty-free, perpetual and +* irrevocable right and license to use, execute, perform, compile, +* display, copy, create derivative works of, distribute and +* sublicense the FreeType Project (in both source and object code +* forms) and derivative works thereof for any purpose; and to +* authorize others to exercise some or all of the rights granted +* herein, subject to the following conditions: + +* o Redistribution of source code must retain this license file +* (`FTL.TXT') unaltered; any additions, deletions or changes to +* the original files must be clearly indicated in accompanying +* documentation. The copyright notices of the unaltered, +* original files must be preserved in all copies of source +* files. + +* o Redistribution in binary form must provide a disclaimer that +* states that the software is based in part of the work of the +* FreeType Team, in the distribution documentation. We also +* encourage you to put an URL to the FreeType web page in your +* documentation, though this isn't mandatory. + +* These conditions apply to any software derived from or based on +* the FreeType Project, not just the unmodified files. If you use +* our work, you must acknowledge us. However, no fee need be paid +* to us. + +* 3. Advertising +* -------------- + +* Neither the FreeType authors and contributors nor you shall use +* the name of the other for commercial, advertising, or promotional +* purposes without specific prior written permission. + +* We suggest, but do not require, that you use one or more of the +* following phrases to refer to this software in your documentation +* or advertising materials: `FreeType Project', `FreeType Engine', +* `FreeType library', or `FreeType Distribution'. + +* As you have not signed this license, you are not required to +* accept it. However, as the FreeType Project is copyrighted +* material, only this license, or another one contracted with the +* authors, grants you the right to use, distribute, and modify it. +* Therefore, by using, distributing, or modifying the FreeType +* Project, you indicate that you understand and accept all the terms +* of this license. + +* 4. Contacts +* ----------- + +* There are two mailing lists related to FreeType: + +* o freetype@nongnu.org + +* Discusses general use and applications of FreeType, as well as +* future and wanted additions to the library and distribution. +* If you are looking for support, start in this list if you +* haven't found anything to help you in the documentation. + +* o freetype-devel@nongnu.org + +* Discusses bugs, as well as engine internals, design issues, +* specific licenses, porting, etc. + +* Our home page can be found at + +* http://www.freetype.org +*/ + +#include +#include +#include +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +constexpr auto MAX_SPANS = 256; +constexpr auto PIXEL_BITS = 8; //must be at least 6 bits! +constexpr auto ONE_PIXEL = (1L << PIXEL_BITS); + +using Area = long; + +struct Band +{ + SwCoord min, max; +}; + +struct Cell +{ + SwCoord x; + SwCoord cover; + Area area; + Cell *next; +}; + +struct RleWorker +{ + SwRleData* rle; + + SwPoint cellPos; + SwPoint cellMin; + SwPoint cellMax; + SwCoord cellXCnt; + SwCoord cellYCnt; + + Area area; + SwCoord cover; + + Cell* cells; + ptrdiff_t maxCells; + ptrdiff_t cellsCnt; + + SwPoint pos; + + SwPoint bezStack[32 * 3 + 1]; + int levStack[32]; + + SwOutline* outline; + + SwSpan spans[MAX_SPANS]; + int spansCnt; + int ySpan; + + int bandSize; + int bandShoot; + + jmp_buf jmpBuf; + + void* buffer; + long bufferSize; + + Cell** yCells; + SwCoord yCnt; + + bool invalid; + bool antiAlias; +}; + + +static inline SwPoint UPSCALE(const SwPoint& pt) +{ + return {SwCoord(((unsigned long) pt.x) << (PIXEL_BITS - 6)), SwCoord(((unsigned long) pt.y) << (PIXEL_BITS - 6))}; +} + + +static inline SwPoint TRUNC(const SwPoint& pt) +{ + return {pt.x >> PIXEL_BITS, pt.y >> PIXEL_BITS}; +} + + +static inline SwCoord TRUNC(const SwCoord x) +{ + return x >> PIXEL_BITS; +} + + +static inline SwPoint SUBPIXELS(const SwPoint& pt) +{ + return {SwCoord(((unsigned long) pt.x) << PIXEL_BITS), SwCoord(((unsigned long) pt.y) << PIXEL_BITS)}; +} + + +static inline SwCoord SUBPIXELS(const SwCoord x) +{ + return SwCoord(((unsigned long) x) << PIXEL_BITS); +} + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +static inline SwCoord HYPOT(SwPoint pt) +{ + if (pt.x < 0) pt.x = -pt.x; + if (pt.y < 0) pt.y = -pt.y; + return ((pt.x > pt.y) ? (pt.x + (3 * pt.y >> 3)) : (pt.y + (3 * pt.x >> 3))); +} + +static void _genSpan(SwRleData* rle, const SwSpan* spans, uint32_t count) +{ + auto newSize = rle->size + count; + + /* allocate enough memory for new spans */ + /* alloc is required to prevent free and reallocation */ + /* when the rle needs to be regenerated because of attribute change. */ + if (rle->alloc < newSize) { + rle->alloc = (newSize * 2); + //OPTIMIZE: use mempool! + rle->spans = static_cast(realloc(rle->spans, rle->alloc * sizeof(SwSpan))); + } + + //copy the new spans to the allocated memory + SwSpan* lastSpan = rle->spans + rle->size; + memcpy(lastSpan, spans, count * sizeof(SwSpan)); + + rle->size = newSize; +} + + +static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord acount) +{ + x += rw.cellMin.x; + y += rw.cellMin.y; + + //Clip Y range + if (y < rw.cellMin.y || y >= rw.cellMax.y) return; + + /* compute the coverage line's coverage, depending on the outline fill rule */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + auto coverage = static_cast(area >> (PIXEL_BITS * 2 + 1 - 8)); //range 0 - 255 + + if (coverage < 0) coverage = -coverage; + + if (rw.outline->fillRule == FillRule::EvenOdd) { + coverage &= 511; + if (coverage > 255) coverage = 511 - coverage; + } else { + //normal non-zero winding rule + if (coverage > 255) coverage = 255; + } + + //span has ushort coordinates. check limit overflow + if (x >= SHRT_MAX) { + TVGERR("SW_ENGINE", "X-coordiante overflow!"); + x = SHRT_MAX; + } + if (y >= SHRT_MAX) { + TVGERR("SW_ENGINE", "Y Coordiante overflow!"); + y = SHRT_MAX; + } + + if (coverage > 0) { + if (!rw.antiAlias) coverage = 255; + auto count = rw.spansCnt; + auto span = rw.spans + count - 1; + + //see whether we can add this span to the current list + if ((count > 0) && (rw.ySpan == y) && + (span->x + span->len == x) && (span->coverage == coverage)) { + + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x < rw.cellMin.x) xOver -= (rw.cellMin.x - x); + + //span->len += (acount + xOver) - 1; + span->len += (acount + xOver); + return; + } + + if (count >= MAX_SPANS) { + _genSpan(rw.rle, rw.spans, count); + rw.spansCnt = 0; + rw.ySpan = 0; + span = rw.spans; + } else { + ++span; + } + + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x < rw.cellMin.x) { + xOver -= (rw.cellMin.x - x); + x = rw.cellMin.x; + } + + //Nothing to draw + if (acount + xOver <= 0) return; + + //add a span to the current list + span->x = x; + span->y = y; + span->len = (acount + xOver); + span->coverage = coverage; + ++rw.spansCnt; + rw.ySpan = y; + } +} + + +static void _sweep(RleWorker& rw) +{ + if (rw.cellsCnt == 0) return; + + rw.spansCnt = 0; + rw.ySpan = 0; + + for (int y = 0; y < rw.yCnt; ++y) { + auto cover = 0; + auto x = 0; + auto cell = rw.yCells[y]; + + while (cell) { + if (cell->x > x && cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), cell->x - x); + cover += cell->cover; + auto area = cover * (ONE_PIXEL * 2) - cell->area; + if (area != 0 && cell->x >= 0) _horizLine(rw, cell->x, y, area, 1); + x = cell->x + 1; + cell = cell->next; + } + + if (cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), rw.cellXCnt - x); + } + + if (rw.spansCnt > 0) _genSpan(rw.rle, rw.spans, rw.spansCnt); +} + + +static Cell* _findCell(RleWorker& rw) +{ + auto x = rw.cellPos.x; + if (x > rw.cellXCnt) x = rw.cellXCnt; + + auto pcell = &rw.yCells[rw.cellPos.y]; + + while(true) { + Cell* cell = *pcell; + if (!cell || cell->x > x) break; + if (cell->x == x) return cell; + pcell = &cell->next; + } + + if (rw.cellsCnt >= rw.maxCells) longjmp(rw.jmpBuf, 1); + + auto cell = rw.cells + rw.cellsCnt++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + cell->next = *pcell; + *pcell = cell; + + return cell; +} + + +static void _recordCell(RleWorker& rw) +{ + if (rw.area | rw.cover) { + auto cell = _findCell(rw); + cell->area += rw.area; + cell->cover += rw.cover; + } +} + + +static void _setCell(RleWorker& rw, SwPoint pos) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the + min_ex - 1 horizontal position. */ + pos.x -= rw.cellMin.x; + pos.y -= rw.cellMin.y; + + if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + + //Are we moving to a different cell? + if (pos != rw.cellPos) { + //Record the current one if it is valid + if (!rw.invalid) _recordCell(rw); + } + + rw.area = 0; + rw.cover = 0; + rw.cellPos = pos; + rw.invalid = ((unsigned)pos.y >= (unsigned)rw.cellYCnt || pos.x >= rw.cellXCnt); +} + + +static void _startCell(RleWorker& rw, SwPoint pos) +{ + if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + if (pos.x < rw.cellMin.x) pos.x = rw.cellMin.x; + + rw.area = 0; + rw.cover = 0; + rw.cellPos = pos - rw.cellMin; + rw.invalid = false; + + _setCell(rw, pos); +} + + +static void _moveTo(RleWorker& rw, const SwPoint& to) +{ + //record current cell, if any */ + if (!rw.invalid) _recordCell(rw); + + //start to a new position + _startCell(rw, TRUNC(to)); + + rw.pos = to; +} + + +static void _lineTo(RleWorker& rw, const SwPoint& to) +{ +#define SW_UDIV(a, b) \ + static_cast(((unsigned long)(a) * (unsigned long)(b)) >> \ + (sizeof(long) * CHAR_BIT - PIXEL_BITS)) + + auto e1 = TRUNC(rw.pos); + auto e2 = TRUNC(to); + + //vertical clipping + if ((e1.y >= rw.cellMax.y && e2.y >= rw.cellMax.y) || (e1.y < rw.cellMin.y && e2.y < rw.cellMin.y)) { + rw.pos = to; + return; + } + + auto diff = to - rw.pos; + auto f1 = rw.pos - SUBPIXELS(e1); + SwPoint f2; + + //inside one cell + if (e1 == e2) { + ; + //any horizontal line + } else if (diff.y == 0) { + e1.x = e2.x; + _setCell(rw, e1); + } else if (diff.x == 0) { + //vertical line up + if (diff.y > 0) { + do { + f2.y = ONE_PIXEL; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = 0; + ++e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + //vertical line down + } else { + do { + f2.y = 0; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = ONE_PIXEL; + --e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + } + //any other line + } else { + Area prod = diff.x * f1.y - diff.y * f1.x; + + /* These macros speed up repetitive divisions by replacing them + with multiplications and right shifts. */ + auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); + auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + auto px = diff.x * ONE_PIXEL; + auto py = diff.y * ONE_PIXEL; + + //left + if (prod <= 0 && prod - px > 0) { + f2 = {0, SW_UDIV(-prod, -dx_r)}; + prod -= py; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {ONE_PIXEL, f2.y}; + --e1.x; + //up + } else if (prod - px <= 0 && prod - px + py > 0) { + prod -= px; + f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, 0}; + ++e1.y; + //right + } else if (prod - px + py <= 0 && prod + py >= 0) { + prod += py; + f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {0, f2.y}; + ++e1.x; + //down + } else { + f2 = {SW_UDIV(prod, -dy_r), 0}; + prod += px; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, ONE_PIXEL}; + --e1.y; + } + + _setCell(rw, e1); + + } while(e1 != e2); + } + + f2 = {to.x - SUBPIXELS(e2.x), to.y - SUBPIXELS(e2.y)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + rw.pos = to; +} + + +static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + auto arc = rw.bezStack; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = rw.pos; + + //Short-cut the arc that crosses the current band + auto min = arc[0].y; + auto max = arc[0].y; + + SwCoord y; + for (auto i = 1; i < 4; ++i) { + y = arc[i].y; + if (y < min) min = y; + if (y > max) max = y; + } + + if (TRUNC(min) >= rw.cellMax.y || TRUNC(max) < rw.cellMin.y) goto draw; + + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ + while (true) { + { + //diff is the P0 - P3 chord vector + auto diff = arc[3] - arc[0]; + auto L = HYPOT(diff); + + //avoid possible arithmetic overflow below by splitting + if (L > SHRT_MAX) goto split; + + //max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1) + auto sLimit = L * (ONE_PIXEL / 6); + + auto diff1 = arc[1] - arc[0]; + auto s = diff.y * diff1.x - diff.x * diff1.y; + if (s < 0) s = -s; + if (s > sLimit) goto split; + + //s is L * the perpendicular distance from P2 to the line P0 - P3 + auto diff2 = arc[2] - arc[0]; + s = diff.y * diff2.x - diff.x * diff2.y; + if (s < 0) s = -s; + if (s > sLimit) goto split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products */ + if (diff1.x * (diff1.x - diff.x) + diff1.y * (diff1.y - diff.y) > 0 || + diff2.x * (diff2.x - diff.x) + diff2.y * (diff2.y - diff.y) > 0) + goto split; + + //no reason to split + goto draw; + } + split: + mathSplitCubic(arc); + arc += 3; + continue; + + draw: + _lineTo(rw, arc[0]); + if (arc == rw.bezStack) return; + arc -= 3; + } +} + + +static bool _decomposeOutline(RleWorker& rw) +{ + auto outline = rw.outline; + auto first = 0; //index of first point in contour + + for (uint32_t n = 0; n < outline->cntrsCnt; ++n) { + auto last = outline->cntrs[n]; + auto limit = outline->pts + last; + auto start = UPSCALE(outline->pts[first]); + auto pt = outline->pts + first; + auto types = outline->types + first; + + /* A contour cannot start with a cubic control point! */ + if (types[0] == SW_CURVE_TYPE_CUBIC) goto invalid_outline; + + _moveTo(rw, UPSCALE(outline->pts[first])); + + while (pt < limit) { + ++pt; + ++types; + + //emit a single line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(rw, UPSCALE(*pt)); + //types cubic + } else { + if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) + goto invalid_outline; + + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); + continue; + } + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); + goto close; + } + } + _lineTo(rw, start); + close: + first = last + 1; + } + + return true; + +invalid_outline: + TVGERR("SW_ENGINE", "Invalid Outline!"); + return false; +} + + +static int _genRle(RleWorker& rw) +{ + if (setjmp(rw.jmpBuf) == 0) { + auto ret = _decomposeOutline(rw); + if (!rw.invalid) _recordCell(rw); + if (ret) return 0; //success + else return 1; //fail + } + return -1; //lack of cell memory +} + + +SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt) +{ + auto out = outSpans; + auto spans = targetRle->spans; + auto end = targetRle->spans + targetRle->size; + auto clipSpans = clip->spans; + auto clipEnd = clip->spans + clip->size; + + while (spanCnt > 0 && spans < end) { + if (clipSpans == clipEnd) { + spans = end; + break; + } + if (clipSpans->y > spans->y) { + ++spans; + continue; + } + if (spans->y != clipSpans->y) { + ++clipSpans; + continue; + } + auto sx1 = spans->x; + auto sx2 = sx1 + spans->len; + auto cx1 = clipSpans->x; + auto cx2 = cx1 + clipSpans->len; + + if (cx1 < sx1 && cx2 < sx1) { + ++clipSpans; + continue; + } + else if (sx1 < cx1 && sx2 < cx1) { + ++spans; + continue; + } + auto x = sx1 > cx1 ? sx1 : cx1; + auto len = (sx2 < cx2 ? sx2 : cx2) - x; + if (len) { + auto spansCorverage = spans->coverage; + auto clipSpansCoverage = clipSpans->coverage; + out->x = sx1 > cx1 ? sx1 : cx1; + out->len = (sx2 < cx2 ? sx2 : cx2) - out->x; + out->y = spans->y; + out->coverage = (uint8_t)(((spansCorverage * clipSpansCoverage) + 0xff) >> 8); + ++out; + --spanCnt; + } + if (sx2 < cx2) ++spans; + else ++clipSpans; + } + return out; +} + + +SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt) +{ + auto out = outSpans; + auto spans = targetRle->spans; + auto end = targetRle->spans + targetRle->size; + auto minx = static_cast(bbox->min.x); + auto miny = static_cast(bbox->min.y); + auto maxx = minx + static_cast(bbox->max.x - bbox->min.x) - 1; + auto maxy = miny + static_cast(bbox->max.y - bbox->min.y) - 1; + + while (spanCnt && spans < end) { + if (spans->y > maxy) { + spans = end; + break; + } + if (spans->y < miny || spans->x > maxx || spans->x + spans->len <= minx) { + ++spans; + continue; + } + if (spans->x < minx) { + out->len = (spans->len - (minx - spans->x)) < (maxx - minx + 1) ? (spans->len - (minx - spans->x)) : (maxx - minx + 1); + out->x = minx; + } + else { + out->x = spans->x; + out->len = spans->len < (maxx - spans->x + 1) ? spans->len : (maxx - spans->x + 1); + } + if (out->len != 0) { + out->y = spans->y; + out->coverage = spans->coverage; + ++out; + } + ++spans; + --spanCnt; + } + return out; +} + + +void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size) +{ + free(rle->spans); + rle->spans = clippedSpans; + rle->size = rle->alloc = size; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias) +{ + constexpr auto RENDER_POOL_SIZE = 16384L; + constexpr auto BAND_SIZE = 40; + + //TODO: We can preserve several static workers in advance + RleWorker rw; + Cell buffer[RENDER_POOL_SIZE / sizeof(Cell)]; + + //Init Cells + rw.buffer = buffer; + rw.bufferSize = sizeof(buffer); + rw.yCells = reinterpret_cast(buffer); + rw.cells = nullptr; + rw.maxCells = 0; + rw.cellsCnt = 0; + rw.area = 0; + rw.cover = 0; + rw.invalid = true; + rw.cellMin = renderRegion.min; + rw.cellMax = renderRegion.max; + rw.cellXCnt = rw.cellMax.x - rw.cellMin.x; + rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; + rw.ySpan = 0; + rw.outline = const_cast(outline); + rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 + rw.bandShoot = 0; + rw.antiAlias = antiAlias; + + if (!rle) rw.rle = reinterpret_cast(calloc(1, sizeof(SwRleData))); + else rw.rle = rle; + + //Generate RLE + Band bands[BAND_SIZE]; + Band* band; + + /* set up vertical bands */ + auto bandCnt = static_cast((rw.cellMax.y - rw.cellMin.y) / rw.bandSize); + if (bandCnt == 0) bandCnt = 1; + else if (bandCnt >= BAND_SIZE) bandCnt = (BAND_SIZE - 1); + + auto min = rw.cellMin.y; + auto yMax = rw.cellMax.y; + SwCoord max; + int ret; + + for (int n = 0; n < bandCnt; ++n, min = max) { + max = min + rw.bandSize; + if (n == bandCnt -1 || max > yMax) max = yMax; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while (band >= bands) { + rw.yCells = static_cast(rw.buffer); + rw.yCnt = band->max - band->min; + + int cellStart = sizeof(Cell*) * (int)rw.yCnt; + int cellMod = cellStart % sizeof(Cell); + + if (cellMod > 0) cellStart += sizeof(Cell) - cellMod; + + auto cellEnd = rw.bufferSize; + cellEnd -= cellEnd % sizeof(Cell); + + auto cellsMax = reinterpret_cast((char*)rw.buffer + cellEnd); + rw.cells = reinterpret_cast((char*)rw.buffer + cellStart); + + if (rw.cells >= cellsMax) goto reduce_bands; + + rw.maxCells = cellsMax - rw.cells; + if (rw.maxCells < 2) goto reduce_bands; + + for (int y = 0; y < rw.yCnt; ++y) + rw.yCells[y] = nullptr; + + rw.cellsCnt = 0; + rw.invalid = true; + rw.cellMin.y = band->min; + rw.cellMax.y = band->max; + rw.cellYCnt = band->max - band->min; + + ret = _genRle(rw); + if (ret == 0) { + _sweep(rw); + --band; + continue; + } else if (ret == 1) { + goto error; + } + + reduce_bands: + /* render pool overflow: we will reduce the render band by half */ + auto bottom = band->min; + auto top = band->max; + auto middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must + be some problems */ + if (middle == bottom) goto error; + + if (bottom - top >= rw.bandSize) ++rw.bandShoot; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + ++band; + } + } + + if (rw.bandShoot > 8 && rw.bandSize > 16) + rw.bandSize = (rw.bandSize >> 1); + + return rw.rle; + +error: + free(rw.rle); + rw.rle = nullptr; + return nullptr; +} + + +void rleReset(SwRleData* rle) +{ + if (!rle) return; + rle->size = 0; +} + + +void rleFree(SwRleData* rle) +{ + if (!rle) return; + if (rle->spans) free(rle->spans); + free(rle); +} + + +void rleClipPath(SwRleData *rle, const SwRleData *clip) +{ + if (rle->size == 0 || clip->size == 0) return; + auto spanCnt = rle->size > clip->size ? rle->size : clip->size; + auto spans = static_cast(malloc(sizeof(SwSpan) * (spanCnt))); + if (!spans) return; + auto spansEnd = _intersectSpansRegion(clip, rle, spans, spanCnt); + + _replaceClipSpan(rle, spans, spansEnd - spans); + + TVGLOG("SW_ENGINE", "Using ClipPath!"); +} + + +void rleClipRect(SwRleData *rle, const SwBBox* clip) +{ + if (rle->size == 0) return; + auto spans = static_cast(malloc(sizeof(SwSpan) * (rle->size))); + if (!spans) return; + auto spansEnd = _intersectSpansRect(clip, rle, spans, rle->size); + + _replaceClipSpan(rle, spans, spansEnd - spans); + + TVGLOG("SW_ENGINE", "Using ClipRect!"); +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp new file mode 100644 index 0000000000..29f559081d --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwShape.cpp @@ -0,0 +1,668 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgSwCommon.h" +#include "tvgBezier.h" +#include +#include + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Line +{ + Point pt1; + Point pt2; +}; + + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +static void _lineSplitAt(const Line& cur, float at, Line& left, Line& right) +{ + auto len = _lineLength(cur.pt1, cur.pt2); + auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at; + auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at; + left.pt1 = cur.pt1; + left.pt2.x = left.pt1.x + dx; + left.pt2.y = left.pt1.y + dy; + right.pt1 = left.pt2; + right.pt2 = cur.pt2; +} + + +static bool _growOutlineContour(SwOutline& outline, uint32_t n) +{ + if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return false; + outline.reservedCntrsCnt = outline.cntrsCnt + n; + outline.cntrs = static_cast(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint16_t))); + return true; +} + + +static void _reserveOutlineClose(SwOutline& outline) +{ + //Dash outlines are always opened. + //Only normal outlines use this information, it sholud be same to their contour counts. + if (outline.closed) free(outline.closed); + outline.closed = static_cast(calloc(outline.reservedCntrsCnt, sizeof(bool))); +} + + +static void _resetOutlineClose(SwOutline& outline) +{ + memset(outline.closed, 0x0, outline.reservedCntrsCnt * sizeof(bool)); +} + + +static void _growOutlinePoint(SwOutline& outline, uint32_t n) +{ + if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; + outline.reservedPtsCnt = outline.ptsCnt + n; + outline.pts = static_cast(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint))); + outline.types = static_cast(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t))); +} + + +static void _outlineEnd(SwOutline& outline) +{ + if (outline.ptsCnt == 0) return; + + _growOutlineContour(outline, 1); + outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1; + ++outline.cntrsCnt; +} + + +static void _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(outline, 1); + + outline.pts[outline.ptsCnt] = mathTransform(to, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + + if (outline.ptsCnt > 0) { + _growOutlineContour(outline, 1); + outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1; + ++outline.cntrsCnt; + } + + ++outline.ptsCnt; +} + + +static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(outline, 1); + + outline.pts[outline.ptsCnt] = mathTransform(to, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline.ptsCnt; +} + + +static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(outline, 3); + + outline.pts[outline.ptsCnt] = mathTransform(ctrl1, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC; + ++outline.ptsCnt; + + outline.pts[outline.ptsCnt] = mathTransform(ctrl2, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC; + ++outline.ptsCnt; + + outline.pts[outline.ptsCnt] = mathTransform(to, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline.ptsCnt; +} + + +static void _outlineClose(SwOutline& outline) +{ + uint32_t i = 0; + + if (outline.cntrsCnt > 0) { + i = outline.cntrs[outline.cntrsCnt - 1] + 1; + } else { + i = 0; //First Path + } + + //Make sure there is at least one point in the current path + if (outline.ptsCnt == i) return; + + //Close the path + _growOutlinePoint(outline, 1); + + outline.pts[outline.ptsCnt] = outline.pts[i]; + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline.ptsCnt; + outline.closed[outline.cntrsCnt] = true; +} + + +static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Line cur = {dash.ptCur, *to}; + auto len = _lineLength(cur.pt1, cur.pt2); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + _outlineLineTo(*dash.outline, to, transform); + } + } else { + while (len > dash.curLen) { + len -= dash.curLen; + Line left, right; + _lineSplitAt(cur, dash.curLen, left, right);; + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.pt1, transform); + _outlineLineTo(*dash.outline, &left.pt2, transform); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = cur.pt1; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.pt1, transform); + _outlineLineTo(*dash.outline, &cur.pt2, transform); + } + if (dash.curLen < 1 && TO_SWCOORD(len) > 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to}; + auto len = bezLength(cur); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform); + } + } else { + while (len > dash.curLen) { + Bezier left, right; + len -= dash.curLen; + bezSplitAt(cur, dash.curLen, left, right); + if (!dash.curOpGap) { + // leftovers from a previous command don't require moveTo + if (dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) { + _outlineMoveTo(*dash.outline, &left.start, transform); + } + _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform); + } + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = right.start; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.start, transform); + _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform); + } + if (dash.curLen < 1 && TO_SWCOORD(len) > 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static SwOutline* _genDashOutline(const Shape* sdata, const Matrix* transform) +{ + const PathCommand* cmds = nullptr; + auto cmdCnt = sdata->pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = sdata->pathCoords(&pts); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + + SwDashStroke dash; + dash.curIdx = 0; + dash.curLen = 0; + dash.ptStart = {0, 0}; + dash.ptCur = {0, 0}; + dash.curOpGap = false; + + const float* pattern; + dash.cnt = sdata->strokeDash(&pattern); + if (dash.cnt == 0) return nullptr; + + //OPTMIZE ME: Use mempool??? + dash.pattern = const_cast(pattern); + dash.outline = static_cast(calloc(1, sizeof(SwOutline))); + + //smart reservation + auto outlinePtsCnt = 0; + auto outlineCntrsCnt = 0; + + for (uint32_t i = 0; i < cmdCnt; ++i) { + switch(*(cmds + i)) { + case PathCommand::Close: { + ++outlinePtsCnt; + break; + } + case PathCommand::MoveTo: { + ++outlineCntrsCnt; + ++outlinePtsCnt; + break; + } + case PathCommand::LineTo: { + ++outlinePtsCnt; + break; + } + case PathCommand::CubicTo: { + outlinePtsCnt += 3; + break; + } + } + } + + ++outlinePtsCnt; //for close + ++outlineCntrsCnt; //for end + + //No idea exact count.... Reserve Approximitely 20x... + _growOutlinePoint(*dash.outline, outlinePtsCnt * 20); + _growOutlineContour(*dash.outline, outlineCntrsCnt * 20); + + while (cmdCnt-- > 0) { + switch(*cmds) { + case PathCommand::Close: { + _dashLineTo(dash, &dash.ptStart, transform); + break; + } + case PathCommand::MoveTo: { + //reset the dash + dash.curIdx = 0; + dash.curLen = *dash.pattern; + dash.curOpGap = false; + dash.ptStart = dash.ptCur = *pts; + ++pts; + break; + } + case PathCommand::LineTo: { + _dashLineTo(dash, pts, transform); + ++pts; + break; + } + case PathCommand::CubicTo: { + _dashCubicTo(dash, pts, pts + 1, pts + 2, transform); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*dash.outline); + + return dash.outline; +} + + +static bool _axisAlignedRect(const SwOutline* outline) +{ + //Fast Track: axis-aligned rectangle? + if (outline->ptsCnt != 5) return false; + + auto pt1 = outline->pts + 0; + auto pt2 = outline->pts + 1; + auto pt3 = outline->pts + 2; + auto pt4 = outline->pts + 3; + + auto a = SwPoint{pt1->x, pt3->y}; + auto b = SwPoint{pt3->x, pt1->y}; + + if ((*pt2 == a && *pt4 == b) || (*pt2 == b && *pt4 == a)) return true; + + return false; +} + + + +static bool _genOutline(SwShape* shape, const Shape* sdata, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite) +{ + const PathCommand* cmds = nullptr; + auto cmdCnt = sdata->pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = sdata->pathCoords(&pts); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return false; + + //smart reservation + auto outlinePtsCnt = 0; + auto outlineCntrsCnt = 0; + auto closeCnt = 0; + + for (uint32_t i = 0; i < cmdCnt; ++i) { + switch(*(cmds + i)) { + case PathCommand::Close: { + ++outlinePtsCnt; + ++closeCnt; + break; + } + case PathCommand::MoveTo: { + ++outlineCntrsCnt; + ++outlinePtsCnt; + break; + } + case PathCommand::LineTo: { + ++outlinePtsCnt; + break; + } + case PathCommand::CubicTo: { + outlinePtsCnt += 3; + break; + } + } + } + + if (static_cast(outlinePtsCnt - closeCnt) > ptsCnt) { + TVGERR("SW_ENGINE", "Wrong a pair of the commands & points - required(%d), current(%d)", outlinePtsCnt - closeCnt, ptsCnt); + return false; + } + + ++outlinePtsCnt; //for close + ++outlineCntrsCnt; //for end + + shape->outline = mpoolReqOutline(mpool, tid); + auto outline = shape->outline; + + _growOutlinePoint(*outline, outlinePtsCnt); + + if (_growOutlineContour(*outline, outlineCntrsCnt)) { + _reserveOutlineClose(*outline); + } else { + _resetOutlineClose(*outline); + } + + //Generate Outlines + while (cmdCnt-- > 0) { + switch(*cmds) { + case PathCommand::Close: { + _outlineClose(*outline); + break; + } + case PathCommand::MoveTo: { + _outlineMoveTo(*outline, pts, transform); + ++pts; + break; + } + case PathCommand::LineTo: { + _outlineLineTo(*outline, pts, transform); + ++pts; + break; + } + case PathCommand::CubicTo: { + _outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*outline); + + outline->fillRule = sdata->fillRule(); + shape->outline = outline; + + shape->fastTrack = (!hasComposite && _axisAlignedRect(shape->outline)); + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool shapePrepare(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite) +{ + if (!_genOutline(shape, sdata, transform, mpool, tid, hasComposite)) return false; + if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false; + + //Keep it for Rasterization Region + shape->bbox = renderRegion; + + //Check valid region + if (renderRegion.max.x - renderRegion.min.x < 1 && renderRegion.max.y - renderRegion.min.y < 1) return false; + + //Check boundary + if (renderRegion.min.x >= clipRegion.max.x || renderRegion.min.y >= clipRegion.max.y || + renderRegion.max.x <= clipRegion.min.x || renderRegion.max.y <= clipRegion.min.y) return false; + + return true; +} + + +bool shapePrepared(const SwShape* shape) +{ + return shape->rle ? true : false; +} + + +bool shapeGenRle(SwShape* shape, TVG_UNUSED const Shape* sdata, bool antiAlias) +{ + //FIXME: Should we draw it? + //Case: Stroke Line + //if (shape.outline->opened) return true; + + //Case A: Fast Track Rectangle Drawing + if (shape->fastTrack) return true; + + //Case B: Normal Shape RLE Drawing + if ((shape->rle = rleRender(shape->rle, shape->outline, shape->bbox, antiAlias))) return true; + + return false; +} + + +void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid) +{ + mpoolRetOutline(mpool, tid); + shape->outline = nullptr; +} + + +void shapeReset(SwShape* shape) +{ + rleReset(shape->rle); + rleReset(shape->strokeRle); + shape->fastTrack = false; + shape->bbox.reset(); +} + + +void shapeFree(SwShape* shape) +{ + rleFree(shape->rle); + shapeDelFill(shape); + + if (shape->stroke) { + rleFree(shape->strokeRle); + strokeFree(shape->stroke); + } +} + + +void shapeDelStroke(SwShape* shape) +{ + if (!shape->stroke) return; + rleFree(shape->strokeRle); + shape->strokeRle = nullptr; + strokeFree(shape->stroke); + shape->stroke = nullptr; +} + + +void shapeResetStroke(SwShape* shape, const Shape* sdata, const Matrix* transform) +{ + if (!shape->stroke) shape->stroke = static_cast(calloc(1, sizeof(SwStroke))); + auto stroke = shape->stroke; + if (!stroke) return; + + strokeReset(stroke, sdata, transform); + rleReset(shape->strokeRle); +} + + +bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid) +{ + SwOutline* shapeOutline = nullptr; + SwOutline* strokeOutline = nullptr; + bool freeOutline = false; + bool ret = true; + + //Dash Style Stroke + if (sdata->strokeDash(nullptr) > 0) { + shapeOutline = _genDashOutline(sdata, transform); + if (!shapeOutline) return false; + freeOutline = true; + //Normal Style stroke + } else { + if (!shape->outline) { + if (!_genOutline(shape, sdata, transform, mpool, tid, false)) return false; + } + shapeOutline = shape->outline; + } + + if (!strokeParseOutline(shape->stroke, *shapeOutline)) { + ret = false; + goto fail; + } + + strokeOutline = strokeExportOutline(shape->stroke, mpool, tid); + if (!strokeOutline) { + ret = false; + goto fail; + } + + if (!mathUpdateOutlineBBox(strokeOutline, clipRegion, renderRegion, false)) { + ret = false; + goto fail; + } + + shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderRegion, true); + +fail: + if (freeOutline) { + if (shapeOutline->cntrs) free(shapeOutline->cntrs); + if (shapeOutline->pts) free(shapeOutline->pts); + if (shapeOutline->types) free(shapeOutline->types); + if (shapeOutline->closed) free(shapeOutline->closed); + free(shapeOutline); + } + mpoolRetStrokeOutline(mpool, tid); + + return ret; +} + + +bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable) +{ + return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable); +} + + +bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable) +{ + return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable); +} + + +void shapeResetFill(SwShape* shape) +{ + if (!shape->fill) { + shape->fill = static_cast(calloc(1, sizeof(SwFill))); + if (!shape->fill) return; + } + fillReset(shape->fill); +} + + +void shapeResetStrokeFill(SwShape* shape) +{ + if (!shape->stroke->fill) { + shape->stroke->fill = static_cast(calloc(1, sizeof(SwFill))); + if (!shape->stroke->fill) return; + } + fillReset(shape->stroke->fill); +} + + +void shapeDelFill(SwShape* shape) +{ + if (!shape->fill) return; + fillFree(shape->fill); + shape->fill = nullptr; +} + + +void shapeDelStrokeFill(SwShape* shape) +{ + if (!shape->stroke->fill) return; + fillFree(shape->stroke->fill); + shape->stroke->fill = nullptr; +} diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp new file mode 100644 index 0000000000..c0cfc1be26 --- /dev/null +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwStroke.cpp @@ -0,0 +1,932 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static constexpr auto SW_STROKE_TAG_POINT = 1; +static constexpr auto SW_STROKE_TAG_CUBIC = 2; +static constexpr auto SW_STROKE_TAG_BEGIN = 4; +static constexpr auto SW_STROKE_TAG_END = 8; + +static inline SwFixed SIDE_TO_ROTATE(const int32_t s) +{ + return (SW_ANGLE_PI2 - static_cast(s) * SW_ANGLE_PI); +} + + +static inline void SCALE(const SwStroke& stroke, SwPoint& pt) +{ + pt.x = static_cast(pt.x * stroke.sx); + pt.y = static_cast(pt.y * stroke.sy); +} + + +static void _growBorder(SwStrokeBorder* border, uint32_t newPts) +{ + auto maxOld = border->maxPts; + auto maxNew = border->ptsCnt + newPts; + + if (maxNew <= maxOld) return; + + auto maxCur = maxOld; + + while (maxCur < maxNew) + maxCur += (maxCur >> 1) + 16; + //OPTIMIZE: use mempool! + border->pts = static_cast(realloc(border->pts, maxCur * sizeof(SwPoint))); + border->tags = static_cast(realloc(border->tags, maxCur * sizeof(uint8_t))); + border->maxPts = maxCur; +} + + +static void _borderClose(SwStrokeBorder* border, bool reverse) +{ + auto start = border->start; + auto count = border->ptsCnt; + + //Don't record empty paths! + if (count <= start + 1U) { + border->ptsCnt = start; + } else { + /* Copy the last point to the start of this sub-path, + since it contains the adjusted starting coordinates */ + border->ptsCnt = --count; + border->pts[start] = border->pts[count]; + + if (reverse) { + //reverse the points + auto pt1 = border->pts + start + 1; + auto pt2 = border->pts + count - 1; + + while (pt1 < pt2) { + auto tmp = *pt1; + *pt1 = *pt2; + *pt2 = tmp; + ++pt1; + --pt2; + } + + //reverse the tags + auto tag1 = border->tags + start + 1; + auto tag2 = border->tags + count - 1; + + while (tag1 < tag2) { + auto tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + ++tag1; + --tag2; + } + } + + border->tags[start] |= SW_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_STROKE_TAG_END; + } + + border->start = -1; + border->movable = false; +} + + +static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + _growBorder(border, 3); + + auto pt = border->pts + border->ptsCnt; + auto tag = border->tags + border->ptsCnt; + + pt[0] = ctrl1; + pt[1] = ctrl2; + pt[2] = to; + + tag[0] = SW_STROKE_TAG_CUBIC; + tag[1] = SW_STROKE_TAG_CUBIC; + tag[2] = SW_STROKE_TAG_POINT; + + border->ptsCnt += 3; + + border->movable = false; +} + + +static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke) +{ + constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2; + SwPoint a = {static_cast(radius), 0}; + mathRotate(a, angleStart); + SCALE(stroke, a); + a += center; + + auto total = angleDiff; + auto angle = angleStart; + auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2; + + while (total != 0) { + auto step = total; + if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE; + else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE; + + auto next = angle + step; + auto theta = step; + if (theta < 0) theta = -theta; + + theta >>= 1; + + //compute end point + SwPoint b = {static_cast(radius), 0}; + mathRotate(b, next); + SCALE(stroke, b); + b += center; + + //compute first and second control points + auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3); + + SwPoint a2 = {static_cast(length), 0}; + mathRotate(a2, angle + rotate); + SCALE(stroke, a2); + a2 += a; + + SwPoint b2 = {static_cast(length), 0}; + mathRotate(b2, next - rotate); + SCALE(stroke, b2); + b2 += b; + + //add cubic arc + _borderCubicTo(border, a2, b2, b); + + //process the rest of the arc? + a = b; + total -= step; + angle = next; + } +} + + +static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable) +{ + if (border->movable) { + //move last point + border->pts[border->ptsCnt - 1] = to; + } else { + + //don't add zero-length line_to + if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return; + + _growBorder(border, 1); + border->pts[border->ptsCnt] = to; + border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT; + border->ptsCnt += 1; + } + + border->movable = movable; +} + + +static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to) +{ + //close current open path if any? + if (border->start >= 0) _borderClose(border, false); + + border->start = border->ptsCnt; + border->movable = false; + + _borderLineTo(border, to, false); +} + + +static void _arcTo(SwStroke& stroke, int32_t side) +{ + auto border = stroke.borders + side; + auto rotate = SIDE_TO_ROTATE(side); + auto total = mathDiff(stroke.angleIn, stroke.angleOut); + if (total == SW_ANGLE_PI) total = -rotate * 2; + + _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke); + border->movable = false; +} + + +static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) +{ + constexpr SwFixed MITER_LIMIT = 4 * (1 << 16); + + auto border = stroke.borders + side; + + if (stroke.join == StrokeJoin::Round) { + _arcTo(stroke, side); + } else { + //this is a mitered (pointed) or beveled (truncated) corner + auto rotate = SIDE_TO_ROTATE(side); + auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false; + SwFixed phi = 0; + SwFixed thcos = 0; + + if (!bevel) { + auto theta = mathDiff(stroke.angleIn, stroke.angleOut); + if (theta == SW_ANGLE_PI) { + theta = rotate; + phi = stroke.angleIn; + } else { + theta /= 2; + phi = stroke.angleIn + theta + rotate; + } + + thcos = mathCos(theta); + auto sigma = mathMultiply(MITER_LIMIT, thcos); + + //is miter limit exceeded? + if (sigma < 0x10000L) bevel = true; + } + + //this is a bevel (broken angle) + if (bevel) { + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + border->movable = false; + _borderLineTo(border, delta, false); + //this is a miter (intersection) + } else { + auto length = mathDivide(stroke.width, thcos); + SwPoint delta = {static_cast(length), 0}; + mathRotate(delta, phi); + SCALE(stroke, delta); + delta += stroke.center; + _borderLineTo(border, delta, false); + + /* Now add and end point + Only needed if not lineto (lineLength is zero for curves) */ + if (lineLength == 0) { + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + _borderLineTo(border, delta, false); + } + } + } +} + + +static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength) +{ + auto border = stroke.borders + side; + auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2; + SwPoint delta; + bool intersect = false; + + /* Only intersect borders if between two line_to's and both + lines are long enough (line length is zero fur curves). */ + if (border->movable && lineLength > 0) { + //compute minimum required length of lines + SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta))); + if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true; + } + + auto rotate = SIDE_TO_ROTATE(side); + + if (!intersect) { + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + border->movable = false; + } else { + //compute median angle + auto phi = stroke.angleIn + theta; + auto thcos = mathCos(theta); + delta = {static_cast(mathDivide(stroke.width, thcos)), 0}; + mathRotate(delta, phi + rotate); + SCALE(stroke, delta); + delta += stroke.center; + } + + _borderLineTo(border, delta, false); +} + + +void _processCorner(SwStroke& stroke, SwFixed lineLength) +{ + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); + + //no specific corner processing is required if the turn is 0 + if (turn == 0) return; + + //when we turn to the right, the inside side is 0 + int32_t inside = 0; + + //otherwise, the inside is 1 + if (turn < 0) inside = 1; + + //process the inside + _inside(stroke, inside, lineLength); + + //process the outside + _outside(stroke, 1 - inside, lineLength); +} + + +void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) +{ + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, startAngle + SW_ANGLE_PI2); + SCALE(stroke, delta); + + auto pt = stroke.center + delta; + auto border = stroke.borders; + _borderMoveTo(border, pt); + + pt = stroke.center - delta; + ++border; + _borderMoveTo(border, pt); + + /* Save angle, position and line length for last join + lineLength is zero for curves */ + stroke.subPathAngle = startAngle; + stroke.firstPt = false; + stroke.subPathLineLength = lineLength; +} + + +static void _lineTo(SwStroke& stroke, const SwPoint& to) +{ + auto delta = to - stroke.center; + + //a zero-length lineto is a no-op; avoid creating a spurious corner + if (delta.zero()) return; + + //compute length of line + auto lineLength = mathLength(delta); + auto angle = mathAtan(delta); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle + SW_ANGLE_PI2); + SCALE(stroke, delta); + + //process corner if necessary + if (stroke.firstPt) { + /* This is the first segment of a subpath. We need to add a point to each border + at their respective starting point locations. */ + _firstSubPath(stroke, angle, lineLength); + } else { + //process the current corner + stroke.angleOut = angle; + _processCorner(stroke, lineLength); + } + + //now add a line segment to both the inside and outside paths + auto border = stroke.borders; + auto side = 1; + + while (side >= 0) { + auto pt = to + delta; + + //the ends of lineto borders are movable + _borderLineTo(border, pt, true); + + delta.x = -delta.x; + delta.y = -delta.y; + + --side; + ++border; + } + + stroke.angleIn = angle; + stroke.center = to; + stroke.lineLength = lineLength; +} + + +static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + /* if all control points are coincident, this is a no-op; + avoid creating a spurious corner */ + if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) { + stroke.center = to; + return; + } + + SwPoint bezStack[37]; //TODO: static? + auto limit = bezStack + 32; + auto arc = bezStack; + auto firstArc = true; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = stroke.center; + + while (arc >= bezStack) { + SwFixed angleIn, angleOut, angleMid; + + //initialize with current direction + angleIn = angleOut = angleMid = stroke.angleIn; + + if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) { + if (stroke.firstPt) stroke.angleIn = angleIn; + mathSplitCubic(arc); + arc += 3; + continue; + } + + if (firstArc) { + firstArc = false; + //process corner if necessary + if (stroke.firstPt) { + _firstSubPath(stroke, angleIn, 0); + } else { + stroke.angleOut = angleIn; + _processCorner(stroke, 0); + } + } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) { + //if the deviation from one arc to the next is too great add a round corner + stroke.center = arc[3]; + stroke.angleOut = angleIn; + stroke.join = StrokeJoin::Round; + + _processCorner(stroke, 0); + + //reinstate line join style + stroke.join = stroke.joinSaved; + } + + //the arc's angle is small enough; we can add it directly to each border + auto theta1 = mathDiff(angleIn, angleMid) / 2; + auto theta2 = mathDiff(angleMid, angleOut) / 2; + auto phi1 = mathMean(angleIn, angleMid); + auto phi2 = mathMean(angleMid, angleOut); + auto length1 = mathDivide(stroke.width, mathCos(theta1)); + auto length2 = mathDivide(stroke.width, mathCos(theta2)); + SwFixed alpha0 = 0; + + //compute direction of original arc + if (stroke.handleWideStrokes) { + alpha0 = mathAtan(arc[0] - arc[3]); + } + + auto border = stroke.borders; + int32_t side = 0; + + while (side <= 1) + { + auto rotate = SIDE_TO_ROTATE(side); + + //compute control points + SwPoint _ctrl1 = {static_cast(length1), 0}; + mathRotate(_ctrl1, phi1 + rotate); + SCALE(stroke, _ctrl1); + _ctrl1 += arc[2]; + + SwPoint _ctrl2 = {static_cast(length2), 0}; + mathRotate(_ctrl2, phi2 + rotate); + SCALE(stroke, _ctrl2); + _ctrl2 += arc[1]; + + //compute end point + SwPoint _end = {static_cast(stroke.width), 0}; + mathRotate(_end, angleOut + rotate); + SCALE(stroke, _end); + _end += arc[0]; + + if (stroke.handleWideStrokes) { + + /* determine whether the border radius is greater than the radius of + curvature of the original arc */ + auto _start = border->pts[border->ptsCnt - 1]; + auto alpha1 = mathAtan(_end - _start); + + //is the direction of the border arc opposite to that of the original arc? + if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) { + + //use the sine rule to find the intersection point + auto beta = mathAtan(arc[3] - _start); + auto gamma = mathAtan(arc[0] - _end); + auto bvec = _end - _start; + auto blen = mathLength(bvec); + auto sinA = abs(mathSin(alpha1 - gamma)); + auto sinB = abs(mathSin(beta - gamma)); + auto alen = mathMulDiv(blen, sinA, sinB); + + SwPoint delta = {static_cast(alen), 0}; + mathRotate(delta, beta); + delta += _start; + + //circumnavigate the negative sector backwards + border->movable = false; + _borderLineTo(border, delta, false); + _borderLineTo(border, _end, false); + _borderCubicTo(border, _ctrl2, _ctrl1, _start); + + //and then move to the endpoint + _borderLineTo(border, _end, false); + + ++side; + ++border; + continue; + } + + //else fall through + } + _borderCubicTo(border, _ctrl1, _ctrl2, _end); + ++side; + ++border; + } + arc -= 3; + stroke.angleIn = angleOut; + } + stroke.center = to; +} + + +static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) +{ + if (stroke.cap == StrokeCap::Square) { + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle); + SCALE(stroke, delta); + + SwPoint delta2 = {static_cast(stroke.width), 0}; + mathRotate(delta2, angle + rotate); + SCALE(stroke, delta2); + delta += stroke.center + delta2; + + _borderLineTo(border, delta, false); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle); + SCALE(stroke, delta); + + delta2 = {static_cast(stroke.width), 0}; + mathRotate(delta2, angle - rotate); + SCALE(stroke, delta2); + delta += delta2 + stroke.center; + + _borderLineTo(border, delta, false); + + } else if (stroke.cap == StrokeCap::Round) { + + stroke.angleIn = angle; + stroke.angleOut = angle + SW_ANGLE_PI; + _arcTo(stroke, side); + return; + + } else { //Butt + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle + rotate); + SCALE(stroke, delta); + delta += stroke.center; + + _borderLineTo(border, delta, false); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle - rotate); + SCALE(stroke, delta); + delta += stroke.center; + + _borderLineTo(border, delta, false); + } +} + + +static void _addReverseLeft(SwStroke& stroke, bool opened) +{ + auto right = stroke.borders + 0; + auto left = stroke.borders + 1; + auto newPts = left->ptsCnt - left->start; + + if (newPts <= 0) return; + + _growBorder(right, newPts); + + auto dstPt = right->pts + right->ptsCnt; + auto dstTag = right->tags + right->ptsCnt; + auto srcPt = left->pts + left->ptsCnt - 1; + auto srcTag = left->tags + left->ptsCnt - 1; + + while (srcPt >= left->pts + left->start) { + *dstPt = *srcPt; + *dstTag = *srcTag; + + if (opened) { + dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + } else { + //switch begin/end tags if necessary + auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END) + dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + } + + --srcPt; + --srcTag; + ++dstPt; + ++dstTag; + } + + left->ptsCnt = left->start; + right->ptsCnt += newPts; + right->movable = false; + left->movable = false; +} + + +static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed) +{ + /* We cannot process the first point because there is not enough + information regarding its corner/cap. Later, it will be processed + in the _endSubPath() */ + + stroke.firstPt = true; + stroke.center = to; + stroke.closedSubPath = closed; + + /* Determine if we need to check whether the border radius is greater + than the radius of curvature of a curve, to handle this case specially. + This is only required if bevel joins or butt caps may be created because + round & miter joins and round & square caps cover the nagative sector + created with wide strokes. */ + if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt)) + stroke.handleWideStrokes = true; + else + stroke.handleWideStrokes = false; + + stroke.ptStartSubPath = to; + stroke.angleIn = 0; +} + + +static void _endSubPath(SwStroke& stroke) +{ + if (stroke.closedSubPath) { + //close the path if needed + if (stroke.center != stroke.ptStartSubPath) + _lineTo(stroke, stroke.ptStartSubPath); + + //process the corner + stroke.angleOut = stroke.subPathAngle; + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); + + //No specific corner processing is required if the turn is 0 + if (turn != 0) { + + //when we turn to the right, the inside is 0 + int32_t inside = 0; + + //otherwise, the inside is 1 + if (turn < 0) inside = 1; + + _inside(stroke, inside, stroke.subPathLineLength); //inside + _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside + } + + _borderClose(stroke.borders + 0, false); + _borderClose(stroke.borders + 1, true); + } else { + auto right = stroke.borders; + + /* all right, this is an opened path, we need to add a cap between + right & left, add the reverse of left, then add a final cap + between left & right */ + _addCap(stroke, stroke.angleIn, 0); + + //add reversed points from 'left' to 'right' + _addReverseLeft(stroke, true); + + //now add the final cap + stroke.center = stroke.ptStartSubPath; + _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0); + + /* now end the right subpath accordingly. The left one is rewind + and deosn't need further processing */ + _borderClose(right, false); + } +} + + +static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt) +{ + auto count = border->ptsCnt; + auto tags = border->tags; + uint32_t _ptsCnt = 0; + uint32_t _cntrsCnt = 0; + bool inCntr = false; + + while (count > 0) { + if (tags[0] & SW_STROKE_TAG_BEGIN) { + if (inCntr) goto fail; + inCntr = true; + } else if (!inCntr) goto fail; + + if (tags[0] & SW_STROKE_TAG_END) { + inCntr = false; + ++_cntrsCnt; + } + --count; + ++_ptsCnt; + ++tags; + } + + if (inCntr) goto fail; + + ptsCnt = _ptsCnt; + cntrsCnt = _cntrsCnt; + + return; + +fail: + ptsCnt = 0; + cntrsCnt = 0; +} + + +static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side) +{ + auto border = stroke.borders + side; + + if (border->ptsCnt == 0) return; //invalid border + + memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint)); + + auto cnt = border->ptsCnt; + auto src = border->tags; + auto tags = outline->types + outline->ptsCnt; + auto cntrs = outline->cntrs + outline->cntrsCnt; + uint16_t idx = outline->ptsCnt; + + while (cnt > 0) { + + if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT; + else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC; + else { + //LOG: What type of stroke outline?? + } + + if (*src & SW_STROKE_TAG_END) { + *cntrs = idx; + ++cntrs; + ++outline->cntrsCnt; + } + ++src; + ++tags; + ++idx; + --cnt; + } + outline->ptsCnt = outline->ptsCnt + border->ptsCnt; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void strokeFree(SwStroke* stroke) +{ + if (!stroke) return; + + //free borders + if (stroke->borders[0].pts) free(stroke->borders[0].pts); + if (stroke->borders[0].tags) free(stroke->borders[0].tags); + if (stroke->borders[1].pts) free(stroke->borders[1].pts); + if (stroke->borders[1].tags) free(stroke->borders[1].tags); + + fillFree(stroke->fill); + stroke->fill = nullptr; + + free(stroke); +} + + +void strokeReset(SwStroke* stroke, const Shape* sdata, const Matrix* transform) +{ + if (transform) { + stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f)); + stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f)); + } else { + stroke->sx = stroke->sy = 1.0f; + } + + stroke->width = HALF_STROKE(sdata->strokeWidth()); + stroke->cap = sdata->strokeCap(); + + //Save line join: it can be temporarily changed when stroking curves... + stroke->joinSaved = stroke->join = sdata->strokeJoin(); + + stroke->borders[0].ptsCnt = 0; + stroke->borders[0].start = -1; + stroke->borders[1].ptsCnt = 0; + stroke->borders[1].start = -1; +} + + +bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline) +{ + uint32_t first = 0; + + for (uint32_t i = 0; i < outline.cntrsCnt; ++i) { + auto last = outline.cntrs[i]; //index of last point in contour + auto limit = outline.pts + last; + + //Skip empty points + if (last <= first) { + first = last + 1; + continue; + } + + auto start = outline.pts[first]; + auto pt = outline.pts + first; + auto types = outline.types + first; + auto type = types[0]; + + //A contour cannot start with a cubic control point + if (type == SW_CURVE_TYPE_CUBIC) return false; + + auto closed = outline.closed ? outline.closed[i]: false; + + _beginSubPath(*stroke, start, closed); + + while (pt < limit) { + ++pt; + ++types; + + //emit a signel line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(*stroke, *pt); + //types cubic + } else { + if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false; + + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(*stroke, pt[-2], pt[-1], pt[0]); + continue; + } + _cubicTo(*stroke, pt[-2], pt[-1], start); + goto close; + } + } + + close: + if (!stroke->firstPt) _endSubPath(*stroke); + first = last + 1; + } + return true; +} + + +SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid) +{ + uint32_t count1, count2, count3, count4; + + _getCounts(stroke->borders + 0, count1, count2); + _getCounts(stroke->borders + 1, count3, count4); + + auto ptsCnt = count1 + count3; + auto cntrsCnt = count2 + count4; + + auto outline = mpoolReqStrokeOutline(mpool, tid); + if (outline->reservedPtsCnt < ptsCnt) { + outline->pts = static_cast(realloc(outline->pts, sizeof(SwPoint) * ptsCnt)); + outline->types = static_cast(realloc(outline->types, sizeof(uint8_t) * ptsCnt)); + outline->reservedPtsCnt = ptsCnt; + } + if (outline->reservedCntrsCnt < cntrsCnt) { + outline->cntrs = static_cast(realloc(outline->cntrs, sizeof(uint16_t) * cntrsCnt)); + outline->reservedCntrsCnt = cntrsCnt; + } + + _exportBorderOutline(*stroke, outline, 0); //left + _exportBorderOutline(*stroke, outline, 1); //right + + return outline; +} diff --git a/thirdparty/thorvg/src/lib/tvgArray.h b/thirdparty/thorvg/src/lib/tvgArray.h new file mode 100644 index 0000000000..7283a833c1 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgArray.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_ARRAY_H_ +#define _TVG_ARRAY_H_ + +#include + +namespace tvg +{ + +template +struct Array +{ + T* data = nullptr; + uint32_t count = 0; + uint32_t reserved = 0; + + void push(T element) + { + if (count + 1 > reserved) { + reserved = (count + 1) * 2; + data = static_cast(realloc(data, sizeof(T) * reserved)); + } + data[count++] = element; + } + + bool reserve(uint32_t size) + { + if (size > reserved) { + reserved = size; + data = static_cast(realloc(data, sizeof(T) * reserved)); + if (!data) return false; + } + return true; + } + + bool grow(uint32_t size) + { + return reserve(count + size); + } + + T* ptr() + { + return data + count; + } + + void pop() + { + if (count > 0) --count; + } + + void reset() + { + if (data) { + free(data); + data = nullptr; + } + count = reserved = 0; + } + + void clear() + { + count = 0; + } + + void operator=(const Array& rhs) + { + reserve(rhs.count); + if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * reserved); + count = rhs.count; + } + + ~Array() + { + if (data) free(data); + } +}; + +} + +#endif //_TVG_ARRAY_H_ diff --git a/thirdparty/thorvg/src/lib/tvgBezier.cpp b/thirdparty/thorvg/src/lib/tvgBezier.cpp new file mode 100644 index 0000000000..2290afa728 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgBezier.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgBezier.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +namespace tvg +{ + +void bezSplit(const Bezier&cur, Bezier& left, Bezier& right) +{ + auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; + left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; + right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f; + left.start.x = cur.start.x; + right.end.x = cur.end.x; + left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; + right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; + left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; + + c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f; + left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f; + right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f; + left.start.y = cur.start.y; + right.end.y = cur.end.y; + left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; + right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; + left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; +} + + +float bezLength(const Bezier& cur) +{ + Bezier left, right; + auto len = _lineLength(cur.start, cur.ctrl1) + _lineLength(cur.ctrl1, cur.ctrl2) + _lineLength(cur.ctrl2, cur.end); + auto chord = _lineLength(cur.start, cur.end); + + if (fabsf(len - chord) > BEZIER_EPSILON) { + bezSplit(cur, left, right); + return bezLength(left) + bezLength(right); + } + return len; +} + + +void bezSplitLeft(Bezier& cur, float at, Bezier& left) +{ + left.start = cur.start; + + left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x); + left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y); + + left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); //temporary holding spot + left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); //temporary holding spot + + cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x); + cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y); + + cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x); + cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y); + + left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x); + left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y); + + left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x); + left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y); +} + + +float bezAt(const Bezier& bz, float at) +{ + auto len = bezLength(bz); + auto biggest = 1.0f; + auto smallest = 0.0f; + auto t = 0.5f; + + //just in case to prevent an infinite loop + if (at <= 0) return 0.0f; + + if (at >= len) return 1.0f; + + while (true) { + auto right = bz; + Bezier left; + bezSplitLeft(right, t, left); + len = bezLength(left); + + if (fabsf(len - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { + break; + } + + if (len < at) { + smallest = t; + t = (t + biggest) * 0.5f; + } else { + biggest = t; + t = (smallest + t) * 0.5f; + } + } + return t; +} + + +void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) +{ + right = cur; + auto t = bezAt(right, at); + bezSplitLeft(right, t, left); +} + +} diff --git a/thirdparty/thorvg/src/lib/tvgBezier.h b/thirdparty/thorvg/src/lib/tvgBezier.h new file mode 100644 index 0000000000..866e39148e --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgBezier.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_BEZIER_H_ +#define _TVG_BEZIER_H_ + +#include "tvgCommon.h" + +namespace tvg +{ + +#define BEZIER_EPSILON 1e-4f + +struct Bezier +{ + Point start; + Point ctrl1; + Point ctrl2; + Point end; +}; + +void bezSplit(const Bezier&cur, Bezier& left, Bezier& right); +float bezLength(const Bezier& cur); +void bezSplitLeft(Bezier& cur, float at, Bezier& left); +float bezAt(const Bezier& bz, float at); +void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right); + +} + +#endif //_TVG_BEZIER_H_ diff --git a/thirdparty/thorvg/src/lib/tvgBinaryDesc.h b/thirdparty/thorvg/src/lib/tvgBinaryDesc.h new file mode 100644 index 0000000000..30ba2d5a29 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgBinaryDesc.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_BINARY_DESC_H_ +#define _TVG_BINARY_DESC_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? */ + +/* Data types, do not change data types once Tvg Format is officially released, + That would occur the abi break. */ + +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 "000400" //Major 00, Minor 04, 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 + + +//Scene +#define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30 + + +//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 +#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50 +#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51 + +//Stroke +#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 + + +//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 + + +//Picture +#define TVG_TAG_PICTURE_RAW_IMAGE (TvgBinTag)0x70 + +#endif //_TVG_BINARY_DESC_H_ diff --git a/thirdparty/thorvg/src/lib/tvgCanvas.cpp b/thirdparty/thorvg/src/lib/tvgCanvas.cpp new file mode 100644 index 0000000000..bb42441c62 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgCanvas.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgCanvasImpl.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Canvas::Canvas(RenderMethod *pRenderer):pImpl(new Impl(pRenderer)) +{ +} + + +Canvas::~Canvas() +{ + delete(pImpl); +} + + +Result Canvas::reserve(uint32_t n) noexcept +{ + if (!pImpl->paints.reserve(n)) return Result::FailedAllocation; + return Result::Success; +} + + +Result Canvas::push(unique_ptr paint) noexcept +{ + return pImpl->push(move(paint)); +} + + +Result Canvas::clear(bool free) noexcept +{ + return pImpl->clear(free); +} + + +Result Canvas::draw() noexcept +{ + return pImpl->draw(); +} + + +Result Canvas::update(Paint* paint) noexcept +{ + return pImpl->update(paint, false); +} + + +Result Canvas::sync() noexcept +{ + return pImpl->sync(); +} diff --git a/thirdparty/thorvg/src/lib/tvgCanvasImpl.h b/thirdparty/thorvg/src/lib/tvgCanvasImpl.h new file mode 100644 index 0000000000..848c03aaed --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgCanvasImpl.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_CANVAS_IMPL_H_ +#define _TVG_CANVAS_IMPL_H_ + +#include "tvgPaint.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Canvas::Impl +{ + Array paints; + RenderMethod* renderer; + bool refresh = false; //if all paints should be updated by force. + bool drawing = false; //on drawing condition? + + Impl(RenderMethod* pRenderer):renderer(pRenderer) + { + } + + ~Impl() + { + clear(true); + delete(renderer); + } + + Result push(unique_ptr paint) + { + //You can not push paints during rendering. + if (drawing) return Result::InsufficientCondition; + + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + paints.push(p); + + return update(p, true); + } + + Result clear(bool free) + { + //Clear render target before drawing + if (!renderer || !renderer->clear()) return Result::InsufficientCondition; + + //Free paints + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + (*paint)->pImpl->dispose(*renderer); + if (free) delete(*paint); + } + + paints.clear(); + + drawing = false; + + return Result::Success; + } + + void needRefresh() + { + refresh = true; + } + + Result update(Paint* paint, bool force) + { + if (paints.count == 0 || drawing || !renderer) return Result::InsufficientCondition; + + Array clips; + auto flag = RenderUpdateFlag::None; + if (refresh || force) flag = RenderUpdateFlag::All; + + //Update single paint node + if (paint) { + //Optimize Me: Can we skip the searching? + for (auto paint2 = paints.data; paint2 < (paints.data + paints.count); ++paint2) { + if ((*paint2) == paint) { + paint->pImpl->update(*renderer, nullptr, 255, clips, flag); + return Result::Success; + } + } + return Result::InvalidArguments; + //Update all retained paint nodes + } else { + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + (*paint)->pImpl->update(*renderer, nullptr, 255, clips, flag); + } + } + + refresh = false; + + return Result::Success; + } + + Result draw() + { + if (drawing || paints.count == 0 || !renderer || !renderer->preRender()) return Result::InsufficientCondition; + + bool rendered = false; + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + if ((*paint)->pImpl->render(*renderer)) rendered = true; + } + + if (!rendered || !renderer->postRender()) return Result::InsufficientCondition; + + drawing = true; + + return Result::Success; + } + + Result sync() + { + if (!drawing) return Result::InsufficientCondition; + + if (renderer->sync()) { + drawing = false; + return Result::Success; + } + + return Result::InsufficientCondition; + } +}; + +#endif /* _TVG_CANVAS_IMPL_H_ */ diff --git a/thirdparty/thorvg/src/lib/tvgCommon.h b/thirdparty/thorvg/src/lib/tvgCommon.h new file mode 100644 index 0000000000..64a41dde08 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgCommon.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_COMMON_H_ +#define _TVG_COMMON_H_ + +#include "config.h" +#include "thorvg.h" + +using namespace std; +using namespace tvg; + +//for MSVC Compat +#ifdef _MSC_VER + #define TVG_UNUSED + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #define TVG_UNUSED __attribute__ ((__unused__)) +#endif + +// Portable 'fallthrough' attribute +#if __has_cpp_attribute(fallthrough) + #ifdef _MSC_VER + #define TVG_FALLTHROUGH [[fallthrough]]; + #else + #define TVG_FALLTHROUGH __attribute__ ((fallthrough)); + #endif +#else + #define TVG_FALLTHROUGH +#endif + +#if defined(_MSC_VER) + #define strncpy strncpy_s + #define strdup _strdup +#endif + +//TVG class identifier values +#define TVG_CLASS_ID_UNDEFINED 0 +#define TVG_CLASS_ID_SHAPE 1 +#define TVG_CLASS_ID_SCENE 2 +#define TVG_CLASS_ID_PICTURE 3 +#define TVG_CLASS_ID_LINEAR 4 +#define TVG_CLASS_ID_RADIAL 5 + +enum class FileType { Tvg = 0, Svg, Raw, Png, Jpg, Unknown }; + +#ifdef THORVG_LOG_ENABLED + #define TVGLOG(tag, fmt, ...) fprintf(stdout, tag ": " fmt "\n", ##__VA_ARGS__) //Log Message for notifying user some useful info + #define TVGERR(tag, fmt, ...) fprintf(stderr, tag ": " fmt "\n", ##__VA_ARGS__) //Error Message for us to fix it +#else + #define TVGERR(...) + #define TVGLOG(...) +#endif + +uint16_t THORVG_VERSION_NUMBER(); + +#endif //_TVG_COMMON_H_ diff --git a/thirdparty/thorvg/src/lib/tvgFill.cpp b/thirdparty/thorvg/src/lib/tvgFill.cpp new file mode 100644 index 0000000000..ab059c7198 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgFill.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgFill.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Fill::Fill():pImpl(new Impl()) +{ +} + + +Fill::~Fill() +{ + delete(pImpl); +} + + +Result Fill::colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept +{ + if ((!colorStops && cnt > 0) || (colorStops && cnt == 0)) return Result::InvalidArguments; + + if (cnt == 0) { + if (pImpl->colorStops) { + free(pImpl->colorStops); + pImpl->colorStops = nullptr; + pImpl->cnt = 0; + } + return Result::Success; + } + + if (pImpl->cnt != cnt) { + pImpl->colorStops = static_cast(realloc(pImpl->colorStops, cnt * sizeof(ColorStop))); + } + + pImpl->cnt = cnt; + memcpy(pImpl->colorStops, colorStops, cnt * sizeof(ColorStop)); + + return Result::Success; +} + + +uint32_t Fill::colorStops(const ColorStop** colorStops) const noexcept +{ + if (colorStops) *colorStops = pImpl->colorStops; + + return pImpl->cnt; +} + + +Result Fill::spread(FillSpread s) noexcept +{ + pImpl->spread = s; + + return Result::Success; +} + + +FillSpread Fill::spread() const noexcept +{ + return pImpl->spread; +} + + +Result Fill::transform(const Matrix& m) noexcept +{ + if (!pImpl->transform) pImpl->transform = new Matrix(); + *pImpl->transform = m; + return Result::Success; +} + + +Matrix Fill::transform() const noexcept +{ + if (pImpl->transform) return *pImpl->transform; + return {1, 0, 0, 0, 1, 0, 0, 0, 1}; +} + + +Fill* Fill::duplicate() const noexcept +{ + return pImpl->duplicate(); +} diff --git a/thirdparty/thorvg/src/lib/tvgFill.h b/thirdparty/thorvg/src/lib/tvgFill.h new file mode 100644 index 0000000000..af33c0616c --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgFill.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_FILL_H_ +#define _TVG_FILL_H_ + +#include +#include +#include "tvgCommon.h" + +template +struct DuplicateMethod +{ + virtual ~DuplicateMethod() {} + virtual T* duplicate() = 0; +}; + +template +struct FillDup : DuplicateMethod +{ + T* inst = nullptr; + + FillDup(T* _inst) : inst(_inst) {} + ~FillDup() {} + + Fill* duplicate() override + { + return inst->duplicate(); + } +}; + +struct Fill::Impl +{ + ColorStop* colorStops = nullptr; + Matrix* transform = nullptr; + uint32_t cnt = 0; + FillSpread spread; + DuplicateMethod* dup = nullptr; + + ~Impl() + { + if (dup) delete(dup); + if (colorStops) free(colorStops); + if (transform) delete(transform); + } + + void method(DuplicateMethod* dup) + { + this->dup = dup; + } + + Fill* duplicate() + { + auto ret = dup->duplicate(); + if (!ret) return nullptr; + + ret->pImpl->cnt = cnt; + ret->pImpl->spread = spread; + ret->pImpl->colorStops = static_cast(malloc(sizeof(ColorStop) * cnt)); + memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt); + if (transform) { + ret->pImpl->transform = new Matrix; + *ret->pImpl->transform = *transform; + } + return ret; + } +}; + +#endif //_TVG_FILL_H_ diff --git a/thirdparty/thorvg/src/lib/tvgGlCanvas.cpp b/thirdparty/thorvg/src/lib/tvgGlCanvas.cpp new file mode 100644 index 0000000000..3a88b6d799 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgGlCanvas.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgCanvasImpl.h" + +#ifdef THORVG_GL_RASTER_SUPPORT + #include "tvgGlRenderer.h" +#else + class GlRenderer : public RenderMethod + { + //Non Supported. Dummy Class */ + }; +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct GlCanvas::Impl +{ +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +#ifdef THORVG_GL_RASTER_SUPPORT +GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(new Impl) +#else +GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(new Impl) +#endif +{ +} + + + +GlCanvas::~GlCanvas() +{ + delete(pImpl); +} + + +Result GlCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept +{ +#ifdef THORVG_GL_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl->renderer); + if (!renderer) return Result::MemoryCorruption; + + if (!renderer->target(buffer, stride, w, h)) return Result::Unknown; + + //Paints must be updated again with this new target. + Canvas::pImpl->needRefresh(); + + return Result::Success; +#endif + return Result::NonSupport; +} + + +unique_ptr GlCanvas::gen() noexcept +{ +#ifdef THORVG_GL_RASTER_SUPPORT + if (GlRenderer::init() <= 0) return nullptr; + return unique_ptr(new GlCanvas); +#endif + return nullptr; +} diff --git a/thirdparty/thorvg/src/lib/tvgInitializer.cpp b/thirdparty/thorvg/src/lib/tvgInitializer.cpp new file mode 100644 index 0000000000..83ec50be95 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgInitializer.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgCommon.h" +#include "tvgTaskScheduler.h" +#include "tvgLoader.h" + +#ifdef _WIN32 + #include +#endif + +#ifdef THORVG_SW_RASTER_SUPPORT + #include "tvgSwRenderer.h" +#endif + +#ifdef THORVG_GL_RASTER_SUPPORT + #include "tvgGlRenderer.h" +#endif + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static int _initCnt = 0; +static uint16_t _version = 0; + + +static bool _buildVersionInfo() +{ + auto SRC = THORVG_VERSION_STRING; //ex) 0.3.99 + auto p = SRC; + const char* x; + + char major[3]; + x = strchr(p, '.'); + if (!x) return false; + memcpy(major, p, x - p); + major[x - p] = '\0'; + p = x + 1; + + char minor[3]; + x = strchr(p, '.'); + if (!x) return false; + memcpy(minor, p, x - p); + minor[x - p] = '\0'; + p = x + 1; + + char micro[3]; + x = SRC + strlen(THORVG_VERSION_STRING); + memcpy(micro, p, x - p); + micro[x - p] = '\0'; + + char sum[7]; + snprintf(sum, sizeof(sum), "%s%s%s", major, minor, micro); + + _version = atoi(sum); + + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept +{ + auto nonSupport = true; + + if (static_cast(engine) & static_cast(CanvasEngine::Sw)) { + #ifdef THORVG_SW_RASTER_SUPPORT + if (!SwRenderer::init(threads)) return Result::FailedAllocation; + nonSupport = false; + #endif + } else if (static_cast(engine) & static_cast(CanvasEngine::Gl)) { + #ifdef THORVG_GL_RASTER_SUPPORT + if (!GlRenderer::init(threads)) return Result::FailedAllocation; + nonSupport = false; + #endif + } else { + return Result::InvalidArguments; + } + + if (nonSupport) return Result::NonSupport; + + if (_initCnt++ > 0) return Result::Success; + + if (!_buildVersionInfo()) return Result::Unknown; + + if (!LoaderMgr::init()) return Result::Unknown; + + TaskScheduler::init(threads); + + return Result::Success; +} + + +Result Initializer::term(CanvasEngine engine) noexcept +{ + if (_initCnt == 0) return Result::InsufficientCondition; + + auto nonSupport = true; + + if (static_cast(engine) & static_cast(CanvasEngine::Sw)) { + #ifdef THORVG_SW_RASTER_SUPPORT + if (!SwRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } else if (static_cast(engine) & static_cast(CanvasEngine::Gl)) { + #ifdef THORVG_GL_RASTER_SUPPORT + if (!GlRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } else { + return Result::InvalidArguments; + } + + if (nonSupport) return Result::NonSupport; + + if (--_initCnt > 0) return Result::Success; + + TaskScheduler::term(); + + if (!LoaderMgr::term()) return Result::Unknown; + + return Result::Success; +} + + +uint16_t THORVG_VERSION_NUMBER() +{ + return _version; +} diff --git a/thirdparty/thorvg/src/lib/tvgIteratorAccessor.h b/thirdparty/thorvg/src/lib/tvgIteratorAccessor.h new file mode 100644 index 0000000000..10b55a536d --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgIteratorAccessor.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_ITERATOR_ACCESSOR_H_ +#define _TVG_ITERATOR_ACCESSOR_H_ + +#include "tvgPaint.h" + +namespace tvg +{ + +class IteratorAccessor +{ +public: + //Utility Method: Iterator Accessor + Iterator* iterator(const Paint* paint) + { + return paint->pImpl->iterator(); + } +}; + +} + +#endif //_TVG_ITERATOR_ACCESSOR_H_ diff --git a/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp b/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp new file mode 100644 index 0000000000..46ca45fcff --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgLinearGradient.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgFill.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct LinearGradient::Impl +{ + float x1 = 0; + float y1 = 0; + float x2 = 0; + float y2 = 0; + + Fill* duplicate() + { + auto ret = LinearGradient::gen(); + if (!ret) return nullptr; + + ret->pImpl->x1 = x1; + ret->pImpl->y1 = y1; + ret->pImpl->x2 = x2; + ret->pImpl->y2 = y2; + + return ret.release(); + } +}; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +LinearGradient::LinearGradient():pImpl(new Impl()) +{ + _id = TVG_CLASS_ID_LINEAR; + Fill::pImpl->method(new FillDup(pImpl)); +} + + +LinearGradient::~LinearGradient() +{ + delete(pImpl); +} + + +Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept +{ + pImpl->x1 = x1; + pImpl->y1 = y1; + pImpl->x2 = x2; + pImpl->y2 = y2; + + return Result::Success; +} + + +Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept +{ + if (x1) *x1 = pImpl->x1; + if (x2) *x2 = pImpl->x2; + if (y1) *y1 = pImpl->y1; + if (y2) *y2 = pImpl->y2; + + return Result::Success; +} + + +unique_ptr LinearGradient::gen() noexcept +{ + return unique_ptr(new LinearGradient); +} + + +uint32_t LinearGradient::identifier() noexcept +{ + return TVG_CLASS_ID_LINEAR; +} diff --git a/thirdparty/thorvg/src/lib/tvgLoadModule.h b/thirdparty/thorvg/src/lib/tvgLoadModule.h new file mode 100644 index 0000000000..70b95b76be --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgLoadModule.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_LOAD_MODULE_H_ +#define _TVG_LOAD_MODULE_H_ + +#include "tvgCommon.h" + +namespace tvg +{ + +class LoadModule +{ +public: + //default view box, if any. + float vx = 0; + float vy = 0; + float vw = 0; + float vh = 0; + float w = 0, h = 0; //default image size + bool preserveAspect = true; //keep aspect ratio by default. + + virtual ~LoadModule() {} + + virtual bool open(const string& path) { return false; }; + virtual bool open(const char* data, uint32_t size, bool copy) { return false; }; + virtual bool open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) { return false; }; + + //Override this if the vector-format has own resizing policy. + virtual bool resize(Paint* paint, float w, float h) { return false; }; + + virtual bool read() = 0; + virtual bool close() = 0; + virtual const uint32_t* pixels() { return nullptr; }; + virtual unique_ptr paint() { return nullptr; }; +}; + +} + +#endif //_TVG_LOAD_MODULE_H_ diff --git a/thirdparty/thorvg/src/lib/tvgLoader.cpp b/thirdparty/thorvg/src/lib/tvgLoader.cpp new file mode 100644 index 0000000000..fb93e0faec --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgLoader.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgLoader.h" + +#ifdef THORVG_SVG_LOADER_SUPPORT + #include "tvgSvgLoader.h" +#endif + +#ifdef THORVG_PNG_LOADER_SUPPORT + #include "tvgPngLoader.h" +#endif + +#ifdef THORVG_TVG_LOADER_SUPPORT + #include "tvgTvgLoader.h" +#endif + +#ifdef THORVG_JPG_LOADER_SUPPORT + #include "tvgJpgLoader.h" +#endif + +#include "tvgRawLoader.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static LoadModule* _find(FileType type) +{ + switch(type) { + case FileType::Tvg: { +#ifdef THORVG_TVG_LOADER_SUPPORT + return new TvgLoader; +#endif + break; + } + case FileType::Svg: { +#ifdef THORVG_SVG_LOADER_SUPPORT + return new SvgLoader; +#endif + break; + } + case FileType::Raw: { + return new RawLoader; + break; + } + case FileType::Png: { +#ifdef THORVG_PNG_LOADER_SUPPORT + return new PngLoader; +#endif + break; + } + case FileType::Jpg: { +#ifdef THORVG_JPG_LOADER_SUPPORT + return new JpgLoader; +#endif + break; + } + default: { + break; + } + } + +#ifdef THORVG_LOG_ENABLED + const char *format; + switch(type) { + case FileType::Tvg: { + format = "TVG"; + break; + } + case FileType::Svg: { + format = "SVG"; + break; + } + case FileType::Raw: { + format = "RAW"; + break; + } + case FileType::Png: { + format = "PNG"; + break; + } + case FileType::Jpg: { + format = "JPG"; + break; + } + default: { + format = "???"; + break; + } + } + TVGLOG("LOADER", "%s format is not supported", format); +#endif + return nullptr; +} + + +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("png")) return _find(FileType::Png); + if (!ext.compare("jpg")) return _find(FileType::Jpg); + return nullptr; +} + + +static LoadModule* _findByType(const string& mimeType) +{ + if (mimeType.empty()) return nullptr; + + auto type = FileType::Unknown; + + if (mimeType == "tvg") type = FileType::Tvg; + else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg; + else if (mimeType == "raw") type = FileType::Raw; + else if (mimeType == "png") type = FileType::Png; + else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg; + else { + TVGLOG("LOADER", "Given mimetype is unknown = \"%s\".", mimeType.c_str()); + return nullptr; + } + + return _find(type); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool LoaderMgr::init() +{ + //TODO: + + return true; +} + + +bool LoaderMgr::term() +{ + //TODO: + + return true; +} + + +shared_ptr LoaderMgr::loader(const string& path, bool* invalid) +{ + *invalid = false; + + if (auto loader = _findByPath(path)) { + if (loader->open(path)) return shared_ptr(loader); + else delete(loader); + *invalid = true; + } + return nullptr; +} + + +shared_ptr LoaderMgr::loader(const char* data, uint32_t size, const string& mimeType, bool copy) +{ + //Try first with the given MimeType + if (auto loader = _findByType(mimeType)) { + if (loader->open(data, size, copy)) { + return shared_ptr(loader); + } else { + TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported. Will try again with other types.", mimeType.c_str()); + delete(loader); + } + } + + //Abnormal MimeType. Try with the candidates in the order + for (int i = 0; i < static_cast(FileType::Unknown); i++) { + auto loader = _find(static_cast(i)); + if (loader) { + if (loader->open(data, size, copy)) return shared_ptr(loader); + else delete(loader); + } + } + return nullptr; +} + + +shared_ptr LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool copy) +{ + //function is dedicated for raw images only + auto loader = new RawLoader; + if (loader->open(data, w, h, copy)) return shared_ptr(loader); + else delete(loader); + + return nullptr; +} diff --git a/thirdparty/thorvg/src/lib/tvgLoader.h b/thirdparty/thorvg/src/lib/tvgLoader.h new file mode 100644 index 0000000000..8ba3c139fa --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgLoader.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_LOADER_H_ +#define _TVG_LOADER_H_ + +#include "tvgLoadModule.h" + +struct LoaderMgr +{ + static bool init(); + static bool term(); + static shared_ptr loader(const string& path, bool* invalid); + static shared_ptr loader(const char* data, uint32_t size, const string& mimeType, bool copy); + static shared_ptr loader(const uint32_t* data, uint32_t w, uint32_t h, bool copy); +}; + +#endif //_TVG_LOADER_H_ diff --git a/thirdparty/thorvg/src/lib/tvgLzw.cpp b/thirdparty/thorvg/src/lib/tvgLzw.cpp new file mode 100644 index 0000000000..adbcb3fc89 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgLzw.cpp @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Lempel–Ziv–Welch (LZW) encoder/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) + * are not stored in the output and the max code length in bits is 12, vs 16 in compress. + * + * EOI is simply detected by the end of the data stream, while CC happens if the + * dictionary gets filled. Data is written/read from bit streams, which handle + * byte-alignment for us in a transparent way. + + * The decoder relies on the hardcoded data layout produced by the encoder, since + * no additional reconstruction data is added to the output, so they must match. + * The nice thing about LZW is that we can reconstruct the dictionary directly from + * the stream of codes generated by the encoder, so this avoids storing additional + * headers in the bit stream. + + * The output code length is variable. It starts with the minimum number of bits + * required to store the base byte-sized dictionary and automatically increases + * as the dictionary gets larger (it starts at 9-bits and grows to 10-bits when + * code 512 is added, then 11-bits when 1024 is added, and so on). If the dictionary + * is filled (4096 items for a 12-bits dictionary), the whole thing is cleared and + * the process starts over. This is the main reason why the encoder and the decoder + * must match perfectly, since the lengths of the codes will not be specified with + * the data itself. + + * USEFUL LINKS: + * https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch + * http://rosettacode.org/wiki/LZW_compression + * http://www.cs.duke.edu/csed/curious/compression/lzw.html + * http://www.cs.cf.ac.uk/Dave/Multimedia/node214.html + * http://marknelson.us/1989/10/01/lzw-data-compression/ + */ +#include "config.h" + +#if defined(THORVG_TVG_SAVER_SUPPORT) || defined(THORVG_TVG_LOADER_SUPPORT) + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#include +#include +#include "tvgLzw.h" + +//LZW Dictionary helper: +constexpr int Nil = -1; +constexpr int MaxDictBits = 12; +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(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. + const int sizeInBytes; // Size of the stream *in bytes*. Might include padding. + const int sizeInBits; // Size of the stream *in bits*, padding *not* include. + int currBytePos = 0; // Current byte being read in the stream. + int nextBitPos = 0; // Bit position within the current byte to access next. 0 to 7. + int numBitsRead = 0; // Total bits read from the stream so far. Never includes byte-rounding padding. + + BitStreamReader(const uint8_t* bitStream, const int byteCount, const int bitCount) : stream(bitStream), sizeInBytes(byteCount), sizeInBits(bitCount) + { + } + + bool readNextBit(int& bitOut) + { + if (numBitsRead >= sizeInBits) return false; //We are done. + + const uint32_t mask = uint32_t(1) << nextBitPos; + bitOut = !!(stream[currBytePos] & mask); + ++numBitsRead; + + if (++nextBitPos == 8) { + nextBitPos = 0; + ++currBytePos; + } + return true; + } + + uint64_t readBitsU64(const int bitCount) + { + uint64_t num = 0; + for (int b = 0; b < bitCount; ++b) { + int bit; + if (!readNextBit(bit)) break; + /* Based on a "Stanford bit-hack": + http://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching */ + const uint64_t mask = uint64_t(1) << b; + num = (num & ~mask) | (-bit & mask); + } + return num; + } + + bool isEndOfStream() const + { + return numBitsRead >= sizeInBits; + } +}; + + +struct Dictionary +{ + struct Entry + { + int code; + int value; + }; + + //Dictionary entries 0-255 are always reserved to the byte/ASCII range. + int size; + Entry entries[MaxDictEntries]; + + Dictionary() + { + /* First 256 dictionary entries are reserved to the byte/ASCII range. + Additional entries follow for the character sequences found in the input. + Up to 4096 - 256 (MaxDictEntries - FirstCode). */ + size = FirstCode; + + for (int i = 0; i < size; ++i) { + entries[i].code = Nil; + entries[i].value = i; + } + } + + 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; + entries[size].code = code; + entries[size].value = value; + ++size; + return true; + } + + bool flush(int & codeBitsWidth) + { + if (size == (1 << codeBitsWidth)) { + ++codeBitsWidth; + if (codeBitsWidth > MaxDictBits) { + //Clear the dictionary (except the first 256 byte entries). + codeBitsWidth = StartBits; + size = FirstCode; + return true; + } + } + return false; + } +}; + + +static bool outputByte(int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar) +{ + if (bytesDecodedSoFar >= outputSizeBytes) return false; + *output++ = static_cast(code); + ++bytesDecodedSoFar; + return true; +} + + +static bool outputSequence(const Dictionary& dict, int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar, int& firstByte) +{ + /* A sequence is stored backwards, so we have to write + it to a temp then output the buffer in reverse. */ + int i = 0; + uint8_t sequence[MaxDictEntries]; + + do { + sequence[i++] = dict.entries[code].value; + code = dict.entries[code].code; + } while (code >= 0); + + firstByte = sequence[--i]; + + for (; i >= 0; --i) { + if (!outputByte(sequence[i], output, outputSizeBytes, bytesDecodedSoFar)) return false; + } + return true; +} + + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +namespace tvg { + +uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes) +{ + int code = Nil; + int prevCode = Nil; + int firstByte = 0; + int bytesDecoded = 0; + int codeBitsWidth = StartBits; + auto uncompressed = (uint8_t*) malloc(sizeof(uint8_t) * uncompressedSizeBytes); + auto ptr = uncompressed; + + /* We'll reconstruct the dictionary based on the bit stream codes. + Unlike Huffman encoding, we don't store the dictionary as a prefix to the data. */ + Dictionary dictionary; + BitStreamReader bitStream(compressed, compressedSizeBytes, compressedSizeBits); + + /* We check to avoid an overflow of the user buffer. + If the buffer is smaller than the decompressed size, we break the loop and return the current decompression count. */ + while (!bitStream.isEndOfStream()) { + code = static_cast(bitStream.readBitsU64(codeBitsWidth)); + + if (prevCode == Nil) { + if (!outputByte(code, ptr, uncompressedSizeBytes, bytesDecoded)) break; + firstByte = code; + prevCode = code; + continue; + } + if (code >= dictionary.size) { + if (!outputSequence(dictionary, prevCode, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break; + if (!outputByte(firstByte, ptr, uncompressedSizeBytes, bytesDecoded)) break; + } else if (!outputSequence(dictionary, code, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break; + + dictionary.add(prevCode, firstByte); + if (dictionary.flush(codeBitsWidth)) prevCode = Nil; + else prevCode = code; + } + + return uncompressed; +} + + +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(); +} + +} + +#endif \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgLzw.h b/thirdparty/thorvg/src/lib/tvgLzw.h new file mode 100644 index 0000000000..3fdb439a02 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgLzw.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_LZW_H_ +#define _TVG_LZW_H_ + +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); +} + +#endif //_TVG_LZW_H \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgPaint.cpp b/thirdparty/thorvg/src/lib/tvgPaint.cpp new file mode 100644 index 0000000000..2b1a65338d --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgPaint.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgPaint.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static inline bool FLT_SAME(float a, float b) +{ + return (fabsf(a - b) < FLT_EPSILON); +} + + +static bool _clipPathFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport) +{ + /* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */ + auto shape = static_cast(cmpTarget); + + //Rectangle Candidates? + const Point* pts; + if (shape->pathCoords(&pts) != 4) return false; + + if (rTransform) rTransform->update(); + + //No Rotation? + if (pTransform && (pTransform->m.e12 != 0 || pTransform->m.e21 != 0 || pTransform->m.e11 != pTransform->m.e22)) return false; + if (rTransform && (rTransform->m.e12 != 0 || rTransform->m.e21 != 0 || rTransform->m.e11 != rTransform->m.e22)) return false; + + //Othogonal Rectangle? + auto pt1 = pts + 0; + auto pt2 = pts + 1; + auto pt3 = pts + 2; + auto pt4 = pts + 3; + + if ((FLT_SAME(pt1->x, pt2->x) && FLT_SAME(pt2->y, pt3->y) && FLT_SAME(pt3->x, pt4->x) && FLT_SAME(pt1->y, pt4->y)) || + (FLT_SAME(pt2->x, pt3->x) && FLT_SAME(pt1->y, pt2->y) && FLT_SAME(pt1->x, pt4->x) && FLT_SAME(pt3->y, pt4->y))) { + + auto x1 = pt1->x; + auto y1 = pt1->y; + auto x2 = pt3->x; + auto y2 = pt3->y; + + if (rTransform) { + x1 = x1 * rTransform->m.e11 + rTransform->m.e13; + y1 = y1 * rTransform->m.e22 + rTransform->m.e23; + x2 = x2 * rTransform->m.e11 + rTransform->m.e13; + y2 = y2 * rTransform->m.e22 + rTransform->m.e23; + } + + if (pTransform) { + x1 = x1 * pTransform->m.e11 + pTransform->m.e13; + y1 = y1 * pTransform->m.e22 + pTransform->m.e23; + x2 = x2 * pTransform->m.e11 + pTransform->m.e13; + y2 = y2 * pTransform->m.e22 + pTransform->m.e23; + } + + if (x1 < 0.0f) x1 = 0.0f; + if (y1 < 0.0f) y1 = 0.0f; + + viewport.x = static_cast(x1); + viewport.y = static_cast(y1); + viewport.w = static_cast(x2 - x1 < 0 ? 0 : roundf(x2 - x1 + 0.5f)); + viewport.h = static_cast(x2 - x1 < 0 ? 0 : roundf(y2 - y1 + 0.5f)); + + return true; + } + + return false; +} + + +Paint* Paint::Impl::duplicate() +{ + auto ret = smethod->duplicate(); + if (!ret) return nullptr; + + //duplicate Transform + if (rTransform) { + ret->pImpl->rTransform = new RenderTransform(); + *ret->pImpl->rTransform = *rTransform; + ret->pImpl->flag |= RenderUpdateFlag::Transform; + } + + ret->pImpl->opacity = opacity; + + if (cmpTarget) ret->pImpl->cmpTarget = cmpTarget->duplicate(); + + ret->pImpl->cmpMethod = cmpMethod; + + return ret; +} + + +bool Paint::Impl::rotate(float degree) +{ + if (rTransform) { + if (fabsf(degree - rTransform->degree) <= FLT_EPSILON) return true; + } else { + if (fabsf(degree) <= FLT_EPSILON) return true; + rTransform = new RenderTransform(); + } + rTransform->degree = degree; + if (!rTransform->overriding) flag |= RenderUpdateFlag::Transform; + + return true; +} + + +bool Paint::Impl::scale(float factor) +{ + if (rTransform) { + if (fabsf(factor - rTransform->scale) <= FLT_EPSILON) return true; + } else { + if (fabsf(factor) <= FLT_EPSILON) return true; + rTransform = new RenderTransform(); + } + rTransform->scale = factor; + if (!rTransform->overriding) flag |= RenderUpdateFlag::Transform; + + return true; +} + + +bool Paint::Impl::translate(float x, float y) +{ + if (rTransform) { + if (fabsf(x - rTransform->x) <= FLT_EPSILON && fabsf(y - rTransform->y) <= FLT_EPSILON) return true; + } else { + if (fabsf(x) <= FLT_EPSILON && fabsf(y) <= FLT_EPSILON) return true; + rTransform = new RenderTransform(); + } + rTransform->x = x; + rTransform->y = y; + if (!rTransform->overriding) flag |= RenderUpdateFlag::Transform; + + return true; +} + + +bool Paint::Impl::render(RenderMethod& renderer) +{ + Compositor* cmp = nullptr; + + /* Note: only ClipPath is processed in update() step. + Create a composition image. */ + if (cmpTarget && cmpMethod != CompositeMethod::ClipPath) { + auto region = smethod->bounds(renderer); + if (region.w == 0 || region.h == 0) return false; + cmp = renderer.target(region); + renderer.beginComposite(cmp, CompositeMethod::None, 255); + cmpTarget->pImpl->render(renderer); + } + + if (cmp) renderer.beginComposite(cmp, cmpMethod, cmpTarget->pImpl->opacity); + + auto ret = smethod->render(renderer); + + if (cmp) renderer.endComposite(cmp); + + return ret; +} + + +void* Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, uint32_t pFlag) +{ + if (flag & RenderUpdateFlag::Transform) { + if (!rTransform) return nullptr; + if (!rTransform->update()) { + delete(rTransform); + rTransform = nullptr; + } + } + + /* 1. Composition Pre Processing */ + void *cmpData = nullptr; + RenderRegion viewport; + bool cmpFastTrack = false; + + if (cmpTarget) { + /* If transform has no rotation factors && ClipPath is a simple rectangle, + we can avoid regular ClipPath sequence but use viewport for performance */ + if (cmpMethod == CompositeMethod::ClipPath) { + RenderRegion viewport2; + if ((cmpFastTrack = _clipPathFastTrack(cmpTarget, pTransform, cmpTarget->pImpl->rTransform, viewport2))) { + viewport = renderer.viewport(); + viewport2.intersect(viewport); + renderer.viewport(viewport2); + } + } + + if (!cmpFastTrack) { + cmpData = cmpTarget->pImpl->update(renderer, pTransform, 255, clips, pFlag); + if (cmpMethod == CompositeMethod::ClipPath) clips.push(cmpData); + } + } + + /* 2. Main Update */ + void *edata = nullptr; + auto newFlag = static_cast(pFlag | flag); + flag = RenderUpdateFlag::None; + opacity = (opacity * this->opacity) / 255; + + if (rTransform && pTransform) { + RenderTransform outTransform(pTransform, rTransform); + edata = smethod->update(renderer, &outTransform, opacity, clips, newFlag); + } else { + auto outTransform = pTransform ? pTransform : rTransform; + edata = smethod->update(renderer, outTransform, opacity, clips, newFlag); + } + + /* 3. Composition Post Processing */ + if (cmpFastTrack) renderer.viewport(viewport); + else if (cmpData && cmpMethod == CompositeMethod::ClipPath) clips.pop(); + + return edata; +} + +bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed) +{ + Matrix* m = nullptr; + + //Case: No transformed, quick return! + if (!transformed || !(m = this->transform())) return smethod->bounds(x, y, w, h); + + //Case: Transformed + auto tx = 0.0f; + auto ty = 0.0f; + auto tw = 0.0f; + auto th = 0.0f; + + auto ret = smethod->bounds(&tx, &ty, &tw, &th); + + //Get vertices + Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}}; + + //New bounding box + auto x1 = FLT_MAX; + auto y1 = FLT_MAX; + auto x2 = -FLT_MAX; + auto y2 = -FLT_MAX; + + //function = Point * Matrix + auto multiply = [&](Point* pt, const Matrix* transform) { + auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13; + auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23; + pt->x = tx; + pt->y = ty; + }; + + //Compute the AABB after transformation + for (int i = 0; i < 4; i++) { + multiply(&pt[i], m); + + if (pt[i].x < x1) x1 = pt[i].x; + if (pt[i].x > x2) x2 = pt[i].x; + if (pt[i].y < y1) y1 = pt[i].y; + if (pt[i].y > y2) y2 = pt[i].y; + } + + if (x) *x = x1; + if (y) *y = y1; + if (w) *w = x2 - x1; + if (h) *h = y2 - y1; + + return ret; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Paint :: Paint() : pImpl(new Impl()) +{ +} + + +Paint :: ~Paint() +{ + delete(pImpl); +} + + +Result Paint::rotate(float degree) noexcept +{ + if (pImpl->rotate(degree)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::scale(float factor) noexcept +{ + if (pImpl->scale(factor)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::translate(float x, float y) noexcept +{ + if (pImpl->translate(x, y)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::transform(const Matrix& m) noexcept +{ + if (pImpl->transform(m)) return Result::Success; + return Result::FailedAllocation; +} + + +Matrix Paint::transform() noexcept +{ + auto pTransform = pImpl->transform(); + if (pTransform) return *pTransform; + return {1, 0, 0, 0, 1, 0, 0, 0, 1}; +} + + +TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept +{ + return this->bounds(x, y, w, h, false); +} + + +Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept +{ + if (pImpl->bounds(x, y, w, h, transform)) return Result::Success; + return Result::InsufficientCondition; +} + + +Paint* Paint::duplicate() const noexcept +{ + return pImpl->duplicate(); +} + + +Result Paint::composite(std::unique_ptr target, CompositeMethod method) noexcept +{ + if (pImpl->composite(target.release(), method)) return Result::Success; + return Result::InvalidArguments; +} + + +CompositeMethod Paint::composite(const Paint** target) const noexcept +{ + if (target) *target = pImpl->cmpTarget; + + return pImpl->cmpMethod; +} + + +Result Paint::opacity(uint8_t o) noexcept +{ + if (pImpl->opacity == o) return Result::Success; + + pImpl->opacity = o; + pImpl->flag |= RenderUpdateFlag::Color; + + return Result::Success; +} + + +uint8_t Paint::opacity() const noexcept +{ + return pImpl->opacity; +} diff --git a/thirdparty/thorvg/src/lib/tvgPaint.h b/thirdparty/thorvg/src/lib/tvgPaint.h new file mode 100644 index 0000000000..f69499caca --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgPaint.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_PAINT_H_ +#define _TVG_PAINT_H_ + +#include "tvgRender.h" + + +namespace tvg +{ + struct Iterator + { + virtual ~Iterator() {} + virtual const Paint* next() = 0; + virtual uint32_t count() = 0; + virtual void begin() = 0; + }; + + struct StrategyMethod + { + virtual ~StrategyMethod() {} + + virtual bool dispose(RenderMethod& renderer) = 0; + virtual void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag) = 0; //Return engine data if it has. + virtual bool render(RenderMethod& renderer) = 0; + virtual bool bounds(float* x, float* y, float* w, float* h) = 0; + virtual RenderRegion bounds(RenderMethod& renderer) const = 0; + virtual Paint* duplicate() = 0; + virtual Iterator* iterator() = 0; + }; + + struct Paint::Impl + { + StrategyMethod* smethod = nullptr; + RenderTransform *rTransform = nullptr; + uint32_t flag = RenderUpdateFlag::None; + Paint* cmpTarget = nullptr; + CompositeMethod cmpMethod = CompositeMethod::None; + uint8_t opacity = 255; + + ~Impl() { + if (cmpTarget) delete(cmpTarget); + if (smethod) delete(smethod); + if (rTransform) delete(rTransform); + } + + void method(StrategyMethod* method) + { + smethod = method; + } + + bool transform(const Matrix& m) + { + if (!rTransform) { + rTransform = new RenderTransform(); + if (!rTransform) return false; + } + rTransform->override(m); + flag |= RenderUpdateFlag::Transform; + + return true; + } + + Matrix* transform() + { + if (rTransform) { + rTransform->update(); + return &rTransform->m; + } + return nullptr; + } + + RenderRegion bounds(RenderMethod& renderer) const + { + return smethod->bounds(renderer); + } + + bool dispose(RenderMethod& renderer) + { + if (cmpTarget) cmpTarget->pImpl->dispose(renderer); + return smethod->dispose(renderer); + } + + Iterator* iterator() + { + return smethod->iterator(); + } + + bool composite(Paint* target, CompositeMethod method) + { + if ((!target && method != CompositeMethod::None) || (target && method == CompositeMethod::None)) return false; + if (cmpTarget) delete(cmpTarget); + cmpTarget = target; + cmpMethod = method; + return true; + } + + bool rotate(float degree); + bool scale(float factor); + bool translate(float x, float y); + bool bounds(float* x, float* y, float* w, float* h, bool transformed); + void* update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, uint32_t pFlag); + bool render(RenderMethod& renderer); + Paint* duplicate(); + }; + + + template + struct PaintMethod : StrategyMethod + { + T* inst = nullptr; + + PaintMethod(T* _inst) : inst(_inst) {} + ~PaintMethod() {} + + bool bounds(float* x, float* y, float* w, float* h) override + { + return inst->bounds(x, y, w, h); + } + + RenderRegion bounds(RenderMethod& renderer) const override + { + return inst->bounds(renderer); + } + + bool dispose(RenderMethod& renderer) override + { + return inst->dispose(renderer); + } + + void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flag) override + { + return inst->update(renderer, transform, opacity, clips, flag); + } + + bool render(RenderMethod& renderer) override + { + return inst->render(renderer); + } + + Paint* duplicate() override + { + return inst->duplicate(); + } + + Iterator* iterator() override + { + return inst->iterator(); + } + }; +} + +#endif //_TVG_PAINT_H_ diff --git a/thirdparty/thorvg/src/lib/tvgPicture.cpp b/thirdparty/thorvg/src/lib/tvgPicture.cpp new file mode 100644 index 0000000000..9aecbb14a0 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgPicture.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgPictureImpl.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Picture::Picture() : pImpl(new Impl(this)) +{ + _id = TVG_CLASS_ID_PICTURE; + Paint::pImpl->method(new PaintMethod(pImpl)); +} + + +Picture::~Picture() +{ + delete(pImpl); +} + + +unique_ptr Picture::gen() noexcept +{ + return unique_ptr(new Picture); +} + + +uint32_t Picture::identifier() noexcept +{ + return TVG_CLASS_ID_PICTURE; +} + + +Result Picture::load(const std::string& path) noexcept +{ + if (path.empty()) return Result::InvalidArguments; + + return pImpl->load(path); +} + + +Result Picture::load(const char* data, uint32_t size, const string& mimeType, bool copy) noexcept +{ + if (!data || size <= 0) return Result::InvalidArguments; + + return pImpl->load(data, size, mimeType, copy); +} + + +TVG_DEPRECATED Result Picture::load(const char* data, uint32_t size, bool copy) noexcept +{ + return load(data, size, "", copy); +} + + +Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept +{ + if (!data || w <= 0 || h <= 0) return Result::InvalidArguments; + + return pImpl->load(data, w, h, copy); +} + + +Result Picture::viewbox(float* x, float* y, float* w, float* h) const noexcept +{ + if (pImpl->viewbox(x, y, w, h)) return Result::Success; + return Result::InsufficientCondition; +} + + +Result Picture::size(float w, float h) noexcept +{ + if (pImpl->size(w, h)) return Result::Success; + return Result::InsufficientCondition; +} + + +Result Picture::size(float* w, float* h) const noexcept +{ + if (!pImpl->loader) return Result::InsufficientCondition; + if (w) *w = pImpl->w; + if (h) *h = pImpl->h; + return Result::Success; +} + + +const uint32_t* Picture::data(uint32_t* w, uint32_t* h) const noexcept +{ + //Try it, If not loaded yet. + pImpl->reload(); + + if (pImpl->loader) { + if (w) *w = static_cast(pImpl->loader->w); + if (h) *h = static_cast(pImpl->loader->h); + } else { + if (w) *w = 0; + if (h) *h = 0; + } + return pImpl->pixels; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgPictureImpl.h b/thirdparty/thorvg/src/lib/tvgPictureImpl.h new file mode 100644 index 0000000000..8a56f10639 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgPictureImpl.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_PICTURE_IMPL_H_ +#define _TVG_PICTURE_IMPL_H_ + +#include +#include "tvgPaint.h" +#include "tvgLoader.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct PictureIterator : Iterator +{ + Paint* paint = nullptr; + Paint* ptr = nullptr; + + PictureIterator(Paint* p) : paint(p) {} + + const Paint* next() override + { + if (!ptr) ptr = paint; + else ptr = nullptr; + return ptr; + } + + uint32_t count() override + { + if (paint) return 1; + else return 0; + } + + void begin() override + { + ptr = nullptr; + } +}; + + +struct Picture::Impl +{ + shared_ptr loader = nullptr; + Paint* paint = nullptr; + uint32_t* pixels = nullptr; + Picture* picture = nullptr; + void* rdata = nullptr; //engine data + float w = 0, h = 0; + bool resizing = false; + + Impl(Picture* p) : picture(p) + { + } + + ~Impl() + { + if (paint) delete(paint); + } + + bool dispose(RenderMethod& renderer) + { + bool ret = true; + if (paint) { + ret = paint->pImpl->dispose(renderer); + } else if (pixels) { + ret = renderer.dispose(rdata); + rdata = nullptr; + } + return ret; + } + + uint32_t reload() + { + if (loader) { + if (!paint) { + if (auto p = loader->paint()) { + paint = p.release(); + loader->close(); + if (w != loader->w || h != loader->h) { + loader->resize(paint, w, h); + resizing = false; + } + if (paint) return RenderUpdateFlag::None; + } + } + if (!pixels) { + pixels = const_cast(loader->pixels()); + loader->close(); + if (pixels) return RenderUpdateFlag::Image; + } + } + return RenderUpdateFlag::None; + } + + RenderTransform resizeTransform(const RenderTransform* pTransform) + { + //Overriding Transformation by the desired image size + auto sx = w / loader->w; + auto sy = h / loader->h; + auto scale = sx < sy ? sx : sy; + + RenderTransform tmp; + tmp.m = {scale, 0, 0, 0, scale, 0, 0, 0, 1}; + + if (!pTransform) return tmp; + else return RenderTransform(pTransform, &tmp); + } + + void* update(RenderMethod &renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag) + { + auto flag = reload(); + + if (pixels) { + auto transform = resizeTransform(pTransform); + rdata = renderer.prepare(*picture, rdata, &transform, opacity, clips, static_cast(pFlag | flag)); + } else if (paint) { + if (resizing) { + loader->resize(paint, w, h); + resizing = false; + } + rdata = paint->pImpl->update(renderer, pTransform, opacity, clips, static_cast(pFlag | flag)); + } + return rdata; + } + + bool render(RenderMethod &renderer) + { + if (pixels) return renderer.renderImage(rdata); + else if (paint) return paint->pImpl->render(renderer); + return false; + } + + bool viewbox(float* x, float* y, float* w, float* h) const + { + if (!loader) return false; + if (x) *x = loader->vx; + if (y) *y = loader->vy; + if (w) *w = loader->vw; + if (h) *h = loader->vh; + return true; + } + + bool size(float w, float h) + { + this->w = w; + this->h = h; + resizing = true; + return true; + } + + bool bounds(float* x, float* y, float* w, float* h) + { + if (x) *x = 0; + if (y) *y = 0; + if (w) *w = this->w; + if (h) *h = this->h; + + return true; + } + + RenderRegion bounds(RenderMethod& renderer) + { + if (rdata) return renderer.region(rdata); + if (paint) return paint->pImpl->bounds(renderer); + return {0, 0, 0, 0}; + } + + Result load(const string& path) + { + if (paint || pixels) return Result::InsufficientCondition; + if (loader) loader->close(); + bool invalid; //Invalid Path + loader = LoaderMgr::loader(path, &invalid); + if (!loader) { + if (invalid) return Result::InvalidArguments; + return Result::NonSupport; + } + if (!loader->read()) return Result::Unknown; + w = loader->w; + h = loader->h; + return Result::Success; + } + + Result load(const char* data, uint32_t size, const string& mimeType, bool copy) + { + if (paint || pixels) return Result::InsufficientCondition; + if (loader) loader->close(); + loader = LoaderMgr::loader(data, size, mimeType, copy); + if (!loader) return Result::NonSupport; + if (!loader->read()) return Result::Unknown; + w = loader->w; + h = loader->h; + return Result::Success; + } + + Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) + { + if (paint || pixels) return Result::InsufficientCondition; + if (loader) loader->close(); + loader = LoaderMgr::loader(data, w, h, copy); + if (!loader) return Result::NonSupport; + this->w = loader->w; + this->h = loader->h; + return Result::Success; + } + + Paint* duplicate() + { + reload(); + + auto ret = Picture::gen(); + if (!ret) return nullptr; + + auto dup = ret.get()->pImpl; + if (paint) dup->paint = paint->duplicate(); + + dup->loader = loader; + dup->pixels = pixels; + dup->w = w; + dup->h = h; + dup->resizing = resizing; + + return ret.release(); + } + + Iterator* iterator() + { + reload(); + return new PictureIterator(paint); + } +}; + +#endif //_TVG_PICTURE_IMPL_H_ diff --git a/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp b/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp new file mode 100644 index 0000000000..c289ca5911 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgRadialGradient.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include "tvgFill.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct RadialGradient::Impl +{ + float cx = 0; + float cy = 0; + float radius = 0; + + Fill* duplicate() + { + auto ret = RadialGradient::gen(); + if (!ret) return nullptr; + + ret->pImpl->cx = cx; + ret->pImpl->cy = cy; + ret->pImpl->radius = radius; + + return ret.release(); + } +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +RadialGradient::RadialGradient():pImpl(new Impl()) +{ + _id = TVG_CLASS_ID_RADIAL; + Fill::pImpl->method(new FillDup(pImpl)); +} + + +RadialGradient::~RadialGradient() +{ + delete(pImpl); +} + + +Result RadialGradient::radial(float cx, float cy, float radius) noexcept +{ + if (radius < 0) return Result::InvalidArguments; + + pImpl->cx = cx; + pImpl->cy = cy; + pImpl->radius = radius; + + return Result::Success; +} + + +Result RadialGradient::radial(float* cx, float* cy, float* radius) const noexcept +{ + if (cx) *cx = pImpl->cx; + if (cy) *cy = pImpl->cy; + if (radius) *radius = pImpl->radius; + + return Result::Success; +} + + +unique_ptr RadialGradient::gen() noexcept +{ + return unique_ptr(new RadialGradient); +} + + +uint32_t RadialGradient::identifier() noexcept +{ + return TVG_CLASS_ID_RADIAL; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgRender.cpp b/thirdparty/thorvg/src/lib/tvgRender.cpp new file mode 100644 index 0000000000..b00fa02377 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgRender.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgRender.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void RenderTransform::override(const Matrix& m) +{ + this->m = m; + + if (m.e11 == 0.0f && m.e12 == 0.0f && m.e13 == 0.0f && + m.e21 == 0.0f && m.e22 == 0.0f && m.e23 == 0.0f && + m.e31 == 0.0f && m.e32 == 0.0f && m.e33 == 0.0f) { + overriding = false; + } else overriding = true; +} + + +bool RenderTransform::update() +{ + constexpr auto PI = 3.141592f; + + if (overriding) return true; + + //Init Status + if (fabsf(x) <= FLT_EPSILON && fabsf(y) <= FLT_EPSILON && + fabsf(degree) <= FLT_EPSILON && fabsf(scale - 1) <= FLT_EPSILON) { + return false; + } + + //identity + m.e11 = 1.0f; + m.e12 = 0.0f; + m.e13 = 0.0f; + m.e21 = 0.0f; + m.e22 = 1.0f; + m.e23 = 0.0f; + m.e31 = 0.0f; + m.e32 = 0.0f; + m.e33 = 1.0f; + + //scale + m.e11 = scale; + m.e22 = scale; + + //rotation + if (fabsf(degree) > FLT_EPSILON) { + auto radian = degree / 180.0f * PI; + auto cosVal = cosf(radian); + auto sinVal = sinf(radian); + + m.e12 = m.e11 * -sinVal; + m.e11 *= cosVal; + m.e21 = m.e22 * sinVal; + m.e22 *= cosVal; + } + + m.e13 = x; + m.e23 = y; + + return true; +} + + +RenderTransform::RenderTransform() +{ +} + + +RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs) +{ + m.e11 = lhs->m.e11 * rhs->m.e11 + lhs->m.e12 * rhs->m.e21 + lhs->m.e13 * rhs->m.e31; + m.e12 = lhs->m.e11 * rhs->m.e12 + lhs->m.e12 * rhs->m.e22 + lhs->m.e13 * rhs->m.e32; + m.e13 = lhs->m.e11 * rhs->m.e13 + lhs->m.e12 * rhs->m.e23 + lhs->m.e13 * rhs->m.e33; + + m.e21 = lhs->m.e21 * rhs->m.e11 + lhs->m.e22 * rhs->m.e21 + lhs->m.e23 * rhs->m.e31; + m.e22 = lhs->m.e21 * rhs->m.e12 + lhs->m.e22 * rhs->m.e22 + lhs->m.e23 * rhs->m.e32; + m.e23 = lhs->m.e21 * rhs->m.e13 + lhs->m.e22 * rhs->m.e23 + lhs->m.e23 * rhs->m.e33; + + m.e31 = lhs->m.e31 * rhs->m.e11 + lhs->m.e32 * rhs->m.e21 + lhs->m.e33 * rhs->m.e31; + m.e32 = lhs->m.e31 * rhs->m.e12 + lhs->m.e32 * rhs->m.e22 + lhs->m.e33 * rhs->m.e32; + m.e33 = lhs->m.e31 * rhs->m.e13 + lhs->m.e32 * rhs->m.e23 + lhs->m.e33 * rhs->m.e33; +} diff --git a/thirdparty/thorvg/src/lib/tvgRender.h b/thirdparty/thorvg/src/lib/tvgRender.h new file mode 100644 index 0000000000..b45405addb --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgRender.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_RENDER_H_ +#define _TVG_RENDER_H_ + +#include "tvgCommon.h" +#include "tvgArray.h" + +namespace tvg +{ + +enum RenderUpdateFlag {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, All = 127}; + +struct Surface +{ + //TODO: Union for multiple types + uint32_t* buffer; + uint32_t stride; + uint32_t w, h; + uint32_t cs; +}; + +using RenderData = void*; + +struct Compositor { + CompositeMethod method; + uint32_t opacity; +}; + +struct RenderRegion { + uint32_t x, y, w, h; + + void intersect(const RenderRegion& rhs) + { + auto x1 = x + w; + auto y1 = y + h; + auto x2 = rhs.x + rhs.w; + auto y2 = rhs.y + rhs.h; + + x = (x > rhs.x) ? x : rhs.x; + y = (y > rhs.y) ? y : rhs.y; + w = ((x1 < x2) ? x1 : x2) - x; + h = ((y1 < y2) ? y1 : y2) - y; + } +}; + +struct RenderTransform +{ + Matrix m; //3x3 Matrix Elements + float x = 0.0f; + float y = 0.0f; + float degree = 0.0f; //rotation degree + float scale = 1.0f; //scale factor + bool overriding = false; //user transform? + + bool update(); + void override(const Matrix& m); + + RenderTransform(); + RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs); +}; + +class RenderMethod +{ +public: + virtual ~RenderMethod() {} + virtual RenderData prepare(const Shape& shape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) = 0; + virtual RenderData prepare(const Picture& picture, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) = 0; + virtual bool preRender() = 0; + virtual bool renderShape(RenderData data) = 0; + virtual bool renderImage(RenderData data) = 0; + virtual bool postRender() = 0; + virtual bool dispose(RenderData data) = 0; + virtual RenderRegion region(RenderData data) = 0; + virtual RenderRegion viewport() = 0; + virtual bool viewport(const RenderRegion& vp) = 0; + + virtual bool clear() = 0; + virtual bool sync() = 0; + + virtual Compositor* target(const RenderRegion& region) = 0; + virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) = 0; + virtual bool endComposite(Compositor* cmp) = 0; +}; + +} + +#endif //_TVG_RENDER_H_ diff --git a/thirdparty/thorvg/src/lib/tvgSaveModule.h b/thirdparty/thorvg/src/lib/tvgSaveModule.h new file mode 100644 index 0000000000..2a0f427f11 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgSaveModule.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SAVE_MODULE_H_ +#define _TVG_SAVE_MODULE_H_ + +#include "tvgIteratorAccessor.h" + +namespace tvg +{ + +class SaveModule : public IteratorAccessor +{ +public: + virtual ~SaveModule() {} + + virtual bool save(Paint* paint, const string& path, bool compress) = 0; + virtual bool close() = 0; +}; + +} + +#endif //_TVG_SAVE_MODULE_H_ diff --git a/thirdparty/thorvg/src/lib/tvgSaver.cpp b/thirdparty/thorvg/src/lib/tvgSaver.cpp new file mode 100644 index 0000000000..ffa2bf1a5b --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgSaver.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgCommon.h" +#include "tvgSaveModule.h" + +#ifdef THORVG_TVG_SAVER_SUPPORT + #include "tvgTvgSaver.h" +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Saver::Impl +{ + SaveModule* saveModule = nullptr; + ~Impl() + { + if (saveModule) delete(saveModule); + } +}; + + +static SaveModule* _find(FileType type) +{ + switch(type) { + case FileType::Tvg: { +#ifdef THORVG_TVG_SAVER_SUPPORT + return new TvgSaver; +#endif + break; + } + default: { + break; + } + } + +#ifdef THORVG_LOG_ENABLED + const char *format; + switch(type) { + case FileType::Tvg: { + format = "TVG"; + break; + } + default: { + format = "???"; + break; + } + } + TVGLOG("SAVER", "%s format is not supported", format); +#endif + return nullptr; +} + + +static SaveModule* _find(const string& path) +{ + auto ext = path.substr(path.find_last_of(".") + 1); + if (!ext.compare("tvg")) { + return _find(FileType::Tvg); + } + return nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Saver::Saver() : pImpl(new Impl()) +{ +} + + +Saver::~Saver() +{ + delete(pImpl); +} + + +Result Saver::save(std::unique_ptr paint, const string& path, bool compress) noexcept +{ + //Already on saving an other resource. + if (pImpl->saveModule) return Result::InsufficientCondition; + + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + + if (auto saveModule = _find(path)) { + if (saveModule->save(p, path, compress)) { + pImpl->saveModule = saveModule; + return Result::Success; + } else { + return Result::Unknown; + } + } + return Result::NonSupport; +} + + +Result Saver::sync() noexcept +{ + if (!pImpl->saveModule) return Result::InsufficientCondition; + pImpl->saveModule->close(); + delete(pImpl->saveModule); + pImpl->saveModule = nullptr; + + return Result::Success; +} + + +unique_ptr Saver::gen() noexcept +{ + return unique_ptr(new Saver); +} diff --git a/thirdparty/thorvg/src/lib/tvgScene.cpp b/thirdparty/thorvg/src/lib/tvgScene.cpp new file mode 100644 index 0000000000..1516b2eb01 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgScene.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgSceneImpl.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Scene::Scene() : pImpl(new Impl()) +{ + _id = TVG_CLASS_ID_SCENE; + Paint::pImpl->method(new PaintMethod(pImpl)); +} + + +Scene::~Scene() +{ + delete(pImpl); +} + + +unique_ptr Scene::gen() noexcept +{ + return unique_ptr(new Scene); +} + + +uint32_t Scene::identifier() noexcept +{ + return TVG_CLASS_ID_SCENE; +} + + +Result Scene::push(unique_ptr paint) noexcept +{ + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + pImpl->paints.push(p); + + return Result::Success; +} + + +Result Scene::reserve(uint32_t size) noexcept +{ + if (!pImpl->paints.reserve(size)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Scene::clear(bool free) noexcept +{ + pImpl->clear(free); + + return Result::Success; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgSceneImpl.h b/thirdparty/thorvg/src/lib/tvgSceneImpl.h new file mode 100644 index 0000000000..fb979ae54a --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgSceneImpl.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SCENE_IMPL_H_ +#define _TVG_SCENE_IMPL_H_ + +#include +#include "tvgPaint.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct SceneIterator : Iterator +{ + Array* paints; + uint32_t idx = 0; + + SceneIterator(Array* p) : paints(p) + { + } + + const Paint* next() override + { + if (idx >= paints->count) return nullptr; + return paints->data[idx++]; + } + + uint32_t count() override + { + return paints->count; + } + + void begin() override + { + idx = 0; + } +}; + +struct Scene::Impl +{ + Array paints; + uint8_t opacity; //for composition + RenderMethod* renderer = nullptr; //keep it for explicit clear + + ~Impl() + { + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + delete(*paint); + } + } + + bool dispose(RenderMethod& renderer) + { + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + (*paint)->pImpl->dispose(renderer); + } + + this->renderer = nullptr; + + return true; + } + + bool needComposition(uint32_t opacity) + { + //Half translucent requires intermediate composition. + if (opacity == 255 || opacity == 0) return false; + + //If scene has several children or only scene, it may require composition. + if (paints.count > 1) return true; + if (paints.count == 1 && (*paints.data)->identifier() == TVG_CLASS_ID_SCENE) return true; + return false; + } + + void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flag) + { + /* Overriding opacity value. If this scene is half-translucent, + It must do intermeidate composition with that opacity value. */ + this->opacity = static_cast(opacity); + if (needComposition(opacity)) opacity = 255; + + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + (*paint)->pImpl->update(renderer, transform, opacity, clips, static_cast(flag)); + } + + /* FXIME: it requires to return list of children engine data + This is necessary for scene composition */ + + this->renderer = &renderer; + + return nullptr; + } + + bool render(RenderMethod& renderer) + { + Compositor* cmp = nullptr; + + if (needComposition(opacity)) { + cmp = renderer.target(bounds(renderer)); + renderer.beginComposite(cmp, CompositeMethod::None, opacity); + } + + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + if (!(*paint)->pImpl->render(renderer)) return false; + } + + if (cmp) renderer.endComposite(cmp); + + return true; + } + + RenderRegion bounds(RenderMethod& renderer) const + { + if (paints.count == 0) return {0, 0, 0, 0}; + + uint32_t x1 = UINT32_MAX; + uint32_t y1 = UINT32_MAX; + uint32_t x2 = 0; + uint32_t y2 = 0; + + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + auto region = (*paint)->pImpl->bounds(renderer); + + //Merge regions + if (region.x < x1) x1 = region.x; + if (x2 < region.x + region.w) x2 = (region.x + region.w); + if (region.y < y1) y1 = region.y; + if (y2 < region.y + region.h) y2 = (region.y + region.h); + } + + return {x1, y1, (x2 - x1), (y2 - y1)}; + } + + bool bounds(float* px, float* py, float* pw, float* ph) + { + if (paints.count == 0) return false; + + auto x1 = FLT_MAX; + auto y1 = FLT_MAX; + auto x2 = -FLT_MAX; + auto y2 = -FLT_MAX; + + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + auto x = FLT_MAX; + auto y = FLT_MAX; + auto w = 0.0f; + auto h = 0.0f; + + if ((*paint)->bounds(&x, &y, &w, &h, true) != tvg::Result::Success) continue; + + //Merge regions + if (x < x1) x1 = x; + if (x2 < x + w) x2 = (x + w); + if (y < y1) y1 = y; + if (y2 < y + h) y2 = (y + h); + } + + if (px) *px = x1; + if (py) *py = y1; + if (pw) *pw = (x2 - x1); + if (ph) *ph = (y2 - y1); + + return true; + } + + Paint* duplicate() + { + auto ret = Scene::gen(); + if (!ret) return nullptr; + auto dup = ret.get()->pImpl; + + dup->paints.reserve(paints.count); + + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + dup->paints.push((*paint)->duplicate()); + } + + return ret.release(); + } + + void clear(bool free) + { + auto dispose = renderer ? true : false; + + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + if (dispose) (*paint)->pImpl->dispose(*renderer); + if (free) delete(*paint); + } + paints.clear(); + renderer = nullptr; + } + + Iterator* iterator() + { + return new SceneIterator(&paints); + } +}; + +#endif //_TVG_SCENE_IMPL_H_ diff --git a/thirdparty/thorvg/src/lib/tvgShape.cpp b/thirdparty/thorvg/src/lib/tvgShape.cpp new file mode 100644 index 0000000000..e1eaa59d91 --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgShape.cpp @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. + +#include +#include +#include +#include "tvgShapeImpl.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +constexpr auto PATH_KAPPA = 0.552284f; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Shape :: Shape() : pImpl(new Impl(this)) +{ + _id = TVG_CLASS_ID_SHAPE; + Paint::pImpl->method(new PaintMethod(pImpl)); +} + + +Shape :: ~Shape() +{ + delete(pImpl); +} + + +unique_ptr Shape::gen() noexcept +{ + return unique_ptr(new Shape); +} + + +uint32_t Shape::identifier() noexcept +{ + return TVG_CLASS_ID_SHAPE; +} + + +Result Shape::reset() noexcept +{ + pImpl->path.reset(); + pImpl->flag = RenderUpdateFlag::Path; + + return Result::Success; +} + + +uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept +{ + if (!cmds) return 0; + + *cmds = pImpl->path.cmds; + + return pImpl->path.cmdCnt; +} + + +uint32_t Shape::pathCoords(const Point** pts) const noexcept +{ + if (!pts) return 0; + + *pts = pImpl->path.pts; + + return pImpl->path.ptsCnt; +} + + +Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept +{ + if (cmdCnt == 0 || ptsCnt == 0 || !cmds || !pts) return Result::InvalidArguments; + + pImpl->path.grow(cmdCnt, ptsCnt); + pImpl->path.append(cmds, cmdCnt, pts, ptsCnt); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::moveTo(float x, float y) noexcept +{ + pImpl->path.moveTo(x, y); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::lineTo(float x, float y) noexcept +{ + pImpl->path.lineTo(x, y); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept +{ + pImpl->path.cubicTo(cx1, cy1, cx2, cy2, x, y); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::close() noexcept +{ + pImpl->path.close(); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept +{ + auto rxKappa = rx * PATH_KAPPA; + auto ryKappa = ry * PATH_KAPPA; + + pImpl->path.grow(6, 13); + pImpl->path.moveTo(cx, cy - ry); + pImpl->path.cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); + pImpl->path.cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry); + pImpl->path.cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); + pImpl->path.cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); + pImpl->path.close(); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + +Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept +{ + const float M_PI_HALF = (float)(M_PI * 0.5f); + + //just circle + if (sweep >= 360 || sweep <= -360) return appendCircle(cx, cy, radius, radius); + + startAngle = (startAngle * M_PI) / 180; + sweep = sweep * M_PI / 180; + + auto nCurves = ceil(fabsf(sweep / M_PI_HALF)); + auto sweepSign = (sweep < 0 ? -1 : 1); + auto fract = fmodf(sweep, M_PI_HALF); + fract = (fabsf(fract) < std::numeric_limits::epsilon()) ? M_PI_HALF * sweepSign : fract; + + //Start from here + Point start = {radius * cosf(startAngle), radius * sinf(startAngle)}; + + if (pie) { + pImpl->path.moveTo(cx, cy); + pImpl->path.lineTo(start.x + cx, start.y + cy); + } else { + pImpl->path.moveTo(start.x + cx, start.y + cy); + } + + for (int i = 0; i < nCurves; ++i) { + auto endAngle = startAngle + ((i != nCurves - 1) ? M_PI_HALF * sweepSign : fract); + Point end = {radius * cosf(endAngle), radius * sinf(endAngle)}; + + //variables needed to calculate bezier control points + + //get bezier control points using article: + //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479) + auto ax = start.x; + auto ay = start.y; + auto bx = end.x; + auto by = end.y; + auto q1 = ax * ax + ay * ay; + auto q2 = ax * bx + ay * by + q1; + auto k2 = (4.0f/3.0f) * ((sqrtf(2 * q1 * q2) - q2) / (ax * by - ay * bx)); + + start = end; //Next start point is the current end point + + end.x += cx; + end.y += cy; + + Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy}; + Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy}; + + pImpl->path.cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y); + + startAngle = endAngle; + } + + if (pie) pImpl->path.close(); + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept +{ + auto halfW = w * 0.5f; + auto halfH = h * 0.5f; + + //clamping cornerRadius by minimum size + if (rx > halfW) rx = halfW; + if (ry > halfH) ry = halfH; + + //rectangle + if (rx == 0 && ry == 0) { + pImpl->path.grow(5, 4); + pImpl->path.moveTo(x, y); + pImpl->path.lineTo(x + w, y); + pImpl->path.lineTo(x + w, y + h); + pImpl->path.lineTo(x, y + h); + pImpl->path.close(); + //circle + } else if (fabsf(rx - halfW) < FLT_EPSILON && fabsf(ry - halfH) < FLT_EPSILON) { + return appendCircle(x + (w * 0.5f), y + (h * 0.5f), rx, ry); + } else { + auto hrx = rx * 0.5f; + auto hry = ry * 0.5f; + pImpl->path.grow(10, 17); + pImpl->path.moveTo(x + rx, y); + pImpl->path.lineTo(x + w - rx, y); + pImpl->path.cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); + pImpl->path.lineTo(x + w, y + h - ry); + pImpl->path.cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); + pImpl->path.lineTo(x + rx, y + h); + pImpl->path.cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); + pImpl->path.lineTo(x, y + ry); + pImpl->path.cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); + pImpl->path.close(); + } + + pImpl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +{ + pImpl->color[0] = r; + pImpl->color[1] = g; + pImpl->color[2] = b; + pImpl->color[3] = a; + pImpl->flag |= RenderUpdateFlag::Color; + + if (pImpl->fill) { + delete(pImpl->fill); + pImpl->fill = nullptr; + pImpl->flag |= RenderUpdateFlag::Gradient; + } + + return Result::Success; +} + + +Result Shape::fill(unique_ptr f) noexcept +{ + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + if (pImpl->fill && pImpl->fill != p) delete(pImpl->fill); + pImpl->fill = p; + pImpl->flag |= RenderUpdateFlag::Gradient; + + return Result::Success; +} + + +Result Shape::fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +{ + if (r) *r = pImpl->color[0]; + if (g) *g = pImpl->color[1]; + if (b) *b = pImpl->color[2]; + if (a) *a = pImpl->color[3]; + + return Result::Success; +} + +const Fill* Shape::fill() const noexcept +{ + return pImpl->fill; +} + + +Result Shape::stroke(float width) noexcept +{ + if (!pImpl->strokeWidth(width)) return Result::FailedAllocation; + + return Result::Success; +} + + +float Shape::strokeWidth() const noexcept +{ + if (!pImpl->stroke) return 0; + return pImpl->stroke->width; +} + + +Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +{ + if (!pImpl->strokeColor(r, g, b, a)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +{ + if (!pImpl->stroke) return Result::InsufficientCondition; + + if (r) *r = pImpl->stroke->color[0]; + if (g) *g = pImpl->stroke->color[1]; + if (b) *b = pImpl->stroke->color[2]; + if (a) *a = pImpl->stroke->color[3]; + + return Result::Success; +} + + +Result Shape::stroke(unique_ptr f) noexcept +{ + return pImpl->strokeFill(move(f)); +} + + +const Fill* Shape::strokeFill() const noexcept +{ + if (!pImpl->stroke) return nullptr; + + return pImpl->stroke->fill; +} + + +Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept +{ + if ((cnt == 1) || (!dashPattern && cnt > 0) || (dashPattern && cnt == 0)) { + return Result::InvalidArguments; + } + + for (uint32_t i = 0; i < cnt; i++) + if (dashPattern[i] < FLT_EPSILON) return Result::InvalidArguments; + + if (!pImpl->strokeDash(dashPattern, cnt)) return Result::FailedAllocation; + + return Result::Success; +} + + +uint32_t Shape::strokeDash(const float** dashPattern) const noexcept +{ + if (!pImpl->stroke) return 0; + + if (dashPattern) *dashPattern = pImpl->stroke->dashPattern; + + return pImpl->stroke->dashCnt; +} + + +Result Shape::stroke(StrokeCap cap) noexcept +{ + if (!pImpl->strokeCap(cap)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::stroke(StrokeJoin join) noexcept +{ + if (!pImpl->strokeJoin(join)) return Result::FailedAllocation; + + return Result::Success; +} + + +StrokeCap Shape::strokeCap() const noexcept +{ + if (!pImpl->stroke) return StrokeCap::Square; + + return pImpl->stroke->cap; +} + + +StrokeJoin Shape::strokeJoin() const noexcept +{ + if (!pImpl->stroke) return StrokeJoin::Bevel; + + return pImpl->stroke->join; +} + + +Result Shape::fill(FillRule r) noexcept +{ + pImpl->rule = r; + + return Result::Success; +} + + +FillRule Shape::fillRule() const noexcept +{ + return pImpl->rule; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgShapeImpl.h b/thirdparty/thorvg/src/lib/tvgShapeImpl.h new file mode 100644 index 0000000000..84af1e6e6d --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgShapeImpl.h @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SHAPE_IMPL_H_ +#define _TVG_SHAPE_IMPL_H_ + +#include +#include "tvgPaint.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct ShapeStroke +{ + float width = 0; + uint8_t color[4] = {0, 0, 0, 0}; + Fill *fill = nullptr; + float* dashPattern = nullptr; + uint32_t dashCnt = 0; + StrokeCap cap = StrokeCap::Square; + StrokeJoin join = StrokeJoin::Bevel; + + ShapeStroke() {} + + ShapeStroke(const ShapeStroke* src) + : width(src->width), + dashCnt(src->dashCnt), + cap(src->cap), + join(src->join) + { + memcpy(color, src->color, sizeof(color)); + if (dashCnt > 0) { + dashPattern = static_cast(malloc(sizeof(float) * dashCnt)); + memcpy(dashPattern, src->dashPattern, sizeof(float) * dashCnt); + } + if (src->fill) fill = src->fill->duplicate(); + } + + ~ShapeStroke() + { + if (dashPattern) free(dashPattern); + if (fill) delete(fill); + } +}; + + +struct ShapePath +{ + PathCommand* cmds = nullptr; + uint32_t cmdCnt = 0; + uint32_t reservedCmdCnt = 0; + + Point *pts = nullptr; + uint32_t ptsCnt = 0; + uint32_t reservedPtsCnt = 0; + + ~ShapePath() + { + if (cmds) free(cmds); + if (pts) free(pts); + } + + ShapePath() + { + } + + void duplicate(const ShapePath* src) + { + cmdCnt = src->cmdCnt; + reservedCmdCnt = src->reservedCmdCnt; + ptsCnt = src->ptsCnt; + reservedPtsCnt = src->reservedPtsCnt; + + cmds = static_cast(malloc(sizeof(PathCommand) * reservedCmdCnt)); + if (!cmds) return; + memcpy(cmds, src->cmds, sizeof(PathCommand) * cmdCnt); + + pts = static_cast(malloc(sizeof(Point) * reservedPtsCnt)); + if (!pts) { + free(cmds); + return; + } + memcpy(pts, src->pts, sizeof(Point) * ptsCnt); + } + + void reserveCmd(uint32_t cmdCnt) + { + if (cmdCnt <= reservedCmdCnt) return; + reservedCmdCnt = cmdCnt; + cmds = static_cast(realloc(cmds, sizeof(PathCommand) * reservedCmdCnt)); + } + + void reservePts(uint32_t ptsCnt) + { + if (ptsCnt <= reservedPtsCnt) return; + reservedPtsCnt = ptsCnt; + pts = static_cast(realloc(pts, sizeof(Point) * reservedPtsCnt)); + } + + void grow(uint32_t cmdCnt, uint32_t ptsCnt) + { + reserveCmd(this->cmdCnt + cmdCnt); + reservePts(this->ptsCnt + ptsCnt); + } + + void reset() + { + cmdCnt = 0; + ptsCnt = 0; + } + + void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) + { + memcpy(this->cmds + this->cmdCnt, cmds, sizeof(PathCommand) * cmdCnt); + memcpy(this->pts + this->ptsCnt, pts, sizeof(Point) * ptsCnt); + this->cmdCnt += cmdCnt; + this->ptsCnt += ptsCnt; + } + + void moveTo(float x, float y) + { + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + if (ptsCnt + 2 > reservedPtsCnt) reservePts((ptsCnt + 2) * 2); + + cmds[cmdCnt++] = PathCommand::MoveTo; + pts[ptsCnt++] = {x, y}; + } + + void lineTo(float x, float y) + { + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + if (ptsCnt + 2 > reservedPtsCnt) reservePts((ptsCnt + 2) * 2); + + cmds[cmdCnt++] = PathCommand::LineTo; + pts[ptsCnt++] = {x, y}; + } + + void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) + { + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + if (ptsCnt + 3 > reservedPtsCnt) reservePts((ptsCnt + 3) * 2); + + cmds[cmdCnt++] = PathCommand::CubicTo; + pts[ptsCnt++] = {cx1, cy1}; + pts[ptsCnt++] = {cx2, cy2}; + pts[ptsCnt++] = {x, y}; + } + + void close() + { + if (cmdCnt > 0 && cmds[cmdCnt - 1] == PathCommand::Close) return; + + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + cmds[cmdCnt++] = PathCommand::Close; + } + + bool bounds(float* x, float* y, float* w, float* h) const + { + if (ptsCnt == 0) return false; + + Point min = { pts[0].x, pts[0].y }; + Point max = { pts[0].x, pts[0].y }; + + for (uint32_t i = 1; i < ptsCnt; ++i) { + if (pts[i].x < min.x) min.x = pts[i].x; + if (pts[i].y < min.y) min.y = pts[i].y; + if (pts[i].x > max.x) max.x = pts[i].x; + if (pts[i].y > max.y) max.y = pts[i].y; + } + + if (x) *x = min.x; + if (y) *y = min.y; + if (w) *w = max.x - min.x; + if (h) *h = max.y - min.y; + + return true; + } +}; + + +struct Shape::Impl +{ + ShapePath path; + Fill *fill = nullptr; + ShapeStroke *stroke = nullptr; + uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a + FillRule rule = FillRule::Winding; + RenderData rdata = nullptr; //engine data + Shape *shape = nullptr; + uint32_t flag = RenderUpdateFlag::None; + + Impl(Shape* s) : shape(s) + { + } + + ~Impl() + { + if (fill) delete(fill); + if (stroke) delete(stroke); + } + + bool dispose(RenderMethod& renderer) + { + auto ret = renderer.dispose(rdata); + rdata = nullptr; + return ret; + } + + bool render(RenderMethod& renderer) + { + return renderer.renderShape(rdata); + } + + void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag) + { + this->rdata = renderer.prepare(*shape, this->rdata, transform, opacity, clips, static_cast(pFlag | flag)); + flag = RenderUpdateFlag::None; + return this->rdata; + } + + RenderRegion bounds(RenderMethod& renderer) + { + return renderer.region(rdata); + } + + bool bounds(float* x, float* y, float* w, float* h) + { + auto ret = path.bounds(x, y, w, h); + + //Stroke feathering + if (stroke) { + if (x) *x -= stroke->width * 0.5f; + if (y) *y -= stroke->width * 0.5f; + if (w) *w += stroke->width; + if (h) *h += stroke->width; + } + return ret; + } + + bool strokeWidth(float width) + { + //TODO: Size Exception? + + if (!stroke) stroke = new ShapeStroke(); + stroke->width = width; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeCap(StrokeCap cap) + { + if (!stroke) stroke = new ShapeStroke(); + stroke->cap = cap; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeJoin(StrokeJoin join) + { + if (!stroke) stroke = new ShapeStroke(); + stroke->join = join; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + if (!stroke) stroke = new ShapeStroke(); + if (stroke->fill) { + delete(stroke->fill); + stroke->fill = nullptr; + flag |= RenderUpdateFlag::GradientStroke; + } + + stroke->color[0] = r; + stroke->color[1] = g; + stroke->color[2] = b; + stroke->color[3] = a; + + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + Result strokeFill(unique_ptr f) + { + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + if (!stroke) stroke = new ShapeStroke(); + if (stroke->fill && stroke->fill != p) delete(stroke->fill); + stroke->fill = p; + + flag |= RenderUpdateFlag::Stroke; + flag |= RenderUpdateFlag::GradientStroke; + + return Result::Success; + } + + bool strokeDash(const float* pattern, uint32_t cnt) + { + //Reset dash + if (!pattern && cnt == 0) { + free(stroke->dashPattern); + stroke->dashPattern = nullptr; + } else { + if (!stroke) stroke = new ShapeStroke(); + if (stroke->dashCnt != cnt) { + free(stroke->dashPattern); + stroke->dashPattern = nullptr; + } + if (!stroke->dashPattern) { + stroke->dashPattern = static_cast(malloc(sizeof(float) * cnt)); + if (!stroke->dashPattern) return false; + } + for (uint32_t i = 0; i < cnt; ++i) { + stroke->dashPattern[i] = pattern[i]; + } + } + stroke->dashCnt = cnt; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + Paint* duplicate() + { + auto ret = Shape::gen(); + if (!ret) return nullptr; + + auto dup = ret.get()->pImpl; + dup->rule = rule; + + //Color + memcpy(dup->color, color, sizeof(color)); + dup->flag = RenderUpdateFlag::Color; + + //Path + dup->path.duplicate(&path); + dup->flag |= RenderUpdateFlag::Path; + + //Stroke + if (stroke) { + dup->stroke = new ShapeStroke(stroke); + dup->flag |= RenderUpdateFlag::Stroke; + + if (stroke->fill) + dup->flag |= RenderUpdateFlag::GradientStroke; + } + + //Fill + if (fill) { + dup->fill = fill->duplicate(); + dup->flag |= RenderUpdateFlag::Gradient; + } + + return ret.release(); + } + + Iterator* iterator() + { + return nullptr; + } +}; + +#endif //_TVG_SHAPE_IMPL_H_ diff --git a/thirdparty/thorvg/src/lib/tvgSwCanvas.cpp b/thirdparty/thorvg/src/lib/tvgSwCanvas.cpp new file mode 100644 index 0000000000..f7a03b819c --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgSwCanvas.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "tvgCanvasImpl.h" + +#ifdef THORVG_SW_RASTER_SUPPORT + #include "tvgSwRenderer.h" +#else + class SwRenderer : public RenderMethod + { + //Non Supported. Dummy Class */ + }; +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct SwCanvas::Impl +{ +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +#ifdef THORVG_SW_RASTER_SUPPORT +SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(new Impl) +#else +SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(new Impl) +#endif +{ +} + + +SwCanvas::~SwCanvas() +{ + delete(pImpl); +} + + +Result SwCanvas::mempool(MempoolPolicy policy) noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl->renderer); + if (!renderer) return Result::MemoryCorruption; + + //It can't change the policy during the running. + if (Canvas::pImpl->paints.count > 0) return Result::InsufficientCondition; + + if (policy == MempoolPolicy::Individual) renderer->mempool(false); + else renderer->mempool(true); + + return Result::Success; +#endif + return Result::NonSupport; +} + + +Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl->renderer); + if (!renderer) return Result::MemoryCorruption; + + if (!renderer->target(buffer, stride, w, h, cs)) return Result::InvalidArguments; + + //Paints must be updated again with this new target. + Canvas::pImpl->needRefresh(); + + return Result::Success; +#endif + return Result::NonSupport; +} + + +unique_ptr SwCanvas::gen() noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + if (SwRenderer::init() <= 0) return nullptr; + return unique_ptr(new SwCanvas); +#endif + return nullptr; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp b/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp new file mode 100644 index 0000000000..780127b87b --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgTaskScheduler.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include +#include +#include +#include "tvgTaskScheduler.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +namespace tvg { + +struct TaskQueue { + deque taskDeque; + mutex mtx; + condition_variable ready; + bool done = false; + + bool tryPop(Task** task) + { + unique_lock lock{mtx, try_to_lock}; + if (!lock || taskDeque.empty()) return false; + *task = taskDeque.front(); + taskDeque.pop_front(); + + return true; + } + + bool tryPush(Task* task) + { + { + unique_lock lock{mtx, try_to_lock}; + if (!lock) return false; + taskDeque.push_back(task); + } + + ready.notify_one(); + + return true; + } + + void complete() + { + { + unique_lock lock{mtx}; + done = true; + } + ready.notify_all(); + } + + bool pop(Task** task) + { + unique_lock lock{mtx}; + + while (taskDeque.empty() && !done) { + ready.wait(lock); + } + + if (taskDeque.empty()) return false; + + *task = taskDeque.front(); + taskDeque.pop_front(); + + return true; + } + + void push(Task* task) + { + { + unique_lock lock{mtx}; + taskDeque.push_back(task); + } + + ready.notify_one(); + } + +}; + + +class TaskSchedulerImpl +{ +public: + unsigned threadCnt; + vector threads; + vector taskQueues; + atomic idx{0}; + + TaskSchedulerImpl(unsigned threadCnt) : threadCnt(threadCnt), taskQueues(threadCnt) + { + for (unsigned i = 0; i < threadCnt; ++i) { + threads.emplace_back([&, i] { run(i); }); + } + } + + ~TaskSchedulerImpl() + { + for (auto& queue : taskQueues) queue.complete(); + for (auto& thread : threads) thread.join(); + } + + void run(unsigned i) + { + Task* task; + + //Thread Loop + while (true) { + auto success = false; + for (unsigned x = 0; x < threadCnt * 2; ++x) { + if (taskQueues[(i + x) % threadCnt].tryPop(&task)) { + success = true; + break; + } + } + + if (!success && !taskQueues[i].pop(&task)) break; + (*task)(i); + } + } + + void request(Task* task) + { + //Async + if (threadCnt > 0) { + task->prepare(); + auto i = idx++; + for (unsigned n = 0; n < threadCnt; ++n) { + if (taskQueues[(i + n) % threadCnt].tryPush(task)) return; + } + taskQueues[i % threadCnt].push(task); + //Sync + } else { + task->run(0); + } + } +}; + +} + +static TaskSchedulerImpl* inst = nullptr; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void TaskScheduler::init(unsigned threads) +{ + if (inst) return; + inst = new TaskSchedulerImpl(threads); +} + + +void TaskScheduler::term() +{ + if (!inst) return; + delete(inst); + inst = nullptr; +} + + +void TaskScheduler::request(Task* task) +{ + if (inst) inst->request(task); +} + + +unsigned TaskScheduler::threads() +{ + if (inst) return inst->threadCnt; + return 0; +} diff --git a/thirdparty/thorvg/src/lib/tvgTaskScheduler.h b/thirdparty/thorvg/src/lib/tvgTaskScheduler.h new file mode 100644 index 0000000000..f30bdf9a8f --- /dev/null +++ b/thirdparty/thorvg/src/lib/tvgTaskScheduler.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_TASK_SCHEDULER_H_ +#define _TVG_TASK_SCHEDULER_H_ + +#include +#include +#include "tvgCommon.h" + +namespace tvg +{ + +struct Task; + +struct TaskScheduler +{ + static unsigned threads(); + static void init(unsigned threads); + static void term(); + static void request(Task* task); +}; + +struct Task +{ +private: + mutex mtx; + condition_variable cv; + bool ready{true}; + bool pending{false}; + +public: + virtual ~Task() = default; + + void done() + { + if (!pending) return; + + unique_lock lock(mtx); + while (!ready) cv.wait(lock); + pending = false; + } + +protected: + virtual void run(unsigned tid) = 0; + +private: + void operator()(unsigned tid) + { + run(tid); + + lock_guard lock(mtx); + ready = true; + cv.notify_one(); + } + + void prepare() + { + ready = false; + pending = true; + } + + friend class TaskSchedulerImpl; +}; + + + +} + +#endif //_TVG_TASK_SCHEDULER_H_ diff --git a/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp new file mode 100644 index 0000000000..76ccdde358 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "tvgLoader.h" +#include "tvgJpgLoader.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +void JpgLoader::clear() +{ + if (freeData) free(data); + data = nullptr; + size = 0; + freeData = false; +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +JpgLoader::JpgLoader() +{ + jpegDecompressor = tjInitDecompress(); +} + + +JpgLoader::~JpgLoader() +{ + if (freeData) free(data); + tjDestroy(jpegDecompressor); + + //This image is shared with raster engine. + tjFree(image); +} + + +bool JpgLoader::open(const string& path) +{ + clear(); + + auto jpegFile = fopen(path.c_str(), "rb"); + if (!jpegFile) return false; + + auto ret = false; + + //determine size + if (fseek(jpegFile, 0, SEEK_END) < 0) goto finalize; + if (((size = ftell(jpegFile)) < 1)) goto finalize; + if (fseek(jpegFile, 0, SEEK_SET)) goto finalize; + + data = (unsigned char *) malloc(size); + if (!data) goto finalize; + + freeData = true; + + if (fread(data, size, 1, jpegFile) < 1) goto failure; + + int width, height, subSample, colorSpace; + if (tjDecompressHeader3(jpegDecompressor, data, size, &width, &height, &subSample, &colorSpace) < 0) { + TVGERR("JPG LOADER", "%s", tjGetErrorStr()); + goto failure; + } + + w = static_cast(width); + h = static_cast(height); + ret = true; + + goto finalize; + +failure: + clear(); + +finalize: + fclose(jpegFile); + return ret; +} + + +bool JpgLoader::open(const char* data, uint32_t size, bool copy) +{ + clear(); + + int width, height, subSample, colorSpace; + if (tjDecompressHeader3(jpegDecompressor, (unsigned char *) data, size, &width, &height, &subSample, &colorSpace) < 0) return false; + + if (copy) { + this->data = (unsigned char *) malloc(size); + if (!this->data) return false; + memcpy((unsigned char *)this->data, data, size); + freeData = true; + } else { + this->data = (unsigned char *) data; + freeData = false; + } + + w = static_cast(width); + h = static_cast(height); + this->size = size; + + return true; +} + + +bool JpgLoader::read() +{ + if (image) tjFree(image); + image = (unsigned char *)tjAlloc(static_cast(w) * static_cast(h) * tjPixelSize[TJPF_BGRX]); + if (!image) return false; + + //decompress jpg image + if (tjDecompress2(jpegDecompressor, data, size, image, static_cast(w), 0, static_cast(h), TJPF_BGRX, 0) < 0) { + TVGERR("JPG LOADER", "%s", tjGetErrorStr()); + tjFree(image); + image = nullptr; + return false; + } + + return true; +} + + +bool JpgLoader::close() +{ + clear(); + return true; +} + + +const uint32_t* JpgLoader::pixels() +{ + return (const uint32_t*) image; +} diff --git a/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h new file mode 100644 index 0000000000..1aeaa04c95 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_JPG_LOADER_H_ +#define _TVG_JPG_LOADER_H_ + +using tjhandle = void*; + +//TODO: Use Task? +class JpgLoader : public LoadModule +{ +public: + JpgLoader(); + ~JpgLoader(); + + using LoadModule::open; + bool open(const string& path) override; + bool open(const char* data, uint32_t size, bool copy) override; + bool read() override; + bool close() override; + + const uint32_t* pixels() override; + +private: + void clear(); + + tjhandle jpegDecompressor; + unsigned char* data = nullptr; + unsigned char *image = nullptr; + unsigned long size = 0; + bool freeData = false; +}; + +#endif //_TVG_JPG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp new file mode 100644 index 0000000000..faafc62aee --- /dev/null +++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgLoader.h" +#include "tvgPngLoader.h" + +PngLoader::PngLoader() +{ + image = static_cast(calloc(1, sizeof(png_image))); + image->version = PNG_IMAGE_VERSION; + image->opaque = NULL; +} + +PngLoader::~PngLoader() +{ + if (content) { + free((void*)content); + content = nullptr; + } + free(image); +} + +bool PngLoader::open(const string& path) +{ + image->opaque = NULL; + + if (!png_image_begin_read_from_file(image, path.c_str())) return false; + + w = (float)image->width; + h = (float)image->height; + + return true; +} + +bool PngLoader::open(const char* data, uint32_t size, bool copy) +{ + image->opaque = NULL; + + if (!png_image_begin_read_from_memory(image, data, size)) return false; + + w = (float)image->width; + h = (float)image->height; + + return true; +} + +bool PngLoader::read() +{ + png_bytep buffer; + image->format = PNG_FORMAT_BGRA; + buffer = static_cast(malloc(PNG_IMAGE_SIZE((*image)))); + if (!buffer) { + //out of memory, only time when libpng doesnt free its data + png_image_free(image); + return false; + } + if (!png_image_finish_read(image, NULL, buffer, 0, NULL)) return false; + content = reinterpret_cast(buffer); + + return true; +} + +bool PngLoader::close() +{ + png_image_free(image); + return true; +} + +const uint32_t* PngLoader::pixels() +{ + return this->content; +} diff --git a/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h new file mode 100644 index 0000000000..ab2c81690a --- /dev/null +++ b/thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_PNG_LOADER_H_ +#define _TVG_PNG_LOADER_H_ + +#include + +class PngLoader : public LoadModule +{ +public: + PngLoader(); + ~PngLoader(); + + using LoadModule::open; + bool open(const string& path) override; + bool open(const char* data, uint32_t size, bool copy) override; + bool read() override; + bool close() override; + + const uint32_t* pixels() override; + +private: + png_imagep image = nullptr; + const uint32_t* content = nullptr; +}; + +#endif //_TVG_PNG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp new file mode 100644 index 0000000000..599d2fea42 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "tvgLoader.h" +#include "tvgJpgLoader.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +void JpgLoader::clear() +{ + jpgdDelete(decoder); + if (freeData) free(data); + decoder = nullptr; + data = nullptr; + freeData = false; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +JpgLoader::~JpgLoader() +{ + jpgdDelete(decoder); + if (freeData) free(data); +} + + +bool JpgLoader::open(const string& path) +{ + clear(); + + int width, height; + decoder = jpgdHeader(path.c_str(), &width, &height); + if (!decoder) return false; + + w = static_cast(width); + h = static_cast(height); + + return true; +} + + +bool JpgLoader::open(const char* data, uint32_t size, bool copy) +{ + clear(); + + if (copy) { + this->data = (char *) malloc(size); + if (!this->data) return false; + memcpy((char *)this->data, data, size); + freeData = true; + } else { + this->data = (char *) data; + freeData = false; + } + + int width, height; + decoder = jpgdHeader(this->data, size, &width, &height); + if (!decoder) return false; + + w = static_cast(width); + h = static_cast(height); + + return true; +} + + + +bool JpgLoader::read() +{ + if (!decoder || w <= 0 || h <= 0) return false; + + TaskScheduler::request(this); + + return true; +} + + +bool JpgLoader::close() +{ + this->done(); + clear(); + return true; +} + + +const uint32_t* JpgLoader::pixels() +{ + this->done(); + + return (const uint32_t*)image; +} + + +void JpgLoader::run(unsigned tid) +{ + image = jpgdDecompress(decoder); +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h new file mode 100644 index 0000000000..e7ec3ab42b --- /dev/null +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_JPG_LOADER_H_ +#define _TVG_JPG_LOADER_H_ + +#include "tvgTaskScheduler.h" +#include "tvgJpgd.h" + +class JpgLoader : public LoadModule, public Task +{ +private: + jpeg_decoder* decoder = nullptr; + char* data = nullptr; + unsigned char *image = nullptr; + bool freeData = false; + + void clear(); + +public: + ~JpgLoader(); + + using LoadModule::open; + bool open(const string& path) override; + bool open(const char* data, uint32_t size, bool copy) override; + bool read() override; + bool close() override; + + const uint32_t* pixels() override; + void run(unsigned tid) override; +}; + +#endif //_TVG_JPG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp new file mode 100644 index 0000000000..fa72734ec4 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp @@ -0,0 +1,3028 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// jpgd.cpp - C++ class for JPEG decompression. +// Public domain, Rich Geldreich +// Alex Evans: Linear memory allocator (taken from jpge.h). +// v1.04, May. 19, 2012: Code tweaks to fix VS2008 static code analysis warnings (all looked harmless) +// +// Supports progressive and baseline sequential JPEG image files, and the most common chroma subsampling factors: Y, H1V1, H2V1, H1V2, and H2V2. +// +// Chroma upsampling quality: H2V2 is upsampled in the frequency domain, H2V1 and H1V2 are upsampled using point sampling. +// Chroma upsampling reference: "Fast Scheme for Image Size Change in the Compressed Domain" +// http://vision.ai.uiuc.edu/~dugad/research/dct/index.html + +#include +#include +#include +#include +#include +#include "tvgJpgd.h" + +#ifdef _MSC_VER + #pragma warning (disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable + #define JPGD_NORETURN __declspec(noreturn) +#elif defined(__GNUC__) + #define JPGD_NORETURN __attribute__ ((noreturn)) +#else + #define JPGD_NORETURN +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +// Set to 1 to enable freq. domain chroma upsampling on images using H2V2 subsampling (0=faster nearest neighbor sampling). +// This is slower, but results in higher quality on images with highly saturated colors. +#define JPGD_SUPPORT_FREQ_DOMAIN_UPSAMPLING 1 + +#define JPGD_ASSERT(x) +#define JPGD_MAX(a,b) (((a)>(b)) ? (a) : (b)) +#define JPGD_MIN(a,b) (((a)<(b)) ? (a) : (b)) + +typedef int16_t jpgd_quant_t; +typedef int16_t jpgd_block_t; + +// Success/failure error codes. +enum jpgd_status +{ + JPGD_SUCCESS = 0, JPGD_FAILED = -1, JPGD_DONE = 1, + JPGD_BAD_DHT_COUNTS = -256, JPGD_BAD_DHT_INDEX, JPGD_BAD_DHT_MARKER, JPGD_BAD_DQT_MARKER, JPGD_BAD_DQT_TABLE, + JPGD_BAD_PRECISION, JPGD_BAD_HEIGHT, JPGD_BAD_WIDTH, JPGD_TOO_MANY_COMPONENTS, + JPGD_BAD_SOF_LENGTH, JPGD_BAD_VARIABLE_MARKER, JPGD_BAD_DRI_LENGTH, JPGD_BAD_SOS_LENGTH, + JPGD_BAD_SOS_COMP_ID, JPGD_W_EXTRA_BYTES_BEFORE_MARKER, JPGD_NO_ARITHMITIC_SUPPORT, JPGD_UNEXPECTED_MARKER, + JPGD_NOT_JPEG, JPGD_UNSUPPORTED_MARKER, JPGD_BAD_DQT_LENGTH, JPGD_TOO_MANY_BLOCKS, + JPGD_UNDEFINED_QUANT_TABLE, JPGD_UNDEFINED_HUFF_TABLE, JPGD_NOT_SINGLE_SCAN, JPGD_UNSUPPORTED_COLORSPACE, + JPGD_UNSUPPORTED_SAMP_FACTORS, JPGD_DECODE_ERROR, JPGD_BAD_RESTART_MARKER, JPGD_ASSERTION_ERROR, + JPGD_BAD_SOS_SPECTRAL, JPGD_BAD_SOS_SUCCESSIVE, JPGD_STREAM_READ, JPGD_NOTENOUGHMEM +}; + +enum +{ + JPGD_IN_BUF_SIZE = 8192, JPGD_MAX_BLOCKS_PER_MCU = 10, JPGD_MAX_HUFF_TABLES = 8, JPGD_MAX_QUANT_TABLES = 4, + JPGD_MAX_COMPONENTS = 4, JPGD_MAX_COMPS_IN_SCAN = 4, JPGD_MAX_BLOCKS_PER_ROW = 8192, JPGD_MAX_HEIGHT = 16384, JPGD_MAX_WIDTH = 16384 +}; + +// Input stream interface. +// Derive from this class to read input data from sources other than files or memory. Set m_eof_flag to true when no more data is available. +// The decoder is rather greedy: it will keep on calling this method until its internal input buffer is full, or until the EOF flag is set. +// It the input stream contains data after the JPEG stream's EOI (end of image) marker it will probably be pulled into the internal buffer. +// Call the get_total_bytes_read() method to determine the actual size of the JPEG stream after successful decoding. +struct jpeg_decoder_stream +{ + jpeg_decoder_stream() { } + virtual ~jpeg_decoder_stream() { } + + // The read() method is called when the internal input buffer is empty. + // Parameters: + // pBuf - input buffer + // max_bytes_to_read - maximum bytes that can be written to pBuf + // pEOF_flag - set this to true if at end of stream (no more bytes remaining) + // Returns -1 on error, otherwise return the number of bytes actually written to the buffer (which may be 0). + // Notes: This method will be called in a loop until you set *pEOF_flag to true or the internal buffer is full. + virtual int read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag) = 0; +}; + + +// stdio FILE stream class. +class jpeg_decoder_file_stream : public jpeg_decoder_stream +{ + jpeg_decoder_file_stream(const jpeg_decoder_file_stream &); + jpeg_decoder_file_stream &operator =(const jpeg_decoder_file_stream &); + + FILE *m_pFile = nullptr; + bool m_eof_flag = false; + bool m_error_flag = false; + +public: + jpeg_decoder_file_stream() {} + virtual ~jpeg_decoder_file_stream(); + bool open(const char *Pfilename); + void close(); + virtual int read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag); + }; + + +// Memory stream class. +class jpeg_decoder_mem_stream : public jpeg_decoder_stream +{ + const uint8_t *m_pSrc_data; + uint32_t m_ofs, m_size; + +public: + jpeg_decoder_mem_stream() : m_pSrc_data(nullptr), m_ofs(0), m_size(0) {} + jpeg_decoder_mem_stream(const uint8_t *pSrc_data, uint32_t size) : m_pSrc_data(pSrc_data), m_ofs(0), m_size(size) {} + virtual ~jpeg_decoder_mem_stream() {} + bool open(const uint8_t *pSrc_data, uint32_t size); + void close() { m_pSrc_data = nullptr; m_ofs = 0; m_size = 0; } + virtual int read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag); +}; + + +class jpeg_decoder +{ +public: + // Call get_error_code() after constructing to determine if the stream is valid or not. You may call the get_width(), get_height(), etc. + // methods after the constructor is called. You may then either destruct the object, or begin decoding the image by calling begin_decoding(), then decode() on each scanline. + jpeg_decoder(jpeg_decoder_stream *pStream); + ~jpeg_decoder(); + + // Call this method after constructing the object to begin decompression. + // If JPGD_SUCCESS is returned you may then call decode() on each scanline. + int begin_decoding(); + // Returns the next scan line. + // For grayscale images, pScan_line will point to a buffer containing 8-bit pixels (get_bytes_per_pixel() will return 1). + // Otherwise, it will always point to a buffer containing 32-bit RGBA pixels (A will always be 255, and get_bytes_per_pixel() will return 4). + // Returns JPGD_SUCCESS if a scan line has been returned. + // Returns JPGD_DONE if all scan lines have been returned. + // Returns JPGD_FAILED if an error occurred. Call get_error_code() for a more info. + int decode(const void** pScan_line, uint32_t* pScan_line_len); + inline jpgd_status get_error_code() const { return m_error_code; } + inline int get_width() const { return m_image_x_size; } + inline int get_height() const { return m_image_y_size; } + inline int get_num_components() const { return m_comps_in_frame; } + inline int get_bytes_per_pixel() const { return m_dest_bytes_per_pixel; } + inline int get_bytes_per_scan_line() const { return m_image_x_size * get_bytes_per_pixel(); } + // Returns the total number of bytes actually consumed by the decoder (which should equal the actual size of the JPEG file). + inline int get_total_bytes_read() const { return m_total_bytes_read; } + +private: + jpeg_decoder(const jpeg_decoder &); + jpeg_decoder &operator =(const jpeg_decoder &); + + typedef void (*pDecode_block_func)(jpeg_decoder *, int, int, int); + + struct huff_tables + { + bool ac_table; + uint32_t look_up[256]; + uint32_t look_up2[256]; + uint8_t code_size[256]; + uint32_t tree[512]; + }; + + struct coeff_buf + { + uint8_t *pData; + int block_num_x, block_num_y; + int block_len_x, block_len_y; + int block_size; + }; + + struct mem_block + { + mem_block *m_pNext; + size_t m_used_count; + size_t m_size; + char m_data[1]; + }; + + jmp_buf m_jmp_state; + mem_block *m_pMem_blocks; + int m_image_x_size; + int m_image_y_size; + jpeg_decoder_stream *m_pStream; + int m_progressive_flag; + uint8_t m_huff_ac[JPGD_MAX_HUFF_TABLES]; + uint8_t* m_huff_num[JPGD_MAX_HUFF_TABLES]; // pointer to number of Huffman codes per bit size + uint8_t* m_huff_val[JPGD_MAX_HUFF_TABLES]; // pointer to Huffman codes per bit size + jpgd_quant_t* m_quant[JPGD_MAX_QUANT_TABLES]; // pointer to quantization tables + int m_scan_type; // Gray, Yh1v1, Yh1v2, Yh2v1, Yh2v2 (CMYK111, CMYK4114 no longer supported) + int m_comps_in_frame; // # of components in frame + int m_comp_h_samp[JPGD_MAX_COMPONENTS]; // component's horizontal sampling factor + int m_comp_v_samp[JPGD_MAX_COMPONENTS]; // component's vertical sampling factor + int m_comp_quant[JPGD_MAX_COMPONENTS]; // component's quantization table selector + int m_comp_ident[JPGD_MAX_COMPONENTS]; // component's ID + int m_comp_h_blocks[JPGD_MAX_COMPONENTS]; + int m_comp_v_blocks[JPGD_MAX_COMPONENTS]; + int m_comps_in_scan; // # of components in scan + int m_comp_list[JPGD_MAX_COMPS_IN_SCAN]; // components in this scan + int m_comp_dc_tab[JPGD_MAX_COMPONENTS]; // component's DC Huffman coding table selector + int m_comp_ac_tab[JPGD_MAX_COMPONENTS]; // component's AC Huffman coding table selector + int m_spectral_start; // spectral selection start + int m_spectral_end; // spectral selection end + int m_successive_low; // successive approximation low + int m_successive_high; // successive approximation high + int m_max_mcu_x_size; // MCU's max. X size in pixels + int m_max_mcu_y_size; // MCU's max. Y size in pixels + int m_blocks_per_mcu; + int m_max_blocks_per_row; + int m_mcus_per_row, m_mcus_per_col; + int m_mcu_org[JPGD_MAX_BLOCKS_PER_MCU]; + int m_total_lines_left; // total # lines left in image + int m_mcu_lines_left; // total # lines left in this MCU + int m_real_dest_bytes_per_scan_line; + int m_dest_bytes_per_scan_line; // rounded up + int m_dest_bytes_per_pixel; // 4 (RGB) or 1 (Y) + huff_tables* m_pHuff_tabs[JPGD_MAX_HUFF_TABLES]; + coeff_buf* m_dc_coeffs[JPGD_MAX_COMPONENTS]; + coeff_buf* m_ac_coeffs[JPGD_MAX_COMPONENTS]; + int m_eob_run; + int m_block_y_mcu[JPGD_MAX_COMPONENTS]; + uint8_t* m_pIn_buf_ofs; + int m_in_buf_left; + int m_tem_flag; + bool m_eof_flag; + uint8_t m_in_buf_pad_start[128]; + uint8_t m_in_buf[JPGD_IN_BUF_SIZE + 128]; + uint8_t m_in_buf_pad_end[128]; + int m_bits_left; + uint32_t m_bit_buf; + int m_restart_interval; + int m_restarts_left; + int m_next_restart_num; + int m_max_mcus_per_row; + int m_max_blocks_per_mcu; + int m_expanded_blocks_per_mcu; + int m_expanded_blocks_per_row; + int m_expanded_blocks_per_component; + bool m_freq_domain_chroma_upsample; + int m_max_mcus_per_col; + uint32_t m_last_dc_val[JPGD_MAX_COMPONENTS]; + jpgd_block_t* m_pMCU_coefficients; + int m_mcu_block_max_zag[JPGD_MAX_BLOCKS_PER_MCU]; + uint8_t* m_pSample_buf; + int m_crr[256]; + int m_cbb[256]; + int m_crg[256]; + int m_cbg[256]; + uint8_t* m_pScan_line_0; + uint8_t* m_pScan_line_1; + jpgd_status m_error_code; + bool m_ready_flag; + int m_total_bytes_read; + + void free_all_blocks(); + JPGD_NORETURN void stop_decoding(jpgd_status status); + void *alloc(size_t n, bool zero = false); + void word_clear(void *p, uint16_t c, uint32_t n); + void prep_in_buffer(); + void read_dht_marker(); + void read_dqt_marker(); + void read_sof_marker(); + void skip_variable_marker(); + void read_dri_marker(); + void read_sos_marker(); + int next_marker(); + int process_markers(); + void locate_soi_marker(); + void locate_sof_marker(); + int locate_sos_marker(); + void init(jpeg_decoder_stream * pStream); + void create_look_ups(); + void fix_in_buffer(); + void transform_mcu(int mcu_row); + void transform_mcu_expand(int mcu_row); + coeff_buf* coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y); + inline jpgd_block_t *coeff_buf_getp(coeff_buf *cb, int block_x, int block_y); + void load_next_row(); + void decode_next_row(); + void make_huff_table(int index, huff_tables *pH); + void check_quant_tables(); + void check_huff_tables(); + void calc_mcu_block_order(); + int init_scan(); + void init_frame(); + void process_restart(); + void decode_scan(pDecode_block_func decode_block_func); + void init_progressive(); + void init_sequential(); + void decode_start(); + void decode_init(jpeg_decoder_stream * pStream); + void H2V2Convert(); + void H2V1Convert(); + void H1V2Convert(); + void H1V1Convert(); + void gray_convert(); + void expanded_convert(); + void find_eoi(); + inline uint32_t get_char(); + inline uint32_t get_char(bool *pPadding_flag); + inline void stuff_char(uint8_t q); + inline uint8_t get_octet(); + inline uint32_t get_bits(int num_bits); + inline uint32_t get_bits_no_markers(int numbits); + inline int huff_decode(huff_tables *pH); + inline int huff_decode(huff_tables *pH, int& extrabits); + static inline uint8_t clamp(int i); + static void decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y); + static void decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y); + static void decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y); + static void decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y); +}; + + +// DCT coefficients are stored in this sequence. +static int g_ZAG[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; + +enum JPEG_MARKER +{ + M_SOF0 = 0xC0, M_SOF1 = 0xC1, M_SOF2 = 0xC2, M_SOF3 = 0xC3, M_SOF5 = 0xC5, M_SOF6 = 0xC6, M_SOF7 = 0xC7, M_JPG = 0xC8, + M_SOF9 = 0xC9, M_SOF10 = 0xCA, M_SOF11 = 0xCB, M_SOF13 = 0xCD, M_SOF14 = 0xCE, M_SOF15 = 0xCF, M_DHT = 0xC4, M_DAC = 0xCC, + M_RST0 = 0xD0, M_RST1 = 0xD1, M_RST2 = 0xD2, M_RST3 = 0xD3, M_RST4 = 0xD4, M_RST5 = 0xD5, M_RST6 = 0xD6, M_RST7 = 0xD7, + M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_DNL = 0xDC, M_DRI = 0xDD, M_DHP = 0xDE, M_EXP = 0xDF, + M_APP0 = 0xE0, M_APP15 = 0xEF, M_JPG0 = 0xF0, M_JPG13 = 0xFD, M_COM = 0xFE, M_TEM = 0x01, M_ERROR = 0x100, RST0 = 0xD0 +}; + +enum JPEG_SUBSAMPLING { JPGD_GRAYSCALE = 0, JPGD_YH1V1, JPGD_YH2V1, JPGD_YH1V2, JPGD_YH2V2 }; + +#define CONST_BITS 13 +#define PASS1_BITS 2 +#define SCALEDONE ((int32_t)1) +#define DESCALE(x,n) (((x) + (SCALEDONE << ((n)-1))) >> (n)) +#define DESCALE_ZEROSHIFT(x,n) (((x) + (128 << (n)) + (SCALEDONE << ((n)-1))) >> (n)) +#define MULTIPLY(var, cnst) ((var) * (cnst)) +#define CLAMP(i) ((static_cast(i) > 255) ? (((~i) >> 31) & 0xFF) : (i)) + +#define FIX_0_298631336 ((int32_t)2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((int32_t)3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((int32_t)4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((int32_t)6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((int32_t)7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((int32_t)9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((int32_t)12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((int32_t)15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((int32_t)16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((int32_t)16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((int32_t)20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((int32_t)25172) /* FIX(3.072711026) */ + + +// Compiler creates a fast path 1D IDCT for X non-zero columns +template +struct Row +{ + static void idct(int* pTemp, const jpgd_block_t* pSrc) + { + // ACCESS_COL() will be optimized at compile time to either an array access, or 0. + #define ACCESS_COL(x) (((x) < NONZERO_COLS) ? (int)pSrc[x] : 0) + + const int z2 = ACCESS_COL(2), z3 = ACCESS_COL(6); + const int z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + const int tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + const int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + const int tmp0 = (ACCESS_COL(0) + ACCESS_COL(4)) << CONST_BITS; + const int tmp1 = (ACCESS_COL(0) - ACCESS_COL(4)) << CONST_BITS; + + const int tmp10 = tmp0 + tmp3, tmp13 = tmp0 - tmp3, tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2; + + const int atmp0 = ACCESS_COL(7), atmp1 = ACCESS_COL(5), atmp2 = ACCESS_COL(3), atmp3 = ACCESS_COL(1); + + const int bz1 = atmp0 + atmp3, bz2 = atmp1 + atmp2, bz3 = atmp0 + atmp2, bz4 = atmp1 + atmp3; + const int bz5 = MULTIPLY(bz3 + bz4, FIX_1_175875602); + + const int az1 = MULTIPLY(bz1, - FIX_0_899976223); + const int az2 = MULTIPLY(bz2, - FIX_2_562915447); + const int az3 = MULTIPLY(bz3, - FIX_1_961570560) + bz5; + const int az4 = MULTIPLY(bz4, - FIX_0_390180644) + bz5; + + const int btmp0 = MULTIPLY(atmp0, FIX_0_298631336) + az1 + az3; + const int btmp1 = MULTIPLY(atmp1, FIX_2_053119869) + az2 + az4; + const int btmp2 = MULTIPLY(atmp2, FIX_3_072711026) + az2 + az3; + const int btmp3 = MULTIPLY(atmp3, FIX_1_501321110) + az1 + az4; + + pTemp[0] = DESCALE(tmp10 + btmp3, CONST_BITS-PASS1_BITS); + pTemp[7] = DESCALE(tmp10 - btmp3, CONST_BITS-PASS1_BITS); + pTemp[1] = DESCALE(tmp11 + btmp2, CONST_BITS-PASS1_BITS); + pTemp[6] = DESCALE(tmp11 - btmp2, CONST_BITS-PASS1_BITS); + pTemp[2] = DESCALE(tmp12 + btmp1, CONST_BITS-PASS1_BITS); + pTemp[5] = DESCALE(tmp12 - btmp1, CONST_BITS-PASS1_BITS); + pTemp[3] = DESCALE(tmp13 + btmp0, CONST_BITS-PASS1_BITS); + pTemp[4] = DESCALE(tmp13 - btmp0, CONST_BITS-PASS1_BITS); + } +}; + + +template <> +struct Row<0> +{ + static void idct(int* pTemp, const jpgd_block_t* pSrc) + { +#ifdef _MSC_VER + pTemp; pSrc; +#endif + } +}; + + +template <> +struct Row<1> +{ + static void idct(int* pTemp, const jpgd_block_t* pSrc) + { + const int dcval = (pSrc[0] << PASS1_BITS); + + pTemp[0] = dcval; + pTemp[1] = dcval; + pTemp[2] = dcval; + pTemp[3] = dcval; + pTemp[4] = dcval; + pTemp[5] = dcval; + pTemp[6] = dcval; + pTemp[7] = dcval; + } +}; + + +// Compiler creates a fast path 1D IDCT for X non-zero rows +template +struct Col +{ + static void idct(uint8_t* pDst_ptr, const int* pTemp) + { + // ACCESS_ROW() will be optimized at compile time to either an array access, or 0. + #define ACCESS_ROW(x) (((x) < NONZERO_ROWS) ? pTemp[x * 8] : 0) + + const int z2 = ACCESS_ROW(2); + const int z3 = ACCESS_ROW(6); + + const int z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + const int tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + const int tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + const int tmp0 = (ACCESS_ROW(0) + ACCESS_ROW(4)) << CONST_BITS; + const int tmp1 = (ACCESS_ROW(0) - ACCESS_ROW(4)) << CONST_BITS; + + const int tmp10 = tmp0 + tmp3, tmp13 = tmp0 - tmp3, tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2; + + const int atmp0 = ACCESS_ROW(7), atmp1 = ACCESS_ROW(5), atmp2 = ACCESS_ROW(3), atmp3 = ACCESS_ROW(1); + + const int bz1 = atmp0 + atmp3, bz2 = atmp1 + atmp2, bz3 = atmp0 + atmp2, bz4 = atmp1 + atmp3; + const int bz5 = MULTIPLY(bz3 + bz4, FIX_1_175875602); + + const int az1 = MULTIPLY(bz1, - FIX_0_899976223); + const int az2 = MULTIPLY(bz2, - FIX_2_562915447); + const int az3 = MULTIPLY(bz3, - FIX_1_961570560) + bz5; + const int az4 = MULTIPLY(bz4, - FIX_0_390180644) + bz5; + + const int btmp0 = MULTIPLY(atmp0, FIX_0_298631336) + az1 + az3; + const int btmp1 = MULTIPLY(atmp1, FIX_2_053119869) + az2 + az4; + const int btmp2 = MULTIPLY(atmp2, FIX_3_072711026) + az2 + az3; + const int btmp3 = MULTIPLY(atmp3, FIX_1_501321110) + az1 + az4; + + int i = DESCALE_ZEROSHIFT(tmp10 + btmp3, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*0] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp10 - btmp3, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*7] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp11 + btmp2, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*1] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp11 - btmp2, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*6] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp12 + btmp1, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*2] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp12 - btmp1, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*5] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp13 + btmp0, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*3] = (uint8_t)CLAMP(i); + + i = DESCALE_ZEROSHIFT(tmp13 - btmp0, CONST_BITS+PASS1_BITS+3); + pDst_ptr[8*4] = (uint8_t)CLAMP(i); + } +}; + + +template <> +struct Col<1> +{ + static void idct(uint8_t* pDst_ptr, const int* pTemp) + { + int dcval = DESCALE_ZEROSHIFT(pTemp[0], PASS1_BITS+3); + const uint8_t dcval_clamped = (uint8_t)CLAMP(dcval); + pDst_ptr[0*8] = dcval_clamped; + pDst_ptr[1*8] = dcval_clamped; + pDst_ptr[2*8] = dcval_clamped; + pDst_ptr[3*8] = dcval_clamped; + pDst_ptr[4*8] = dcval_clamped; + pDst_ptr[5*8] = dcval_clamped; + pDst_ptr[6*8] = dcval_clamped; + pDst_ptr[7*8] = dcval_clamped; + } +}; + + +static const uint8_t s_idct_row_table[] = { + 1,0,0,0,0,0,0,0, 2,0,0,0,0,0,0,0, 2,1,0,0,0,0,0,0, 2,1,1,0,0,0,0,0, 2,2,1,0,0,0,0,0, 3,2,1,0,0,0,0,0, 4,2,1,0,0,0,0,0, 4,3,1,0,0,0,0,0, + 4,3,2,0,0,0,0,0, 4,3,2,1,0,0,0,0, 4,3,2,1,1,0,0,0, 4,3,2,2,1,0,0,0, 4,3,3,2,1,0,0,0, 4,4,3,2,1,0,0,0, 5,4,3,2,1,0,0,0, 6,4,3,2,1,0,0,0, + 6,5,3,2,1,0,0,0, 6,5,4,2,1,0,0,0, 6,5,4,3,1,0,0,0, 6,5,4,3,2,0,0,0, 6,5,4,3,2,1,0,0, 6,5,4,3,2,1,1,0, 6,5,4,3,2,2,1,0, 6,5,4,3,3,2,1,0, + 6,5,4,4,3,2,1,0, 6,5,5,4,3,2,1,0, 6,6,5,4,3,2,1,0, 7,6,5,4,3,2,1,0, 8,6,5,4,3,2,1,0, 8,7,5,4,3,2,1,0, 8,7,6,4,3,2,1,0, 8,7,6,5,3,2,1,0, + 8,7,6,5,4,2,1,0, 8,7,6,5,4,3,1,0, 8,7,6,5,4,3,2,0, 8,7,6,5,4,3,2,1, 8,7,6,5,4,3,2,2, 8,7,6,5,4,3,3,2, 8,7,6,5,4,4,3,2, 8,7,6,5,5,4,3,2, + 8,7,6,6,5,4,3,2, 8,7,7,6,5,4,3,2, 8,8,7,6,5,4,3,2, 8,8,8,6,5,4,3,2, 8,8,8,7,5,4,3,2, 8,8,8,7,6,4,3,2, 8,8,8,7,6,5,3,2, 8,8,8,7,6,5,4,2, + 8,8,8,7,6,5,4,3, 8,8,8,7,6,5,4,4, 8,8,8,7,6,5,5,4, 8,8,8,7,6,6,5,4, 8,8,8,7,7,6,5,4, 8,8,8,8,7,6,5,4, 8,8,8,8,8,6,5,4, 8,8,8,8,8,7,5,4, + 8,8,8,8,8,7,6,4, 8,8,8,8,8,7,6,5, 8,8,8,8,8,7,6,6, 8,8,8,8,8,7,7,6, 8,8,8,8,8,8,7,6, 8,8,8,8,8,8,8,6, 8,8,8,8,8,8,8,7, 8,8,8,8,8,8,8,8, +}; + + +static const uint8_t s_idct_col_table[] = { 1, 1, 2, 3, 3, 3, 3, 3, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; + + +void idct(const jpgd_block_t* pSrc_ptr, uint8_t* pDst_ptr, int block_max_zag) +{ + JPGD_ASSERT(block_max_zag >= 1); + JPGD_ASSERT(block_max_zag <= 64); + + if (block_max_zag <= 1) { + int k = ((pSrc_ptr[0] + 4) >> 3) + 128; + k = CLAMP(k); + k = k | (k<<8); + k = k | (k<<16); + for (int i = 8; i > 0; i--) { + *(int*)&pDst_ptr[0] = k; + *(int*)&pDst_ptr[4] = k; + pDst_ptr += 8; + } + return; + } + + int temp[64]; + const jpgd_block_t* pSrc = pSrc_ptr; + int* pTemp = temp; + const uint8_t* pRow_tab = &s_idct_row_table[(block_max_zag - 1) * 8]; + int i; + for (i = 8; i > 0; i--, pRow_tab++) { + switch (*pRow_tab) { + case 0: Row<0>::idct(pTemp, pSrc); break; + case 1: Row<1>::idct(pTemp, pSrc); break; + case 2: Row<2>::idct(pTemp, pSrc); break; + case 3: Row<3>::idct(pTemp, pSrc); break; + case 4: Row<4>::idct(pTemp, pSrc); break; + case 5: Row<5>::idct(pTemp, pSrc); break; + case 6: Row<6>::idct(pTemp, pSrc); break; + case 7: Row<7>::idct(pTemp, pSrc); break; + case 8: Row<8>::idct(pTemp, pSrc); break; + } + pSrc += 8; + pTemp += 8; + } + + pTemp = temp; + + const int nonzero_rows = s_idct_col_table[block_max_zag - 1]; + for (i = 8; i > 0; i--) { + switch (nonzero_rows) { + case 1: Col<1>::idct(pDst_ptr, pTemp); break; + case 2: Col<2>::idct(pDst_ptr, pTemp); break; + case 3: Col<3>::idct(pDst_ptr, pTemp); break; + case 4: Col<4>::idct(pDst_ptr, pTemp); break; + case 5: Col<5>::idct(pDst_ptr, pTemp); break; + case 6: Col<6>::idct(pDst_ptr, pTemp); break; + case 7: Col<7>::idct(pDst_ptr, pTemp); break; + case 8: Col<8>::idct(pDst_ptr, pTemp); break; + } + pTemp++; + pDst_ptr++; + } +} + + +void idct_4x4(const jpgd_block_t* pSrc_ptr, uint8_t* pDst_ptr) +{ + int temp[64]; + int* pTemp = temp; + const jpgd_block_t* pSrc = pSrc_ptr; + + for (int i = 4; i > 0; i--) { + Row<4>::idct(pTemp, pSrc); + pSrc += 8; + pTemp += 8; + } + + pTemp = temp; + + for (int i = 8; i > 0; i--) { + Col<4>::idct(pDst_ptr, pTemp); + pTemp++; + pDst_ptr++; + } +} + + +// Retrieve one character from the input stream. +inline uint32_t jpeg_decoder::get_char() +{ + // Any bytes remaining in buffer? + if (!m_in_buf_left) { + // Try to get more bytes. + prep_in_buffer(); + // Still nothing to get? + if (!m_in_buf_left) { + // Pad the end of the stream with 0xFF 0xD9 (EOI marker) + int t = m_tem_flag; + m_tem_flag ^= 1; + if (t) return 0xD9; + else return 0xFF; + } + } + uint32_t c = *m_pIn_buf_ofs++; + m_in_buf_left--; + return c; +} + + +// Same as previous method, except can indicate if the character is a pad character or not. +inline uint32_t jpeg_decoder::get_char(bool *pPadding_flag) +{ + if (!m_in_buf_left) { + prep_in_buffer(); + if (!m_in_buf_left) { + *pPadding_flag = true; + int t = m_tem_flag; + m_tem_flag ^= 1; + if (t) return 0xD9; + else return 0xFF; + } + } + *pPadding_flag = false; + uint32_t c = *m_pIn_buf_ofs++; + m_in_buf_left--; + + return c; +} + + +// Inserts a previously retrieved character back into the input buffer. +inline void jpeg_decoder::stuff_char(uint8_t q) +{ + *(--m_pIn_buf_ofs) = q; + m_in_buf_left++; +} + + +// Retrieves one character from the input stream, but does not read past markers. Will continue to return 0xFF when a marker is encountered. +inline uint8_t jpeg_decoder::get_octet() +{ + bool padding_flag; + int c = get_char(&padding_flag); + + if (c == 0xFF) { + if (padding_flag) return 0xFF; + + c = get_char(&padding_flag); + if (padding_flag) { + stuff_char(0xFF); + return 0xFF; + } + if (c == 0x00) return 0xFF; + else { + stuff_char(static_cast(c)); + stuff_char(0xFF); + return 0xFF; + } + } + return static_cast(c); +} + + +// Retrieves a variable number of bits from the input stream. Does not recognize markers. +inline uint32_t jpeg_decoder::get_bits(int num_bits) +{ + if (!num_bits) return 0; + + uint32_t i = m_bit_buf >> (32 - num_bits); + + if ((m_bits_left -= num_bits) <= 0) { + m_bit_buf <<= (num_bits += m_bits_left); + uint32_t c1 = get_char(); + uint32_t c2 = get_char(); + m_bit_buf = (m_bit_buf & 0xFFFF0000) | (c1 << 8) | c2; + m_bit_buf <<= -m_bits_left; + m_bits_left += 16; + JPGD_ASSERT(m_bits_left >= 0); + } + else m_bit_buf <<= num_bits; + + return i; +} + + +// Retrieves a variable number of bits from the input stream. Markers will not be read into the input bit buffer. Instead, an infinite number of all 1's will be returned when a marker is encountered. +inline uint32_t jpeg_decoder::get_bits_no_markers(int num_bits) +{ + if (!num_bits)return 0; + + uint32_t i = m_bit_buf >> (32 - num_bits); + + if ((m_bits_left -= num_bits) <= 0) { + m_bit_buf <<= (num_bits += m_bits_left); + if ((m_in_buf_left < 2) || (m_pIn_buf_ofs[0] == 0xFF) || (m_pIn_buf_ofs[1] == 0xFF)) { + uint32_t c1 = get_octet(); + uint32_t c2 = get_octet(); + m_bit_buf |= (c1 << 8) | c2; + } else { + m_bit_buf |= ((uint32_t)m_pIn_buf_ofs[0] << 8) | m_pIn_buf_ofs[1]; + m_in_buf_left -= 2; + m_pIn_buf_ofs += 2; + } + m_bit_buf <<= -m_bits_left; + m_bits_left += 16; + JPGD_ASSERT(m_bits_left >= 0); + } else m_bit_buf <<= num_bits; + + return i; +} + + +// Decodes a Huffman encoded symbol. +inline int jpeg_decoder::huff_decode(huff_tables *pH) +{ + int symbol; + + // Check first 8-bits: do we have a complete symbol? + if ((symbol = pH->look_up[m_bit_buf >> 24]) < 0) { + // Decode more bits, use a tree traversal to find symbol. + int ofs = 23; + do { + symbol = pH->tree[-(int)(symbol + ((m_bit_buf >> ofs) & 1))]; + ofs--; + } while (symbol < 0); + get_bits_no_markers(8 + (23 - ofs)); + } else get_bits_no_markers(pH->code_size[symbol]); + + return symbol; +} + + +// Decodes a Huffman encoded symbol. +inline int jpeg_decoder::huff_decode(huff_tables *pH, int& extra_bits) +{ + int symbol; + + // Check first 8-bits: do we have a complete symbol? + if ((symbol = pH->look_up2[m_bit_buf >> 24]) < 0) { + // Use a tree traversal to find symbol. + int ofs = 23; + do { + symbol = pH->tree[-(int)(symbol + ((m_bit_buf >> ofs) & 1))]; + ofs--; + } while (symbol < 0); + + get_bits_no_markers(8 + (23 - ofs)); + extra_bits = get_bits_no_markers(symbol & 0xF); + } else { + JPGD_ASSERT(((symbol >> 8) & 31) == pH->code_size[symbol & 255] + ((symbol & 0x8000) ? (symbol & 15) : 0)); + + if (symbol & 0x8000) { + get_bits_no_markers((symbol >> 8) & 31); + extra_bits = symbol >> 16; + } else { + int code_size = (symbol >> 8) & 31; + int num_extra_bits = symbol & 0xF; + int bits = code_size + num_extra_bits; + if (bits <= (m_bits_left + 16)) extra_bits = get_bits_no_markers(bits) & ((1 << num_extra_bits) - 1); + else { + get_bits_no_markers(code_size); + extra_bits = get_bits_no_markers(num_extra_bits); + } + } + symbol &= 0xFF; + } + return symbol; +} + + +// Tables and macro used to fully decode the DPCM differences. +static const int s_extend_test[16] = { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; +static const unsigned int s_extend_offset[16] = { 0, ((-1u)<<1) + 1, ((-1u)<<2) + 1, ((-1u)<<3) + 1, ((-1u)<<4) + 1, ((-1u)<<5) + 1, ((-1u)<<6) + 1, ((-1u)<<7) + 1, ((-1u)<<8) + 1, ((-1u)<<9) + 1, ((-1u)<<10) + 1, ((-1u)<<11) + 1, ((-1u)<<12) + 1, ((-1u)<<13) + 1, ((-1u)<<14) + 1, ((-1u)<<15) + 1 }; + +// The logical AND's in this macro are to shut up static code analysis (aren't really necessary - couldn't find another way to do this) +#define JPGD_HUFF_EXTEND(x, s) (((x) < s_extend_test[s & 15]) ? ((x) + s_extend_offset[s & 15]) : (x)) + + +// Clamps a value between 0-255. +inline uint8_t jpeg_decoder::clamp(int i) +{ + if (static_cast(i) > 255) i = (((~i) >> 31) & 0xFF); + return static_cast(i); +} + + +namespace DCT_Upsample +{ + struct Matrix44 + { + typedef int Element_Type; + enum { NUM_ROWS = 4, NUM_COLS = 4 }; + + Element_Type v[NUM_ROWS][NUM_COLS]; + + inline int rows() const { return NUM_ROWS; } + inline int cols() const { return NUM_COLS; } + inline const Element_Type & at(int r, int c) const { return v[r][c]; } + inline Element_Type & at(int r, int c) { return v[r][c]; } + + inline Matrix44() {} + + inline Matrix44& operator += (const Matrix44& a) + { + for (int r = 0; r < NUM_ROWS; r++) { + at(r, 0) += a.at(r, 0); + at(r, 1) += a.at(r, 1); + at(r, 2) += a.at(r, 2); + at(r, 3) += a.at(r, 3); + } + return *this; + } + + inline Matrix44& operator -= (const Matrix44& a) + { + for (int r = 0; r < NUM_ROWS; r++) { + at(r, 0) -= a.at(r, 0); + at(r, 1) -= a.at(r, 1); + at(r, 2) -= a.at(r, 2); + at(r, 3) -= a.at(r, 3); + } + return *this; + } + + friend inline Matrix44 operator + (const Matrix44& a, const Matrix44& b) + { + Matrix44 ret; + for (int r = 0; r < NUM_ROWS; r++) { + ret.at(r, 0) = a.at(r, 0) + b.at(r, 0); + ret.at(r, 1) = a.at(r, 1) + b.at(r, 1); + ret.at(r, 2) = a.at(r, 2) + b.at(r, 2); + ret.at(r, 3) = a.at(r, 3) + b.at(r, 3); + } + return ret; + } + + friend inline Matrix44 operator - (const Matrix44& a, const Matrix44& b) + { + Matrix44 ret; + for (int r = 0; r < NUM_ROWS; r++) { + ret.at(r, 0) = a.at(r, 0) - b.at(r, 0); + ret.at(r, 1) = a.at(r, 1) - b.at(r, 1); + ret.at(r, 2) = a.at(r, 2) - b.at(r, 2); + ret.at(r, 3) = a.at(r, 3) - b.at(r, 3); + } + return ret; + } + + static inline void add_and_store(jpgd_block_t* pDst, const Matrix44& a, const Matrix44& b) + { + for (int r = 0; r < 4; r++) { + pDst[0*8 + r] = static_cast(a.at(r, 0) + b.at(r, 0)); + pDst[1*8 + r] = static_cast(a.at(r, 1) + b.at(r, 1)); + pDst[2*8 + r] = static_cast(a.at(r, 2) + b.at(r, 2)); + pDst[3*8 + r] = static_cast(a.at(r, 3) + b.at(r, 3)); + } + } + + static inline void sub_and_store(jpgd_block_t* pDst, const Matrix44& a, const Matrix44& b) + { + for (int r = 0; r < 4; r++) { + pDst[0*8 + r] = static_cast(a.at(r, 0) - b.at(r, 0)); + pDst[1*8 + r] = static_cast(a.at(r, 1) - b.at(r, 1)); + pDst[2*8 + r] = static_cast(a.at(r, 2) - b.at(r, 2)); + pDst[3*8 + r] = static_cast(a.at(r, 3) - b.at(r, 3)); + } + } + }; + + const int FRACT_BITS = 10; + const int SCALE = 1 << FRACT_BITS; + + typedef int Temp_Type; + #define D(i) (((i) + (SCALE >> 1)) >> FRACT_BITS) + #define F(i) ((int)((i) * SCALE + .5f)) + + // Any decent C++ compiler will optimize this at compile time to a 0, or an array access. + #define AT(c, r) ((((c)>=NUM_COLS)||((r)>=NUM_ROWS)) ? 0 : pSrc[(c)+(r)*8]) + + // NUM_ROWS/NUM_COLS = # of non-zero rows/cols in input matrix + template + struct P_Q + { + static void calc(Matrix44& P, Matrix44& Q, const jpgd_block_t* pSrc) + { + // 4x8 = 4x8 times 8x8, matrix 0 is constant + const Temp_Type X000 = AT(0, 0); + const Temp_Type X001 = AT(0, 1); + const Temp_Type X002 = AT(0, 2); + const Temp_Type X003 = AT(0, 3); + const Temp_Type X004 = AT(0, 4); + const Temp_Type X005 = AT(0, 5); + const Temp_Type X006 = AT(0, 6); + const Temp_Type X007 = AT(0, 7); + const Temp_Type X010 = D(F(0.415735f) * AT(1, 0) + F(0.791065f) * AT(3, 0) + F(-0.352443f) * AT(5, 0) + F(0.277785f) * AT(7, 0)); + const Temp_Type X011 = D(F(0.415735f) * AT(1, 1) + F(0.791065f) * AT(3, 1) + F(-0.352443f) * AT(5, 1) + F(0.277785f) * AT(7, 1)); + const Temp_Type X012 = D(F(0.415735f) * AT(1, 2) + F(0.791065f) * AT(3, 2) + F(-0.352443f) * AT(5, 2) + F(0.277785f) * AT(7, 2)); + const Temp_Type X013 = D(F(0.415735f) * AT(1, 3) + F(0.791065f) * AT(3, 3) + F(-0.352443f) * AT(5, 3) + F(0.277785f) * AT(7, 3)); + const Temp_Type X014 = D(F(0.415735f) * AT(1, 4) + F(0.791065f) * AT(3, 4) + F(-0.352443f) * AT(5, 4) + F(0.277785f) * AT(7, 4)); + const Temp_Type X015 = D(F(0.415735f) * AT(1, 5) + F(0.791065f) * AT(3, 5) + F(-0.352443f) * AT(5, 5) + F(0.277785f) * AT(7, 5)); + const Temp_Type X016 = D(F(0.415735f) * AT(1, 6) + F(0.791065f) * AT(3, 6) + F(-0.352443f) * AT(5, 6) + F(0.277785f) * AT(7, 6)); + const Temp_Type X017 = D(F(0.415735f) * AT(1, 7) + F(0.791065f) * AT(3, 7) + F(-0.352443f) * AT(5, 7) + F(0.277785f) * AT(7, 7)); + const Temp_Type X020 = AT(4, 0); + const Temp_Type X021 = AT(4, 1); + const Temp_Type X022 = AT(4, 2); + const Temp_Type X023 = AT(4, 3); + const Temp_Type X024 = AT(4, 4); + const Temp_Type X025 = AT(4, 5); + const Temp_Type X026 = AT(4, 6); + const Temp_Type X027 = AT(4, 7); + const Temp_Type X030 = D(F(0.022887f) * AT(1, 0) + F(-0.097545f) * AT(3, 0) + F(0.490393f) * AT(5, 0) + F(0.865723f) * AT(7, 0)); + const Temp_Type X031 = D(F(0.022887f) * AT(1, 1) + F(-0.097545f) * AT(3, 1) + F(0.490393f) * AT(5, 1) + F(0.865723f) * AT(7, 1)); + const Temp_Type X032 = D(F(0.022887f) * AT(1, 2) + F(-0.097545f) * AT(3, 2) + F(0.490393f) * AT(5, 2) + F(0.865723f) * AT(7, 2)); + const Temp_Type X033 = D(F(0.022887f) * AT(1, 3) + F(-0.097545f) * AT(3, 3) + F(0.490393f) * AT(5, 3) + F(0.865723f) * AT(7, 3)); + const Temp_Type X034 = D(F(0.022887f) * AT(1, 4) + F(-0.097545f) * AT(3, 4) + F(0.490393f) * AT(5, 4) + F(0.865723f) * AT(7, 4)); + const Temp_Type X035 = D(F(0.022887f) * AT(1, 5) + F(-0.097545f) * AT(3, 5) + F(0.490393f) * AT(5, 5) + F(0.865723f) * AT(7, 5)); + const Temp_Type X036 = D(F(0.022887f) * AT(1, 6) + F(-0.097545f) * AT(3, 6) + F(0.490393f) * AT(5, 6) + F(0.865723f) * AT(7, 6)); + const Temp_Type X037 = D(F(0.022887f) * AT(1, 7) + F(-0.097545f) * AT(3, 7) + F(0.490393f) * AT(5, 7) + F(0.865723f) * AT(7, 7)); + + // 4x4 = 4x8 times 8x4, matrix 1 is constant + P.at(0, 0) = X000; + P.at(0, 1) = D(X001 * F(0.415735f) + X003 * F(0.791065f) + X005 * F(-0.352443f) + X007 * F(0.277785f)); + P.at(0, 2) = X004; + P.at(0, 3) = D(X001 * F(0.022887f) + X003 * F(-0.097545f) + X005 * F(0.490393f) + X007 * F(0.865723f)); + P.at(1, 0) = X010; + P.at(1, 1) = D(X011 * F(0.415735f) + X013 * F(0.791065f) + X015 * F(-0.352443f) + X017 * F(0.277785f)); + P.at(1, 2) = X014; + P.at(1, 3) = D(X011 * F(0.022887f) + X013 * F(-0.097545f) + X015 * F(0.490393f) + X017 * F(0.865723f)); + P.at(2, 0) = X020; + P.at(2, 1) = D(X021 * F(0.415735f) + X023 * F(0.791065f) + X025 * F(-0.352443f) + X027 * F(0.277785f)); + P.at(2, 2) = X024; + P.at(2, 3) = D(X021 * F(0.022887f) + X023 * F(-0.097545f) + X025 * F(0.490393f) + X027 * F(0.865723f)); + P.at(3, 0) = X030; + P.at(3, 1) = D(X031 * F(0.415735f) + X033 * F(0.791065f) + X035 * F(-0.352443f) + X037 * F(0.277785f)); + P.at(3, 2) = X034; + P.at(3, 3) = D(X031 * F(0.022887f) + X033 * F(-0.097545f) + X035 * F(0.490393f) + X037 * F(0.865723f)); + // 40 muls 24 adds + + // 4x4 = 4x8 times 8x4, matrix 1 is constant + Q.at(0, 0) = D(X001 * F(0.906127f) + X003 * F(-0.318190f) + X005 * F(0.212608f) + X007 * F(-0.180240f)); + Q.at(0, 1) = X002; + Q.at(0, 2) = D(X001 * F(-0.074658f) + X003 * F(0.513280f) + X005 * F(0.768178f) + X007 * F(-0.375330f)); + Q.at(0, 3) = X006; + Q.at(1, 0) = D(X011 * F(0.906127f) + X013 * F(-0.318190f) + X015 * F(0.212608f) + X017 * F(-0.180240f)); + Q.at(1, 1) = X012; + Q.at(1, 2) = D(X011 * F(-0.074658f) + X013 * F(0.513280f) + X015 * F(0.768178f) + X017 * F(-0.375330f)); + Q.at(1, 3) = X016; + Q.at(2, 0) = D(X021 * F(0.906127f) + X023 * F(-0.318190f) + X025 * F(0.212608f) + X027 * F(-0.180240f)); + Q.at(2, 1) = X022; + Q.at(2, 2) = D(X021 * F(-0.074658f) + X023 * F(0.513280f) + X025 * F(0.768178f) + X027 * F(-0.375330f)); + Q.at(2, 3) = X026; + Q.at(3, 0) = D(X031 * F(0.906127f) + X033 * F(-0.318190f) + X035 * F(0.212608f) + X037 * F(-0.180240f)); + Q.at(3, 1) = X032; + Q.at(3, 2) = D(X031 * F(-0.074658f) + X033 * F(0.513280f) + X035 * F(0.768178f) + X037 * F(-0.375330f)); + Q.at(3, 3) = X036; + // 40 muls 24 adds + } + }; + + + template + struct R_S + { + static void calc(Matrix44& R, Matrix44& S, const jpgd_block_t* pSrc) + { + // 4x8 = 4x8 times 8x8, matrix 0 is constant + const Temp_Type X100 = D(F(0.906127f) * AT(1, 0) + F(-0.318190f) * AT(3, 0) + F(0.212608f) * AT(5, 0) + F(-0.180240f) * AT(7, 0)); + const Temp_Type X101 = D(F(0.906127f) * AT(1, 1) + F(-0.318190f) * AT(3, 1) + F(0.212608f) * AT(5, 1) + F(-0.180240f) * AT(7, 1)); + const Temp_Type X102 = D(F(0.906127f) * AT(1, 2) + F(-0.318190f) * AT(3, 2) + F(0.212608f) * AT(5, 2) + F(-0.180240f) * AT(7, 2)); + const Temp_Type X103 = D(F(0.906127f) * AT(1, 3) + F(-0.318190f) * AT(3, 3) + F(0.212608f) * AT(5, 3) + F(-0.180240f) * AT(7, 3)); + const Temp_Type X104 = D(F(0.906127f) * AT(1, 4) + F(-0.318190f) * AT(3, 4) + F(0.212608f) * AT(5, 4) + F(-0.180240f) * AT(7, 4)); + const Temp_Type X105 = D(F(0.906127f) * AT(1, 5) + F(-0.318190f) * AT(3, 5) + F(0.212608f) * AT(5, 5) + F(-0.180240f) * AT(7, 5)); + const Temp_Type X106 = D(F(0.906127f) * AT(1, 6) + F(-0.318190f) * AT(3, 6) + F(0.212608f) * AT(5, 6) + F(-0.180240f) * AT(7, 6)); + const Temp_Type X107 = D(F(0.906127f) * AT(1, 7) + F(-0.318190f) * AT(3, 7) + F(0.212608f) * AT(5, 7) + F(-0.180240f) * AT(7, 7)); + const Temp_Type X110 = AT(2, 0); + const Temp_Type X111 = AT(2, 1); + const Temp_Type X112 = AT(2, 2); + const Temp_Type X113 = AT(2, 3); + const Temp_Type X114 = AT(2, 4); + const Temp_Type X115 = AT(2, 5); + const Temp_Type X116 = AT(2, 6); + const Temp_Type X117 = AT(2, 7); + const Temp_Type X120 = D(F(-0.074658f) * AT(1, 0) + F(0.513280f) * AT(3, 0) + F(0.768178f) * AT(5, 0) + F(-0.375330f) * AT(7, 0)); + const Temp_Type X121 = D(F(-0.074658f) * AT(1, 1) + F(0.513280f) * AT(3, 1) + F(0.768178f) * AT(5, 1) + F(-0.375330f) * AT(7, 1)); + const Temp_Type X122 = D(F(-0.074658f) * AT(1, 2) + F(0.513280f) * AT(3, 2) + F(0.768178f) * AT(5, 2) + F(-0.375330f) * AT(7, 2)); + const Temp_Type X123 = D(F(-0.074658f) * AT(1, 3) + F(0.513280f) * AT(3, 3) + F(0.768178f) * AT(5, 3) + F(-0.375330f) * AT(7, 3)); + const Temp_Type X124 = D(F(-0.074658f) * AT(1, 4) + F(0.513280f) * AT(3, 4) + F(0.768178f) * AT(5, 4) + F(-0.375330f) * AT(7, 4)); + const Temp_Type X125 = D(F(-0.074658f) * AT(1, 5) + F(0.513280f) * AT(3, 5) + F(0.768178f) * AT(5, 5) + F(-0.375330f) * AT(7, 5)); + const Temp_Type X126 = D(F(-0.074658f) * AT(1, 6) + F(0.513280f) * AT(3, 6) + F(0.768178f) * AT(5, 6) + F(-0.375330f) * AT(7, 6)); + const Temp_Type X127 = D(F(-0.074658f) * AT(1, 7) + F(0.513280f) * AT(3, 7) + F(0.768178f) * AT(5, 7) + F(-0.375330f) * AT(7, 7)); + const Temp_Type X130 = AT(6, 0); + const Temp_Type X131 = AT(6, 1); + const Temp_Type X132 = AT(6, 2); + const Temp_Type X133 = AT(6, 3); + const Temp_Type X134 = AT(6, 4); + const Temp_Type X135 = AT(6, 5); + const Temp_Type X136 = AT(6, 6); + const Temp_Type X137 = AT(6, 7); + // 80 muls 48 adds + + // 4x4 = 4x8 times 8x4, matrix 1 is constant + R.at(0, 0) = X100; + R.at(0, 1) = D(X101 * F(0.415735f) + X103 * F(0.791065f) + X105 * F(-0.352443f) + X107 * F(0.277785f)); + R.at(0, 2) = X104; + R.at(0, 3) = D(X101 * F(0.022887f) + X103 * F(-0.097545f) + X105 * F(0.490393f) + X107 * F(0.865723f)); + R.at(1, 0) = X110; + R.at(1, 1) = D(X111 * F(0.415735f) + X113 * F(0.791065f) + X115 * F(-0.352443f) + X117 * F(0.277785f)); + R.at(1, 2) = X114; + R.at(1, 3) = D(X111 * F(0.022887f) + X113 * F(-0.097545f) + X115 * F(0.490393f) + X117 * F(0.865723f)); + R.at(2, 0) = X120; + R.at(2, 1) = D(X121 * F(0.415735f) + X123 * F(0.791065f) + X125 * F(-0.352443f) + X127 * F(0.277785f)); + R.at(2, 2) = X124; + R.at(2, 3) = D(X121 * F(0.022887f) + X123 * F(-0.097545f) + X125 * F(0.490393f) + X127 * F(0.865723f)); + R.at(3, 0) = X130; + R.at(3, 1) = D(X131 * F(0.415735f) + X133 * F(0.791065f) + X135 * F(-0.352443f) + X137 * F(0.277785f)); + R.at(3, 2) = X134; + R.at(3, 3) = D(X131 * F(0.022887f) + X133 * F(-0.097545f) + X135 * F(0.490393f) + X137 * F(0.865723f)); + // 40 muls 24 adds + // 4x4 = 4x8 times 8x4, matrix 1 is constant + S.at(0, 0) = D(X101 * F(0.906127f) + X103 * F(-0.318190f) + X105 * F(0.212608f) + X107 * F(-0.180240f)); + S.at(0, 1) = X102; + S.at(0, 2) = D(X101 * F(-0.074658f) + X103 * F(0.513280f) + X105 * F(0.768178f) + X107 * F(-0.375330f)); + S.at(0, 3) = X106; + S.at(1, 0) = D(X111 * F(0.906127f) + X113 * F(-0.318190f) + X115 * F(0.212608f) + X117 * F(-0.180240f)); + S.at(1, 1) = X112; + S.at(1, 2) = D(X111 * F(-0.074658f) + X113 * F(0.513280f) + X115 * F(0.768178f) + X117 * F(-0.375330f)); + S.at(1, 3) = X116; + S.at(2, 0) = D(X121 * F(0.906127f) + X123 * F(-0.318190f) + X125 * F(0.212608f) + X127 * F(-0.180240f)); + S.at(2, 1) = X122; + S.at(2, 2) = D(X121 * F(-0.074658f) + X123 * F(0.513280f) + X125 * F(0.768178f) + X127 * F(-0.375330f)); + S.at(2, 3) = X126; + S.at(3, 0) = D(X131 * F(0.906127f) + X133 * F(-0.318190f) + X135 * F(0.212608f) + X137 * F(-0.180240f)); + S.at(3, 1) = X132; + S.at(3, 2) = D(X131 * F(-0.074658f) + X133 * F(0.513280f) + X135 * F(0.768178f) + X137 * F(-0.375330f)); + S.at(3, 3) = X136; + // 40 muls 24 adds + } + }; +} // end namespace DCT_Upsample + + +// Unconditionally frees all allocated m_blocks. +void jpeg_decoder::free_all_blocks() +{ + m_pStream = nullptr; + for (mem_block *b = m_pMem_blocks; b; ) { + mem_block *n = b->m_pNext; + free(b); + b = n; + } + m_pMem_blocks = nullptr; +} + + +// This method handles all errors. It will never return. +// It could easily be changed to use C++ exceptions. +JPGD_NORETURN void jpeg_decoder::stop_decoding(jpgd_status status) +{ + m_error_code = status; + free_all_blocks(); + longjmp(m_jmp_state, status); +} + + +void *jpeg_decoder::alloc(size_t nSize, bool zero) +{ + nSize = (JPGD_MAX(nSize, 1) + 3) & ~3; + char *rv = nullptr; + for (mem_block *b = m_pMem_blocks; b; b = b->m_pNext) { + if ((b->m_used_count + nSize) <= b->m_size) { + rv = b->m_data + b->m_used_count; + b->m_used_count += nSize; + break; + } + } + if (!rv) { + int capacity = JPGD_MAX(32768 - 256, (nSize + 2047) & ~2047); + mem_block *b = (mem_block*)malloc(sizeof(mem_block) + capacity); + if (!b) stop_decoding(JPGD_NOTENOUGHMEM); + b->m_pNext = m_pMem_blocks; m_pMem_blocks = b; + b->m_used_count = nSize; + b->m_size = capacity; + rv = b->m_data; + } + if (zero) memset(rv, 0, nSize); + return rv; +} + + +void jpeg_decoder::word_clear(void *p, uint16_t c, uint32_t n) +{ + uint8_t *pD = (uint8_t*)p; + const uint8_t l = c & 0xFF, h = (c >> 8) & 0xFF; + while (n) { + pD[0] = l; pD[1] = h; pD += 2; + n--; + } +} + + +// Refill the input buffer. +// This method will sit in a loop until (A) the buffer is full or (B) +// the stream's read() method reports and end of file condition. +void jpeg_decoder::prep_in_buffer() +{ + m_in_buf_left = 0; + m_pIn_buf_ofs = m_in_buf; + + if (m_eof_flag) return; + + do { + int bytes_read = m_pStream->read(m_in_buf + m_in_buf_left, JPGD_IN_BUF_SIZE - m_in_buf_left, &m_eof_flag); + if (bytes_read == -1) stop_decoding(JPGD_STREAM_READ); + m_in_buf_left += bytes_read; + } while ((m_in_buf_left < JPGD_IN_BUF_SIZE) && (!m_eof_flag)); + + m_total_bytes_read += m_in_buf_left; + + // Pad the end of the block with M_EOI (prevents the decompressor from going off the rails if the stream is invalid). + // (This dates way back to when this decompressor was written in C/asm, and the all-asm Huffman decoder did some fancy things to increase perf.) + word_clear(m_pIn_buf_ofs + m_in_buf_left, 0xD9FF, 64); +} + + +// Read a Huffman code table. +void jpeg_decoder::read_dht_marker() +{ + int i, index, count; + uint8_t huff_num[17]; + uint8_t huff_val[256]; + uint32_t num_left = get_bits(16); + + if (num_left < 2) stop_decoding(JPGD_BAD_DHT_MARKER); + num_left -= 2; + + while (num_left) { + index = get_bits(8); + huff_num[0] = 0; + count = 0; + + for (i = 1; i <= 16; i++) { + huff_num[i] = static_cast(get_bits(8)); + count += huff_num[i]; + } + + if (count > 255) stop_decoding(JPGD_BAD_DHT_COUNTS); + + for (i = 0; i < count; i++) + huff_val[i] = static_cast(get_bits(8)); + + i = 1 + 16 + count; + + if (num_left < (uint32_t)i) stop_decoding(JPGD_BAD_DHT_MARKER); + num_left -= i; + + if ((index & 0x10) > 0x10) stop_decoding(JPGD_BAD_DHT_INDEX); + index = (index & 0x0F) + ((index & 0x10) >> 4) * (JPGD_MAX_HUFF_TABLES >> 1); + if (index >= JPGD_MAX_HUFF_TABLES) stop_decoding(JPGD_BAD_DHT_INDEX); + + if (!m_huff_num[index]) m_huff_num[index] = (uint8_t *)alloc(17); + if (!m_huff_val[index]) m_huff_val[index] = (uint8_t *)alloc(256); + + m_huff_ac[index] = (index & 0x10) != 0; + memcpy(m_huff_num[index], huff_num, 17); + memcpy(m_huff_val[index], huff_val, 256); + } +} + + +// Read a quantization table. +void jpeg_decoder::read_dqt_marker() +{ + int n, i, prec; + uint32_t temp; + uint32_t num_left = get_bits(16); + if (num_left < 2) stop_decoding(JPGD_BAD_DQT_MARKER); + num_left -= 2; + + while (num_left) { + n = get_bits(8); + prec = n >> 4; + n &= 0x0F; + + if (n >= JPGD_MAX_QUANT_TABLES) stop_decoding(JPGD_BAD_DQT_TABLE); + + if (!m_quant[n]) m_quant[n] = (jpgd_quant_t *)alloc(64 * sizeof(jpgd_quant_t)); + + // read quantization entries, in zag order + for (i = 0; i < 64; i++) { + temp = get_bits(8); + if (prec) temp = (temp << 8) + get_bits(8); + m_quant[n][i] = static_cast(temp); + } + i = 64 + 1; + if (prec) i += 64; + if (num_left < (uint32_t)i) stop_decoding(JPGD_BAD_DQT_LENGTH); + num_left -= i; + } +} + + +// Read the start of frame (SOF) marker. +void jpeg_decoder::read_sof_marker() +{ + int i; + uint32_t num_left = get_bits(16); + + if (get_bits(8) != 8) stop_decoding(JPGD_BAD_PRECISION); /* precision: sorry, only 8-bit precision is supported right now */ + + m_image_y_size = get_bits(16); + if ((m_image_y_size < 1) || (m_image_y_size > JPGD_MAX_HEIGHT)) stop_decoding(JPGD_BAD_HEIGHT); + + m_image_x_size = get_bits(16); + if ((m_image_x_size < 1) || (m_image_x_size > JPGD_MAX_WIDTH)) stop_decoding(JPGD_BAD_WIDTH); + + m_comps_in_frame = get_bits(8); + if (m_comps_in_frame > JPGD_MAX_COMPONENTS) stop_decoding(JPGD_TOO_MANY_COMPONENTS); + + if (num_left != (uint32_t)(m_comps_in_frame * 3 + 8)) stop_decoding(JPGD_BAD_SOF_LENGTH); + + for (i = 0; i < m_comps_in_frame; i++) { + m_comp_ident[i] = get_bits(8); + m_comp_h_samp[i] = get_bits(4); + m_comp_v_samp[i] = get_bits(4); + m_comp_quant[i] = get_bits(8); + } +} + + +// Used to skip unrecognized markers. +void jpeg_decoder::skip_variable_marker() +{ + uint32_t num_left = get_bits(16); + if (num_left < 2) stop_decoding(JPGD_BAD_VARIABLE_MARKER); + num_left -= 2; + + while (num_left) { + get_bits(8); + num_left--; + } +} + + +// Read a define restart interval (DRI) marker. +void jpeg_decoder::read_dri_marker() +{ + if (get_bits(16) != 4) stop_decoding(JPGD_BAD_DRI_LENGTH); + m_restart_interval = get_bits(16); +} + + +// Read a start of scan (SOS) marker. +void jpeg_decoder::read_sos_marker() +{ + int i, ci, c, cc; + uint32_t num_left = get_bits(16); + int n = get_bits(8); + + m_comps_in_scan = n; + num_left -= 3; + + if ( (num_left != (uint32_t)(n * 2 + 3)) || (n < 1) || (n > JPGD_MAX_COMPS_IN_SCAN) ) stop_decoding(JPGD_BAD_SOS_LENGTH); + + for (i = 0; i < n; i++) { + cc = get_bits(8); + c = get_bits(8); + num_left -= 2; + + for (ci = 0; ci < m_comps_in_frame; ci++) + if (cc == m_comp_ident[ci]) break; + + if (ci >= m_comps_in_frame) stop_decoding(JPGD_BAD_SOS_COMP_ID); + + m_comp_list[i] = ci; + m_comp_dc_tab[ci] = (c >> 4) & 15; + m_comp_ac_tab[ci] = (c & 15) + (JPGD_MAX_HUFF_TABLES >> 1); + } + m_spectral_start = get_bits(8); + m_spectral_end = get_bits(8); + m_successive_high = get_bits(4); + m_successive_low = get_bits(4); + + if (!m_progressive_flag) { + m_spectral_start = 0; + m_spectral_end = 63; + } + num_left -= 3; + + while (num_left) { /* read past whatever is num_left */ + get_bits(8); + num_left--; + } +} + + +// Finds the next marker. +int jpeg_decoder::next_marker() +{ + uint32_t c, bytes = 0; + + do { + do { + bytes++; + c = get_bits(8); + } while (c != 0xFF); + + do { + c = get_bits(8); + } while (c == 0xFF); + } while (c == 0); + + // If bytes > 0 here, there where extra bytes before the marker (not good). + return c; +} + + +// Process markers. Returns when an SOFx, SOI, EOI, or SOS marker is +// encountered. +int jpeg_decoder::process_markers() +{ + int c; + + for ( ; ; ) { + c = next_marker(); + switch (c) { + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + // case M_JPG: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + case M_SOI: + case M_EOI: + case M_SOS: return c; + case M_DHT: { + read_dht_marker(); + break; + } + // No arithmitic support - dumb patents! + case M_DAC: { + stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT); + break; + } + case M_DQT: { + read_dqt_marker(); + break; + } + case M_DRI: { + read_dri_marker(); + break; + } + //case M_APP0: /* no need to read the JFIF marker */ + case M_JPG: + case M_RST0: /* no parameters */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: { + stop_decoding(JPGD_UNEXPECTED_MARKER); + break; + } + default: { /* must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn or APP0 */ + skip_variable_marker(); + break; + } + } + } +} + + +// Finds the start of image (SOI) marker. +// This code is rather defensive: it only checks the first 512 bytes to avoid +// false positives. +void jpeg_decoder::locate_soi_marker() +{ + uint32_t lastchar = get_bits(8); + uint32_t thischar = get_bits(8); + + /* ok if it's a normal JPEG file without a special header */ + if ((lastchar == 0xFF) && (thischar == M_SOI)) return; + + uint32_t bytesleft = 4096; //512; + + while (true) { + if (--bytesleft == 0) stop_decoding(JPGD_NOT_JPEG); + + lastchar = thischar; + thischar = get_bits(8); + + if (lastchar == 0xFF) { + if (thischar == M_SOI) break; + else if (thischar == M_EOI) stop_decoding(JPGD_NOT_JPEG); // get_bits will keep returning M_EOI if we read past the end + } + } + + // Check the next character after marker: if it's not 0xFF, it can't be the start of the next marker, so the file is bad. + thischar = (m_bit_buf >> 24) & 0xFF; + if (thischar != 0xFF) stop_decoding(JPGD_NOT_JPEG); +} + + +// Find a start of frame (SOF) marker. +void jpeg_decoder::locate_sof_marker() +{ + locate_soi_marker(); + int c = process_markers(); + + switch (c) { + case M_SOF2: m_progressive_flag = true; + case M_SOF0: /* baseline DCT */ + case M_SOF1: { /* extended sequential DCT */ + read_sof_marker(); + break; + } + case M_SOF9: { /* Arithmitic coding */ + stop_decoding(JPGD_NO_ARITHMITIC_SUPPORT); + break; + } + default: { + stop_decoding(JPGD_UNSUPPORTED_MARKER); + break; + } + } +} + + +// Find a start of scan (SOS) marker. +int jpeg_decoder::locate_sos_marker() +{ + int c = process_markers(); + if (c == M_EOI) return false; + else if (c != M_SOS) stop_decoding(JPGD_UNEXPECTED_MARKER); + read_sos_marker(); + return true; +} + + +// Reset everything to default/uninitialized state. +void jpeg_decoder::init(jpeg_decoder_stream *pStream) +{ + m_pMem_blocks = nullptr; + m_error_code = JPGD_SUCCESS; + m_ready_flag = false; + m_image_x_size = m_image_y_size = 0; + m_pStream = pStream; + m_progressive_flag = false; + + memset(m_huff_ac, 0, sizeof(m_huff_ac)); + memset(m_huff_num, 0, sizeof(m_huff_num)); + memset(m_huff_val, 0, sizeof(m_huff_val)); + memset(m_quant, 0, sizeof(m_quant)); + + m_scan_type = 0; + m_comps_in_frame = 0; + + memset(m_comp_h_samp, 0, sizeof(m_comp_h_samp)); + memset(m_comp_v_samp, 0, sizeof(m_comp_v_samp)); + memset(m_comp_quant, 0, sizeof(m_comp_quant)); + memset(m_comp_ident, 0, sizeof(m_comp_ident)); + memset(m_comp_h_blocks, 0, sizeof(m_comp_h_blocks)); + memset(m_comp_v_blocks, 0, sizeof(m_comp_v_blocks)); + + m_comps_in_scan = 0; + memset(m_comp_list, 0, sizeof(m_comp_list)); + memset(m_comp_dc_tab, 0, sizeof(m_comp_dc_tab)); + memset(m_comp_ac_tab, 0, sizeof(m_comp_ac_tab)); + + m_spectral_start = 0; + m_spectral_end = 0; + m_successive_low = 0; + m_successive_high = 0; + m_max_mcu_x_size = 0; + m_max_mcu_y_size = 0; + m_blocks_per_mcu = 0; + m_max_blocks_per_row = 0; + m_mcus_per_row = 0; + m_mcus_per_col = 0; + m_expanded_blocks_per_component = 0; + m_expanded_blocks_per_mcu = 0; + m_expanded_blocks_per_row = 0; + m_freq_domain_chroma_upsample = false; + + memset(m_mcu_org, 0, sizeof(m_mcu_org)); + + m_total_lines_left = 0; + m_mcu_lines_left = 0; + m_real_dest_bytes_per_scan_line = 0; + m_dest_bytes_per_scan_line = 0; + m_dest_bytes_per_pixel = 0; + + memset(m_pHuff_tabs, 0, sizeof(m_pHuff_tabs)); + + memset(m_dc_coeffs, 0, sizeof(m_dc_coeffs)); + memset(m_ac_coeffs, 0, sizeof(m_ac_coeffs)); + memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu)); + + m_eob_run = 0; + + memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu)); + + m_pIn_buf_ofs = m_in_buf; + m_in_buf_left = 0; + m_eof_flag = false; + m_tem_flag = 0; + + memset(m_in_buf_pad_start, 0, sizeof(m_in_buf_pad_start)); + memset(m_in_buf, 0, sizeof(m_in_buf)); + memset(m_in_buf_pad_end, 0, sizeof(m_in_buf_pad_end)); + + m_restart_interval = 0; + m_restarts_left = 0; + m_next_restart_num = 0; + + m_max_mcus_per_row = 0; + m_max_blocks_per_mcu = 0; + m_max_mcus_per_col = 0; + + memset(m_last_dc_val, 0, sizeof(m_last_dc_val)); + m_pMCU_coefficients = nullptr; + m_pSample_buf = nullptr; + + m_total_bytes_read = 0; + + m_pScan_line_0 = nullptr; + m_pScan_line_1 = nullptr; + + // Ready the input buffer. + prep_in_buffer(); + + // Prime the bit buffer. + m_bits_left = 16; + m_bit_buf = 0; + + get_bits(16); + get_bits(16); + + for (int i = 0; i < JPGD_MAX_BLOCKS_PER_MCU; i++) { + m_mcu_block_max_zag[i] = 64; + } +} + +#define SCALEBITS 16 +#define ONE_HALF ((int) 1 << (SCALEBITS-1)) +#define FIX(x) ((int) ((x) * (1L<> SCALEBITS; + m_cbb[i] = ( FIX(1.77200f) * k + ONE_HALF) >> SCALEBITS; + m_crg[i] = (-FIX(0.71414f)) * k; + m_cbg[i] = (-FIX(0.34414f)) * k + ONE_HALF; + } +} + + +// This method throws back into the stream any bytes that where read +// into the bit buffer during initial marker scanning. +void jpeg_decoder::fix_in_buffer() +{ + // In case any 0xFF's where pulled into the buffer during marker scanning. + JPGD_ASSERT((m_bits_left & 7) == 0); + + if (m_bits_left == 16) stuff_char( (uint8_t)(m_bit_buf & 0xFF)); + if (m_bits_left >= 8) stuff_char( (uint8_t)((m_bit_buf >> 8) & 0xFF)); + + stuff_char((uint8_t)((m_bit_buf >> 16) & 0xFF)); + stuff_char((uint8_t)((m_bit_buf >> 24) & 0xFF)); + + m_bits_left = 16; + get_bits_no_markers(16); + get_bits_no_markers(16); +} + + +void jpeg_decoder::transform_mcu(int mcu_row) +{ + jpgd_block_t* pSrc_ptr = m_pMCU_coefficients; + uint8_t* pDst_ptr = m_pSample_buf + mcu_row * m_blocks_per_mcu * 64; + + for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) { + idct(pSrc_ptr, pDst_ptr, m_mcu_block_max_zag[mcu_block]); + pSrc_ptr += 64; + pDst_ptr += 64; + } +} + + +static const uint8_t s_max_rc[64] = +{ + 17, 18, 34, 50, 50, 51, 52, 52, 52, 68, 84, 84, 84, 84, 85, 86, 86, 86, 86, 86, + 102, 118, 118, 118, 118, 118, 118, 119, 120, 120, 120, 120, 120, 120, 120, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136 +}; + + +void jpeg_decoder::transform_mcu_expand(int mcu_row) +{ + jpgd_block_t* pSrc_ptr = m_pMCU_coefficients; + uint8_t* pDst_ptr = m_pSample_buf + mcu_row * m_expanded_blocks_per_mcu * 64; + + // Y IDCT + int mcu_block; + for (mcu_block = 0; mcu_block < m_expanded_blocks_per_component; mcu_block++) { + idct(pSrc_ptr, pDst_ptr, m_mcu_block_max_zag[mcu_block]); + pSrc_ptr += 64; + pDst_ptr += 64; + } + + // Chroma IDCT, with upsampling + jpgd_block_t temp_block[64]; + + for (int i = 0; i < 2; i++) { + DCT_Upsample::Matrix44 P, Q, R, S; + JPGD_ASSERT(m_mcu_block_max_zag[mcu_block] >= 1); + JPGD_ASSERT(m_mcu_block_max_zag[mcu_block] <= 64); + + int max_zag = m_mcu_block_max_zag[mcu_block++] - 1; + if (max_zag <= 0) max_zag = 0; // should never happen, only here to shut up static analysis + + switch (s_max_rc[max_zag]) { + case 1*16+1: + DCT_Upsample::P_Q<1, 1>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<1, 1>::calc(R, S, pSrc_ptr); + break; + case 1*16+2: + DCT_Upsample::P_Q<1, 2>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<1, 2>::calc(R, S, pSrc_ptr); + break; + case 2*16+2: + DCT_Upsample::P_Q<2, 2>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<2, 2>::calc(R, S, pSrc_ptr); + break; + case 3*16+2: + DCT_Upsample::P_Q<3, 2>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<3, 2>::calc(R, S, pSrc_ptr); + break; + case 3*16+3: + DCT_Upsample::P_Q<3, 3>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<3, 3>::calc(R, S, pSrc_ptr); + break; + case 3*16+4: + DCT_Upsample::P_Q<3, 4>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<3, 4>::calc(R, S, pSrc_ptr); + break; + case 4*16+4: + DCT_Upsample::P_Q<4, 4>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<4, 4>::calc(R, S, pSrc_ptr); + break; + case 5*16+4: + DCT_Upsample::P_Q<5, 4>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<5, 4>::calc(R, S, pSrc_ptr); + break; + case 5*16+5: + DCT_Upsample::P_Q<5, 5>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<5, 5>::calc(R, S, pSrc_ptr); + break; + case 5*16+6: + DCT_Upsample::P_Q<5, 6>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<5, 6>::calc(R, S, pSrc_ptr); + break; + case 6*16+6: + DCT_Upsample::P_Q<6, 6>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<6, 6>::calc(R, S, pSrc_ptr); + break; + case 7*16+6: + DCT_Upsample::P_Q<7, 6>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<7, 6>::calc(R, S, pSrc_ptr); + break; + case 7*16+7: + DCT_Upsample::P_Q<7, 7>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<7, 7>::calc(R, S, pSrc_ptr); + break; + case 7*16+8: + DCT_Upsample::P_Q<7, 8>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<7, 8>::calc(R, S, pSrc_ptr); + break; + case 8*16+8: + DCT_Upsample::P_Q<8, 8>::calc(P, Q, pSrc_ptr); + DCT_Upsample::R_S<8, 8>::calc(R, S, pSrc_ptr); + break; + default: + JPGD_ASSERT(false); + } + DCT_Upsample::Matrix44 a(P + Q); P -= Q; + DCT_Upsample::Matrix44& b = P; + DCT_Upsample::Matrix44 c(R + S); R -= S; + DCT_Upsample::Matrix44& d = R; + + DCT_Upsample::Matrix44::add_and_store(temp_block, a, c); + idct_4x4(temp_block, pDst_ptr); + pDst_ptr += 64; + + DCT_Upsample::Matrix44::sub_and_store(temp_block, a, c); + idct_4x4(temp_block, pDst_ptr); + pDst_ptr += 64; + + DCT_Upsample::Matrix44::add_and_store(temp_block, b, d); + idct_4x4(temp_block, pDst_ptr); + pDst_ptr += 64; + + DCT_Upsample::Matrix44::sub_and_store(temp_block, b, d); + idct_4x4(temp_block, pDst_ptr); + pDst_ptr += 64; + pSrc_ptr += 64; + } +} + + +// Loads and dequantizes the next row of (already decoded) coefficients. +// Progressive images only. +void jpeg_decoder::load_next_row() +{ + int i; + jpgd_block_t *p; + jpgd_quant_t *q; + int mcu_row, mcu_block, row_block = 0; + int component_num, component_id; + int block_x_mcu[JPGD_MAX_COMPONENTS]; + + memset(block_x_mcu, 0, JPGD_MAX_COMPONENTS * sizeof(int)); + + for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) { + int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0; + + for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) { + component_id = m_mcu_org[mcu_block]; + q = m_quant[m_comp_quant[component_id]]; + p = m_pMCU_coefficients + 64 * mcu_block; + + jpgd_block_t* pAC = coeff_buf_getp(m_ac_coeffs[component_id], block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs); + jpgd_block_t* pDC = coeff_buf_getp(m_dc_coeffs[component_id], block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs); + p[0] = pDC[0]; + memcpy(&p[1], &pAC[1], 63 * sizeof(jpgd_block_t)); + + for (i = 63; i > 0; i--) { + if (p[g_ZAG[i]]) break; + } + + m_mcu_block_max_zag[mcu_block] = i + 1; + + for ( ; i >= 0; i--) { + if (p[g_ZAG[i]]) { + p[g_ZAG[i]] = static_cast(p[g_ZAG[i]] * q[i]); + } + } + + row_block++; + + if (m_comps_in_scan == 1) block_x_mcu[component_id]++; + else { + if (++block_x_mcu_ofs == m_comp_h_samp[component_id]) block_x_mcu_ofs = 0; + if (++block_y_mcu_ofs == m_comp_v_samp[component_id]) { + block_y_mcu_ofs = 0; + block_x_mcu[component_id] += m_comp_h_samp[component_id]; + } + } + } + if (m_freq_domain_chroma_upsample) transform_mcu_expand(mcu_row); + else transform_mcu(mcu_row); + } + if (m_comps_in_scan == 1) m_block_y_mcu[m_comp_list[0]]++; + else { + for (component_num = 0; component_num < m_comps_in_scan; component_num++) { + component_id = m_comp_list[component_num]; + m_block_y_mcu[component_id] += m_comp_v_samp[component_id]; + } + } +} + + +// Restart interval processing. +void jpeg_decoder::process_restart() +{ + int i; + int c = 0; + + // Align to a byte boundry + // FIXME: Is this really necessary? get_bits_no_markers() never reads in markers! + //get_bits_no_markers(m_bits_left & 7); + + // Let's scan a little bit to find the marker, but not _too_ far. + // 1536 is a "fudge factor" that determines how much to scan. + for (i = 1536; i > 0; i--) { + if (get_char() == 0xFF) break; + } + if (i == 0) stop_decoding(JPGD_BAD_RESTART_MARKER); + + for ( ; i > 0; i--) { + if ((c = get_char()) != 0xFF) break; + } + if (i == 0) stop_decoding(JPGD_BAD_RESTART_MARKER); + + // Is it the expected marker? If not, something bad happened. + if (c != (m_next_restart_num + M_RST0)) stop_decoding(JPGD_BAD_RESTART_MARKER); + + // Reset each component's DC prediction values. + memset(&m_last_dc_val, 0, m_comps_in_frame * sizeof(uint32_t)); + + m_eob_run = 0; + m_restarts_left = m_restart_interval; + m_next_restart_num = (m_next_restart_num + 1) & 7; + + // Get the bit buffer going again... + m_bits_left = 16; + get_bits_no_markers(16); + get_bits_no_markers(16); +} + + +static inline int dequantize_ac(int c, int q) +{ + c *= q; + return c; +} + +// Decodes and dequantizes the next row of coefficients. +void jpeg_decoder::decode_next_row() +{ + int row_block = 0; + + for (int mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) { + if ((m_restart_interval) && (m_restarts_left == 0)) process_restart(); + + jpgd_block_t* p = m_pMCU_coefficients; + + for (int mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++, p += 64) { + int component_id = m_mcu_org[mcu_block]; + jpgd_quant_t* q = m_quant[m_comp_quant[component_id]]; + + int r, s; + s = huff_decode(m_pHuff_tabs[m_comp_dc_tab[component_id]], r); + s = JPGD_HUFF_EXTEND(r, s); + + m_last_dc_val[component_id] = (s += m_last_dc_val[component_id]); + + p[0] = static_cast(s * q[0]); + + int prev_num_set = m_mcu_block_max_zag[mcu_block]; + huff_tables *pH = m_pHuff_tabs[m_comp_ac_tab[component_id]]; + int k; + for (k = 1; k < 64; k++) { + int extra_bits; + s = huff_decode(pH, extra_bits); + r = s >> 4; + s &= 15; + + if (s) { + if (r) { + if ((k + r) > 63) stop_decoding(JPGD_DECODE_ERROR); + if (k < prev_num_set) { + int n = JPGD_MIN(r, prev_num_set - k); + int kt = k; + while (n--) p[g_ZAG[kt++]] = 0; + } + k += r; + } + s = JPGD_HUFF_EXTEND(extra_bits, s); + JPGD_ASSERT(k < 64); + p[g_ZAG[k]] = static_cast(dequantize_ac(s, q[k])); //s * q[k]; + } else { + if (r == 15) { + if ((k + 16) > 64) stop_decoding(JPGD_DECODE_ERROR); + if (k < prev_num_set) { + int n = JPGD_MIN(16, prev_num_set - k); + int kt = k; + while (n--) { + JPGD_ASSERT(kt <= 63); + p[g_ZAG[kt++]] = 0; + } + } + k += 16 - 1; // - 1 because the loop counter is k + JPGD_ASSERT(p[g_ZAG[k]] == 0); + } else break; + } + } + + if (k < prev_num_set) { + int kt = k; + while (kt < prev_num_set) p[g_ZAG[kt++]] = 0; + } + + m_mcu_block_max_zag[mcu_block] = k; + row_block++; + } + if (m_freq_domain_chroma_upsample) transform_mcu_expand(mcu_row); + else transform_mcu(mcu_row); + m_restarts_left--; + } +} + + +// YCbCr H1V1 (1x1:1:1, 3 m_blocks per MCU) to RGB +void jpeg_decoder::H1V1Convert() +{ + int row = m_max_mcu_y_size - m_mcu_lines_left; + uint8_t *d = m_pScan_line_0; + uint8_t *s = m_pSample_buf + row * 8; + + for (int i = m_max_mcus_per_row; i > 0; i--) { + for (int j = 0; j < 8; j++) { + int y = s[j]; + int cb = s[64+j]; + int cr = s[128+j]; + + d[0] = clamp(y + m_crr[cr]); + d[1] = clamp(y + ((m_crg[cr] + m_cbg[cb]) >> 16)); + d[2] = clamp(y + m_cbb[cb]); + d[3] = 255; + d += 4; + } + s += 64*3; + } +} + + +// YCbCr H2V1 (2x1:1:1, 4 m_blocks per MCU) to RGB +void jpeg_decoder::H2V1Convert() +{ + int row = m_max_mcu_y_size - m_mcu_lines_left; + uint8_t *d0 = m_pScan_line_0; + uint8_t *y = m_pSample_buf + row * 8; + uint8_t *c = m_pSample_buf + 2*64 + row * 8; + + for (int i = m_max_mcus_per_row; i > 0; i--) { + for (int l = 0; l < 2; l++) { + for (int j = 0; j < 4; j++) { + int cb = c[0]; + int cr = c[64]; + + int rc = m_crr[cr]; + int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); + int bc = m_cbb[cb]; + + int yy = y[j<<1]; + d0[0] = clamp(yy+rc); + d0[1] = clamp(yy+gc); + d0[2] = clamp(yy+bc); + d0[3] = 255; + + yy = y[(j<<1)+1]; + d0[4] = clamp(yy+rc); + d0[5] = clamp(yy+gc); + d0[6] = clamp(yy+bc); + d0[7] = 255; + d0 += 8; + c++; + } + y += 64; + } + y += 64*4 - 64*2; + c += 64*4 - 8; + } +} + + +// YCbCr H2V1 (1x2:1:1, 4 m_blocks per MCU) to RGB +void jpeg_decoder::H1V2Convert() +{ + int row = m_max_mcu_y_size - m_mcu_lines_left; + uint8_t *d0 = m_pScan_line_0; + uint8_t *d1 = m_pScan_line_1; + uint8_t *y; + uint8_t *c; + + if (row < 8) y = m_pSample_buf + row * 8; + else y = m_pSample_buf + 64*1 + (row & 7) * 8; + + c = m_pSample_buf + 64*2 + (row >> 1) * 8; + + for (int i = m_max_mcus_per_row; i > 0; i--) { + for (int j = 0; j < 8; j++) { + int cb = c[0+j]; + int cr = c[64+j]; + + int rc = m_crr[cr]; + int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); + int bc = m_cbb[cb]; + + int yy = y[j]; + d0[0] = clamp(yy+rc); + d0[1] = clamp(yy+gc); + d0[2] = clamp(yy+bc); + d0[3] = 255; + + yy = y[8+j]; + d1[0] = clamp(yy+rc); + d1[1] = clamp(yy+gc); + d1[2] = clamp(yy+bc); + d1[3] = 255; + + d0 += 4; + d1 += 4; + } + y += 64*4; + c += 64*4; + } +} + + +// YCbCr H2V2 (2x2:1:1, 6 m_blocks per MCU) to RGB +void jpeg_decoder::H2V2Convert() +{ + int row = m_max_mcu_y_size - m_mcu_lines_left; + uint8_t *d0 = m_pScan_line_0; + uint8_t *d1 = m_pScan_line_1; + uint8_t *y; + uint8_t *c; + + if (row < 8) y = m_pSample_buf + row * 8; + else y = m_pSample_buf + 64*2 + (row & 7) * 8; + + c = m_pSample_buf + 64*4 + (row >> 1) * 8; + + for (int i = m_max_mcus_per_row; i > 0; i--) { + for (int l = 0; l < 2; l++) { + for (int j = 0; j < 8; j += 2) { + int cb = c[0]; + int cr = c[64]; + + int rc = m_crr[cr]; + int gc = ((m_crg[cr] + m_cbg[cb]) >> 16); + int bc = m_cbb[cb]; + + int yy = y[j]; + d0[0] = clamp(yy+rc); + d0[1] = clamp(yy+gc); + d0[2] = clamp(yy+bc); + d0[3] = 255; + + yy = y[j+1]; + d0[4] = clamp(yy+rc); + d0[5] = clamp(yy+gc); + d0[6] = clamp(yy+bc); + d0[7] = 255; + + yy = y[j+8]; + d1[0] = clamp(yy+rc); + d1[1] = clamp(yy+gc); + d1[2] = clamp(yy+bc); + d1[3] = 255; + + yy = y[j+8+1]; + d1[4] = clamp(yy+rc); + d1[5] = clamp(yy+gc); + d1[6] = clamp(yy+bc); + d1[7] = 255; + + d0 += 8; + d1 += 8; + + c++; + } + y += 64; + } + y += 64*6 - 64*2; + c += 64*6 - 8; + } +} + + +// Y (1 block per MCU) to 8-bit grayscale +void jpeg_decoder::gray_convert() +{ + int row = m_max_mcu_y_size - m_mcu_lines_left; + uint8_t *d = m_pScan_line_0; + uint8_t *s = m_pSample_buf + row * 8; + + for (int i = m_max_mcus_per_row; i > 0; i--) { + *(uint32_t *)d = *(uint32_t *)s; + *(uint32_t *)(&d[4]) = *(uint32_t *)(&s[4]); + s += 64; + d += 8; + } +} + + +void jpeg_decoder::expanded_convert() +{ + int row = m_max_mcu_y_size - m_mcu_lines_left; + uint8_t* Py = m_pSample_buf + (row / 8) * 64 * m_comp_h_samp[0] + (row & 7) * 8; + uint8_t* d = m_pScan_line_0; + + for (int i = m_max_mcus_per_row; i > 0; i--) { + for (int k = 0; k < m_max_mcu_x_size; k += 8) { + const int Y_ofs = k * 8; + const int Cb_ofs = Y_ofs + 64 * m_expanded_blocks_per_component; + const int Cr_ofs = Y_ofs + 64 * m_expanded_blocks_per_component * 2; + for (int j = 0; j < 8; j++) { + int y = Py[Y_ofs + j]; + int cb = Py[Cb_ofs + j]; + int cr = Py[Cr_ofs + j]; + + d[0] = clamp(y + m_crr[cr]); + d[1] = clamp(y + ((m_crg[cr] + m_cbg[cb]) >> 16)); + d[2] = clamp(y + m_cbb[cb]); + d[3] = 255; + + d += 4; + } + } + Py += 64 * m_expanded_blocks_per_mcu; + } +} + + +// Find end of image (EOI) marker, so we can return to the user the exact size of the input stream. +void jpeg_decoder::find_eoi() +{ + if (!m_progressive_flag) { + // Attempt to read the EOI marker. + //get_bits_no_markers(m_bits_left & 7); + + // Prime the bit buffer + m_bits_left = 16; + get_bits(16); + get_bits(16); + + // The next marker _should_ be EOI + process_markers(); + } + m_total_bytes_read -= m_in_buf_left; +} + + +int jpeg_decoder::decode(const void** pScan_line, uint32_t* pScan_line_len) +{ + if ((m_error_code) || (!m_ready_flag)) return JPGD_FAILED; + if (m_total_lines_left == 0) return JPGD_DONE; + if (m_mcu_lines_left == 0) { + if (setjmp(m_jmp_state)) return JPGD_FAILED; + if (m_progressive_flag) load_next_row(); + else decode_next_row(); + // Find the EOI marker if that was the last row. + if (m_total_lines_left <= m_max_mcu_y_size) find_eoi(); + m_mcu_lines_left = m_max_mcu_y_size; + } + + if (m_freq_domain_chroma_upsample) { + expanded_convert(); + *pScan_line = m_pScan_line_0; + } else { + switch (m_scan_type) { + case JPGD_YH2V2: { + if ((m_mcu_lines_left & 1) == 0) { + H2V2Convert(); + *pScan_line = m_pScan_line_0; + } + else *pScan_line = m_pScan_line_1; + break; + } + case JPGD_YH2V1: { + H2V1Convert(); + *pScan_line = m_pScan_line_0; + break; + } + case JPGD_YH1V2: { + if ((m_mcu_lines_left & 1) == 0) { + H1V2Convert(); + *pScan_line = m_pScan_line_0; + } else *pScan_line = m_pScan_line_1; + break; + } + case JPGD_YH1V1: { + H1V1Convert(); + *pScan_line = m_pScan_line_0; + break; + } + case JPGD_GRAYSCALE: { + gray_convert(); + *pScan_line = m_pScan_line_0; + break; + } + } + } + + *pScan_line_len = m_real_dest_bytes_per_scan_line; + m_mcu_lines_left--; + m_total_lines_left--; + + return JPGD_SUCCESS; +} + + +// Creates the tables needed for efficient Huffman decoding. +void jpeg_decoder::make_huff_table(int index, huff_tables *pH) +{ + int p, i, l, si; + uint8_t huffsize[257]; + uint32_t huffcode[257]; + uint32_t code; + uint32_t subtree; + int code_size; + int lastp; + int nextfreeentry; + int currententry; + + pH->ac_table = m_huff_ac[index] != 0; + p = 0; + + for (l = 1; l <= 16; l++) { + for (i = 1; i <= m_huff_num[index][l]; i++) { + huffsize[p++] = static_cast(l); + } + } + + huffsize[p] = 0; + lastp = p; + code = 0; + si = huffsize[0]; + p = 0; + + while (huffsize[p]) { + while (huffsize[p] == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + memset(pH->look_up, 0, sizeof(pH->look_up)); + memset(pH->look_up2, 0, sizeof(pH->look_up2)); + memset(pH->tree, 0, sizeof(pH->tree)); + memset(pH->code_size, 0, sizeof(pH->code_size)); + + nextfreeentry = -1; + p = 0; + + while (p < lastp) { + i = m_huff_val[index][p]; + code = huffcode[p]; + code_size = huffsize[p]; + pH->code_size[i] = static_cast(code_size); + + if (code_size <= 8) { + code <<= (8 - code_size); + for (l = 1 << (8 - code_size); l > 0; l--) { + JPGD_ASSERT(i < 256); + pH->look_up[code] = i; + bool has_extrabits = false; + int extra_bits = 0; + int num_extra_bits = i & 15; + int bits_to_fetch = code_size; + + if (num_extra_bits) { + int total_codesize = code_size + num_extra_bits; + if (total_codesize <= 8) { + has_extrabits = true; + extra_bits = ((1 << num_extra_bits) - 1) & (code >> (8 - total_codesize)); + JPGD_ASSERT(extra_bits <= 0x7FFF); + bits_to_fetch += num_extra_bits; + } + } + if (!has_extrabits) pH->look_up2[code] = i | (bits_to_fetch << 8); + else pH->look_up2[code] = i | 0x8000 | (extra_bits << 16) | (bits_to_fetch << 8); + code++; + } + } else { + subtree = (code >> (code_size - 8)) & 0xFF; + currententry = pH->look_up[subtree]; + + if (currententry == 0) { + pH->look_up[subtree] = currententry = nextfreeentry; + pH->look_up2[subtree] = currententry = nextfreeentry; + nextfreeentry -= 2; + } + + code <<= (16 - (code_size - 8)); + + for (l = code_size; l > 9; l--) { + if ((code & 0x8000) == 0) currententry--; + if (pH->tree[-currententry - 1] == 0) { + pH->tree[-currententry - 1] = nextfreeentry; + currententry = nextfreeentry; + nextfreeentry -= 2; + } else currententry = pH->tree[-currententry - 1]; + code <<= 1; + } + if ((code & 0x8000) == 0) currententry--; + pH->tree[-currententry - 1] = i; + } + p++; + } +} + + +// Verifies the quantization tables needed for this scan are available. +void jpeg_decoder::check_quant_tables() +{ + for (int i = 0; i < m_comps_in_scan; i++) { + if (m_quant[m_comp_quant[m_comp_list[i]]] == nullptr) stop_decoding(JPGD_UNDEFINED_QUANT_TABLE); + } +} + + +// Verifies that all the Huffman tables needed for this scan are available. +void jpeg_decoder::check_huff_tables() +{ + for (int i = 0; i < m_comps_in_scan; i++) { + if ((m_spectral_start == 0) && (m_huff_num[m_comp_dc_tab[m_comp_list[i]]] == nullptr)) stop_decoding(JPGD_UNDEFINED_HUFF_TABLE); + if ((m_spectral_end > 0) && (m_huff_num[m_comp_ac_tab[m_comp_list[i]]] == nullptr)) stop_decoding(JPGD_UNDEFINED_HUFF_TABLE); + } + + for (int i = 0; i < JPGD_MAX_HUFF_TABLES; i++) { + if (m_huff_num[i]) { + if (!m_pHuff_tabs[i]) m_pHuff_tabs[i] = (huff_tables *)alloc(sizeof(huff_tables)); + make_huff_table(i, m_pHuff_tabs[i]); + } + } +} + + +// Determines the component order inside each MCU. +// Also calcs how many MCU's are on each row, etc. +void jpeg_decoder::calc_mcu_block_order() +{ + int component_num, component_id; + int max_h_samp = 0, max_v_samp = 0; + + for (component_id = 0; component_id < m_comps_in_frame; component_id++) { + if (m_comp_h_samp[component_id] > max_h_samp) { + max_h_samp = m_comp_h_samp[component_id]; + } + if (m_comp_v_samp[component_id] > max_v_samp) { + max_v_samp = m_comp_v_samp[component_id]; + } + } + + for (component_id = 0; component_id < m_comps_in_frame; component_id++) { + m_comp_h_blocks[component_id] = ((((m_image_x_size * m_comp_h_samp[component_id]) + (max_h_samp - 1)) / max_h_samp) + 7) / 8; + m_comp_v_blocks[component_id] = ((((m_image_y_size * m_comp_v_samp[component_id]) + (max_v_samp - 1)) / max_v_samp) + 7) / 8; + } + + if (m_comps_in_scan == 1) { + m_mcus_per_row = m_comp_h_blocks[m_comp_list[0]]; + m_mcus_per_col = m_comp_v_blocks[m_comp_list[0]]; + } else { + m_mcus_per_row = (((m_image_x_size + 7) / 8) + (max_h_samp - 1)) / max_h_samp; + m_mcus_per_col = (((m_image_y_size + 7) / 8) + (max_v_samp - 1)) / max_v_samp; + } + + if (m_comps_in_scan == 1) { + m_mcu_org[0] = m_comp_list[0]; + m_blocks_per_mcu = 1; + } else { + m_blocks_per_mcu = 0; + + for (component_num = 0; component_num < m_comps_in_scan; component_num++) { + int num_blocks; + component_id = m_comp_list[component_num]; + num_blocks = m_comp_h_samp[component_id] * m_comp_v_samp[component_id]; + while (num_blocks--) m_mcu_org[m_blocks_per_mcu++] = component_id; + } + } +} + + +// Starts a new scan. +int jpeg_decoder::init_scan() +{ + if (!locate_sos_marker()) return false; + + calc_mcu_block_order(); + check_huff_tables(); + check_quant_tables(); + + memset(m_last_dc_val, 0, m_comps_in_frame * sizeof(uint32_t)); + + m_eob_run = 0; + + if (m_restart_interval) { + m_restarts_left = m_restart_interval; + m_next_restart_num = 0; + } + fix_in_buffer(); + return true; +} + + +// Starts a frame. Determines if the number of components or sampling factors +// are supported. +void jpeg_decoder::init_frame() +{ + int i; + + if (m_comps_in_frame == 1) { + if ((m_comp_h_samp[0] != 1) || (m_comp_v_samp[0] != 1)) stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); + m_scan_type = JPGD_GRAYSCALE; + m_max_blocks_per_mcu = 1; + m_max_mcu_x_size = 8; + m_max_mcu_y_size = 8; + } else if (m_comps_in_frame == 3) { + if (((m_comp_h_samp[1] != 1) || (m_comp_v_samp[1] != 1)) || ((m_comp_h_samp[2] != 1) || (m_comp_v_samp[2] != 1))) + stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); + + if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) { + m_scan_type = JPGD_YH1V1; + m_max_blocks_per_mcu = 3; + m_max_mcu_x_size = 8; + m_max_mcu_y_size = 8; + } else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) { + m_scan_type = JPGD_YH2V1; + m_max_blocks_per_mcu = 4; + m_max_mcu_x_size = 16; + m_max_mcu_y_size = 8; + } else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 2)) { + m_scan_type = JPGD_YH1V2; + m_max_blocks_per_mcu = 4; + m_max_mcu_x_size = 8; + m_max_mcu_y_size = 16; + } else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) { + m_scan_type = JPGD_YH2V2; + m_max_blocks_per_mcu = 6; + m_max_mcu_x_size = 16; + m_max_mcu_y_size = 16; + } else stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS); + } else stop_decoding(JPGD_UNSUPPORTED_COLORSPACE); + + m_max_mcus_per_row = (m_image_x_size + (m_max_mcu_x_size - 1)) / m_max_mcu_x_size; + m_max_mcus_per_col = (m_image_y_size + (m_max_mcu_y_size - 1)) / m_max_mcu_y_size; + + // These values are for the *destination* pixels: after conversion. + if (m_scan_type == JPGD_GRAYSCALE) m_dest_bytes_per_pixel = 1; + else m_dest_bytes_per_pixel = 4; + + m_dest_bytes_per_scan_line = ((m_image_x_size + 15) & 0xFFF0) * m_dest_bytes_per_pixel; + m_real_dest_bytes_per_scan_line = (m_image_x_size * m_dest_bytes_per_pixel); + + // Initialize two scan line buffers. + m_pScan_line_0 = (uint8_t *)alloc(m_dest_bytes_per_scan_line, true); + if ((m_scan_type == JPGD_YH1V2) || (m_scan_type == JPGD_YH2V2)) { + m_pScan_line_1 = (uint8_t *)alloc(m_dest_bytes_per_scan_line, true); + } + + m_max_blocks_per_row = m_max_mcus_per_row * m_max_blocks_per_mcu; + + // Should never happen + if (m_max_blocks_per_row > JPGD_MAX_BLOCKS_PER_ROW) stop_decoding(JPGD_ASSERTION_ERROR); + + // Allocate the coefficient buffer, enough for one MCU + m_pMCU_coefficients = (jpgd_block_t*)alloc(m_max_blocks_per_mcu * 64 * sizeof(jpgd_block_t)); + + for (i = 0; i < m_max_blocks_per_mcu; i++) { + m_mcu_block_max_zag[i] = 64; + } + + m_expanded_blocks_per_component = m_comp_h_samp[0] * m_comp_v_samp[0]; + m_expanded_blocks_per_mcu = m_expanded_blocks_per_component * m_comps_in_frame; + m_expanded_blocks_per_row = m_max_mcus_per_row * m_expanded_blocks_per_mcu; + // Freq. domain chroma upsampling is only supported for H2V2 subsampling factor (the most common one I've seen). + m_freq_domain_chroma_upsample = false; +#if JPGD_SUPPORT_FREQ_DOMAIN_UPSAMPLING + m_freq_domain_chroma_upsample = (m_expanded_blocks_per_mcu == 4*3); +#endif + + if (m_freq_domain_chroma_upsample) + m_pSample_buf = (uint8_t *)alloc(m_expanded_blocks_per_row * 64); + else + m_pSample_buf = (uint8_t *)alloc(m_max_blocks_per_row * 64); + + m_total_lines_left = m_image_y_size; + m_mcu_lines_left = 0; + create_look_ups(); +} + + +// The coeff_buf series of methods originally stored the coefficients +// into a "virtual" file which was located in EMS, XMS, or a disk file. A cache +// was used to make this process more efficient. Now, we can store the entire +// thing in RAM. +jpeg_decoder::coeff_buf* jpeg_decoder::coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y) +{ + coeff_buf* cb = (coeff_buf*)alloc(sizeof(coeff_buf)); + cb->block_num_x = block_num_x; + cb->block_num_y = block_num_y; + cb->block_len_x = block_len_x; + cb->block_len_y = block_len_y; + cb->block_size = (block_len_x * block_len_y) * sizeof(jpgd_block_t); + cb->pData = (uint8_t *)alloc(cb->block_size * block_num_x * block_num_y, true); + return cb; +} + + +inline jpgd_block_t *jpeg_decoder::coeff_buf_getp(coeff_buf *cb, int block_x, int block_y) +{ + JPGD_ASSERT((block_x < cb->block_num_x) && (block_y < cb->block_num_y)); + return (jpgd_block_t *)(cb->pData + block_x * cb->block_size + block_y * (cb->block_size * cb->block_num_x)); +} + + +// The following methods decode the various types of m_blocks encountered +// in progressively encoded images. +void jpeg_decoder::decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y) +{ + int s, r; + jpgd_block_t *p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y); + + if ((s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_dc_tab[component_id]])) != 0) { + r = pD->get_bits_no_markers(s); + s = JPGD_HUFF_EXTEND(r, s); + } + pD->m_last_dc_val[component_id] = (s += pD->m_last_dc_val[component_id]); + p[0] = static_cast(s << pD->m_successive_low); +} + + +void jpeg_decoder::decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y) +{ + if (pD->get_bits_no_markers(1)) { + jpgd_block_t *p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y); + p[0] |= (1 << pD->m_successive_low); + } +} + + +void jpeg_decoder::decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y) +{ + int k, s, r; + + if (pD->m_eob_run) { + pD->m_eob_run--; + return; + } + jpgd_block_t *p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y); + + for (k = pD->m_spectral_start; k <= pD->m_spectral_end; k++) { + s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_ac_tab[component_id]]); + r = s >> 4; + s &= 15; + if (s) { + if ((k += r) > 63) pD->stop_decoding(JPGD_DECODE_ERROR); + r = pD->get_bits_no_markers(s); + s = JPGD_HUFF_EXTEND(r, s); + p[g_ZAG[k]] = static_cast(s << pD->m_successive_low); + } else { + if (r == 15) { + if ((k += 15) > 63) pD->stop_decoding(JPGD_DECODE_ERROR); + } else { + pD->m_eob_run = 1 << r; + if (r) pD->m_eob_run += pD->get_bits_no_markers(r); + pD->m_eob_run--; + break; + } + } + } +} + + +void jpeg_decoder::decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y) +{ + int s, k, r; + int p1 = 1 << pD->m_successive_low; + int m1 = (-1) << pD->m_successive_low; + jpgd_block_t *p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y); + + JPGD_ASSERT(pD->m_spectral_end <= 63); + + k = pD->m_spectral_start; + + if (pD->m_eob_run == 0) { + for ( ; k <= pD->m_spectral_end; k++) { + s = pD->huff_decode(pD->m_pHuff_tabs[pD->m_comp_ac_tab[component_id]]); + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) pD->stop_decoding(JPGD_DECODE_ERROR); + if (pD->get_bits_no_markers(1)) s = p1; + else s = m1; + } else { + if (r != 15) { + pD->m_eob_run = 1 << r; + if (r) pD->m_eob_run += pD->get_bits_no_markers(r); + break; + } + } + + do { + jpgd_block_t *this_coef = p + g_ZAG[k & 63]; + + if (*this_coef != 0) { + if (pD->get_bits_no_markers(1)) { + if ((*this_coef & p1) == 0) { + if (*this_coef >= 0) *this_coef = static_cast(*this_coef + p1); + else *this_coef = static_cast(*this_coef + m1); + } + } + } else { + if (--r < 0) break; + } + k++; + } while (k <= pD->m_spectral_end); + + if ((s) && (k < 64)) { + p[g_ZAG[k]] = static_cast(s); + } + } + } + + if (pD->m_eob_run > 0) { + for ( ; k <= pD->m_spectral_end; k++) { + jpgd_block_t *this_coef = p + g_ZAG[k & 63]; // logical AND to shut up static code analysis + + if (*this_coef != 0) { + if (pD->get_bits_no_markers(1)) { + if ((*this_coef & p1) == 0) { + if (*this_coef >= 0) *this_coef = static_cast(*this_coef + p1); + else *this_coef = static_cast(*this_coef + m1); + } + } + } + } + pD->m_eob_run--; + } +} + + +// Decode a scan in a progressively encoded image. +void jpeg_decoder::decode_scan(pDecode_block_func decode_block_func) +{ + int mcu_row, mcu_col, mcu_block; + int block_x_mcu[JPGD_MAX_COMPONENTS], m_block_y_mcu[JPGD_MAX_COMPONENTS]; + + memset(m_block_y_mcu, 0, sizeof(m_block_y_mcu)); + + for (mcu_col = 0; mcu_col < m_mcus_per_col; mcu_col++) { + int component_num, component_id; + memset(block_x_mcu, 0, sizeof(block_x_mcu)); + + for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) { + int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0; + + if ((m_restart_interval) && (m_restarts_left == 0)) process_restart(); + + for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) { + component_id = m_mcu_org[mcu_block]; + decode_block_func(this, component_id, block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs); + + if (m_comps_in_scan == 1) block_x_mcu[component_id]++; + else { + if (++block_x_mcu_ofs == m_comp_h_samp[component_id]) { + block_x_mcu_ofs = 0; + + if (++block_y_mcu_ofs == m_comp_v_samp[component_id]) { + block_y_mcu_ofs = 0; + block_x_mcu[component_id] += m_comp_h_samp[component_id]; + } + } + } + } + m_restarts_left--; + } + + if (m_comps_in_scan == 1) m_block_y_mcu[m_comp_list[0]]++; + else { + for (component_num = 0; component_num < m_comps_in_scan; component_num++) { + component_id = m_comp_list[component_num]; + m_block_y_mcu[component_id] += m_comp_v_samp[component_id]; + } + } + } +} + + +// Decode a progressively encoded image. +void jpeg_decoder::init_progressive() +{ + int i; + + if (m_comps_in_frame == 4) stop_decoding(JPGD_UNSUPPORTED_COLORSPACE); + + // Allocate the coefficient buffers. + for (i = 0; i < m_comps_in_frame; i++) { + m_dc_coeffs[i] = coeff_buf_open(m_max_mcus_per_row * m_comp_h_samp[i], m_max_mcus_per_col * m_comp_v_samp[i], 1, 1); + m_ac_coeffs[i] = coeff_buf_open(m_max_mcus_per_row * m_comp_h_samp[i], m_max_mcus_per_col * m_comp_v_samp[i], 8, 8); + } + + while (true) { + int dc_only_scan, refinement_scan; + pDecode_block_func decode_block_func; + + if (!init_scan()) break; + + dc_only_scan = (m_spectral_start == 0); + refinement_scan = (m_successive_high != 0); + + if ((m_spectral_start > m_spectral_end) || (m_spectral_end > 63)) stop_decoding(JPGD_BAD_SOS_SPECTRAL); + + if (dc_only_scan) { + if (m_spectral_end) stop_decoding(JPGD_BAD_SOS_SPECTRAL); + } else if (m_comps_in_scan != 1) { /* AC scans can only contain one component */ + stop_decoding(JPGD_BAD_SOS_SPECTRAL); + } + + if ((refinement_scan) && (m_successive_low != m_successive_high - 1)) stop_decoding(JPGD_BAD_SOS_SUCCESSIVE); + + if (dc_only_scan) { + if (refinement_scan) decode_block_func = decode_block_dc_refine; + else decode_block_func = decode_block_dc_first; + } else { + if (refinement_scan) decode_block_func = decode_block_ac_refine; + else decode_block_func = decode_block_ac_first; + } + decode_scan(decode_block_func); + m_bits_left = 16; + get_bits(16); + get_bits(16); + } + + m_comps_in_scan = m_comps_in_frame; + + for (i = 0; i < m_comps_in_frame; i++) { + m_comp_list[i] = i; + } + + calc_mcu_block_order(); +} + + +void jpeg_decoder::init_sequential() +{ + if (!init_scan()) stop_decoding(JPGD_UNEXPECTED_MARKER); +} + + +void jpeg_decoder::decode_start() +{ + init_frame(); + if (m_progressive_flag) init_progressive(); + else init_sequential(); +} + + +void jpeg_decoder::decode_init(jpeg_decoder_stream *pStream) +{ + init(pStream); + locate_sof_marker(); +} + + +jpeg_decoder::jpeg_decoder(jpeg_decoder_stream *pStream) +{ + if (setjmp(m_jmp_state)) return; + decode_init(pStream); +} + + +int jpeg_decoder::begin_decoding() +{ + if (m_ready_flag) return JPGD_SUCCESS; + if (m_error_code) return JPGD_FAILED; + if (setjmp(m_jmp_state)) return JPGD_FAILED; + + decode_start(); + m_ready_flag = true; + + return JPGD_SUCCESS; +} + + +jpeg_decoder::~jpeg_decoder() +{ + free_all_blocks(); + delete(m_pStream); +} + + +void jpeg_decoder_file_stream::close() +{ + if (m_pFile) { + fclose(m_pFile); + m_pFile = nullptr; + } + m_eof_flag = false; + m_error_flag = false; +} + + +jpeg_decoder_file_stream::~jpeg_decoder_file_stream() +{ + close(); +} + + +bool jpeg_decoder_file_stream::open(const char *Pfilename) +{ + close(); + + m_eof_flag = false; + m_error_flag = false; + +#if defined(_MSC_VER) + m_pFile = nullptr; + fopen_s(&m_pFile, Pfilename, "rb"); +#else + m_pFile = fopen(Pfilename, "rb"); +#endif + return m_pFile != nullptr; +} + + +int jpeg_decoder_file_stream::read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag) +{ + if (!m_pFile) return -1; + + if (m_eof_flag) { + *pEOF_flag = true; + return 0; + } + + if (m_error_flag) return -1; + + int bytes_read = static_cast(fread(pBuf, 1, max_bytes_to_read, m_pFile)); + if (bytes_read < max_bytes_to_read) { + if (ferror(m_pFile)) { + m_error_flag = true; + return -1; + } + m_eof_flag = true; + *pEOF_flag = true; + } + return bytes_read; +} + + +bool jpeg_decoder_mem_stream::open(const uint8_t *pSrc_data, uint32_t size) +{ + close(); + m_pSrc_data = pSrc_data; + m_ofs = 0; + m_size = size; + return true; +} + + +int jpeg_decoder_mem_stream::read(uint8_t *pBuf, int max_bytes_to_read, bool *pEOF_flag) +{ + *pEOF_flag = false; + if (!m_pSrc_data) return -1; + + uint32_t bytes_remaining = m_size - m_ofs; + if ((uint32_t)max_bytes_to_read > bytes_remaining) { + max_bytes_to_read = bytes_remaining; + *pEOF_flag = true; + } + memcpy(pBuf, m_pSrc_data + m_ofs, max_bytes_to_read); + m_ofs += max_bytes_to_read; + + return max_bytes_to_read; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +jpeg_decoder* jpgdHeader(const char* data, int size, int* width, int* height) +{ + auto decoder = new jpeg_decoder(new jpeg_decoder_mem_stream((const uint8_t*)data, size)); + if (decoder->get_error_code() != JPGD_SUCCESS) { + delete(decoder); + return nullptr; + } + + if (width) *width = decoder->get_width(); + if (height) *height = decoder->get_height(); + + return decoder; +} + + +jpeg_decoder* jpgdHeader(const char* filename, int* width, int* height) +{ + auto fileStream = new jpeg_decoder_file_stream(); + if (!fileStream->open(filename)) return nullptr; + + auto decoder = new jpeg_decoder(fileStream); + if (decoder->get_error_code() != JPGD_SUCCESS) { + delete(decoder); + return nullptr; + } + + if (width) *width = decoder->get_width(); + if (height) *height = decoder->get_height(); + + return decoder; +} + + +void jpgdDelete(jpeg_decoder* decoder) +{ + delete(decoder); +} + + +unsigned char* jpgdDecompress(jpeg_decoder* decoder) +{ + if (!decoder) return nullptr; + + int req_comps = 4; //TODO: fixed 4 channel components now? + if ((req_comps != 1) && (req_comps != 3) && (req_comps != 4)) return nullptr; + + auto image_width = decoder->get_width(); + auto image_height = decoder->get_height(); + //auto actual_comps = decoder->get_num_components(); + + if (decoder->begin_decoding() != JPGD_SUCCESS) return nullptr; + + const int dst_bpl = image_width * req_comps; + uint8_t *pImage_data = (uint8_t*)malloc(dst_bpl * image_height); + if (!pImage_data) return nullptr; + + for (int y = 0; y < image_height; y++) { + const uint8_t* pScan_line; + uint32_t scan_line_len; + if (decoder->decode((const void**)&pScan_line, &scan_line_len) != JPGD_SUCCESS) { + free(pImage_data); + return nullptr; + } + + uint8_t *pDst = pImage_data + y * dst_bpl; + + //Return as BGRA + if ((req_comps == 4) && (decoder->get_num_components() == 3)) { + for (int x = 0; x < image_width; x++) { + pDst[0] = pScan_line[x*4+2]; + pDst[1] = pScan_line[x*4+1]; + pDst[2] = pScan_line[x*4+0]; + pDst[3] = 255; + pDst += 4; + } + } else if (((req_comps == 1) && (decoder->get_num_components() == 1)) || ((req_comps == 4) && (decoder->get_num_components() == 3))) { + memcpy(pDst, pScan_line, dst_bpl); + } else if (decoder->get_num_components() == 1) { + if (req_comps == 3) { + for (int x = 0; x < image_width; x++) { + uint8_t luma = pScan_line[x]; + pDst[0] = luma; + pDst[1] = luma; + pDst[2] = luma; + pDst += 3; + } + } else { + for (int x = 0; x < image_width; x++) { + uint8_t luma = pScan_line[x]; + pDst[0] = luma; + pDst[1] = luma; + pDst[2] = luma; + pDst[3] = 255; + pDst += 4; + } + } + } else if (decoder->get_num_components() == 3) { + if (req_comps == 1) { + const int YR = 19595, YG = 38470, YB = 7471; + for (int x = 0; x < image_width; x++) { + int r = pScan_line[x*4+0]; + int g = pScan_line[x*4+1]; + int b = pScan_line[x*4+2]; + *pDst++ = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + } + } else { + for (int x = 0; x < image_width; x++) { + pDst[0] = pScan_line[x*4+0]; + pDst[1] = pScan_line[x*4+1]; + pDst[2] = pScan_line[x*4+2]; + pDst += 3; + } + } + } + } + return pImage_data; +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h new file mode 100644 index 0000000000..d32ffd99d4 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// jpgd.h - C++ class for JPEG decompression. +// Public domain, Rich Geldreich +#ifndef _TVG_JPGD_H_ +#define _TVG_JPGD_H_ + +class jpeg_decoder; + +jpeg_decoder* jpgdHeader(const char* data, int size, int* width, int* height); +jpeg_decoder* jpgdHeader(const char* filename, int* width, int* height); +unsigned char* jpgdDecompress(jpeg_decoder* decoder); +void jpgdDelete(jpeg_decoder* decoder); + +#endif //_TVG_JPGD_H_ diff --git a/thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp b/thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp new file mode 100644 index 0000000000..b50c7e57d9 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/png/tvgLodePng.cpp @@ -0,0 +1,2632 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + LodePNG version 20200306 + + Copyright (c) 2005-2020 Lode Vandevenne + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any sourcedistribution. +*/ + +#include +#include "tvgLodePng.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ + #pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ + #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define LODEPNG_INLINE inline +#else + #define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) + #define LODEPNG_RESTRICT __restrict +#else + #define LODEPNG_RESTRICT /* not available */ +#endif + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, const void* LODEPNG_RESTRICT src, size_t size) +{ + size_t i; + for (i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, int value, size_t num) +{ + size_t i; + for (i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) +{ + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while (*a) a++; + return (size_t)(a - orig); +} + + +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) +{ + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} + + +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) +{ + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + + +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) +{ + size_t d; + if (lodepng_addofl(a, b, &d)) return 1; + return d > c; +} + + +/* + Often in case of an error a value is assigned to a variable and then it breaks + out of a loop (to go to the cleanup phase of a function). This macro does that. + It makes the error handling code shorter and more readable. + + Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/* version of CERROR_BREAK that assumes the common case where the error variable is named "error" */ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/* Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/* Try the code, if it returns error, also return the error. */ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/* Set error var to the error code, and return from the void function. */ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + + +/* dynamic vector of unsigned chars */ +struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +}; + + +/* returns 1 if success, 0 if failure ==> nothing done */ +static unsigned ucvector_resize(ucvector* p, size_t size) +{ + if (size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + + +static ucvector ucvector_init(unsigned char* buffer, size_t size) +{ + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + + +static unsigned lodepng_read32bitInt(const unsigned char* buffer) +{ + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} + + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +struct LodePNGBitReader +{ + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +}; + + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) +{ + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if (lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if (lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ + } + +/* + ensureBits functions: + Ensures the reader can at least read nbits bits in one or more readBits calls, + safely even if not enough bits are available. + Returns 1 if there are enough bits available, 0 if not. +*/ + +/*See ensureBits documentation above. This one ensures exactly 1 bit */ +/*static unsigned ensureBits1(LodePNGBitReader* reader) { + if(reader->bp >= reader->bitsize) return 0; + reader->buffer = (unsigned)reader->data[reader->bp >> 3u] >> (reader->bp & 7u); + return 1; +}*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static unsigned ensureBits9(LodePNGBitReader* reader, size_t nbits) +{ + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if (start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if (start + 0u < size) reader->buffer |= reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static unsigned ensureBits17(LodePNGBitReader* reader, size_t nbits) +{ + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if (start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if (start + 0u < size) reader->buffer |= reader->data[start + 0]; + if (start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE unsigned ensureBits25(LodePNGBitReader* reader, size_t nbits) +{ + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if (start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if (start + 0u < size) reader->buffer |= reader->data[start + 0]; + if (start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if (start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE unsigned ensureBits32(LodePNGBitReader* reader, size_t nbits) +{ + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + return 1; + } else { + reader->buffer = 0; + if (start + 0u < size) reader->buffer |= reader->data[start + 0]; + if (start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if (start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if (start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static unsigned peekBits(LodePNGBitReader* reader, size_t nbits) +{ + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + + +/* Must have enough bits available with ensureBits */ +static void advanceBits(LodePNGBitReader* reader, size_t nbits) +{ + reader->buffer >>= nbits; + reader->bp += nbits; +} + + +/* Must have enough bits available with ensureBits */ +static unsigned readBits(LodePNGBitReader* reader, size_t nbits) +{ + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} + + +/* Public for testing only. steps and result must have numsteps values. */ +unsigned lode_png_test_bitreader(const unsigned char* data, size_t size, size_t numsteps, const size_t* steps, unsigned* result) +{ + size_t i; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, data, size); + if (error) return 0; + for (i = 0; i < numsteps; i++) { + size_t step = steps[i]; + unsigned ok; + if (step > 25) ok = ensureBits32(&reader, step); + else if (step > 17) ok = ensureBits25(&reader, step); + else if (step > 9) ok = ensureBits17(&reader, step); + else ok = ensureBits9(&reader, step); + if (!ok) return 0; + result[i] = readBits(&reader, step); + } + return 1; +} + + +static unsigned reverseBits(unsigned bits, unsigned num) +{ + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for (i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +struct HuffmanTree +{ + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +}; + + +static void HuffmanTree_init(HuffmanTree* tree) +{ + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + free(tree->codes); + free(tree->lengths); + free(tree->table_len); + free(tree->table_value); +} + + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) +{ + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)malloc(headsize * sizeof(unsigned)); + if (!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for (i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for (i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if (l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)malloc(size * sizeof(*tree->table_value)); + if (!tree->table_len || !tree->table_value) { + free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for (i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for (i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for (i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if (l == 0) continue; + numpresent++; + + if (l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for (j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if (maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for (j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if (numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for (i = 0; i < size; ++i) { + if (tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for (i = 0; i < size; ++i) { + if (tree->table_len[i] == 16) return 55; + } + } + return 0; +} + + +/* + Second step for the ...makeFromLengths and ...makeFromFrequencies functions. + numcodes, lengths and maxbitlen must already be filled in correctly. return + value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) +{ + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if (!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if (!error) { + for (n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for (bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for (n = 0; n != tree->numcodes; ++n) { + if (tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + free(blcount); + free(nextcode); + + if (!error) error = HuffmanTree_makeTable(tree); + return error; +} + + +/* + given the code lengths (as stored in the PNG file), generate the tree as defined + by Deflate. maxbitlen is the maximum bits that a code in the tree can have. + return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + tree->lengths = (unsigned*)malloc(numcodes * sizeof(unsigned)); + if (!tree->lengths) return 83; /*alloc fail*/ + for (i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if (!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for (i = 0; i <= 143; ++i) bitlen[i] = 8; + for (i = 144; i <= 255; ++i) bitlen[i] = 9; + for (i = 256; i <= 279; ++i) bitlen[i] = 7; + for (i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + free(bitlen); + return error; +} + + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if (!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for (i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + free(bitlen); + return error; +} + + +/* + returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) +{ + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if (l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + unsigned index2; + advanceBits(reader, FIRSTBITS); + index2 = value + peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[index2] - FIRSTBITS); + return codetree->table_value[index2]; + } +} + + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) +{ + unsigned error = generateFixedLitLenTree(tree_ll); + if (error) return error; + return generateFixedDistanceTree(tree_d); +} + + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, LodePNGBitReader* reader) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if (!ensureBits17(reader, 14)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while (!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if (lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for (i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for (i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if (!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while (i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if (code <= 15) /*a length code*/ { + if (i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if (code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if (i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if (i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for (n = 0; n < replength; ++n) { + if (i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if (i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for (n = 0; n < replength; ++n) { + if (i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if (i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for (n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if (reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if (error) break; + + if (bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if (error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + free(bitlen_cl); + free(bitlen_ll); + free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, unsigned btype) +{ + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if (btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + while (!error) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + ensureBits25(reader, 20); /* up to 15 for the huffman symbol, up to 5 for the length extra bits */ + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if (code_ll <= 255) /*literal symbol*/ { + if (!ucvector_resize(out, out->size + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[out->size - 1] = (unsigned char)code_ll; + } else if (code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if (numextrabits_l != 0) { + /* bits already ensured above */ + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if (code_d > 29) { + if (code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if (numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if (distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if (!ucvector_resize(out, out->size + length)) ERROR_BREAK(83 /*alloc fail*/); + if (distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for (forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if (code_ll == 256) { + break; /*end code, break the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if (reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, const LodePNGDecompressSettings* settings) +{ + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if (bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if (!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if (!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if (bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + + reader->bp = bytepos << 3u; + + return error; +} + + +static unsigned lodepng_inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) +{ + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if (error) return error; + + while (!BFINAL) { + unsigned BTYPE; + if (!ensureBits9(&reader, 3)) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if (BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if (BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE); /*compression, BTYPE 01 or 10*/ + + if (error) return error; + } + + return error; +} + + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) +{ + if (settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while (len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for (i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned lodepng_zlib_decompressv(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if (insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if ((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if (CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if (FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if (error) return error; + + if (!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) +{ + if(settings->custom_zlib) { + return settings->custom_zlib(out, outsize, in, insize, settings); + } else { + unsigned error; + ucvector v = ucvector_init(*out, *outsize); + if (expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; + } +} + + +static void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) +{ + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#if 0 //thorvg don't use crc +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + + +/* Calculate CRC32 of buffer + Return the CRC of the bytes buf[0..len-1]. */ +static unsigned lodepng_crc32(const unsigned char* data, size_t length) +{ + unsigned r = 0xffffffffu; + size_t i; + for (i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#endif + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for (i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if (bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* + The lodepng_chunk functions are normally not needed, except to traverse the + unknown chunks stored in the LodePNGInfo struct, or add new ones to it. + It also allows traversing the chunks of an encoded PNG file yourself. + + The chunk pointer always points to the beginning of the chunk itself, that is + the first byte of the 4 length bytes. + + In the PNG file format, chunks have the following format: + -4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) + -4 bytes chunk type (ASCII a-z,A-Z only, see below) + -length bytes of data (may be 0 bytes if length was 0) + -4 bytes of CRC, computed on chunk name + data + + The first chunk starts at the 8th byte of the PNG file, the entire rest of the file + exists out of concatenated chunks with the above format. + + PNG standard chunk ASCII naming conventions: + -First byte: uppercase = critical, lowercase = ancillary + -Second byte: uppercase = public, lowercase = private + -Third byte: must be uppercase + -Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + + +/* + Gets the length of the data of the chunk. Total chunk length has 12 bytes more. + There must be at least 4 bytes to read from. If the result value is too large, + it may be corrupt data. +*/ +static unsigned lodepng_chunk_length(const unsigned char* chunk) +{ + return lodepng_read32bitInt(&chunk[0]); +} + + +/* check if the type is the given type */ +static unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) +{ + if (lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + + +/* 0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard) */ +static unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) +{ + return ((chunk[4] & 32) != 0); +} + + +static const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) +{ + return &chunk[8]; +} + +#if 0 //thorvg don't use crc +/* returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!) */ +static unsigned lodepng_chunk_check_crc(const unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if (CRC != checksum) return 1; + else return 0; +} +#endif + +static const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) +{ + if (chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if (chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if (lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if (result < chunk) return end; /*pointer overflow*/ + return result; + } +} + + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) +{ + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + + +static unsigned getNumColorChannels(LodePNGColorType colortype) +{ + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) +{ + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + + +static void lodepng_color_mode_init(LodePNGColorMode* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) +{ + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if (!info->palette) info->palette = (unsigned char*)malloc(1024); + if (!info->palette) return; /*alloc fail*/ + for (i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +static void lodepng_palette_clear(LodePNGColorMode* info) +{ + if (info->palette) free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + + +static void lodepng_color_mode_cleanup(LodePNGColorMode* info) +{ + lodepng_palette_clear(info); +} + + +/*return value is error code (0 means no error)*/ +static unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) +{ + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if (source->palette) { + dest->palette = (unsigned char*)malloc(1024); + if (!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) +{ + size_t i; + if (a->colortype != b->colortype) return 0; + if (a->bitdepth != b->bitdepth) return 0; + if (a->key_defined != b->key_defined) return 0; + if (a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if (a->palettesize != b->palettesize) return 0; + for (i = 0; i != a->palettesize * 4; ++i) { + if (a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + + +/* Returns the byte size of a raw image buffer with given width, height and color mode */ +static size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) +{ + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + + +/* Safely checks whether size_t overflow can be caused due to amount of pixels. + This check is overcautious rather than precise. If this check indicates no overflow, + you can safely compute in a size_t (but not an unsigned): + -(size_t)w * (size_t)h * 8 + -amount of bytes in IDAT (including filter, padding and Adam7 bytes) + -amount of bytes in raw color model + Returns 1 if overflow possible, 0 if not. */ +static int lodepng_pixel_overflow(unsigned w, unsigned h, const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) +{ + size_t bpp = LODEPNG_MAX(lodepng_get_bpp_lct(pngcolor->colortype, pngcolor->bitdepth), lodepng_get_bpp_lct(rawcolor->colortype, rawcolor->bitdepth)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if (lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if (lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if (lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if (lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if (lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if (lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} + + +static void lodepng_info_init(LodePNGInfo* info) +{ + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +} + + +static void lodepng_info_cleanup(LodePNGInfo* info) +{ + lodepng_color_mode_cleanup(&info->color); +} + + +/* index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to */ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) +{ + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +/* + One node of a color tree + This is the data structure used to count the number of unique colors and to get a palette + index for a color. It's like an octree, but because the alpha channel is used too, each + node has 16 instead of 8 children. +*/ +struct ColorTree +{ + ColorTree* children[16]; /* up to 16 pointers to ColorTree of next level */ + int index; /* the payload. Only has a meaningful value if this is in the last level */ +}; + +static void color_tree_init(ColorTree* tree) +{ + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) +{ + int i; + for (i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + free(tree->children[i]); + } + } +} + + +/* returns -1 if color not present, its index otherwise */ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int bit = 0; + for (bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if (!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + + +/* color is not allowed to already exist. + Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") + Returns error code, or 0 if ok */ +static unsigned color_tree_add(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) +{ + int bit; + for (bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if (!tree->children[i]) { + tree->children[i] = (ColorTree*)malloc(sizeof(ColorTree)); + if (!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/* put a pixel, given its RGBA color, into image of any color type */ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if (mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if (mode->bitdepth == 8) out[i] = gray; + else if (mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if (mode->colortype == LCT_RGB) { + if (mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if (index < 0) return 82; /*color not in palette*/ + if (mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if (mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if (mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if (mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if (mode->colortype == LCT_RGBA) { + if (mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + return 0; /*no error*/ +} + + +/* put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type */ +static void rgba16ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, unsigned short r, unsigned short g, unsigned short b, unsigned short a) +{ + if (mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if (mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if (mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if (mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + + +/* Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type. */ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode) +{ + if (mode->colortype == LCT_GREY) { + if (mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if (mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if (mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if (mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /* highest possible value for this bit depth */ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if (mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if (mode->colortype == LCT_RGB) { + if (mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if (mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if (mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if (mode->colortype == LCT_PALETTE) { + unsigned index; + if (mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if (mode->colortype == LCT_GREY_ALPHA) { + if (mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if (mode->colortype == LCT_RGBA) { + if (mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + + +/* Similar to getPixelColorRGBA8, but with all the for loops inside of the color + mode test cases, optimized to convert the colors much faster, when converting + to the common case of RGBA with 8 bit per channel. buffer must be RGBA with + enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode) +{ + unsigned num_channels = 4; + size_t i; + if (mode->colortype == LCT_GREY) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if (mode->key_defined) { + buffer -= numpixels * num_channels; + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if (mode->bitdepth == 16) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /* highest possible value for this bit depth */ + size_t j = 0; + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if (mode->colortype == LCT_RGB) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + //lodepng_memcpy(buffer, &in[i * 3], 3); + //Convert colortype to LCT_BGR? + buffer[0] = in[i * 3 + 2]; + buffer[1] = in[i * 3 + 1]; + buffer[2] = in[i * 3 + 0]; + buffer[3] = 255; + } + if (mode->key_defined) { + buffer -= numpixels * num_channels; + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + if (buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if (mode->colortype == LCT_PALETTE) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if (mode->colortype == LCT_GREY_ALPHA) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if (mode->colortype == LCT_RGBA) { + if (mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + + +/* Similar to getPixelColorsRGBA8, but with 3-channel RGB output. */ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode) +{ + const unsigned num_channels = 3; + size_t i; + if (mode->colortype == LCT_GREY) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if (mode->bitdepth == 16) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /* highest possible value for this bit depth */ + size_t j = 0; + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if (mode->colortype == LCT_RGB) { + if (mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if (mode->colortype == LCT_PALETTE) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /* out of bounds of palette not checked: see lodepng_color_mode_alloc_palette. */ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if (mode->colortype == LCT_GREY_ALPHA) { + if (mode->bitdepth == 8) { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if (mode->colortype == LCT_RGBA) { + if (mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for (i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + + +/* Get RGBA16 color of pixel with index i (y * width + x) from the raw image with + given color type, but the given color type must be 16-bit itself. */ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode) +{ + if (mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if (mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if (mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if (mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if (mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if (mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +/* + Converts raw buffer from one color type to another color type, based on + LodePNGColorMode structs to describe the input and output color type. + See the reference manual at the end of this header file to see which color conversions are supported. + return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) + The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel + of the output color type (lodepng_get_bpp). + For < 8 bpp images, there should not be padding bits at the end of scanlines. + For 16-bit per channel colors, uses big endian format like PNG does. + Return value is LodePNG error code +*/ +static unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h) +{ + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if (mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if (lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if (mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /* if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if (palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /* if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if (mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if (palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for (i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if (error) break; + } + } + + if (!error) { + if (mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for (i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if (mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for (i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if (error) break; + } + } + } + + if (mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Paeth predictor, used by PNG filter type 4 + The parameters are of type short, but should come from unsigned chars, the shorts + are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) +{ + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if (pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + + +/*shared values used by multiple Adam7 related functions*/ +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* Outputs various dimensions and positions in the image related to the Adam7 reduced images. + passw: output containing the width of the 7 passes + passh: output containing the height of the 7 passes + filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes + padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines + passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images + w, h: width and height of non-interlaced image + bpp: bits per pixel + "padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte */ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /* the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass */ + unsigned i; + + /* calculate width and height in pixels of each pass */ + for (i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for (i = 0; i != 7; ++i) { + /* if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte) */ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /* bits padded if needed to fill full byte at end of each scanline */ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /* only padded at end of reduced image */ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned char filterType, size_t length) +{ + /* For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. */ + + size_t i; + switch (filterType) { + case 0: + for (i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for (i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for (i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if (precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if (precon) { + for (i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + for (i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1u); + } else { + for (i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for (i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1u); + } + break; + case 4: + if (precon) { + for (i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version would also be possible but that + adds too much code. Whether this actually speeds anything up at all depends on compiler and settings. */ + if (bytewidth >= 4) { + for (; i + 3 < length; i += 4) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if (bytewidth >= 3) { + for (; i + 2 < length; i += 3) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if (bytewidth >= 2) { + for (; i + 1 < length; i += 2) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for (; i != length; ++i) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } else { + for (i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for (i = bytewidth; i < length; ++i) { + /* paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth] */ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /* error: invalid filter type given */ + } + return 0; +} + + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) */ + + unsigned y; + unsigned char* prevline = 0; + + /* bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise */ + size_t bytewidth = (bpp + 7u) / 8u; + /* the width of a scanline in bytes, not including the filter type */ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for (y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /* the extra filterbyte added to each row */ + unsigned char filterType = in[inindex]; + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + prevline = &out[outindex]; + } + + return 0; +} + +/* in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. + out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h + bpp: bits per pixel + out has the following size in bits: w * h * bpp. + in is possibly bigger due to padding bits between reduced images. + out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation + (because that's likely a little bit faster) + NOTE: comments about padding bits are only relevant if bpp < 8 */ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if (bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for (y = 0; y < passh[i]; ++y) + for (x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for (b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /* bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers */ { + for (i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /* bit pointers (for out and in buffer) */ + for (y = 0; y < passh[i]; ++y) + for (x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for (b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + + +static void removePaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) +{ + /* After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for (y = 0; y < h; ++y) { + size_t x; + for (x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + + +/* out must be buffer big enough to contain full image, and in must contain the full decompressed data from + the IDAT chunks (with filter index bytes and possible padding bits) + return value is error */ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, unsigned w, unsigned h, const LodePNGInfo* info_png) +{ + /* This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! */ + unsigned bpp = lodepng_get_bpp_lct(info_png->color.colortype, info_png->color.bitdepth); + if (bpp == 0) return 31; /* error: invalid colortype */ + + if (info_png->interlace_method == 0) { + if (bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /* we can immediately filter into the out buffer, no other steps needed */ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /* interlace_method is 1 (Adam7) */ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for (i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /* TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all */ + if (bpp < 8) { + /* remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte */ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + Adam7_deinterlace(out, in, w, h, bpp); + } + return 0; +} + + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if (color->palettesize == 0 || color->palettesize > 256) return 38; /* error: palette too small or big */ + lodepng_color_mode_alloc_palette(color); + if (!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /* alloc fail */ + } + + for (i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned i; + if (color->colortype == LCT_PALETTE) { + /* error: more alpha values given than there are palette entries */ + if (chunkLength > color->palettesize) return 39; + + for (i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if (color->colortype == LCT_GREY) { + /* error: this chunk must be 2 bytes for grayscale image */ + if (chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if (color->colortype == LCT_RGB) { + /* error: this chunk must be 6 bytes for RGB image */ + if (chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /* error: tRNS chunk not allowed for other color models */ + + return 0; /* OK */ +} + + +/* read a PNG, the result will be in the same color type as the PNG (hence "generic") */ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if (state->error) return; + + if (lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)malloc(insize); + if (!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while (!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if ((size_t)((chunk - in) + 12) > insize || chunk < in) { + if (state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if (chunkLength > 2147483647) { + if (state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if ((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + /*for unknown chunk order*/ + //unsigned unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if (lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if (lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if (newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; + } else if (lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if (lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if (state->error) break; + } else if (lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if (state->error) break; + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if (!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + //unknown = 1; + } + +#if 0 //We don't use CRC + if (!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if (lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } +#endif + if (!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if (state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if (!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if (state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp_lct(state->info_png.color.colortype, state->info_png.color.bitdepth); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp_lct(state->info_png.color.colortype, state->info_png.color.bitdepth); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if (*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if (*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if (*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + + if (!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + free(idat); + + if (!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)malloc(outsize); + if (!*out) state->error = 83; /*alloc fail*/ + } + if (!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + free(scanlines); +} + + +static void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) +{ + settings->color_convert = 1; + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) +{ + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if (insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if (insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /* when decoding a new PNG image, make sure all parameters created after previous decoding are reset */ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if (in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if (lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if (!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if (w) *w = width; + if (h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if (width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if (state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if (info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if (info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if (info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + +#if 0 //thorvg don't use crc + if (!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if (CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } +#endif + return state->error; +} + + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) +{ + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if (state->error) return state->error; + if (!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if (!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if (state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if (!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)malloc(outsize); + if (!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, &state->info_png.color, *w, *h); + free(data); + } + return state->error; +} + + +void lodepng_state_init(LodePNGState* state) +{ + lodepng_decoder_settings_init(&state->decoder); + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + + +void lodepng_state_cleanup(LodePNGState* state) +{ + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/png/tvgLodePng.h b/thirdparty/thorvg/src/loaders/png/tvgLodePng.h new file mode 100644 index 0000000000..02e1feeec5 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/png/tvgLodePng.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + LodePNG version 20200306 + + Copyright (c) 2005-2020 Lode Vandevenne + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TVG_LODEPNG_H_ +#define _TVG_LODEPNG_H_ + +#include + +/*The PNG color types (also used for raw image).*/ +enum LodePNGColorType +{ + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +}; + +/*Settings for zlib decompression*/ +struct LodePNGDecompressSettings +{ + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate)*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +/* + Color mode of an image. Contains all information required to decode the pixel + bits to RGBA colors. This information is the same as used in the PNG file + format, and is used both for PNG and raw image data in LodePNG. +*/ +struct LodePNGColorMode +{ + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +}; + +/*Information about the PNG image, except pixels, width and height.*/ +struct LodePNGInfo +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ +}; + +/* + Settings for the decoder. This contains settings for the PNG and the Zlib + decoder, but not the Info settings from the Info structs. +*/ +struct LodePNGDecoderSettings +{ + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ +}; + +/*The settings, state and information for extended encoding and decoding.*/ +struct LodePNGState +{ + LodePNGDecoderSettings decoder; /*the decoding settings*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +}; + +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize); +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize); + +#endif //_TVG_LODEPNG_H_ \ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp new file mode 100644 index 0000000000..e50f34e0f8 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include "tvgLoader.h" +#include "tvgPngLoader.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +void PngLoader::clear() +{ + lodepng_state_cleanup(&state); + + if (freeData) free(data); + data = nullptr; + size = 0; + freeData = false; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +PngLoader::PngLoader() +{ + lodepng_state_init(&state); +} + + +PngLoader::~PngLoader() +{ + if (freeData) free(data); +} + + +bool PngLoader::open(const string& path) +{ + clear(); + + auto pngFile = fopen(path.c_str(), "rb"); + if (!pngFile) return false; + + auto ret = false; + + //determine size + if (fseek(pngFile, 0, SEEK_END) < 0) goto finalize; + if (((size = ftell(pngFile)) < 1)) goto finalize; + if (fseek(pngFile, 0, SEEK_SET)) goto finalize; + + data = (unsigned char *) malloc(size); + if (!data) goto finalize; + + freeData = true; + + if (fread(data, size, 1, pngFile) < 1) goto failure; + + lodepng_state_init(&state); + + unsigned int width, height; + if (lodepng_inspect(&width, &height, &state, data, size) > 0) goto failure; + + w = static_cast(width); + h = static_cast(height); + ret = true; + + goto finalize; + +failure: + clear(); + +finalize: + fclose(pngFile); + return ret; +} + + +bool PngLoader::open(const char* data, uint32_t size, bool copy) +{ + clear(); + + lodepng_state_init(&state); + + unsigned int width, height; + if (lodepng_inspect(&width, &height, &state, (unsigned char*)(data), size) > 0) return false; + + if (copy) { + this->data = (unsigned char *) malloc(size); + if (!this->data) return false; + memcpy((unsigned char *)this->data, data, size); + freeData = true; + } else { + this->data = (unsigned char *) data; + freeData = false; + } + + w = static_cast(width); + h = static_cast(height); + this->size = size; + + return true; +} + + +bool PngLoader::read() +{ + if (!data || w <= 0 || h <= 0) return false; + + TaskScheduler::request(this); + + return true; +} + + +bool PngLoader::close() +{ + this->done(); + clear(); + return true; +} + + +const uint32_t* PngLoader::pixels() +{ + this->done(); + + return (const uint32_t*) image; +} + + +void PngLoader::run(unsigned tid) +{ + auto width = static_cast(w); + auto height = static_cast(h); + + lodepng_decode(&image, &width, &height, &state, data, size); +} \ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/png/tvgPngLoader.h b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.h new file mode 100644 index 0000000000..fa1860c42d --- /dev/null +++ b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_PNG_LOADER_H_ +#define _TVG_PNG_LOADER_H_ + +#include "tvgLodePng.h" +#include "tvgTaskScheduler.h" + + +class PngLoader : public LoadModule, public Task +{ +private: + LodePNGState state; + unsigned char* data = nullptr; + unsigned char *image = nullptr; + unsigned long size = 0; + bool freeData = false; + + void clear(); + +public: + PngLoader(); + ~PngLoader(); + + using LoadModule::open; + bool open(const string& path) override; + bool open(const char* data, uint32_t size, bool copy) override; + bool read() override; + bool close() override; + + const uint32_t* pixels() override; + void run(unsigned tid) override; +}; + +#endif //_TVG_PNG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp new file mode 100644 index 0000000000..a38dc5ef4f --- /dev/null +++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgLoader.h" +#include "tvgRawLoader.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +RawLoader::~RawLoader() +{ + if (copy && content) { + free((void*)content); + content = nullptr; + } +} + + +bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) +{ + if (!data || w == 0 || h == 0) return false; + + this->w = (float)w; + this->h = (float)h; + this->copy = copy; + + if (copy) { + content = (uint32_t*)malloc(sizeof(uint32_t) * w * h); + if (!content) return false; + memcpy((void*)content, data, sizeof(uint32_t) * w * h); + } + else content = data; + + return true; +} + + +bool RawLoader::read() +{ + return true; +} + + +bool RawLoader::close() +{ + return true; +} + + +const uint32_t* RawLoader::pixels() +{ + return this->content; +} diff --git a/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h new file mode 100644 index 0000000000..f6eed20ec4 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_RAW_LOADER_H_ +#define _TVG_RAW_LOADER_H_ + +class RawLoader : public LoadModule +{ +public: + const uint32_t* content = nullptr; + bool copy = false; + + ~RawLoader(); + + using LoadModule::open; + bool open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) override; + bool read() override; + bool close() override; + + const uint32_t* pixels() override; +}; + + +#endif //_TVG_RAW_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp new file mode 100644 index 0000000000..e587c3a5ff --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -0,0 +1,2976 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Copyright notice for the EFL: + + * Copyright (C) EFL developers (see AUTHORS) + + * All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. + +#include +#include +#include +#include "tvgLoader.h" +#include "tvgXmlParser.h" +#include "tvgSvgLoader.h" +#include "tvgSvgSceneBuilder.h" +#include "tvgSvgUtil.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +/* + * According to: https://www.w3.org/TR/SVG2/coords.html#Units + * and: https://www.w3.org/TR/css-values-4/#absolute-lengths + */ +#define PX_PER_IN 96 //1 in = 96 px +#define PX_PER_PC 16 //1 pc = 1/6 in -> PX_PER_IN/6 +#define PX_PER_PT 1.333333f //1 pt = 1/72 in -> PX_PER_IN/72 +#define PX_PER_MM 3.779528f //1 in = 25.4 mm -> PX_PER_IN/25.4 +#define PX_PER_CM 37.79528f //1 in = 2.54 cm -> PX_PER_IN/2.54 + + +typedef SvgNode* (*FactoryMethod)(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength); +typedef SvgStyleGradient* (*GradientFactoryMethod)(SvgLoaderData* loader, const char* buf, unsigned bufLength); + + +static char* _skipSpace(const char* str, const char* end) +{ + while (((end && str < end) || (!end && *str != '\0')) && isspace(*str)) { + ++str; + } + return (char*) str; +} + + +static string* _copyId(const char* str) +{ + if (!str) return nullptr; + + return new string(str); +} + + +static const char* _skipComma(const char* content) +{ + content = _skipSpace(content, nullptr); + if (*content == ',') return content + 1; + return content; +} + + +static bool _parseNumber(const char** content, float* number) +{ + char* end = nullptr; + + *number = svgUtilStrtof(*content, &end); + //If the start of string is not number + if ((*content) == end) return false; + //Skip comma if any + *content = _skipComma(end); + return true; +} + +/** + * According to https://www.w3.org/TR/SVG/coords.html#Units + */ +static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type) +{ + float parsedValue = svgUtilStrtof(str, nullptr); + + if (strstr(str, "cm")) parsedValue *= PX_PER_CM; + else if (strstr(str, "mm")) parsedValue *= PX_PER_MM; + else if (strstr(str, "pt")) parsedValue *= PX_PER_PT; + else if (strstr(str, "pc")) parsedValue *= PX_PER_PC; + else if (strstr(str, "in")) parsedValue *= PX_PER_IN; + else if (strstr(str, "%")) { + if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0) * svgParse->global.h; + else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0) * svgParse->global.w; + else //if other then it's radius + { + float max = (float)svgParse->global.w; + if (max < svgParse->global.h) + max = (float)svgParse->global.h; + parsedValue = (parsedValue / 100.0) * max; + } + } + //TODO: Implement 'em', 'ex' attributes + + return parsedValue; +} + + +static float _gradientToFloat(const SvgParser* svgParse, const char* str, bool& isPercentage) +{ + char* end = nullptr; + + float parsedValue = svgUtilStrtof(str, &end); + isPercentage = false; + + if (strstr(str, "%")) { + parsedValue = parsedValue / 100.0; + isPercentage = true; + } + else if (strstr(str, "cm")) parsedValue *= PX_PER_CM; + else if (strstr(str, "mm")) parsedValue *= PX_PER_MM; + else if (strstr(str, "pt")) parsedValue *= PX_PER_PT; + else if (strstr(str, "pc")) parsedValue *= PX_PER_PC; + else if (strstr(str, "in")) parsedValue *= PX_PER_IN; + //TODO: Implement 'em', 'ex' attributes + + return parsedValue; +} + + +static float _toOffset(const char* str) +{ + char* end = nullptr; + auto strEnd = str + strlen(str); + + float parsedValue = svgUtilStrtof(str, &end); + + end = _skipSpace(end, nullptr); + auto ptr = strstr(str, "%"); + + if (ptr) { + parsedValue = parsedValue / 100.0; + if (end != ptr || (end + 1) != strEnd) return 0; + } else if (end != strEnd) return 0; + + return parsedValue; +} + + +static int _toOpacity(const char* str) +{ + char* end = nullptr; + float opacity = svgUtilStrtof(str, &end); + + if (end) { + if (end[0] == '%' && end[1] == '\0') return lrint(opacity * 2.55f); + else if (*end == '\0') return lrint(opacity * 255); + } + return 255; +} + + +#define _PARSE_TAG(Type, Name, Name1, Tags_Array, Default) \ + static Type _to##Name1(const char* str) \ + { \ + unsigned int i; \ + \ + for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \ + if (!strcmp(str, Tags_Array[i].tag)) return Tags_Array[i].Name; \ + } \ + return Default; \ + } + + +/* parse the line cap used during stroking a path. + * Value: butt | round | square | inherit + * Initial: butt + * https://www.w3.org/TR/SVG/painting.html + */ +static constexpr struct +{ + StrokeCap lineCap; + const char* tag; +} lineCapTags[] = { + { StrokeCap::Butt, "butt" }, + { StrokeCap::Round, "round" }, + { StrokeCap::Square, "square" } +}; + + +_PARSE_TAG(StrokeCap, lineCap, LineCap, lineCapTags, StrokeCap::Butt) + + +/* parse the line join used during stroking a path. + * Value: miter | round | bevel | inherit + * Initial: miter + * https://www.w3.org/TR/SVG/painting.html + */ +static constexpr struct +{ + StrokeJoin lineJoin; + const char* tag; +} lineJoinTags[] = { + { StrokeJoin::Miter, "miter" }, + { StrokeJoin::Round, "round" }, + { StrokeJoin::Bevel, "bevel" } +}; + + +_PARSE_TAG(StrokeJoin, lineJoin, LineJoin, lineJoinTags, StrokeJoin::Miter) + + +/* parse the fill rule used during filling a path. + * Value: nonzero | evenodd | inherit + * Initial: nonzero + * https://www.w3.org/TR/SVG/painting.html + */ +static constexpr struct +{ + FillRule fillRule; + const char* tag; +} fillRuleTags[] = { + { FillRule::EvenOdd, "evenodd" } +}; + + +_PARSE_TAG(FillRule, fillRule, FillRule, fillRuleTags, FillRule::Winding) + + +/* parse the dash pattern used during stroking a path. + * Value: none | | inherit + * Initial: none + * https://www.w3.org/TR/SVG/painting.html + */ +static inline void +_parseDashArray(SvgLoaderData* loader, const char *str, SvgDash* dash) +{ + if (!strncmp(str, "none", 4)) return; + + char *end = nullptr; + + while (*str) { + str = _skipComma(str); + float parsedValue = svgUtilStrtof(str, &end); + if (str == end) break; + if (parsedValue <= 0.0f) break; + if (*end == '%') { + ++end; + //Refers to the diagonal length of the viewport. + //https://www.w3.org/TR/SVG2/coords.html#Units + parsedValue = (sqrtf(pow(loader->svgParse->global.w, 2) + pow(loader->svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f); + } + (*dash).array.push(parsedValue); + str = end; + } + //If dash array size is 1, it means that dash and gap size are the same. + if ((*dash).array.count == 1) (*dash).array.push((*dash).array.data[0]); +} + +static string* _idFromUrl(const char* url) +{ + url = _skipSpace(url, nullptr); + if ((*url) == '(') { + ++url; + url = _skipSpace(url, nullptr); + } + + if ((*url) == '\'') ++url; + if ((*url) == '#') ++url; + + int i = 0; + while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i; + + return new string(url, i); +} + + +static unsigned char _parserColor(const char* value, char** end) +{ + float r; + + r = svgUtilStrtof(value, end); + *end = _skipSpace(*end, nullptr); + if (**end == '%') r = 255 * r / 100; + *end = _skipSpace(*end, nullptr); + + if (r < 0 || r > 255) { + *end = nullptr; + return 0; + } + + return lrint(r); +} + + +static constexpr struct +{ + const char* name; + unsigned int value; +} colors[] = { + { "aliceblue", 0xfff0f8ff }, + { "antiquewhite", 0xfffaebd7 }, + { "aqua", 0xff00ffff }, + { "aquamarine", 0xff7fffd4 }, + { "azure", 0xfff0ffff }, + { "beige", 0xfff5f5dc }, + { "bisque", 0xffffe4c4 }, + { "black", 0xff000000 }, + { "blanchedalmond", 0xffffebcd }, + { "blue", 0xff0000ff }, + { "blueviolet", 0xff8a2be2 }, + { "brown", 0xffa52a2a }, + { "burlywood", 0xffdeb887 }, + { "cadetblue", 0xff5f9ea0 }, + { "chartreuse", 0xff7fff00 }, + { "chocolate", 0xffd2691e }, + { "coral", 0xffff7f50 }, + { "cornflowerblue", 0xff6495ed }, + { "cornsilk", 0xfffff8dc }, + { "crimson", 0xffdc143c }, + { "cyan", 0xff00ffff }, + { "darkblue", 0xff00008b }, + { "darkcyan", 0xff008b8b }, + { "darkgoldenrod", 0xffb8860b }, + { "darkgray", 0xffa9a9a9 }, + { "darkgrey", 0xffa9a9a9 }, + { "darkgreen", 0xff006400 }, + { "darkkhaki", 0xffbdb76b }, + { "darkmagenta", 0xff8b008b }, + { "darkolivegreen", 0xff556b2f }, + { "darkorange", 0xffff8c00 }, + { "darkorchid", 0xff9932cc }, + { "darkred", 0xff8b0000 }, + { "darksalmon", 0xffe9967a }, + { "darkseagreen", 0xff8fbc8f }, + { "darkslateblue", 0xff483d8b }, + { "darkslategray", 0xff2f4f4f }, + { "darkslategrey", 0xff2f4f4f }, + { "darkturquoise", 0xff00ced1 }, + { "darkviolet", 0xff9400d3 }, + { "deeppink", 0xffff1493 }, + { "deepskyblue", 0xff00bfff }, + { "dimgray", 0xff696969 }, + { "dimgrey", 0xff696969 }, + { "dodgerblue", 0xff1e90ff }, + { "firebrick", 0xffb22222 }, + { "floralwhite", 0xfffffaf0 }, + { "forestgreen", 0xff228b22 }, + { "fuchsia", 0xffff00ff }, + { "gainsboro", 0xffdcdcdc }, + { "ghostwhite", 0xfff8f8ff }, + { "gold", 0xffffd700 }, + { "goldenrod", 0xffdaa520 }, + { "gray", 0xff808080 }, + { "grey", 0xff808080 }, + { "green", 0xff008000 }, + { "greenyellow", 0xffadff2f }, + { "honeydew", 0xfff0fff0 }, + { "hotpink", 0xffff69b4 }, + { "indianred", 0xffcd5c5c }, + { "indigo", 0xff4b0082 }, + { "ivory", 0xfffffff0 }, + { "khaki", 0xfff0e68c }, + { "lavender", 0xffe6e6fa }, + { "lavenderblush", 0xfffff0f5 }, + { "lawngreen", 0xff7cfc00 }, + { "lemonchiffon", 0xfffffacd }, + { "lightblue", 0xffadd8e6 }, + { "lightcoral", 0xfff08080 }, + { "lightcyan", 0xffe0ffff }, + { "lightgoldenrodyellow", 0xfffafad2 }, + { "lightgray", 0xffd3d3d3 }, + { "lightgrey", 0xffd3d3d3 }, + { "lightgreen", 0xff90ee90 }, + { "lightpink", 0xffffb6c1 }, + { "lightsalmon", 0xffffa07a }, + { "lightseagreen", 0xff20b2aa }, + { "lightskyblue", 0xff87cefa }, + { "lightslategray", 0xff778899 }, + { "lightslategrey", 0xff778899 }, + { "lightsteelblue", 0xffb0c4de }, + { "lightyellow", 0xffffffe0 }, + { "lime", 0xff00ff00 }, + { "limegreen", 0xff32cd32 }, + { "linen", 0xfffaf0e6 }, + { "magenta", 0xffff00ff }, + { "maroon", 0xff800000 }, + { "mediumaquamarine", 0xff66cdaa }, + { "mediumblue", 0xff0000cd }, + { "mediumorchid", 0xffba55d3 }, + { "mediumpurple", 0xff9370d8 }, + { "mediumseagreen", 0xff3cb371 }, + { "mediumslateblue", 0xff7b68ee }, + { "mediumspringgreen", 0xff00fa9a }, + { "mediumturquoise", 0xff48d1cc }, + { "mediumvioletred", 0xffc71585 }, + { "midnightblue", 0xff191970 }, + { "mintcream", 0xfff5fffa }, + { "mistyrose", 0xffffe4e1 }, + { "moccasin", 0xffffe4b5 }, + { "navajowhite", 0xffffdead }, + { "navy", 0xff000080 }, + { "oldlace", 0xfffdf5e6 }, + { "olive", 0xff808000 }, + { "olivedrab", 0xff6b8e23 }, + { "orange", 0xffffa500 }, + { "orangered", 0xffff4500 }, + { "orchid", 0xffda70d6 }, + { "palegoldenrod", 0xffeee8aa }, + { "palegreen", 0xff98fb98 }, + { "paleturquoise", 0xffafeeee }, + { "palevioletred", 0xffd87093 }, + { "papayawhip", 0xffffefd5 }, + { "peachpuff", 0xffffdab9 }, + { "peru", 0xffcd853f }, + { "pink", 0xffffc0cb }, + { "plum", 0xffdda0dd }, + { "powderblue", 0xffb0e0e6 }, + { "purple", 0xff800080 }, + { "red", 0xffff0000 }, + { "rosybrown", 0xffbc8f8f }, + { "royalblue", 0xff4169e1 }, + { "saddlebrown", 0xff8b4513 }, + { "salmon", 0xfffa8072 }, + { "sandybrown", 0xfff4a460 }, + { "seagreen", 0xff2e8b57 }, + { "seashell", 0xfffff5ee }, + { "sienna", 0xffa0522d }, + { "silver", 0xffc0c0c0 }, + { "skyblue", 0xff87ceeb }, + { "slateblue", 0xff6a5acd }, + { "slategray", 0xff708090 }, + { "slategrey", 0xff708090 }, + { "snow", 0xfffffafa }, + { "springgreen", 0xff00ff7f }, + { "steelblue", 0xff4682b4 }, + { "tan", 0xffd2b48c }, + { "teal", 0xff008080 }, + { "thistle", 0xffd8bfd8 }, + { "tomato", 0xffff6347 }, + { "turquoise", 0xff40e0d0 }, + { "violet", 0xffee82ee }, + { "wheat", 0xfff5deb3 }, + { "white", 0xffffffff }, + { "whitesmoke", 0xfff5f5f5 }, + { "yellow", 0xffffff00 }, + { "yellowgreen", 0xff9acd32 } +}; + + +static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, string** ref) +{ + unsigned int len = strlen(str); + char *red, *green, *blue; + unsigned char tr, tg, tb; + + if (len == 4 && str[0] == '#') { + //Case for "#456" should be interprete as "#445566" + if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) { + char tmp[3] = { '\0', '\0', '\0' }; + tmp[0] = str[1]; + tmp[1] = str[1]; + *r = strtol(tmp, nullptr, 16); + tmp[0] = str[2]; + tmp[1] = str[2]; + *g = strtol(tmp, nullptr, 16); + tmp[0] = str[3]; + tmp[1] = str[3]; + *b = strtol(tmp, nullptr, 16); + } + } else if (len == 7 && str[0] == '#') { + if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) { + char tmp[3] = { '\0', '\0', '\0' }; + tmp[0] = str[1]; + tmp[1] = str[2]; + *r = strtol(tmp, nullptr, 16); + tmp[0] = str[3]; + tmp[1] = str[4]; + *g = strtol(tmp, nullptr, 16); + tmp[0] = str[5]; + tmp[1] = str[6]; + *b = strtol(tmp, nullptr, 16); + } + } else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') { + tr = _parserColor(str + 4, &red); + if (red && *red == ',') { + tg = _parserColor(red + 1, &green); + if (green && *green == ',') { + tb = _parserColor(green + 1, &blue); + if (blue && blue[0] == ')' && blue[1] == '\0') { + *r = tr; + *g = tg; + *b = tb; + } + } + } + } else if (len >= 3 && !strncmp(str, "url", 3)) { + *ref = _idFromUrl((const char*)(str + 3)); + } else { + //Handle named color + for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) { + if (!strcasecmp(colors[i].name, str)) { + *r = (((uint8_t*)(&(colors[i].value)))[2]); + *g = (((uint8_t*)(&(colors[i].value)))[1]); + *b = (((uint8_t*)(&(colors[i].value)))[0]); + return; + } + } + } +} + + +static char* _parseNumbersArray(char* str, float* points, int* ptCount, int len) +{ + int count = 0; + char* end = nullptr; + + str = _skipSpace(str, nullptr); + while ((count < len) && (isdigit(*str) || *str == '-' || *str == '+' || *str == '.')) { + points[count++] = svgUtilStrtof(str, &end); + str = end; + str = _skipSpace(str, nullptr); + if (*str == ',') ++str; + //Eat the rest of space + str = _skipSpace(str, nullptr); + } + *ptCount = count; + return str; +} + + +enum class MatrixState { + Unknown, + Matrix, + Translate, + Rotate, + Scale, + SkewX, + SkewY +}; + + +#define MATRIX_DEF(Name, Value) \ + { \ +#Name, sizeof(#Name), Value \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + MatrixState state; +} matrixTags[] = { + MATRIX_DEF(matrix, MatrixState::Matrix), + MATRIX_DEF(translate, MatrixState::Translate), + MATRIX_DEF(rotate, MatrixState::Rotate), + MATRIX_DEF(scale, MatrixState::Scale), + MATRIX_DEF(skewX, MatrixState::SkewX), + MATRIX_DEF(skewY, MatrixState::SkewY) +}; + + +static void _matrixCompose(const Matrix* m1, const Matrix* m2, Matrix* dst) +{ + auto a11 = (m1->e11 * m2->e11) + (m1->e12 * m2->e21) + (m1->e13 * m2->e31); + auto a12 = (m1->e11 * m2->e12) + (m1->e12 * m2->e22) + (m1->e13 * m2->e32); + auto a13 = (m1->e11 * m2->e13) + (m1->e12 * m2->e23) + (m1->e13 * m2->e33); + + auto a21 = (m1->e21 * m2->e11) + (m1->e22 * m2->e21) + (m1->e23 * m2->e31); + auto a22 = (m1->e21 * m2->e12) + (m1->e22 * m2->e22) + (m1->e23 * m2->e32); + auto a23 = (m1->e21 * m2->e13) + (m1->e22 * m2->e23) + (m1->e23 * m2->e33); + + auto a31 = (m1->e31 * m2->e11) + (m1->e32 * m2->e21) + (m1->e33 * m2->e31); + auto a32 = (m1->e31 * m2->e12) + (m1->e32 * m2->e22) + (m1->e33 * m2->e32); + auto a33 = (m1->e31 * m2->e13) + (m1->e32 * m2->e23) + (m1->e33 * m2->e33); + + dst->e11 = a11; + dst->e12 = a12; + dst->e13 = a13; + dst->e21 = a21; + dst->e22 = a22; + dst->e23 = a23; + dst->e31 = a31; + dst->e32 = a32; + dst->e33 = a33; +} + + +/* parse transform attribute + * https://www.w3.org/TR/SVG/coords.html#TransformAttribute + */ +static Matrix* _parseTransformationMatrix(const char* value) +{ + const int POINT_CNT = 8; + + auto matrix = (Matrix*)malloc(sizeof(Matrix)); + if (!matrix) return nullptr; + *matrix = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + + float points[POINT_CNT]; + int ptCount = 0; + char* str = (char*)value; + char* end = str + strlen(str); + + while (str < end) { + auto state = MatrixState::Unknown; + + if (isspace(*str) || (*str == ',')) { + ++str; + continue; + } + for (unsigned int i = 0; i < sizeof(matrixTags) / sizeof(matrixTags[0]); i++) { + if (!strncmp(matrixTags[i].tag, str, matrixTags[i].sz - 1)) { + state = matrixTags[i].state; + str += (matrixTags[i].sz - 1); + break; + } + } + if (state == MatrixState::Unknown) goto error; + + str = _skipSpace(str, end); + if (*str != '(') goto error; + ++str; + str = _parseNumbersArray(str, points, &ptCount, POINT_CNT); + if (*str != ')') goto error; + ++str; + + if (state == MatrixState::Matrix) { + if (ptCount != 6) goto error; + Matrix tmp = {points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1}; + _matrixCompose(matrix, &tmp, matrix); + } else if (state == MatrixState::Translate) { + if (ptCount == 1) { + Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1}; + _matrixCompose(matrix, &tmp, matrix); + } else if (ptCount == 2) { + Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1}; + _matrixCompose(matrix, &tmp, matrix); + } else goto error; + } else if (state == MatrixState::Rotate) { + //Transform to signed. + points[0] = fmod(points[0], 360); + if (points[0] < 0) points[0] += 360; + auto c = cosf(points[0] * (M_PI / 180.0)); + auto s = sinf(points[0] * (M_PI / 180.0)); + if (ptCount == 1) { + Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else if (ptCount == 3) { + Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else { + goto error; + } + } else if (state == MatrixState::Scale) { + if (ptCount < 1 || ptCount > 2) goto error; + auto sx = points[0]; + auto sy = sx; + if (ptCount == 2) sy = points[1]; + Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } + } + return matrix; +error: + if (matrix) free(matrix); + return nullptr; +} + + +#define LENGTH_DEF(Name, Value) \ + { \ +#Name, sizeof(#Name), Value \ + } + + +/* +// TODO - remove? +static constexpr struct +{ + const char* tag; + int sz; + SvgLengthType type; +} lengthTags[] = { + LENGTH_DEF(%, SvgLengthType::Percent), + LENGTH_DEF(px, SvgLengthType::Px), + LENGTH_DEF(pc, SvgLengthType::Pc), + LENGTH_DEF(pt, SvgLengthType::Pt), + LENGTH_DEF(mm, SvgLengthType::Mm), + LENGTH_DEF(cm, SvgLengthType::Cm), + LENGTH_DEF(in, SvgLengthType::In) +}; + +static float _parseLength(const char* str, SvgLengthType* type) +{ + float value; + int sz = strlen(str); + + *type = SvgLengthType::Px; + for (unsigned int i = 0; i < sizeof(lengthTags) / sizeof(lengthTags[0]); i++) { + if (lengthTags[i].sz - 1 == sz && !strncmp(lengthTags[i].tag, str, sz)) *type = lengthTags[i].type; + } + value = svgUtilStrtof(str, nullptr); + return value; +} +*/ + +static bool _parseStyleAttr(void* data, const char* key, const char* value); +static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style); + + +static bool _attrParseSvgNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgDocNode* doc = &(node->node.doc); + + if (!strcmp(key, "width")) { + doc->w = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + } else if (!strcmp(key, "height")) { + doc->h = _toFloat(loader->svgParse, value, SvgParserLengthType::Vertical); + } else if (!strcmp(key, "viewBox")) { + if (_parseNumber(&value, &doc->vx)) { + if (_parseNumber(&value, &doc->vy)) { + if (_parseNumber(&value, &doc->vw)) { + _parseNumber(&value, &doc->vh); + loader->svgParse->global.h = (uint32_t)doc->vh; + } + loader->svgParse->global.w = (uint32_t)doc->vw; + } + loader->svgParse->global.y = (int)doc->vy; + } + loader->svgParse->global.x = (int)doc->vx; + } else if (!strcmp(key, "preserveAspectRatio")) { + if (!strcmp(value, "none")) doc->preserveAspect = false; + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } +#ifdef THORVG_LOG_ENABLED + else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON ) { + TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value); + } +#endif + else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +//https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint +static void _handlePaintAttr(SvgPaint* paint, const char* value) +{ + if (!strcmp(value, "none")) { + //No paint property + paint->none = true; + return; + } + paint->none = false; + if (!strcmp(value, "currentColor")) { + paint->curColor = true; + return; + } + _toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url); +} + + +static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->curColorSet = true; + _toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr); +} + + +static void _handleFillAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->fill.flags = (SvgFillFlags)((int)style->fill.flags | (int)SvgFillFlags::Paint); + _handlePaintAttr(&style->fill.paint, value); +} + + +static void _handleStrokeAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->stroke.flags = (SvgStrokeFlags)((int)style->stroke.flags | (int)SvgStrokeFlags::Paint); + _handlePaintAttr(&style->stroke.paint, value); +} + + +static void _handleStrokeOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Opacity); + node->style->stroke.opacity = _toOpacity(value); +} + +static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Dash); + _parseDashArray(loader, value, &node->style->stroke.dash); +} + +static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Width); + node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + + +static void _handleStrokeLineCapAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Cap); + node->style->stroke.cap = _toLineCap(value); +} + + +static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Join); + node->style->stroke.join = _toLineJoin(value); +} + + +static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::FillRule); + node->style->fill.fillRule = _toFillRule(value); +} + + +static void _handleOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->opacity = _toOpacity(value); +} + + +static void _handleFillOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::Opacity); + node->style->fill.opacity = _toOpacity(value); +} + + +static void _handleTransformAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->transform = _parseTransformationMatrix(value); +} + + +static void _handleClipPathAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + int len = strlen(value); + if (len >= 3 && !strncmp(value, "url", 3)) { + if (style->clipPath.url) delete(style->clipPath.url); + style->clipPath.url = _idFromUrl((const char*)(value + 3)); + } +} + + +static void _handleMaskAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + int len = strlen(value); + if (len >= 3 && !strncmp(value, "url", 3)) { + if (style->mask.url) delete(style->mask.url); + style->mask.url = _idFromUrl((const char*)(value + 3)); + } +} + + +static void _handleDisplayAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + //TODO : The display attribute can have various values as well as "none". + // The default is "inline" which means visible and "none" means invisible. + // Depending on the type of node, additional functionality may be required. + // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display + if (!strcmp(value, "none")) node->display = false; + else node->display = true; +} + + +typedef void (*styleMethod)(SvgLoaderData* loader, SvgNode* node, const char* value); + +#define STYLE_DEF(Name, Name1, Flag) { #Name, sizeof(#Name), _handle##Name1##Attr, Flag } + + +static constexpr struct +{ + const char* tag; + int sz; + styleMethod tagHandler; + SvgStyleFlags flag; +} styleTags[] = { + STYLE_DEF(color, Color, SvgStyleFlags::Color), + STYLE_DEF(fill, Fill, SvgStyleFlags::Fill), + STYLE_DEF(fill-rule, FillRule, SvgStyleFlags::FillRule), + STYLE_DEF(fill-opacity, FillOpacity, SvgStyleFlags::FillOpacity), + STYLE_DEF(opacity, Opacity, SvgStyleFlags::Opacity), + STYLE_DEF(stroke, Stroke, SvgStyleFlags::Stroke), + STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth), + STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin), + STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap), + STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity), + STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray), + STYLE_DEF(transform, Transform, SvgStyleFlags::Transform), + STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath), + STYLE_DEF(mask, Mask, SvgStyleFlags::Mask), + STYLE_DEF(display, Display, SvgStyleFlags::Display) +}; + + +static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + int sz; + if (!key || !value) return false; + + //Trim the white space + key = _skipSpace(key, nullptr); + value = _skipSpace(value, nullptr); + + sz = strlen(key); + for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) { + if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) { + if (style) { + styleTags[i].tagHandler(loader, node, value); + node->style->flags = (SvgStyleFlags)((int)node->style->flags | (int)styleTags[i].flag); + } else if (!((int)node->style->flags & (int)styleTags[i].flag)) { + styleTags[i].tagHandler(loader, node, value); + } + return true; + } + } + + return false; +} + + +static bool _parseStyleAttr(void* data, const char* key, const char* value) +{ + return _parseStyleAttr(data, key, value, true); +} + + +/* parse g node + * https://www.w3.org/TR/SVG/struct.html#Groups + */ +static bool _attrParseGNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + + if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "transform")) { + node->transform = _parseTransformationMatrix(value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +/* parse clipPath node + * https://www.w3.org/TR/SVG/struct.html#Groups + */ +static bool _attrParseClipPathNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + + if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "transform")) { + node->transform = _parseTransformationMatrix(value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static bool _attrParseMaskNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + + if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "transform")) { + node->transform = _parseTransformationMatrix(value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static SvgNode* _createNode(SvgNode* parent, SvgNodeType type) +{ + SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode)); + + if (!node) return nullptr; + + //Default fill property + node->style = (SvgStyleProperty*)calloc(1, sizeof(SvgStyleProperty)); + + if (!node->style) { + free(node); + return nullptr; + } + + //Update the default value of stroke and fill + //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint + node->style->fill.paint.none = false; + //Default fill opacity is 1 + node->style->fill.opacity = 255; + node->style->opacity = 255; + //Default current color is not set + node->style->fill.paint.curColor = false; + node->style->curColorSet = false; + //Default fill rule is nonzero + node->style->fill.fillRule = FillRule::Winding; + + //Default stroke is none + node->style->stroke.paint.none = true; + //Default stroke opacity is 1 + node->style->stroke.opacity = 255; + //Default stroke current color is not set + node->style->stroke.paint.curColor = false; + //Default stroke width is 1 + node->style->stroke.width = 1; + //Default line cap is butt + node->style->stroke.cap = StrokeCap::Butt; + //Default line join is miter + node->style->stroke.join = StrokeJoin::Miter; + node->style->stroke.scale = 1.0; + + //Default display is true("inline"). + node->display = true; + + node->parent = parent; + node->type = type; + + if (parent) parent->child.push(node); + return node; +} + + +static SvgNode* _createDefsNode(TVG_UNUSED SvgLoaderData* loader, TVG_UNUSED SvgNode* parent, const char* buf, unsigned bufLength) +{ + if (loader->def && loader->doc->node.doc.defs) return loader->def; + SvgNode* node = _createNode(nullptr, SvgNodeType::Defs); + + loader->def = node; + loader->doc->node.doc.defs = node; + return node; +} + + +static SvgNode* _createGNode(TVG_UNUSED SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::G); + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseGNode, loader); + return loader->svgParse->node; +} + + +static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Doc); + if (!loader->svgParse->node) return nullptr; + SvgDocNode* doc = &(loader->svgParse->node->node.doc); + + loader->svgParse->global.w = 0; + loader->svgParse->global.h = 0; + + doc->preserveAspect = true; + simpleXmlParseAttributes(buf, bufLength, _attrParseSvgNode, loader); + + if (loader->svgParse->global.w == 0) { + if (doc->w < FLT_EPSILON) loader->svgParse->global.w = 1; + else loader->svgParse->global.w = (uint32_t)doc->w; + } + if (loader->svgParse->global.h == 0) { + if (doc->h < FLT_EPSILON) loader->svgParse->global.h = 1; + else loader->svgParse->global.h = (uint32_t)doc->h; + } + + return loader->svgParse->node; +} + + +static SvgNode* _createMaskNode(SvgLoaderData* loader, SvgNode* parent, TVG_UNUSED const char* buf, TVG_UNUSED unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Mask); + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseMaskNode, loader); + + return loader->svgParse->node; +} + + +static SvgNode* _createClipPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::ClipPath); + + if (!loader->svgParse->node) return nullptr; + + loader->svgParse->node->display = false; + + simpleXmlParseAttributes(buf, bufLength, _attrParseClipPathNode, loader); + + return loader->svgParse->node; +} + +static bool _attrParsePathNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgPathNode* path = &(node->node.path); + + if (!strcmp(key, "d")) { + //Temporary: need to copy + path->path = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static SvgNode* _createPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Path); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParsePathNode, loader); + + return loader->svgParse->node; +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} circleTags[] = { + {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgCircleNode, cx)}, + {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgCircleNode, cy)}, + {"r", SvgParserLengthType::Other, sizeof("r"), offsetof(SvgCircleNode, r)} +}; + + +/* parse the attributes for a circle element. + * https://www.w3.org/TR/SVG/shapes.html#CircleElement + */ +static bool _attrParseCircleNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgCircleNode* circle = &(node->node.circle); + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)circle; + for (unsigned int i = 0; i < sizeof(circleTags) / sizeof(circleTags[0]); i++) { + if (circleTags[i].sz - 1 == sz && !strncmp(circleTags[i].tag, key, sz)) { + *((float*)(array + circleTags[i].offset)) = _toFloat(loader->svgParse, value, circleTags[i].type); + return true; + } + } + + if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static SvgNode* _createCircleNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Circle); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseCircleNode, loader); + return loader->svgParse->node; +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} ellipseTags[] = { + {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgEllipseNode, cx)}, + {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgEllipseNode, cy)}, + {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgEllipseNode, rx)}, + {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgEllipseNode, ry)} +}; + + +/* parse the attributes for an ellipse element. + * https://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ +static bool _attrParseEllipseNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgEllipseNode* ellipse = &(node->node.ellipse); + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)ellipse; + for (unsigned int i = 0; i < sizeof(ellipseTags) / sizeof(ellipseTags[0]); i++) { + if (ellipseTags[i].sz - 1 == sz && !strncmp(ellipseTags[i].tag, key, sz)) { + *((float*)(array + ellipseTags[i].offset)) = _toFloat(loader->svgParse, value, ellipseTags[i].type); + return true; + } + } + + if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Ellipse); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseEllipseNode, loader); + return loader->svgParse->node; +} + + +static bool _attrParsePolygonPoints(const char* str, float** points, int* ptCount) +{ + float tmp[50]; + int tmpCount = 0; + int count = 0; + float num; + float *pointArray = nullptr, *tmpArray; + + while (_parseNumber(&str, &num)) { + tmp[tmpCount++] = num; + if (tmpCount == 50) { + tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float)); + if (!tmpArray) goto error_alloc; + pointArray = tmpArray; + memcpy(&pointArray[count], tmp, tmpCount * sizeof(float)); + count += tmpCount; + tmpCount = 0; + } + } + + if (tmpCount > 0) { + tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float)); + if (!tmpArray) goto error_alloc; + pointArray = tmpArray; + memcpy(&pointArray[count], tmp, tmpCount * sizeof(float)); + count += tmpCount; + } + *ptCount = count; + *points = pointArray; + return true; + +error_alloc: + return false; +} + + +/* parse the attributes for a polygon element. + * https://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ +static bool _attrParsePolygonNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgPolygonNode* polygon = nullptr; + + if (node->type == SvgNodeType::Polygon) polygon = &(node->node.polygon); + else polygon = &(node->node.polyline); + + if (!strcmp(key, "points")) { + return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static SvgNode* _createPolygonNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Polygon); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader); + return loader->svgParse->node; +} + + +static SvgNode* _createPolylineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Polyline); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader); + return loader->svgParse->node; +} + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} rectTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)}, + {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgRectNode, rx)}, + {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgRectNode, ry)} +}; + + +/* parse the attributes for a rect element. + * https://www.w3.org/TR/SVG/shapes.html#RectElement + */ +static bool _attrParseRectNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgRectNode* rect = &(node->node.rect); + unsigned char* array; + bool ret = true; + int sz = strlen(key); + + array = (unsigned char*)rect; + for (unsigned int i = 0; i < sizeof(rectTags) / sizeof(rectTags[0]); i++) { + if (rectTags[i].sz - 1 == sz && !strncmp(rectTags[i].tag, key, sz)) { + *((float*)(array + rectTags[i].offset)) = _toFloat(loader->svgParse, value, rectTags[i].type); + + //Case if only rx or ry is declared + if (!strncmp(rectTags[i].tag, "rx", sz)) rect->hasRx = true; + if (!strncmp(rectTags[i].tag, "ry", sz)) rect->hasRy = true; + + if ((rect->rx > FLT_EPSILON) && (rect->ry <= FLT_EPSILON) && rect->hasRx && !rect->hasRy) rect->ry = rect->rx; + if ((rect->ry > FLT_EPSILON) && (rect->rx <= FLT_EPSILON) && !rect->hasRx && rect->hasRy) rect->rx = rect->ry; + return ret; + } + } + + if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + ret = simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + ret = _parseStyleAttr(loader, key, value, false); + } + + return ret; +} + + +static SvgNode* _createRectNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Rect); + + if (!loader->svgParse->node) return nullptr; + + loader->svgParse->node->node.rect.hasRx = loader->svgParse->node->node.rect.hasRy = false; + + simpleXmlParseAttributes(buf, bufLength, _attrParseRectNode, loader); + return loader->svgParse->node; +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} lineTags[] = { + {"x1", SvgParserLengthType::Horizontal, sizeof("x1"), offsetof(SvgLineNode, x1)}, + {"y1", SvgParserLengthType::Vertical, sizeof("y1"), offsetof(SvgLineNode, y1)}, + {"x2", SvgParserLengthType::Horizontal, sizeof("x2"), offsetof(SvgLineNode, x2)}, + {"y2", SvgParserLengthType::Vertical, sizeof("y2"), offsetof(SvgLineNode, y2)} +}; + + +/* parse the attributes for a line element. + * https://www.w3.org/TR/SVG/shapes.html#LineElement + */ +static bool _attrParseLineNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgLineNode* line = &(node->node.line); + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)line; + for (unsigned int i = 0; i < sizeof(lineTags) / sizeof(lineTags[0]); i++) { + if (lineTags[i].sz - 1 == sz && !strncmp(lineTags[i].tag, key, sz)) { + *((float*)(array + lineTags[i].offset)) = _toFloat(loader->svgParse, value, lineTags[i].type); + return true; + } + } + + if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + return _parseStyleAttr(loader, key, value, false); + } + return true; +} + + +static SvgNode* _createLineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Line); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseLineNode, loader); + return loader->svgParse->node; +} + + +static string* _idFromHref(const char* href) +{ + href = _skipSpace(href, nullptr); + if ((*href) == '#') href++; + return new string(href); +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} imageTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)}, +}; + + +/* parse the attributes for a image element. + * https://www.w3.org/TR/SVG/embedded.html#ImageElement + */ +static bool _attrParseImageNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgImageNode* image = &(node->node.image); + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)image; + for (unsigned int i = 0; i < sizeof(imageTags) / sizeof(imageTags[0]); i++) { + if (imageTags[i].sz - 1 == sz && !strncmp(imageTags[i].tag, key, sz)) { + *((float*)(array + imageTags[i].offset)) = _toFloat(loader->svgParse, value, imageTags[i].type); + return true; + } + } + + if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + image->href = _idFromHref(value); + } else if (!strcmp(key, "id")) { + if (node->id && value) delete node->id; + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createImageNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Image); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseImageNode, loader); + return loader->svgParse->node; +} + + +static SvgNode* _getDefsNode(SvgNode* node) +{ + if (!node) return nullptr; + + while (node->parent != nullptr) { + node = node->parent; + } + + if (node->type == SvgNodeType::Doc) return node->node.doc.defs; + + return nullptr; +} + + +static SvgNode* _findChildById(const SvgNode* node, const char* id) +{ + if (!node) return nullptr; + + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + if (((*child)->id != nullptr) && !strcmp((*child)->id->c_str(), id)) return (*child); + } + return nullptr; +} + +static SvgNode* _findNodeById(SvgNode *node, string* id) +{ + SvgNode* result = nullptr; + if (node->id && !node->id->compare(*id)) return node; + + if (node->child.count > 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + result = _findNodeById(*child, id); + if (result) break; + } + } + return result; +} + +static void _cloneGradStops(Array& dst, const Array& src) +{ + for (uint32_t i = 0; i < src.count; ++i) { + dst.push(src.data[i]); + } +} + + +static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from) +{ + if (!from) return nullptr; + + auto grad = new SvgStyleGradient; + grad->type = from->type; + grad->id = from->id ? _copyId(from->id->c_str()) : nullptr; + grad->ref = from->ref ? _copyId(from->ref->c_str()) : nullptr; + grad->spread = from->spread; + grad->userSpace = from->userSpace; + + if (from->transform) { + grad->transform = (Matrix*)calloc(1, sizeof(Matrix)); + if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix)); + } + + if (grad->type == SvgGradientType::Linear) { + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + if (!grad->linear) goto error_grad_alloc; + memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient)); + } else if (grad->type == SvgGradientType::Radial) { + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + if (!grad->radial) goto error_grad_alloc; + memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient)); + } + + _cloneGradStops(grad->stops, from->stops); + + return grad; + +error_grad_alloc: + if (grad) delete(grad); + return nullptr; +} + + +static void _copyAttr(SvgNode* to, const SvgNode* from) +{ + //Copy matrix attribute + if (from->transform) { + to->transform = (Matrix*)malloc(sizeof(Matrix)); + if (to->transform) *to->transform = *from->transform; + } + //Copy style attribute + *to->style = *from->style; + if (from->style->fill.paint.url) to->style->fill.paint.url = new string(from->style->fill.paint.url->c_str()); + if (from->style->stroke.paint.url) to->style->stroke.paint.url = new string(from->style->stroke.paint.url->c_str()); + if (from->style->clipPath.url) to->style->clipPath.url = new string(from->style->clipPath.url->c_str()); + if (from->style->mask.url) to->style->mask.url = new string(from->style->mask.url->c_str()); + + //Copy node attribute + switch (from->type) { + case SvgNodeType::Circle: { + to->node.circle.cx = from->node.circle.cx; + to->node.circle.cy = from->node.circle.cy; + to->node.circle.r = from->node.circle.r; + break; + } + case SvgNodeType::Ellipse: { + to->node.ellipse.cx = from->node.ellipse.cx; + to->node.ellipse.cy = from->node.ellipse.cy; + to->node.ellipse.rx = from->node.ellipse.rx; + to->node.ellipse.ry = from->node.ellipse.ry; + break; + } + case SvgNodeType::Rect: { + to->node.rect.x = from->node.rect.x; + to->node.rect.y = from->node.rect.y; + to->node.rect.w = from->node.rect.w; + to->node.rect.h = from->node.rect.h; + to->node.rect.rx = from->node.rect.rx; + to->node.rect.ry = from->node.rect.ry; + to->node.rect.hasRx = from->node.rect.hasRx; + to->node.rect.hasRy = from->node.rect.hasRy; + break; + } + case SvgNodeType::Line: { + to->node.line.x1 = from->node.line.x1; + to->node.line.y1 = from->node.line.y1; + to->node.line.x2 = from->node.line.x2; + to->node.line.y2 = from->node.line.y2; + break; + } + case SvgNodeType::Path: { + if (from->node.path.path) to->node.path.path = new string(from->node.path.path->c_str()); + break; + } + case SvgNodeType::Polygon: { + to->node.polygon.pointsCount = from->node.polygon.pointsCount; + to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float)); + memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float)); + break; + } + case SvgNodeType::Polyline: { + to->node.polyline.pointsCount = from->node.polyline.pointsCount; + to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float)); + memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float)); + break; + } + case SvgNodeType::Image: { + to->node.image.x = from->node.image.x; + to->node.image.y = from->node.image.y; + to->node.image.w = from->node.image.w; + to->node.image.h = from->node.image.h; + if (from->node.image.href) to->node.image.href = new string(from->node.image.href->c_str()); + break; + } + default: { + break; + } + } +} + + +static void _cloneNode(SvgNode* from, SvgNode* parent) +{ + SvgNode* newNode; + if (!from || !parent) return; + + newNode = _createNode(parent, from->type); + + if (!newNode) return; + + _copyAttr(newNode, from); + + auto child = from->child.data; + for (uint32_t i = 0; i < from->child.count; ++i, ++child) { + _cloneNode(*child, newNode); + } +} + + +static void _postponeCloneNode(SvgLoaderData* loader, SvgNode *node, string* id) { + SvgNodeIdPair nodeIdPair; + nodeIdPair.node = node; + nodeIdPair.id = id; + loader->cloneNodes.push(nodeIdPair); +} + + +static void _clonePostponedNodes(Array* cloneNodes) { + for (uint32_t i = 0; i < cloneNodes->count; ++i) { + SvgNodeIdPair nodeIdPair = cloneNodes->data[i]; + SvgNode *defs = _getDefsNode(nodeIdPair.node); + SvgNode *nodeFrom = _findChildById(defs, nodeIdPair.id->c_str()); + _cloneNode(nodeFrom, nodeIdPair.node); + delete nodeIdPair.id; + } +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} useTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)} +}; + + +static bool _attrParseUseNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode *defs, *nodeFrom, *node = loader->svgParse->node; + string* id; + + SvgUseNode* use = &(node->node.use); + int sz = strlen(key); + unsigned char* array = (unsigned char*)use; + for (unsigned int i = 0; i < sizeof(useTags) / sizeof(useTags[0]); i++) { + if (useTags[i].sz - 1 == sz && !strncmp(useTags[i].tag, key, sz)) { + *((float*)(array + useTags[i].offset)) = _toFloat(loader->svgParse, value, useTags[i].type); + return true; + } + } + + if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + id = _idFromHref(value); + defs = _getDefsNode(node); + nodeFrom = _findChildById(defs, id->c_str()); + if (nodeFrom) { + _cloneNode(nodeFrom, node); + delete id; + } else { + //some svg export software include element at the end of the file + //if so the 'from' element won't be found now and we have to repeat finding + //after the whole file is parsed + _postponeCloneNode(loader, node, id); + } + } else if (!strcmp(key, "clip-path")) { + _handleClipPathAttr(loader, node, value); + } else if (!strcmp(key, "mask")) { + _handleMaskAttr(loader, node, value); + } else { + return _attrParseGNode(data, key, value); + } + return true; +} + + +static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Use); + + if (!loader->svgParse->node) return nullptr; + + simpleXmlParseAttributes(buf, bufLength, _attrParseUseNode, loader); + return loader->svgParse->node; +} + +//TODO: Implement 'text' primitive +static constexpr struct +{ + const char* tag; + int sz; + FactoryMethod tagHandler; +} graphicsTags[] = { + {"use", sizeof("use"), _createUseNode}, + {"circle", sizeof("circle"), _createCircleNode}, + {"ellipse", sizeof("ellipse"), _createEllipseNode}, + {"path", sizeof("path"), _createPathNode}, + {"polygon", sizeof("polygon"), _createPolygonNode}, + {"rect", sizeof("rect"), _createRectNode}, + {"polyline", sizeof("polyline"), _createPolylineNode}, + {"line", sizeof("line"), _createLineNode}, + {"image", sizeof("image"), _createImageNode} +}; + + +static constexpr struct +{ + const char* tag; + int sz; + FactoryMethod tagHandler; +} groupTags[] = { + {"defs", sizeof("defs"), _createDefsNode}, + {"g", sizeof("g"), _createGNode}, + {"svg", sizeof("svg"), _createSvgNode}, + {"mask", sizeof("mask"), _createMaskNode}, + {"clipPath", sizeof("clipPath"), _createClipPathNode} +}; + + +#define FIND_FACTORY(Short_Name, Tags_Array) \ + static FactoryMethod \ + _find##Short_Name##Factory(const char* name) \ + { \ + unsigned int i; \ + int sz = strlen(name); \ + \ + for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \ + if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \ + return Tags_Array[i].tagHandler; \ + } \ + } \ + return nullptr; \ + } + +FIND_FACTORY(Group, groupTags) +FIND_FACTORY(Graphics, graphicsTags) + + +FillSpread _parseSpreadValue(const char* value) +{ + auto spread = FillSpread::Pad; + + if (!strcmp(value, "reflect")) { + spread = FillSpread::Reflect; + } else if (!strcmp(value, "repeat")) { + spread = FillSpread::Repeat; + } + + return spread; +} + + +static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->cx = _gradientToFloat(loader->svgParse, value, radial->isCxPercentage); + if (!loader->svgParse->gradient.parsedFx) { + radial->fx = radial->cx; + radial->isFxPercentage = radial->isCxPercentage; + } +} + + +static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->cy = _gradientToFloat(loader->svgParse, value, radial->isCyPercentage); + if (!loader->svgParse->gradient.parsedFy) { + radial->fy = radial->cy; + radial->isFyPercentage = radial->isCyPercentage; + } +} + + +static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fx = _gradientToFloat(loader->svgParse, value, radial->isFxPercentage); + loader->svgParse->gradient.parsedFx = true; +} + + +static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fy = _gradientToFloat(loader->svgParse, value, radial->isFyPercentage); + loader->svgParse->gradient.parsedFy = true; +} + + +static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->r = _gradientToFloat(loader->svgParse, value, radial->isRPercentage); +} + + +static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (userSpace && !radial->isCxPercentage) radial->cx = radial->cx / loader->svgParse->global.w; +} + + +static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (userSpace && !radial->isCyPercentage) radial->cy = radial->cy / loader->svgParse->global.h; +} + + +static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (userSpace && !radial->isFxPercentage) radial->fx = radial->fx / loader->svgParse->global.w; +} + + +static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (userSpace && !radial->isFyPercentage) radial->fy = radial->fy / loader->svgParse->global.h; +} + + +static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrtf(2.0)); +} + + +typedef void (*radialMethod)(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value); +typedef void (*radialMethodRecalc)(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace); + + +#define RADIAL_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _handleRadial##Name1##Attr, _recalcRadial##Name1##Attr \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + radialMethod tagHandler; + radialMethodRecalc tagRecalc; +} radialTags[] = { + RADIAL_DEF(cx, Cx), + RADIAL_DEF(cy, Cy), + RADIAL_DEF(fx, Fx), + RADIAL_DEF(fy, Fy), + RADIAL_DEF(r, R) +}; + + +static bool _attrParseRadialGradientNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgStyleGradient* grad = loader->svgParse->styleGrad; + SvgRadialGradient* radial = grad->radial; + int sz = strlen(key); + + for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { + if (radialTags[i].sz - 1 == sz && !strncmp(radialTags[i].tag, key, sz)) { + radialTags[i].tagHandler(loader, radial, value); + return true; + } + } + + if (!strcmp(key, "id")) { + grad->id = _copyId(value); + } else if (!strcmp(key, "spreadMethod")) { + grad->spread = _parseSpreadValue(value); + } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + grad->ref = _idFromHref(value); + } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { + grad->userSpace = true; + } else if (!strcmp(key, "gradientTransform")) { + grad->transform = _parseTransformationMatrix(value); + } else { + return false; + } + + return true; +} + + +static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength) +{ + auto grad = new SvgStyleGradient; + loader->svgParse->styleGrad = grad; + + grad->type = SvgGradientType::Radial; + grad->userSpace = false; + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + if (!grad->radial) { + delete(grad); + return nullptr; + } + /** + * Default values of gradient transformed into global percentage + */ + grad->radial->cx = 0.5f; + grad->radial->cy = 0.5f; + grad->radial->fx = 0.5f; + grad->radial->fy = 0.5f; + grad->radial->r = 0.5f; + grad->radial->isCxPercentage = true; + grad->radial->isCyPercentage = true; + grad->radial->isFxPercentage = true; + grad->radial->isFyPercentage = true; + grad->radial->isRPercentage = true; + + loader->svgParse->gradient.parsedFx = false; + loader->svgParse->gradient.parsedFy = false; + simpleXmlParseAttributes(buf, bufLength, + _attrParseRadialGradientNode, loader); + + for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { + radialTags[i].tagRecalc(loader, grad->radial, grad->userSpace); + } + + return loader->svgParse->styleGrad; +} + + +static bool _attrParseStopsStyle(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + auto stop = &loader->svgParse->gradStop; + + if (!strcmp(key, "stop-opacity")) { + stop->a = _toOpacity(value); + loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopOpacity); + } else if (!strcmp(key, "stop-color")) { + _toColor(value, &stop->r, &stop->g, &stop->b, nullptr); + loader->svgParse->flags = (SvgStopStyleFlags)((int)loader->svgParse->flags | (int)SvgStopStyleFlags::StopColor); + } else { + return false; + } + + return true; +} + + +static bool _attrParseStops(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + auto stop = &loader->svgParse->gradStop; + + if (!strcmp(key, "offset")) { + stop->offset = _toOffset(value); + } else if (!strcmp(key, "stop-opacity")) { + if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopOpacity)) { + stop->a = _toOpacity(value); + } + } else if (!strcmp(key, "stop-color")) { + if (!((int)loader->svgParse->flags & (int)SvgStopStyleFlags::StopColor)) { + _toColor(value, &stop->r, &stop->g, &stop->b, nullptr); + } + } else if (!strcmp(key, "style")) { + simpleXmlParseW3CAttribute(value, _attrParseStopsStyle, data); + } else { + return false; + } + + return true; +} + + +static void _handleLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->x1 = _gradientToFloat(loader->svgParse, value, linear->isX1Percentage); +} + + +static void _handleLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->y1 = _gradientToFloat(loader->svgParse, value, linear->isY1Percentage); +} + + +static void _handleLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->x2 = _gradientToFloat(loader->svgParse, value, linear->isX2Percentage); +} + + +static void _handleLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->y2 = _gradientToFloat(loader->svgParse, value, linear->isY2Percentage); +} + + +static void _recalcLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (userSpace && !linear->isX1Percentage) linear->x1 = linear->x1 / loader->svgParse->global.w; +} + + +static void _recalcLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (userSpace && !linear->isY1Percentage) linear->y1 = linear->y1 / loader->svgParse->global.h; +} + + +static void _recalcLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (userSpace && !linear->isX2Percentage) linear->x2 = linear->x2 / loader->svgParse->global.w; +} + + +static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (userSpace && !linear->isY2Percentage) linear->y2 = linear->y2 / loader->svgParse->global.h; +} + + +typedef void (*Linear_Method)(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value); +typedef void (*Linear_Method_Recalc)(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace); + + +#define LINEAR_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _handleLinear##Name1##Attr, _recalcLinear##Name1##Attr \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + Linear_Method tagHandler; + Linear_Method_Recalc tagRecalc; +} linear_tags[] = { + LINEAR_DEF(x1, X1), + LINEAR_DEF(y1, Y1), + LINEAR_DEF(x2, X2), + LINEAR_DEF(y2, Y2) +}; + + +static bool _attrParseLinearGradientNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgStyleGradient* grad = loader->svgParse->styleGrad; + SvgLinearGradient* linear = grad->linear; + int sz = strlen(key); + + for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { + if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) { + linear_tags[i].tagHandler(loader, linear, value); + return true; + } + } + + if (!strcmp(key, "id")) { + grad->id = _copyId(value); + } else if (!strcmp(key, "spreadMethod")) { + grad->spread = _parseSpreadValue(value); + } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + grad->ref = _idFromHref(value); + } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { + grad->userSpace = true; + } else if (!strcmp(key, "gradientTransform")) { + grad->transform = _parseTransformationMatrix(value); + } else { + return false; + } + + return true; +} + + +static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength) +{ + auto grad = new SvgStyleGradient; + loader->svgParse->styleGrad = grad; + + grad->type = SvgGradientType::Linear; + grad->userSpace = false; + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + if (!grad->linear) { + delete(grad); + return nullptr; + } + /** + * Default value of x2 is 100% - transformed to the global percentage + */ + grad->linear->x2 = 1.0f; + grad->linear->isX2Percentage = true; + + simpleXmlParseAttributes(buf, bufLength, _attrParseLinearGradientNode, loader); + + for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { + linear_tags[i].tagRecalc(loader, grad->linear, grad->userSpace); + } + + return loader->svgParse->styleGrad; +} + + +#define GRADIENT_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _create##Name1 \ + } + + +/** + * In the case when the gradients lengths are given as numbers (not percentages) + * in the current user coordinate system, they are recalculated into percentages + * related to the canvas width and height. + */ +static constexpr struct +{ + const char* tag; + int sz; + GradientFactoryMethod tagHandler; +} gradientTags[] = { + GRADIENT_DEF(linearGradient, LinearGradient), + GRADIENT_DEF(radialGradient, RadialGradient) +}; + + +static GradientFactoryMethod _findGradientFactory(const char* name) +{ + int sz = strlen(name); + + for (unsigned int i = 0; i < sizeof(gradientTags) / sizeof(gradientTags[0]); i++) { + if (gradientTags[i].sz - 1 == sz && !strncmp(gradientTags[i].tag, name, sz)) { + return gradientTags[i].tagHandler; + } + } + return nullptr; +} + + +static constexpr struct +{ + const char* tag; + size_t sz; +} popArray[] = { + {"g", sizeof("g")}, + {"svg", sizeof("svg")}, + {"defs", sizeof("defs")}, + {"mask", sizeof("mask")}, + {"clipPath", sizeof("clipPath")} +}; + + +static void _svgLoaderParerXmlClose(SvgLoaderData* loader, const char* content) +{ + content = _skipSpace(content, nullptr); + + for (unsigned int i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) { + if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) { + loader->stack.pop(); + break; + } + } + + loader->level--; +} + + +static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length, bool empty) +{ + const char* attrs = nullptr; + int attrsLength = 0; + int sz = length; + char tagName[20] = ""; + FactoryMethod method; + GradientFactoryMethod gradientMethod; + SvgNode *node = nullptr, *parent = nullptr; + loader->level++; + attrs = simpleXmlFindAttributesTag(content, length); + + if (!attrs) { + //Parse the empty tag + attrs = content; + while ((attrs != nullptr) && *attrs != '>') attrs++; + } + + if (attrs) { + //Find out the tag name starting from content till sz length + sz = attrs - content; + while ((sz > 0) && (isspace(content[sz - 1]))) sz--; + if ((unsigned)sz >= sizeof(tagName)) return; + strncpy(tagName, content, sz); + tagName[sz] = '\0'; + attrsLength = length - sz; + } + + if ((method = _findGroupFactory(tagName))) { + //Group + if (!loader->doc) { + if (strcmp(tagName, "svg")) return; //Not a valid svg document + node = method(loader, nullptr, attrs, attrsLength); + loader->doc = node; + } else { + if (!strcmp(tagName, "svg")) return; //Already loaded (SvgNodeType::Doc) tag + if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1]; + else parent = loader->doc; + node = method(loader, parent, attrs, attrsLength); + } + + if (!node) return; + if (node->type != SvgNodeType::Defs || !empty) { + loader->stack.push(node); + } + } else if ((method = _findGraphicsFactory(tagName))) { + if (loader->stack.count > 0) parent = loader->stack.data[loader->stack.count - 1]; + else parent = loader->doc; + node = method(loader, parent, attrs, attrsLength); + } else if ((gradientMethod = _findGradientFactory(tagName))) { + SvgStyleGradient* gradient; + gradient = gradientMethod(loader, attrs, attrsLength); + //FIXME: The current parsing structure does not distinguish end tags. + // There is no way to know if the currently parsed gradient is in defs. + // If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs. + // But finally, the loader has a gradient style list regardless of defs. + // This is only to support this when multiple gradients are declared, even if no defs are declared. + // refer to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs + if (loader->def && loader->doc->node.doc.defs) { + loader->def->node.defs.gradients.push(gradient); + } else { + loader->gradients.push(gradient); + } + loader->latestGradient = gradient; + } else if (!strcmp(tagName, "stop")) { + if (!loader->latestGradient) { + TVGLOG("SVG", "Stop element is used outside of the Gradient element"); + return; + } + /* default value for opacity */ + loader->svgParse->gradStop = {0.0f, 0, 0, 0, 255}; + simpleXmlParseAttributes(attrs, attrsLength, _attrParseStops, loader); + loader->latestGradient->stops.push(loader->svgParse->gradStop); + } else if (!isIgnoreUnsupportedLogElements(tagName)) { + TVGLOG("SVG", "Unsupported elements used [Elements: %s]", tagName); + } +} + + +static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content, unsigned int length) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + + switch (type) { + case SimpleXMLType::Open: { + _svgLoaderParserXmlOpen(loader, content, length, false); + break; + } + case SimpleXMLType::OpenEmpty: { + _svgLoaderParserXmlOpen(loader, content, length, true); + break; + } + case SimpleXMLType::Close: { + _svgLoaderParerXmlClose(loader, content); + break; + } + case SimpleXMLType::Data: + case SimpleXMLType::CData: + case SimpleXMLType::DoctypeChild: { + break; + } + case SimpleXMLType::Ignored: + case SimpleXMLType::Comment: + case SimpleXMLType::Doctype: { + break; + } + default: { + break; + } + } + + return true; +} + + +static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent) +{ + if (parent == nullptr) return; + //Inherit the property of parent if not present in child. + //Fill + if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) { + child->fill.paint.color = parent->fill.paint.color; + child->fill.paint.none = parent->fill.paint.none; + child->fill.paint.curColor = parent->fill.paint.curColor; + if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url->c_str()); + } else if (child->fill.paint.curColor && !child->curColorSet) { + child->color = parent->color; + } + if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) { + child->fill.opacity = parent->fill.opacity; + } + if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) { + child->fill.fillRule = parent->fill.fillRule; + } + //Stroke + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) { + child->stroke.paint.color = parent->stroke.paint.color; + child->stroke.paint.none = parent->stroke.paint.none; + child->stroke.paint.curColor = parent->stroke.paint.curColor; + child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url->c_str()) : nullptr; + } else if (child->stroke.paint.curColor && !child->curColorSet) { + child->color = parent->color; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) { + child->stroke.opacity = parent->stroke.opacity; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) { + child->stroke.width = parent->stroke.width; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) { + if (parent->stroke.dash.array.count > 0) { + child->stroke.dash.array.clear(); + child->stroke.dash.array.reserve(parent->stroke.dash.array.count); + for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) { + child->stroke.dash.array.push(parent->stroke.dash.array.data[i]); + } + } + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) { + child->stroke.cap = parent->stroke.cap; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { + child->stroke.join = parent->stroke.join; + } +} + + +static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node){ +#ifdef THORVG_LOG_ENABLED + auto type = simpleXmlNodeTypeToString(node->type); + + if (!node->display && node->type != SvgNodeType::ClipPath) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type); + if (node->style->opacity == 0) TVGLOG("SVG", "Inefficient elements used [Opacity is zero][Node Type : %s]", type); + if (node->style->fill.opacity == 0 && node->style->stroke.opacity == 0) TVGLOG("SVG", "Inefficient elements used [Fill opacity and stroke opacity are zero][Node Type : %s]", type); + + switch (node->type) { + case SvgNodeType::Path: { + if (!node->node.path.path || node->node.path.path->empty()) TVGLOG("SVG", "Inefficient elements used [Empty path][Node Type : %s]", type); + break; + } + case SvgNodeType::Ellipse: { + if (node->node.ellipse.rx == 0 && node->node.ellipse.ry == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type); + break; + } + case SvgNodeType::Polygon: + case SvgNodeType::Polyline: { + if (node->node.polygon.pointsCount < 2) TVGLOG("SVG", "Inefficient elements used [Invalid Polygon][Node Type : %s]", type); + break; + } + case SvgNodeType::Circle: { + if (node->node.circle.r == 0) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type); + break; + } + case SvgNodeType::Rect: { + if (node->node.rect.w == 0 && node->node.rect.h) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type); + break; + } + case SvgNodeType::Line: { + if (node->node.line.x1 == node->node.line.x2 && node->node.line.y1 == node->node.line.y2) TVGLOG("SVG", "Inefficient elements used [Size is zero][Node Type : %s]", type); + break; + } + default: break; + } +#endif +} + + +static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle) +{ + _styleInherit(node->style, parentStyle); + _inefficientNodeCheck(node); + + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + _updateStyle(*child, node->style); + } +} + + +static SvgStyleGradient* _gradientDup(Array* gradients, const string* id) +{ + SvgStyleGradient* result = nullptr; + + auto gradList = gradients->data; + + for (uint32_t i = 0; i < gradients->count; ++i) { + if (!((*gradList)->id->compare(*id))) { + result = _cloneGradient(*gradList); + break; + } + ++gradList; + } + + if (result && result->ref) { + gradList = gradients->data; + for (uint32_t i = 0; i < gradients->count; ++i) { + if (!((*gradList)->id->compare(*result->ref))) { + if (result->stops.count == 0) { + _cloneGradStops(result->stops, (*gradList)->stops); + } + //TODO: Properly inherit other property + break; + } + ++gradList; + } + } + + return result; +} + + +static void _updateGradient(SvgNode* node, Array* gradients) +{ + if (node->child.count > 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + _updateGradient(*child, gradients); + } + } else { + if (node->style->fill.paint.url) { + if (node->style->fill.paint.gradient) delete(node->style->fill.paint.gradient); + node->style->fill.paint.gradient = _gradientDup(gradients, node->style->fill.paint.url); + } + if (node->style->stroke.paint.url) { + if (node->style->stroke.paint.gradient) delete(node->style->stroke.paint.gradient); + node->style->stroke.paint.gradient = _gradientDup(gradients, node->style->stroke.paint.url); + } + } +} + + +static void _updateComposite(SvgNode* node, SvgNode* root) +{ + if (node->style->clipPath.url && !node->style->clipPath.node) { + SvgNode *findResult = _findNodeById(root, node->style->clipPath.url); + if (findResult) node->style->clipPath.node = findResult; + } + if (node->style->mask.url && !node->style->mask.node) { + SvgNode *findResult = _findNodeById(root, node->style->mask.url); + if (findResult) node->style->mask.node = findResult; + } + if (node->child.count > 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + _updateComposite(*child, root); + } + } +} + + +static void _freeNodeStyle(SvgStyleProperty* style) +{ + if (!style) return; + + //style->clipPath.node and style->mask.node has only the addresses of node. Therefore, node is released from _freeNode. + delete(style->clipPath.url); + delete(style->mask.url); + + if (style->fill.paint.gradient) delete(style->fill.paint.gradient); + if (style->stroke.paint.gradient) delete(style->stroke.paint.gradient); + delete(style->fill.paint.url); + delete(style->stroke.paint.url); + style->stroke.dash.array.reset(); + free(style); +} + + +static void _freeNode(SvgNode* node) +{ + if (!node) return; + + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + _freeNode(*child); + } + node->child.reset(); + + delete node->id; + free(node->transform); + _freeNodeStyle(node->style); + switch (node->type) { + case SvgNodeType::Path: { + delete node->node.path.path; + break; + } + case SvgNodeType::Polygon: { + free(node->node.polygon.points); + break; + } + case SvgNodeType::Polyline: { + free(node->node.polyline.points); + break; + } + case SvgNodeType::Doc: { + _freeNode(node->node.doc.defs); + break; + } + case SvgNodeType::Defs: { + auto gradients = node->node.defs.gradients.data; + for (size_t i = 0; i < node->node.defs.gradients.count; ++i) { + delete(*gradients); + ++gradients; + } + node->node.defs.gradients.reset(); + break; + } + case SvgNodeType::Image: { + delete node->node.image.href; + break; + } + default: { + break; + } + } + free(node); +} + + +static bool _svgLoaderParserForValidCheckXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length) +{ + const char* attrs = nullptr; + int sz = length; + char tagName[20] = ""; + FactoryMethod method; + SvgNode *node = nullptr; + int attrsLength = 0; + loader->level++; + attrs = simpleXmlFindAttributesTag(content, length); + + if (!attrs) { + //Parse the empty tag + attrs = content; + while ((attrs != nullptr) && *attrs != '>') attrs++; + } + + if (attrs) { + sz = attrs - content; + while ((sz > 0) && (isspace(content[sz - 1]))) sz--; + if ((unsigned)sz >= sizeof(tagName)) return false; + strncpy(tagName, content, sz); + tagName[sz] = '\0'; + attrsLength = length - sz; + } + + if ((method = _findGroupFactory(tagName))) { + if (!loader->doc) { + if (strcmp(tagName, "svg")) return true; //Not a valid svg document + node = method(loader, nullptr, attrs, attrsLength); + loader->doc = node; + loader->stack.push(node); + return false; + } + } + return true; +} + + +static bool _svgLoaderParserForValidCheck(void* data, SimpleXMLType type, const char* content, unsigned int length) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + bool res = true;; + + switch (type) { + case SimpleXMLType::Open: + case SimpleXMLType::OpenEmpty: { + //If 'res' is false, it means tag is found. + res = _svgLoaderParserForValidCheckXmlOpen(loader, content, length); + break; + } + default: { + break; + } + } + + return res; +} + + +void SvgLoader::clear() +{ + if (copy) free((char*)content); + size = 0; + content = nullptr; + copy = false; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SvgLoader::SvgLoader() +{ +} + + +SvgLoader::~SvgLoader() +{ + close(); +} + + +void SvgLoader::run(unsigned tid) +{ + if (!simpleXmlParse(content, size, true, _svgLoaderParser, &(loaderData))) return; + + if (loaderData.doc) { + _updateStyle(loaderData.doc, nullptr); + auto defs = loaderData.doc->node.doc.defs; + if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients); + + if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients); + + _updateComposite(loaderData.doc, loaderData.doc); + if (defs) _updateComposite(loaderData.doc, defs); + + if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes); + } + root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, preserveAspect, svgPath); +} + + +bool SvgLoader::header() +{ + //For valid check, only tag is parsed first. + //If the tag is found, the loaded file is valid and stores viewbox information. + //After that, the remaining content data is parsed in order with async. + loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser)); + if (!loaderData.svgParse) return false; + + loaderData.svgParse->flags = SvgStopStyleFlags::StopDefault; + + simpleXmlParse(content, size, true, _svgLoaderParserForValidCheck, &(loaderData)); + + if (loaderData.doc && loaderData.doc->type == SvgNodeType::Doc) { + //Return the brief resource info such as viewbox: + vx = loaderData.doc->node.doc.vx; + vy = loaderData.doc->node.doc.vy; + w = vw = loaderData.doc->node.doc.vw; + h = vh = loaderData.doc->node.doc.vh; + + //Override size + if (loaderData.doc->node.doc.w > 0) { + w = loaderData.doc->node.doc.w; + if (vw < FLT_EPSILON) vw = w; + } + if (loaderData.doc->node.doc.h > 0) { + h = loaderData.doc->node.doc.h; + if (vh < FLT_EPSILON) vh = h; + } + + preserveAspect = loaderData.doc->node.doc.preserveAspect; + } else { + TVGLOG("SVG", "No SVG File. There is no "); + return false; + } + + return true; +} + + +bool SvgLoader::open(const char* data, uint32_t size, bool copy) +{ + clear(); + + if (copy) { + content = (char*)malloc(size); + if (!content) return false; + memcpy((char*)content, data, size); + } else content = data; + + this->size = size; + this->copy = copy; + + return header(); +} + + +bool SvgLoader::open(const string& path) +{ + clear(); + + ifstream f; + f.open(path); + + if (!f.is_open()) return false; + + svgPath = path; + getline(f, filePath, '\0'); + f.close(); + + if (filePath.empty()) return false; + + content = filePath.c_str(); + size = filePath.size(); + + return header(); +} + + +bool SvgLoader::resize(Paint* paint, float w, float h) +{ + if (!paint) return false; + + auto sx = w / this->w; + auto sy = h / this->h; + + if (preserveAspect) { + //Scale + auto scale = sx < sy ? sx : sy; + paint->scale(scale); + //Align + auto tx = 0.0f; + auto ty = 0.0f; + auto tw = this->w * scale; + auto th = this->h * scale; + if (tw > th) ty -= (h - th) * 0.5f; + else tx -= (w - tw) * 0.5f; + paint->translate(-tx, -ty); + } else { + //Align + auto tx = 0.0f; + auto ty = 0.0f; + auto tw = this->w * sx; + auto th = this->h * sy; + if (tw > th) ty -= (h - th) * 0.5f; + else tx -= (w - tw) * 0.5f; + + Matrix m = {sx, 0, -tx, 0, sy, -ty, 0, 0, 1}; + paint->transform(m); + } + return true; +} + + +bool SvgLoader::read() +{ + if (!content || size == 0) return false; + + TaskScheduler::request(this); + + return true; +} + + +bool SvgLoader::close() +{ + this->done(); + + if (loaderData.svgParse) { + free(loaderData.svgParse); + loaderData.svgParse = nullptr; + } + auto gradients = loaderData.gradients.data; + for (size_t i = 0; i < loaderData.gradients.count; ++i) { + delete(*gradients); + ++gradients; + } + loaderData.gradients.reset(); + + _freeNode(loaderData.doc); + loaderData.doc = nullptr; + loaderData.stack.reset(); + + clear(); + + return true; +} + + +unique_ptr SvgLoader::paint() +{ + this->done(); + if (root) return move(root); + else return nullptr; +} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h new file mode 100644 index 0000000000..468f05801d --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SVG_LOADER_H_ +#define _TVG_SVG_LOADER_H_ + +#include "tvgTaskScheduler.h" +#include "tvgSvgLoaderCommon.h" + +class SvgLoader : public LoadModule, public Task +{ +public: + string filePath; + string svgPath = ""; + const char* content = nullptr; + uint32_t size = 0; + + SvgLoaderData loaderData; + unique_ptr root; + + bool copy = false; + + SvgLoader(); + ~SvgLoader(); + + using LoadModule::open; + bool open(const string& path) override; + bool open(const char* data, uint32_t size, bool copy) override; + bool resize(Paint* paint, float w, float h) override; + bool read() override; + bool close() override; + unique_ptr paint() override; + +private: + bool header(); + void clear(); + void run(unsigned tid) override; +}; + + +#endif //_TVG_SVG_LOADER_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h new file mode 100644 index 0000000000..b7c69cd642 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_SVG_LOADER_COMMON_H_ +#define _TVG_SVG_LOADER_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgArray.h" + +struct SvgNode; +struct SvgStyleGradient; + +//NOTE: Please update simpleXmlNodeTypeToString() as well. +enum class SvgNodeType +{ + Doc, + G, + Defs, + Animation, + Arc, + Circle, + Ellipse, + Image, + Line, + Path, + Polygon, + Polyline, + Rect, + Text, + TextArea, + Tspan, + Use, + Video, + ClipPath, + Mask, + Unknown +}; + +/* +// TODO - remove? +enum class SvgLengthType +{ + Percent, + Px, + Pc, + Pt, + Mm, + Cm, + In, +}; +*/ + +enum class SvgFillFlags +{ + Paint = 0x01, + Opacity = 0x02, + Gradient = 0x04, + FillRule = 0x08, + ClipPath = 0x16 +}; + +enum class SvgStrokeFlags +{ + Paint = 0x1, + Opacity = 0x2, + Gradient = 0x4, + Scale = 0x8, + Width = 0x10, + Cap = 0x20, + Join = 0x40, + Dash = 0x80, +}; + +enum class SvgGradientType +{ + Linear, + Radial +}; + +enum class SvgStyleFlags +{ + Color = 0x01, + Fill = 0x02, + FillRule = 0x04, + FillOpacity = 0x08, + Opacity = 0x010, + Stroke = 0x20, + StrokeWidth = 0x40, + StrokeLineJoin = 0x80, + StrokeLineCap = 0x100, + StrokeOpacity = 0x200, + StrokeDashArray = 0x400, + Transform = 0x800, + ClipPath = 0x1000, + Mask = 0x2000, + Display = 0x4000 +}; + +enum class SvgStopStyleFlags +{ + StopDefault = 0x0, + StopOpacity = 0x01, + StopColor = 0x02 +}; + +enum class SvgFillRule +{ + Winding = 0, + OddEven = 1 +}; + +//Length type to recalculate %, pt, pc, mm, cm etc +enum class SvgParserLengthType +{ + Vertical, + Horizontal, + //In case of, for example, radius of radial gradient + Other +}; + +struct SvgDocNode +{ + float w; + float h; + float vx; + float vy; + float vw; + float vh; + SvgNode* defs; + bool preserveAspect; +}; + +struct SvgGNode +{ +}; + +struct SvgDefsNode +{ + Array gradients; +}; + +struct SvgUseNode +{ + float x, y, w, h; +}; + + +struct SvgEllipseNode +{ + float cx; + float cy; + float rx; + float ry; +}; + +struct SvgCircleNode +{ + float cx; + float cy; + float r; +}; + +struct SvgRectNode +{ + float x; + float y; + float w; + float h; + float rx; + float ry; + bool hasRx; + bool hasRy; +}; + +struct SvgLineNode +{ + float x1; + float y1; + float x2; + float y2; +}; + +struct SvgImageNode +{ + float x, y, w, h; + string *href; +}; + +struct SvgPathNode +{ + string* path; +}; + +struct SvgPolygonNode +{ + int pointsCount; + float* points; +}; + +struct SvgLinearGradient +{ + float x1; + float y1; + float x2; + float y2; + bool isX1Percentage; + bool isY1Percentage; + bool isX2Percentage; + bool isY2Percentage; + +}; + +struct SvgRadialGradient +{ + float cx; + float cy; + float fx; + float fy; + float r; + bool isCxPercentage; + bool isCyPercentage; + bool isFxPercentage; + bool isFyPercentage; + bool isRPercentage; +}; + +struct SvgComposite +{ + string *url; + SvgNode* node; + bool applying; //flag for checking circular dependency. +}; + +struct SvgColor +{ + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct SvgPaint +{ + SvgStyleGradient* gradient; + string *url; + SvgColor color; + bool none; + bool curColor; +}; + +struct SvgDash +{ + Array array; +}; + +struct SvgStyleGradient +{ + SvgGradientType type = SvgGradientType::Linear; + string *id = nullptr; + string *ref = nullptr; + FillSpread spread = FillSpread::Pad; + SvgRadialGradient* radial = nullptr; + SvgLinearGradient* linear = nullptr; + Matrix* transform = nullptr; + Array stops; + bool userSpace = false; + + ~SvgStyleGradient() + { + stops.reset(); + free(transform); + free(radial); + free(linear); + delete(ref); + delete(id); + } +}; + +struct SvgStyleFill +{ + SvgFillFlags flags; + SvgPaint paint; + int opacity; + FillRule fillRule; +}; + +struct SvgStyleStroke +{ + SvgStrokeFlags flags; + SvgPaint paint; + int opacity; + float scale; + float width; + float centered; + StrokeCap cap; + StrokeJoin join; + SvgDash dash; + int dashCount; +}; + +struct SvgStyleProperty +{ + SvgStyleFill fill; + SvgStyleStroke stroke; + SvgComposite clipPath; + SvgComposite mask; + int opacity; + SvgColor color; + bool curColorSet; + SvgStyleFlags flags; +}; + +struct SvgNode +{ + SvgNodeType type; + SvgNode* parent; + Array child; + string *id; + SvgStyleProperty *style; + Matrix* transform; + union { + SvgGNode g; + SvgDocNode doc; + SvgDefsNode defs; + SvgUseNode use; + SvgCircleNode circle; + SvgEllipseNode ellipse; + SvgPolygonNode polygon; + SvgPolygonNode polyline; + SvgRectNode rect; + SvgPathNode path; + SvgLineNode line; + SvgImageNode image; + } node; + bool display; + ~SvgNode(); +}; + +struct SvgParser +{ + SvgNode* node; + SvgStyleGradient* styleGrad; + Fill::ColorStop gradStop; + SvgStopStyleFlags flags; + struct + { + int x, y; + uint32_t w, h; + } global; + struct + { + bool parsedFx; + bool parsedFy; + } gradient; +}; + +struct SvgNodeIdPair +{ + SvgNode* node; + string *id; +}; + +struct SvgLoaderData +{ + Array stack = {nullptr, 0, 0}; + SvgNode* doc = nullptr; + SvgNode* def = nullptr; + Array gradients; + SvgStyleGradient* latestGradient = nullptr; //For stops + SvgParser* svgParse = nullptr; + Array cloneNodes; + int level = 0; + bool result = false; +}; + +/* + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtod-l-wcstod-wcstod-l?view=vs-2017 + * + * src should be one of the following form : + * + * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits] + * [whitespace] [sign] {INF | INFINITY} + * [whitespace] [sign] NAN [sequence] + * + * No hexadecimal form supported + * no sequence supported after NAN + */ +float customStrtof(const char *nptr, char **endptr); + +#endif diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp new file mode 100644 index 0000000000..2b62315de8 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Copyright notice for the EFL: + + * Copyright (C) EFL developers (see AUTHORS) + + * All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. + +#include +#include +#include +#include "tvgSvgLoaderCommon.h" +#include "tvgSvgPath.h" +#include "tvgSvgUtil.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static char* _skipComma(const char* content) +{ + while (*content && isspace(*content)) { + content++; + } + if (*content == ',') return (char*)content + 1; + return (char*)content; +} + + +static bool _parseNumber(char** content, float* number) +{ + char* end = NULL; + *number = svgUtilStrtof(*content, &end); + //If the start of string is not number + if ((*content) == end) return false; + //Skip comma if any + *content = _skipComma(end); + return true; +} + + +static bool _parseFlag(char** content, int* number) +{ + char* end = NULL; + if (*(*content) != '0' && *(*content) != '1') return false; + *number = *(*content) - '0'; + *content += 1; + end = *content; + *content = _skipComma(end); + + return true; +} + + +void _pathAppendArcTo(Array* cmds, Array* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep) +{ + float cxp, cyp, cx, cy; + float sx, sy; + float cosPhi, sinPhi; + float dx2, dy2; + float x1p, y1p; + float x1p2, y1p2; + float rx2, ry2; + float lambda; + float c; + float at; + float theta1, deltaTheta; + float nat; + float delta, bcp; + float cosPhiRx, cosPhiRy; + float sinPhiRx, sinPhiRy; + float cosTheta1, sinTheta1; + int segments; + + //Some helpful stuff is available here: + //http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + sx = cur->x; + sy = cur->y; + + //If start and end points are identical, then no arc is drawn + if ((fabsf(x - sx) < (1.0f / 256.0f)) && (fabsf(y - sy) < (1.0f / 256.0f))) return; + + //Correction of out-of-range radii, see F6.6.1 (step 2) + rx = fabsf(rx); + ry = fabsf(ry); + + angle = angle * M_PI / 180.0f; + cosPhi = cosf(angle); + sinPhi = sinf(angle); + dx2 = (sx - x) / 2.0f; + dy2 = (sy - y) / 2.0f; + x1p = cosPhi * dx2 + sinPhi * dy2; + y1p = cosPhi * dy2 - sinPhi * dx2; + x1p2 = x1p * x1p; + y1p2 = y1p * y1p; + rx2 = rx * rx; + ry2 = ry * ry; + lambda = (x1p2 / rx2) + (y1p2 / ry2); + + //Correction of out-of-range radii, see F6.6.2 (step 4) + if (lambda > 1.0f) { + //See F6.6.3 + float lambdaRoot = sqrtf(lambda); + + rx *= lambdaRoot; + ry *= lambdaRoot; + //Update rx2 and ry2 + rx2 = rx * rx; + ry2 = ry * ry; + } + + c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2); + + //Check if there is no possible solution + //(i.e. we can't do a square root of a negative value) + if (c < 0.0f) { + //Scale uniformly until we have a single solution + //(see F6.2) i.e. when c == 0.0 + float scale = sqrtf(1.0f - c / (rx2 * ry2)); + rx *= scale; + ry *= scale; + //Update rx2 and ry2 + rx2 = rx * rx; + ry2 = ry * ry; + + //Step 2 (F6.5.2) - simplified since c == 0.0 + cxp = 0.0f; + cyp = 0.0f; + //Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0 + cx = 0.0f; + cy = 0.0f; + } else { + //Complete c calculation + c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2))); + //Inverse sign if Fa == Fs + if (largeArc == sweep) c = -c; + + //Step 2 (F6.5.2) + cxp = c * (rx * y1p / ry); + cyp = c * (-ry * x1p / rx); + + //Step 3 (F6.5.3 first part) + cx = cosPhi * cxp - sinPhi * cyp; + cy = sinPhi * cxp + cosPhi * cyp; + } + + //Step 3 (F6.5.3 second part) we now have the center point of the ellipse + cx += (sx + x) / 2.0f; + cy += (sy + y) / 2.0f; + + //Sstep 4 (F6.5.4) + //We dont' use arccos (as per w3c doc), see + //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm + //Note: atan2 (0.0, 1.0) == 0.0 + at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx)); + theta1 = (at < 0.0f) ? 2.0f * M_PI + at : at; + + nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); + deltaTheta = (nat < at) ? 2.0f * M_PI - at + nat : nat - at; + + if (sweep) { + //Ensure delta theta < 0 or else add 360 degrees + if (deltaTheta < 0.0f) deltaTheta += (float)(2.0f * M_PI); + } else { + //Ensure delta theta > 0 or else substract 360 degrees + if (deltaTheta > 0.0f) deltaTheta -= (float)(2.0f * M_PI); + } + + //Add several cubic bezier to approximate the arc + //(smaller than 90 degrees) + //We add one extra segment because we want something + //Smaller than 90deg (i.e. not 90 itself) + segments = static_cast(fabsf(deltaTheta / float(M_PI_2)) + 1.0f); + delta = deltaTheta / segments; + + //http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13) + bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f); + + cosPhiRx = cosPhi * rx; + cosPhiRy = cosPhi * ry; + sinPhiRx = sinPhi * rx; + sinPhiRy = sinPhi * ry; + + cosTheta1 = cosf(theta1); + sinTheta1 = sinf(theta1); + + for (int i = 0; i < segments; ++i) { + //End angle (for this segment) = current + delta + float c1x, c1y, ex, ey, c2x, c2y; + float theta2 = theta1 + delta; + float cosTheta2 = cosf(theta2); + float sinTheta2 = sinf(theta2); + Point p[3]; + + //First control point (based on start point sx,sy) + c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1); + c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1); + + //End point (for this segment) + ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2); + ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2); + + //Second control point (based on end point ex,ey) + c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2); + c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2); + cmds->push(PathCommand::CubicTo); + p[0] = {c1x, c1y}; + p[1] = {c2x, c2y}; + p[2] = {ex, ey}; + pts->push(p[0]); + pts->push(p[1]); + pts->push(p[2]); + *curCtl = p[1]; + *cur = p[2]; + + //Next start point is the current end point (same for angle) + sx = ex; + sy = ey; + theta1 = theta2; + //Avoid recomputations + cosTheta1 = cosTheta2; + sinTheta1 = sinTheta2; + } +} + +static int _numberCount(char cmd) +{ + int count = 0; + switch (cmd) { + case 'M': + case 'm': + case 'L': + case 'l': + case 'T': + case 't': { + count = 2; + break; + } + case 'C': + case 'c': + case 'E': + case 'e': { + count = 6; + break; + } + case 'H': + case 'h': + case 'V': + case 'v': { + count = 1; + break; + } + case 'S': + case 's': + case 'Q': + case 'q': { + count = 4; + break; + } + case 'A': + case 'a': { + count = 7; + break; + } + default: + break; + } + return count; +} + + +static bool _processCommand(Array* cmds, Array* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic) +{ + switch (cmd) { + case 'm': + case 'l': + case 'c': + case 's': + case 'q': + case 't': { + for (int i = 0; i < count - 1; i += 2) { + arr[i] = arr[i] + cur->x; + arr[i + 1] = arr[i + 1] + cur->y; + } + break; + } + case 'h': { + arr[0] = arr[0] + cur->x; + break; + } + case 'v': { + arr[0] = arr[0] + cur->y; + break; + } + case 'a': { + arr[5] = arr[5] + cur->x; + arr[6] = arr[6] + cur->y; + break; + } + default: { + break; + } + } + + switch (cmd) { + case 'm': + case 'M': { + Point p = {arr[0], arr[1]}; + cmds->push(PathCommand::MoveTo); + pts->push(p); + *cur = {arr[0], arr[1]}; + *startPoint = {arr[0], arr[1]}; + break; + } + case 'l': + case 'L': { + Point p = {arr[0], arr[1]}; + cmds->push(PathCommand::LineTo); + pts->push(p); + *cur = {arr[0], arr[1]}; + break; + } + case 'c': + case 'C': { + Point p[3]; + cmds->push(PathCommand::CubicTo); + p[0] = {arr[0], arr[1]}; + p[1] = {arr[2], arr[3]}; + p[2] = {arr[4], arr[5]}; + pts->push(p[0]); + pts->push(p[1]); + pts->push(p[2]); + *curCtl = p[1]; + *cur = p[2]; + *isQuadratic = false; + break; + } + case 's': + case 'S': { + Point p[3], ctrl; + if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) && + !(*isQuadratic)) { + ctrl.x = 2 * cur->x - curCtl->x; + ctrl.y = 2 * cur->y - curCtl->y; + } else { + ctrl = *cur; + } + cmds->push(PathCommand::CubicTo); + p[0] = ctrl; + p[1] = {arr[0], arr[1]}; + p[2] = {arr[2], arr[3]}; + pts->push(p[0]); + pts->push(p[1]); + pts->push(p[2]); + *curCtl = p[1]; + *cur = p[2]; + *isQuadratic = false; + break; + } + case 'q': + case 'Q': { + Point p[3]; + float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0 / 3.0); + float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0 / 3.0); + float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0 / 3.0); + float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0 / 3.0); + cmds->push(PathCommand::CubicTo); + p[0] = {ctrl_x0, ctrl_y0}; + p[1] = {ctrl_x1, ctrl_y1}; + p[2] = {arr[2], arr[3]}; + pts->push(p[0]); + pts->push(p[1]); + pts->push(p[2]); + *curCtl = {arr[0], arr[1]}; + *cur = p[2]; + *isQuadratic = true; + break; + } + case 't': + case 'T': { + Point p[3], ctrl; + if ((cmds->count > 1) && (cmds->data[cmds->count - 1] == PathCommand::CubicTo) && + *isQuadratic) { + ctrl.x = 2 * cur->x - curCtl->x; + ctrl.y = 2 * cur->y - curCtl->y; + } else { + ctrl = *cur; + } + float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0 / 3.0); + float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0 / 3.0); + float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0 / 3.0); + float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0 / 3.0); + cmds->push(PathCommand::CubicTo); + p[0] = {ctrl_x0, ctrl_y0}; + p[1] = {ctrl_x1, ctrl_y1}; + p[2] = {arr[0], arr[1]}; + pts->push(p[0]); + pts->push(p[1]); + pts->push(p[2]); + *curCtl = {ctrl.x, ctrl.y}; + *cur = p[2]; + *isQuadratic = true; + break; + } + case 'h': + case 'H': { + Point p = {arr[0], cur->y}; + cmds->push(PathCommand::LineTo); + pts->push(p); + cur->x = arr[0]; + break; + } + case 'v': + case 'V': { + Point p = {cur->x, arr[0]}; + cmds->push(PathCommand::LineTo); + pts->push(p); + cur->y = arr[0]; + break; + } + case 'z': + case 'Z': { + cmds->push(PathCommand::Close); + *cur = *startPoint; + break; + } + case 'a': + case 'A': { + _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], arr[0], arr[1], arr[2], arr[3], arr[4]); + *cur = *curCtl = {arr[5], arr[6]}; + *isQuadratic = false; + break; + } + default: { + return false; + } + } + return true; +} + + +static char* _nextCommand(char* path, char* cmd, float* arr, int* count) +{ + int large, sweep; + + path = _skipComma(path); + if (isalpha(*path)) { + *cmd = *path; + path++; + *count = _numberCount(*cmd); + } else { + if (*cmd == 'm') *cmd = 'l'; + else if (*cmd == 'M') *cmd = 'L'; + } + if (*count == 7) { + //Special case for arc command + if (_parseNumber(&path, &arr[0])) { + if (_parseNumber(&path, &arr[1])) { + if (_parseNumber(&path, &arr[2])) { + if (_parseFlag(&path, &large)) { + if (_parseFlag(&path, &sweep)) { + if (_parseNumber(&path, &arr[5])) { + if (_parseNumber(&path, &arr[6])) { + arr[3] = (float)large; + arr[4] = (float)sweep; + return path; + } + } + } + } + } + } + } + *count = 0; + return NULL; + } + for (int i = 0; i < *count; i++) { + if (!_parseNumber(&path, &arr[i])) { + *count = 0; + return NULL; + } + path = _skipComma(path); + } + return path; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool svgPathToTvgPath(const char* svgPath, Array& cmds, Array& pts) +{ + float numberArray[7]; + int numberCount = 0; + Point cur = { 0, 0 }; + Point curCtl = { 0, 0 }; + Point startPoint = { 0, 0 }; + char cmd = 0; + bool isQuadratic = false; + char* path = (char*)svgPath; + char* curLocale; + + curLocale = setlocale(LC_NUMERIC, NULL); + if (curLocale) curLocale = strdup(curLocale); + setlocale(LC_NUMERIC, "POSIX"); + + while ((path[0] != '\0')) { + path = _nextCommand(path, &cmd, numberArray, &numberCount); + if (!path) break; + if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic)) break; + } + + setlocale(LC_NUMERIC, curLocale); + if (curLocale) free(curLocale); + + return true; +} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h new file mode 100644 index 0000000000..7f26c4a213 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SVG_PATH_H_ +#define _TVG_SVG_PATH_H_ + +#include + +bool svgPathToTvgPath(const char* svgPath, Array& cmds, Array& pts); + +#endif //_TVG_SVG_PATH_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp new file mode 100644 index 0000000000..f36d9df2aa --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -0,0 +1,651 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Copyright notice for the EFL: + + * Copyright (C) EFL developers (see AUTHORS) + + * All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include "tvgSvgLoaderCommon.h" +#include "tvgSvgSceneBuilder.h" +#include "tvgSvgPath.h" +#include "tvgSvgUtil.h" +#include + +static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh); +static unique_ptr _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath); + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static inline bool _isGroupType(SvgNodeType type) +{ + if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true; + return false; +} + + +static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf) +{ + gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13; + gradTransf->e12 *= mBBox->e11; + gradTransf->e11 *= mBBox->e11; + + gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23; + gradTransf->e22 *= mBBox->e22; + gradTransf->e21 *= mBBox->e22; +} + + +static unique_ptr _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, float rx, float ry, float rw, float rh, int opacity) +{ + Fill::ColorStop* stops; + int stopCount = 0; + auto fillGrad = LinearGradient::gen(); + + bool isTransform = (g->transform ? true : false); + Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if (isTransform) finalTransform = *g->transform; + + if (g->userSpace) { + g->linear->x1 = g->linear->x1 * rw; + g->linear->y1 = g->linear->y1 * rh; + g->linear->x2 = g->linear->x2 * rw; + g->linear->y2 = g->linear->y2 * rh; + } else { + Matrix m = {rw, 0, rx, 0, rh, ry, 0, 0, 1}; + if (isTransform) _transformMultiply(&m, &finalTransform); + else { + finalTransform = m; + isTransform = true; + } + } + + if (isTransform) fillGrad->transform(finalTransform); + + fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2); + fillGrad->spread(g->spread); + + //Update the stops + stopCount = g->stops.count; + if (stopCount > 0) { + stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); + if (!stops) return fillGrad; + auto prevOffset = 0.0f; + for (uint32_t i = 0; i < g->stops.count; ++i) { + auto colorStop = &g->stops.data[i]; + //Use premultiplied color + stops[i].r = colorStop->r; + stops[i].g = colorStop->g; + stops[i].b = colorStop->b; + stops[i].a = static_cast((colorStop->a * opacity) / 255); + stops[i].offset = colorStop->offset; + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; + else if (colorStop->offset > 1) stops[i].offset = 1; + prevOffset = stops[i].offset; + } + fillGrad->colorStops(stops, stopCount); + free(stops); + } + return fillGrad; +} + + +static unique_ptr _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, float rx, float ry, float rw, float rh, int opacity) +{ + Fill::ColorStop *stops; + int stopCount = 0; + auto fillGrad = RadialGradient::gen(); + + bool isTransform = (g->transform ? true : false); + Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + if (isTransform) finalTransform = *g->transform; + + if (g->userSpace) { + //The radius scalling is done according to the Units section: + //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + g->radial->cx = g->radial->cx * rw; + g->radial->cy = g->radial->cy * rh; + g->radial->r = g->radial->r * sqrtf(powf(rw, 2.0f) + powf(rh, 2.0f)) / sqrtf(2.0f); + g->radial->fx = g->radial->fx * rw; + g->radial->fy = g->radial->fy * rh; + } else { + Matrix m = {rw, 0, rx, 0, rh, ry, 0, 0, 1}; + if (isTransform) _transformMultiply(&m, &finalTransform); + else { + finalTransform = m; + isTransform = true; + } + } + + if (isTransform) fillGrad->transform(finalTransform); + + //TODO: Tvg is not support to focal + //if (g->radial->fx != 0 && g->radial->fy != 0) { + // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r); + //} + fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r); + fillGrad->spread(g->spread); + + //Update the stops + stopCount = g->stops.count; + if (stopCount > 0) { + stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); + if (!stops) return fillGrad; + auto prevOffset = 0.0f; + for (uint32_t i = 0; i < g->stops.count; ++i) { + auto colorStop = &g->stops.data[i]; + //Use premultiplied color + stops[i].r = colorStop->r; + stops[i].g = colorStop->g; + stops[i].b = colorStop->b; + stops[i].a = static_cast((colorStop->a * opacity) / 255); + stops[i].offset = colorStop->offset; + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; + else if (colorStop->offset > 1) stops[i].offset = 1; + prevOffset = stops[i].offset; + } + fillGrad->colorStops(stops, stopCount); + free(stops); + } + return fillGrad; +} + + +static bool _appendChildShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh) +{ + auto valid = false; + + if (_appendShape(node, shape, vx, vy, vw, vh)) valid = true; + + if (node->child.count > 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + if (_appendChildShape(*child, shape, vx, vy, vw, vh)) valid = true; + } + } + + return valid; +} + + +static void _applyComposition(Paint* paint, const SvgNode* node, float vx, float vy, float vw, float vh) +{ + /* ClipPath */ + /* Do not drop in Circular Dependency for ClipPath. + Composition can be applied recursively if its children nodes have composition target to this one. */ + if (node->style->clipPath.applying) { + TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?"); + } else { + auto compNode = node->style->clipPath.node; + if (compNode && compNode->child.count > 0) { + node->style->clipPath.applying = true; + + auto comp = Shape::gen(); + comp->fill(255, 255, 255, 255); + if (node->transform) comp->transform(*node->transform); + + auto child = compNode->child.data; + auto valid = false; //Composite only when valid shapes are existed + + for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) { + if (_appendChildShape(*child, comp.get(), vx, vy, vw, vh)) valid = true; + } + + if (valid) paint->composite(move(comp), CompositeMethod::ClipPath); + + node->style->clipPath.applying = false; + } + } + + /* Mask */ + /* Do not drop in Circular Dependency for Mask. + Composition can be applied recursively if its children nodes have composition target to this one. */ + if (node->style->mask.applying) { + TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?"); + } else { + auto compNode = node->style->mask.node; + if (compNode && compNode->child.count > 0) { + node->style->mask.applying = true; + + auto comp = Shape::gen(); + comp->fill(255, 255, 255, 255); + if (node->transform) comp->transform(*node->transform); + + auto child = compNode->child.data; + auto valid = false; //Composite only when valid shapes are existed + + for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) { + if (_appendChildShape(*child, comp.get(), vx, vy, vw, vh)) valid = true; + } + + if (valid) paint->composite(move(comp), CompositeMethod::AlphaMask); + + node->style->mask.applying = false; + } + } +} + + +static void _applyProperty(SvgNode* node, Shape* vg, float vx, float vy, float vw, float vh) +{ + SvgStyleProperty* style = node->style; + + if (node->transform) vg->transform(*node->transform); + if (node->type == SvgNodeType::Doc || !node->display) return; + + //If fill property is nullptr then do nothing + if (style->fill.paint.none) { + //Do nothing + } else if (style->fill.paint.gradient) { + if (!style->fill.paint.gradient->userSpace) { + vg->bounds(&vx, &vy, &vw, &vh, false); + //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph) + //a stroke width should be ignored for bounding box calculations + if (auto strokeW = vg->strokeWidth()) { + vx += 0.5f * strokeW; + vy += 0.5f * strokeW; + vw -= strokeW; + vh -= strokeW; + } + } + + if (style->fill.paint.gradient->type == SvgGradientType::Linear) { + auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh, style->fill.opacity); + vg->fill(move(linear)); + } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) { + auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh, style->fill.opacity); + vg->fill(move(radial)); + } + } else if (style->fill.paint.url) { + //TODO: Apply the color pointed by url + } else if (style->fill.paint.curColor) { + //Apply the current style color + vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity); + } else { + //Apply the fill color + vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity); + } + + //Apply the fill rule + vg->fill((tvg::FillRule)style->fill.fillRule); + + //Apply node opacity + if (style->opacity < 255) vg->opacity(style->opacity); + + if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return; + + //Apply the stroke style property + vg->stroke(style->stroke.width); + vg->stroke(style->stroke.cap); + vg->stroke(style->stroke.join); + if (style->stroke.dash.array.count > 0) { + vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count); + } + + //If stroke property is nullptr then do nothing + if (style->stroke.paint.none) { + //Do nothing + } else if (style->stroke.paint.gradient) { + if (!style->stroke.paint.gradient->userSpace) { + //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph) + //a stroke width should be ignored for bounding box calculations + vg->bounds(&vx, &vy, &vw, &vh, false); + if (auto strokeW = vg->strokeWidth()) { + vx += 0.5f * strokeW; + vy += 0.5f * strokeW; + vw -= strokeW; + vh -= strokeW; + } + } + + if (style->stroke.paint.gradient->type == SvgGradientType::Linear) { + auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, vx, vy, vw, vh, style->stroke.opacity); + vg->stroke(move(linear)); + } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) { + auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, vx, vy, vw, vh, style->stroke.opacity); + vg->stroke(move(radial)); + } + } else if (style->stroke.paint.url) { + //TODO: Apply the color pointed by url + } else if (style->stroke.paint.curColor) { + //Apply the current style color + vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity); + } else { + //Apply the stroke color + vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity); + } + + _applyComposition(vg, node, vx, vy, vw, vh); +} + + +static unique_ptr _shapeBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh) +{ + auto shape = Shape::gen(); + if (_appendShape(node, shape.get(), vx, vy, vw, vh)) return shape; + else return nullptr; +} + + +static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh) +{ + Array cmds; + Array pts; + + switch (node->type) { + case SvgNodeType::Path: { + if (node->node.path.path) { + if (svgPathToTvgPath(node->node.path.path->c_str(), cmds, pts)) { + shape->appendPath(cmds.data, cmds.count, pts.data, pts.count); + } + } + break; + } + case SvgNodeType::Ellipse: { + shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry); + break; + } + case SvgNodeType::Polygon: { + if (node->node.polygon.pointsCount < 2) break; + shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); + for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) { + shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); + } + shape->close(); + break; + } + case SvgNodeType::Polyline: { + if (node->node.polygon.pointsCount < 2) break; + shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); + for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) { + shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); + } + break; + } + case SvgNodeType::Circle: { + shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r); + break; + } + case SvgNodeType::Rect: { + shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry); + break; + } + case SvgNodeType::Line: { + shape->moveTo(node->node.line.x1, node->node.line.y1); + shape->lineTo(node->node.line.x2, node->node.line.y2); + break; + } + default: { + return false; + } + } + + _applyProperty(node, shape, vx, vy, vw, vh); + return true; +} + + +enum class imageMimeTypeEncoding +{ + base64 = 0x1, + utf8 = 0x2 +}; +constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { + return static_cast(static_cast(a) | static_cast(b)); +} +constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { + return (static_cast(a) & static_cast(b)); +} + + +static constexpr struct +{ + const char* name; + int sz; + imageMimeTypeEncoding encoding; +} imageMimeTypes[] = { + {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64}, + {"png", sizeof("png"), imageMimeTypeEncoding::base64}, + {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8}, +}; + + +static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) { + if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type + *href += sizeof("image/") - 1; + + //RFC2397 data:[][;base64], + //mediatype := [ type "/" subtype ] *( ";" parameter ) + //parameter := attribute "=" value + for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) { + if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) { + *href += imageMimeTypes[i].sz - 1; + *mimetype = imageMimeTypes[i].name; + + while (**href && **href != ',') { + while (**href && **href != ';') ++(*href); + if (!**href) return false; + ++(*href); + + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) { + if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) { + *href += sizeof("base64,") - 1; + *encoding = imageMimeTypeEncoding::base64; + return true; //valid base64 + } + } + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) { + if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) { + *href += sizeof("utf8,") - 1; + *encoding = imageMimeTypeEncoding::utf8; + return true; //valid utf8 + } + } + } + //no encoding defined + if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) { + ++(*href); + *encoding = imageMimeTypeEncoding::utf8; + return true; //allow no encoding defined if utf8 expected + } + return false; + } + } + return false; +} + + +static unique_ptr _imageBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath) +{ + if (!node->node.image.href) return nullptr; + auto picture = Picture::gen(); + + const char* href = (*node->node.image.href).c_str(); + if (!strncmp(href, "data:", sizeof("data:") - 1)) { + href += sizeof("data:") - 1; + const char* mimetype; + imageMimeTypeEncoding encoding; + if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding + if (encoding == imageMimeTypeEncoding::base64) { + string decoded = svgUtilBase64Decode(href); + if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr; + } else { + string decoded = svgUtilURLDecode(href); + if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr; + } + } else { + if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1; + //TODO: protect against recursive svg image loading + //Temporarily disable embedded svg: + const char *dot = strrchr(href, '.'); + if (dot && !strcmp(dot, ".svg")) { + TVGLOG("SVG", "Embedded svg file is disabled."); + return nullptr; + } + string imagePath = href; + if (strncmp(href, "/", 1)) { + auto last = svgPath.find_last_of("/"); + imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath; + } + if (picture->load(imagePath) != Result::Success) return nullptr; + } + + float w, h; + if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { + auto sx = node->node.image.w / w; + auto sy = node->node.image.h / h; + Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; + picture->transform(m); + } + + _applyComposition(picture.get(), node, vx, vy, vw, vh); + return picture; +} + + +static unique_ptr _useBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath) +{ + auto scene = _sceneBuildHelper(node, vx, vy, vw, vh, svgPath); + if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { + scene->translate(node->node.use.x, node->node.use.y); + } + if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) { + //TODO: handle width/height properties + } + return scene; +} + + +static unique_ptr _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath) +{ + if (_isGroupType(node->type)) { + auto scene = Scene::gen(); + if (node->transform) scene->transform(*node->transform); + + if (node->display && node->style->opacity != 0) { + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + if (_isGroupType((*child)->type)) { + if ((*child)->type == SvgNodeType::Use) + scene->push(_useBuildHelper(*child, vx, vy, vw, vh, svgPath)); + else + scene->push(_sceneBuildHelper(*child, vx, vy, vw, vh, svgPath)); + } else if ((*child)->type == SvgNodeType::Image) { + auto image = _imageBuildHelper(*child, vx, vy, vw, vh, svgPath); + if (image) scene->push(move(image)); + } else { + auto shape = _shapeBuildHelper(*child, vx, vy, vw, vh); + if (shape) scene->push(move(shape)); + } + } + _applyComposition(scene.get(), node, vx, vy, vw, vh); + scene->opacity(node->style->opacity); + } + return scene; + } + return nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +unique_ptr svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath) +{ + if (!node || (node->type != SvgNodeType::Doc)) return nullptr; + + auto docNode = _sceneBuildHelper(node, vx, vy, vw, vh, svgPath); + + if (fabsf(w - vw) > FLT_EPSILON || fabsf(h - vh) > FLT_EPSILON) { + auto sx = w / vw; + auto sy = h / vh; + + if (preserveAspect) { + //Scale + auto scale = sx < sy ? sx : sy; + docNode->scale(scale); + //Align + auto tvx = vx * scale; + auto tvy = vy * scale; + auto tvw = vw * scale; + auto tvh = vh * scale; + if (vw > vh) tvy -= (h - tvh) * 0.5f; + else tvx -= (w - tvw) * 0.5f; + docNode->translate(-tvx, -tvy); + } else { + //Align + auto tvx = vx * sx; + auto tvy = vy * sy; + auto tvw = vw * sx; + auto tvh = vh * sy; + if (tvw > tvh) tvy -= (h - tvh) * 0.5f; + else tvx -= (w - tvw) * 0.5f; + Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1}; + docNode->transform(m); + } + } else if (fabs(vx) > FLT_EPSILON || fabs(vy) > FLT_EPSILON) { + docNode->translate(-vx, -vy); + } + + auto viewBoxClip = Shape::gen(); + viewBoxClip->appendRect(0, 0, w, h, 0, 0); + viewBoxClip->fill(0, 0, 0, 255); + + auto compositeLayer = Scene::gen(); + compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath); + compositeLayer->push(move(docNode)); + + auto root = Scene::gen(); + root->push(move(compositeLayer)); + + return root; +} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h new file mode 100644 index 0000000000..4232aca612 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SVG_SCENE_BUILDER_H_ +#define _TVG_SVG_SCENE_BUILDER_H_ + +#include "tvgCommon.h" + +unique_ptr svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath); + +#endif //_TVG_SVG_SCENE_BUILDER_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp new file mode 100644 index 0000000000..d5b9cdcf7b --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "tvgSvgUtil.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static inline bool _floatExact(float a, float b) +{ + return memcmp(&a, &b, sizeof(float)) == 0; +} + +static uint8_t _hexCharToDec(const char c) +{ + if (c >= 'a') return c - 'a' + 10; + else if (c >= 'A') return c - 'A' + 10; + else return c - '0'; +} + +static uint8_t _base64Value(const char chr) +{ + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; + else return 63; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +/* + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160 + * + * src should be one of the following form : + * + * [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits] + * [whitespace] [sign] {INF | INFINITY} + * [whitespace] [sign] NAN [sequence] + * + * No hexadecimal form supported + * no sequence supported after NAN + */ +float svgUtilStrtof(const char *nPtr, char **endPtr) +{ + if (endPtr) *endPtr = (char*)(nPtr); + if (!nPtr) return 0.0f; + + auto a = nPtr; + auto iter = nPtr; + auto val = 0.0f; + unsigned long long integerPart = 0; + int minus = 1; + + //ignore leading whitespaces + while (isspace(*iter)) iter++; + + //signed or not + if (*iter == '-') { + minus = -1; + iter++; + } else if (*iter == '+') { + iter++; + } + + if (tolower(*iter) == 'i') { + if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3; + else goto error; + + if (tolower(*(iter + 3)) == 'i') { + if ((tolower(*(iter + 4)) == 'n') && (tolower(*(iter + 5)) == 'i') && (tolower(*(iter + 6)) == 't') && (tolower(*(iter + 7)) == 'y')) iter += 5; + else goto error; + } + if (endPtr) *endPtr = (char *)(iter); + return (minus == -1) ? -INFINITY : INFINITY; + } + + if (tolower(*iter) == 'n') { + if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3; + else goto error; + + if (endPtr) *endPtr = (char *)(iter); + return (minus == -1) ? -NAN : NAN; + } + + //Optional: integer part before dot + if (isdigit(*iter)) { + for (; isdigit(*iter); iter++) { + integerPart = integerPart * 10ULL + (unsigned long long)(*iter - '0'); + } + a = iter; + } else if (*iter != '.') { + goto success; + } + + val = static_cast(integerPart); + + //Optional: decimal part after dot + if (*iter == '.') { + unsigned long long decimalPart = 0; + unsigned long long pow10 = 1; + int count = 0; + + iter++; + + if (isdigit(*iter)) { + for (; isdigit(*iter); iter++, count++) { + if (count < 19) { + decimalPart = decimalPart * 10ULL + + static_cast(*iter - '0'); + pow10 *= 10ULL; + } + } + } + val += static_cast(decimalPart) / static_cast(pow10); + a = iter; + } + + //Optional: exponent + if (*iter == 'e' || *iter == 'E') { + ++iter; + + //Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em + if ((*iter == 'm') || (*iter == 'M')) { + //TODO: We don't support font em unit now, but has to multiply val * font size later... + a = iter + 1; + goto success; + } + + //signed or not + int minus_e = 1; + + if (*iter == '-') { + minus_e = -1; + ++iter; + } else if (*iter == '+') { + iter++; + } + + unsigned int exponentPart = 0; + + if (isdigit(*iter)) { + while (*iter == '0') iter++; + for (; isdigit(*iter); iter++) { + exponentPart = exponentPart * 10U + static_cast(*iter - '0'); + } + } else if (!isdigit(*(a - 1))) { + a = nPtr; + goto success; + } else if (*iter == 0) { + goto success; + } + + //if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast(exponentPart)) <= -308)) { + if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast(exponentPart)) <= -38)) { + //val *= 1.0e-308f; + val *= 1.0e-38f; + a = iter; + goto success; + } + + a = iter; + auto scale = 1.0f; + + while (exponentPart >= 8U) { + scale *= 1E8; + exponentPart -= 8U; + } + while (exponentPart > 0U) { + scale *= 10.0f; + exponentPart--; + } + val = (minus_e == -1) ? (val / scale) : (val * scale); + } else if ((iter > nPtr) && !isdigit(*(iter - 1))) { + a = nPtr; + goto success; + } + +success: + if (endPtr) *endPtr = (char*)(a); + return minus * val; + +error: + if (endPtr) *endPtr = (char*)(nPtr); + return 0.0f; +} + + +string svgUtilURLDecode(const char *src) +{ + if (!src) return nullptr; + + auto length = strlen(src); + if (length == 0) return nullptr; + + string decoded; + decoded.reserve(length); + + char a, b; + while (*src) { + if (*src == '%' && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) { + decoded += (_hexCharToDec(a) << 4) + _hexCharToDec(b); + src+=3; + } else if (*src == '+') { + decoded += ' '; + src++; + } else { + decoded += *src++; + } + } + return decoded; +} + + +string svgUtilBase64Decode(const char *src) +{ + if (!src) return nullptr; + + auto length = strlen(src); + if (length == 0) return nullptr; + + string decoded; + decoded.reserve(3*(1+(length >> 2))); + + while (*src && *(src+1)) { + if (*src <= 0x20) { + ++src; + continue; + } + + auto value1 = _base64Value(src[0]); + auto value2 = _base64Value(src[1]); + decoded += (value1 << 2) + ((value2 & 0x30) >> 4); + + if (!src[2] || src[2] == '=' || src[2] == '.') break; + auto value3 = _base64Value(src[2]); + decoded += ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2); + + if (!src[3] || src[3] == '=' || src[3] == '.') break; + auto value4 = _base64Value(src[3]); + decoded += ((value3 & 0x03) << 6) + value4; + src += 4; + } + return decoded; +} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h new file mode 100644 index 0000000000..4320cfed4e --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SVG_UTIL_H_ +#define _TVG_SVG_UTIL_H_ + +#include "tvgCommon.h" + +float svgUtilStrtof(const char *nPtr, char **endPtr); + +string svgUtilURLDecode(const char *src); +string svgUtilBase64Decode(const char *src); + +#endif //_TVG_SVG_UTIL_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp new file mode 100644 index 0000000000..f3bcc7d6f0 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#ifdef _WIN32 + #include +#else + #include +#endif + +#include "tvgXmlParser.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue) +{ +#ifdef THORVG_LOG_ENABLED + const auto attributesNum = 6; + const struct + { + const char* tag; + bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*) + const char* value; + } attributes[] = { + {"id", false, nullptr}, + {"data-name", false, nullptr}, + {"overflow", false, "visible"}, + {"version", false, nullptr}, + {"xmlns", true, nullptr}, + {"xml:space", false, nullptr}, + }; + + for (unsigned int i = 0; i < attributesNum; ++i) { + if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) { + if (attributes[i].value && tagValue) { + if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) { + return true; + } else continue; + } + return true; + } + } + return false; +#endif + return true; +} + + +static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if (isspace((unsigned char)*itr)) break; + } + return itr; +} + + +static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if (!isspace((unsigned char)*itr)) break; + } + return itr; +} + + +static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart) +{ + for (itr--; itr > itrStart; itr--) { + if (!isspace((unsigned char)*itr)) break; + } + return itr + 1; +} + + +static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd) +{ + auto p = itr; + while (itr < itrEnd && *itr == '&') { + for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) { + if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) { + itr += xmlEntityLength[i]; + break; + } + } + if (itr == p) break; + p = itr; + } + return itr; +} + + +static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart) +{ + auto p = itr; + while (itr > itrStart && *(itr - 1) == ';') { + for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) { + if (itr - xmlEntityLength[i] > itrStart && + strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) { + itr -= xmlEntityLength[i]; + break; + } + } + if (itr == p) break; + p = itr; + } + return itr; +} + + +static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd) +{ + itr = _simpleXmlSkipWhiteSpace(itr, itrEnd); + auto p = itr; + while (true) { + if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr; + else break; + if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr; + else break; + } + return itr; +} + + +static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart) +{ + itr = _simpleXmlUnskipWhiteSpace(itr, itrStart); + auto p = itr; + while (true) { + if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr; + else break; + if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr; + else break; + } + return itr; +} + + +static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd) +{ + return (const char*)memchr(itr, '<', itrEnd - itr); +} + + +static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd) +{ + bool insideQuote = false; + for (; itr < itrEnd; itr++) { + if (*itr == '"') insideQuote = !insideQuote; + if (!insideQuote) { + if ((*itr == '>') || (*itr == '<')) + return itr; + } + } + return nullptr; +} + + +static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2; + } + return nullptr; +} + + +static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2; + } + return nullptr; +} + + +static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if (*itr == '>') return itr; + } + return nullptr; +} + + +static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff) +{ + toff = 0; + if (itr[1] == '/') { + toff = 1; + return SimpleXMLType::Close; + } else if (itr[1] == '?') { + toff = 1; + return SimpleXMLType::Processing; + } else if (itr[1] == '!') { + if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) { + toff = sizeof("!DOCTYPE") - 1; + return SimpleXMLType::Doctype; + } else if (itr + sizeof("") - 1 < itrEnd) { + toff = sizeof("!") - 1; + return SimpleXMLType::DoctypeChild; + } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) { + toff = sizeof("![CDATA[") - 1; + return SimpleXMLType::CData; + } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) { + toff = sizeof("!--") - 1; + return SimpleXMLType::Comment; + } + return SimpleXMLType::Open; + } + return SimpleXMLType::Open; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type) +{ +#ifdef THORVG_LOG_ENABLED + static const char* TYPE_NAMES[] = { + "Svg", + "G", + "Defs", + "Animation", + "Arc", + "Circle", + "Ellipse", + "Image", + "Line", + "Path", + "Polygon", + "Polyline", + "Rect", + "Text", + "TextArea", + "Tspan", + "Use", + "Video", + "ClipPath", + "Mask", + "Unknown", + }; + return TYPE_NAMES[(int) type]; +#endif + return nullptr; +} + + +bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName) +{ +#ifdef THORVG_LOG_ENABLED + const auto elementsNum = 1; + const char* const elements[] = { "title" }; + + for (unsigned int i = 0; i < elementsNum; ++i) { + if (!strncmp(tagName, elements[i], strlen(tagName))) { + return true; + } + } + return false; +#else + return true; +#endif +} + + +bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data) +{ + const char *itr = buf, *itrEnd = buf + bufLength; + char* tmpBuf = (char*)alloca(bufLength + 1); + + if (!buf || !func) return false; + + while (itr < itrEnd) { + const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd); + const char *key, *keyEnd, *value, *valueEnd; + char* tval; + + if (p == itrEnd) return true; + + key = p; + for (keyEnd = key; keyEnd < itrEnd; keyEnd++) { + if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break; + } + if (keyEnd == itrEnd) return false; + if (keyEnd == key) continue; + + if (*keyEnd == '=') value = keyEnd + 1; + else { + value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd); + if (!value) return false; + value++; + } + keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key); + + value = _skipWhiteSpacesAndXmlEntities(value, itrEnd); + if (value == itrEnd) return false; + + if ((*value == '"') || (*value == '\'')) { + valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value); + if (!valueEnd) return false; + value++; + } else { + valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd); + } + + itr = valueEnd + 1; + + value = _skipWhiteSpacesAndXmlEntities(value, itrEnd); + valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value); + + memcpy(tmpBuf, key, keyEnd - key); + tmpBuf[keyEnd - key] = '\0'; + + tval = tmpBuf + (keyEnd - key) + 1; + int i = 0; + while (value < valueEnd) { + value = _simpleXmlSkipXmlEntities(value, valueEnd); + tval[i++] = *value; + value++; + } + tval[i] = '\0'; + + if (!func((void*)data, tmpBuf, tval)) { + if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) { + TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id->c_str() : "NO_ID", tmpBuf, tval ? tval : "NONE"); + } + } + } + return true; +} + + +bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data) +{ + const char *itr = buf, *itrEnd = buf + bufLength; + + if (!buf || !func) return false; + + while (itr < itrEnd) { + if (itr[0] == '<') { + //Invalid case + if (itr + 1 >= itrEnd) return false; + + size_t toff = 0; + SimpleXMLType type = _getXMLType(itr, itrEnd, toff); + + const char* p; + if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd); + else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd); + else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd); + else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd); + + if (p) { + //Invalid case: '<' nested + if (*p == '<') return false; + const char *start, *end; + + start = itr + 1 + toff; + end = p; + + switch (type) { + case SimpleXMLType::Open: { + if (p[-1] == '/') { + type = SimpleXMLType::OpenEmpty; + end--; + } + break; + } + case SimpleXMLType::CData: { + if (!memcmp(p - 2, "]]", 2)) end -= 2; + break; + } + case SimpleXMLType::Processing: { + if (p[-1] == '?') end--; + break; + } + case SimpleXMLType::Comment: { + if (!memcmp(p - 2, "--", 2)) end -= 2; + break; + } + default: { + break; + } + } + + if (strip && (type != SimpleXMLType::CData)) { + start = _skipWhiteSpacesAndXmlEntities(start, end); + end = _unskipWhiteSpacesAndXmlEntities(end, start); + } + + if (!func((void*)data, type, start, (unsigned int)(end - start))) return false; + + itr = p + 1; + } else { + return false; + } + } else { + const char *p, *end; + + if (strip) { + p = itr; + p = _skipWhiteSpacesAndXmlEntities(p, itrEnd); + if (p) { + if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false; + itr = p; + } + } + + p = _simpleXmlFindStartTag(itr, itrEnd); + if (!p) p = itrEnd; + + end = p; + if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr); + + if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false; + + if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false; + + itr = p; + } + } + return true; +} + + +bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data) +{ + const char* end; + char* key; + char* val; + char* next; + + if (!buf) return false; + + end = buf + strlen(buf); + key = (char*)alloca(end - buf + 1); + val = (char*)alloca(end - buf + 1); + + if (buf == end) return true; + + do { + char* sep = (char*)strchr(buf, ':'); + next = (char*)strchr(buf, ';'); + + key[0] = '\0'; + val[0] = '\0'; + + if (next == nullptr && sep != nullptr) { + memcpy(key, buf, sep - buf); + key[sep - buf] = '\0'; + + memcpy(val, sep + 1, end - sep - 1); + val[end - sep - 1] = '\0'; + } else if (sep < next && sep != nullptr) { + memcpy(key, buf, sep - buf); + key[sep - buf] = '\0'; + + memcpy(val, sep + 1, next - sep - 1); + val[next - sep - 1] = '\0'; + } else if (next) { + memcpy(key, buf, next - buf); + key[next - buf] = '\0'; + } + + if (key[0]) { + key = const_cast(_simpleXmlSkipWhiteSpace(key, key + strlen(key))); + key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0'; + val = const_cast(_simpleXmlSkipWhiteSpace(val, val + strlen(val))); + val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0'; + + if (!func((void*)data, key, val)) { + if (!_isIgnoreUnsupportedLogAttributes(key, val)) { + TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id->c_str() : "NO_ID", key, val ? val : "NONE"); + } + } + } + + buf = next + 1; + } while (next != nullptr); + + return true; +} + + +const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength) +{ + const char *itr = buf, *itrEnd = buf + bufLength; + + for (; itr < itrEnd; itr++) { + if (!isspace((unsigned char)*itr)) { + //User skip tagname and already gave it the attributes. + if (*itr == '=') return buf; + } else { + itr = _simpleXmlUnskipXmlEntities(itr, buf); + if (itr == itrEnd) return nullptr; + return itr; + } + } + + return nullptr; +} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h new file mode 100644 index 0000000000..d96a631528 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_SIMPLE_XML_PARSER_H_ +#define _TVG_SIMPLE_XML_PARSER_H_ + +#include "tvgSvgLoaderCommon.h" + +#define NUMBER_OF_XML_ENTITIES 8 +const char* const xmlEntity[] = {""", " ", "'", "&", "<", ">", "#", "'"}; +const int xmlEntityLength[] = {6, 6, 6, 5, 4, 4, 6, 6}; + +enum class SimpleXMLType +{ + Open = 0, //!< \ + OpenEmpty, //!< \ + Close, //!< \ + Data, //!< tag text data + CData, //!< \ + Error, //!< error contents + Processing, //!< \ \ + Doctype, //!< \ + Ignored, //!< whatever is ignored by parser, like whitespace + DoctypeChild //!< \ + +#ifdef _WIN32 + #include +#else + #include +#endif + +#include "tvgTvgCommon.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(*block.data); + + ptr = block.end; + + auto cmpBlock = _readBlock(ptr); + if (cmpBlock.end > end) return false; + + paint->composite(unique_ptr(_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; + auto transform = (Matrix*)(block.data); + paint->transform(*transform); + 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(paint); + + //Case1: scene reserve count + if (block.type == TVG_TAG_SCENE_RESERVEDCNT) { + if (block.length != SIZE(uint32_t)) return false; + uint32_t reservedCnt; + READ_UI32(&reservedCnt, block.data); + scene->reserve(reservedCnt); + return true; + } + + //Case2: Base Paint Properties + if (_parsePaintProperty(block, scene)) return true; + + //Case3: A Child paint + if (auto paint = _parsePaint(block)) { + scene->push(unique_ptr(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(cmds[i]); + } + + shape->appendPath(inCmds, cmdCnt, pts, ptsCnt); + + return true; +} + + +static unique_ptr _parseShapeFill(const char *ptr, const char *end) +{ + unique_ptr 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 = move(fillGradRadial); + 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 = 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; + auto transform = (Matrix*)(block.data); + fillGrad->transform(*transform); + 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); + const float* dashPattern = (float*) ptr; + ptr += SIZE(float) * dashPatternCnt; + + if (ptr > end) return false; + + shape->stroke(dashPattern, dashPatternCnt); + 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->stroke((StrokeCap) *block.data); + break; + } + case TVG_TAG_SHAPE_STROKE_JOIN: { + if (block.length != SIZE(TvgBinFlag)) return false; + shape->stroke((StrokeJoin) *block.data); + break; + } + case TVG_TAG_SHAPE_STROKE_WIDTH: { + if (block.length != SIZE(float)) return false; + float width; + READ_FLOAT(&width, block.data); + shape->stroke(width); + break; + } + case TVG_TAG_SHAPE_STROKE_COLOR: { + if (block.length != 4) return false; + shape->stroke(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->stroke(move(move(fill))); + break; + } + case TVG_TAG_SHAPE_STROKE_DASHPTRN: { + if (!_parseShapeStrokeDashPattern(block.data, block.end, shape)) return false; + 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(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(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(paint); + + //Case1: Image Picture + if (block.type == 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); + return true; + } + + //Case2: Base Paint Properties + 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 Subsquent 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 */ +/************************************************************************/ + +unique_ptr 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(_parsePaint(block))); + ptr = block.end; + } + + return scene; +} diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h b/thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h new file mode 100644 index 0000000000..e7c3eba488 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgCommon.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_TVG_COMMON_H_ +#define _TVG_TVG_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgBinaryDesc.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 unique_ptr run(const char* ptr, const char* end) = 0; +}; + + +/* Version 0 */ +class TvgBinInterpreter : public TvgBinInterpreterBase +{ +public: + unique_ptr run(const char* ptr, const char* end) override; +}; + + +#endif //_TVG_TVG_COMMON_H_ \ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp new file mode 100644 index 0000000000..d7f3184435 --- /dev/null +++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgLoader.h" +#include "tvgTvgLoader.h" +#include "tvgLzw.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +void TvgLoader::clear() +{ + if (copy) free((char*)data); + ptr = data = nullptr; + size = 0; + copy = false; + + if (interpreter) { + delete(interpreter); + interpreter = nullptr; + } +} + + +/* 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; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +TvgLoader::~TvgLoader() +{ + close(); +} + + +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, 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; + + TaskScheduler::request(this); + + return true; +} + + +bool TvgLoader::close() +{ + this->done(); + clear(); + return true; +} + + +void TvgLoader::run(unsigned tid) +{ + if (root) root.reset(); + + auto data = const_cast(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); + } + + if (!root) clear(); +} + + +unique_ptr TvgLoader::paint() +{ + this->done(); + if (root) return move(root); + return nullptr; +} diff --git a/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h new file mode 100644 index 0000000000..d276ded33a --- /dev/null +++ b/thirdparty/thorvg/src/loaders/tvg/tvgTvgLoader.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_TVG_LOADER_H_ +#define _TVG_TVG_LOADER_H_ + +#include "tvgTaskScheduler.h" +#include "tvgTvgCommon.h" + + +class TvgLoader : public LoadModule, public Task +{ +public: + const char* data = nullptr; + const char* ptr = nullptr; + uint32_t size = 0; + uint16_t version = 0; + unique_ptr root = nullptr; + TvgBinInterpreterBase* interpreter = nullptr; + uint32_t uncompressedSize = 0; + uint32_t compressedSize = 0; + uint32_t compressedSizeBits = 0; + bool copy = false; + bool compressed = false; + + ~TvgLoader(); + + using LoadModule::open; + bool open(const string &path) override; + bool open(const char *data, uint32_t size, bool copy) override; + bool read() override; + bool close() override; + bool resize(Paint* paint, float w, float h) override; + unique_ptr paint() override; + +private: + bool readHeader(); + void run(unsigned tid) override; + void clear(); +}; + +#endif //_TVG_TVG_LOADER_H_ diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp new file mode 100644 index 0000000000..ffa79e18f2 --- /dev/null +++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include "tvgSaveModule.h" +#include "tvgTvgSaver.h" +#include "tvgLzw.h" + +#ifdef _WIN32 + #include +#else + #include +#endif + +static FILE* _fopen(const char* filename, const char* mode) +{ +#if defined(__clang__) && !defined(__EMSCRIPTEN__) + 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; +} + + +static Matrix _multiply(const Matrix* lhs, const Matrix* rhs) +{ + Matrix m; + + m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31; + m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32; + m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33; + + m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31; + m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32; + m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33; + + m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31; + m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32; + m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33; + + return m; +} + + +static void _multiply(Point* pt, const Matrix* transform) +{ + auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13; + auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23; + pt->x = tx; + pt->y = ty; +} + + +/* if the properties are identical, we can merge the shapes. */ +static bool _merge(Shape* from, Shape* to) +{ + uint8_t r, g, b, a; + uint8_t r2, g2, b2, a2; + + //fill + if (from->fill() || to->fill()) return false; + + r = g = b = a = r2 = g2 = b2 = a2 = 0; + + from->fillColor(&r, &g, &b, &a); + to->fillColor(&r2, &g2, &b2, &a2); + + if (r != r2 || g != g2 || b != b2 || a != a2) return false; + + //composition + if (from->composite(nullptr) != CompositeMethod::None) return false; + if (to->composite(nullptr) != CompositeMethod::None) return false; + + //opacity + if (from->opacity() != to->opacity()) return false; + + //transform + auto t1 = from->transform(); + auto t2 = to->transform(); + + if (fabs(t1.e11 - t2.e11) > FLT_EPSILON || fabs(t1.e12 - t2.e12) > FLT_EPSILON || fabs(t1.e13 - t2.e13) > FLT_EPSILON || + fabs(t1.e21 - t2.e21) > FLT_EPSILON || fabs(t1.e22 - t2.e22) > FLT_EPSILON || fabs(t1.e23 - t2.e23) > FLT_EPSILON || + fabs(t1.e31 - t2.e31) > FLT_EPSILON || fabs(t1.e32 - t2.e32) > FLT_EPSILON || fabs(t1.e33 - t2.e33) > FLT_EPSILON) { + return false; + } + + //stroke + r = g = b = a = r2 = g2 = b2 = a2 = 0; + + from->strokeColor(&r, &g, &b, &a); + to->strokeColor(&r2, &g2, &b2, &a2); + + if (r != r2 || g != g2 || b != b2 || a != a2) return false; + + if (fabs(from->strokeWidth() - to->strokeWidth()) > FLT_EPSILON) return false; + + //OPTIMIZE: Yet we can't merge outlining shapes unless we can support merging shapes feature. + if (from->strokeWidth() > 0 || to->strokeWidth() > 0) return false; + + if (from->strokeCap() != to->strokeCap()) return false; + if (from->strokeJoin() != to->strokeJoin()) return false; + if (from->strokeDash(nullptr) > 0 || to->strokeDash(nullptr) > 0) return false; + if (from->strokeFill() || to->strokeFill()) return false; + + //fill rule + if (from->fillRule() != to->fillRule()) return false; + + //Good, identical shapes, we can merge them. + const PathCommand* cmds = nullptr; + auto cmdCnt = from->pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = from->pathCoords(&pts); + + to->appendPath(cmds, cmdCnt, pts, ptsCnt); + + return true; +} + + +bool TvgSaver::saveEncoding(const std::string& path) +{ + if (!compress) return flushTo(path); + + //Try encoding + auto uncompressed = buffer.data + headerSize; + auto uncompressedSize = buffer.count - headerSize; + + uint32_t compressedSize, compressedSizeBits; + + auto compressed = lzwEncode(uncompressed, uncompressedSize, &compressedSize, &compressedSizeBits); + + //Failed compression. + if (!compressed) return flushTo(path); + + //Optimization is ineffective. + if (compressedSize >= uncompressedSize) { + free(compressed); + return flushTo(path); + } + + TVGLOG("TVG_SAVER", "%s, compressed: %d -> %d, saved rate: %3.2f%%", path.c_str(), uncompressedSize, compressedSize, (1 - ((float) compressedSize / (float) uncompressedSize)) * 100); + + //Update compress size in the header. + uncompressed -= (TVG_HEADER_COMPRESS_SIZE + TVG_HEADER_RESERVED_LENGTH); + + //Compression Flag + *uncompressed |= TVG_HEAD_FLAG_COMPRESSED; + uncompressed += TVG_HEADER_RESERVED_LENGTH; + + //Uncompressed Size + memcpy(uncompressed, &uncompressedSize, TVG_HEADER_UNCOMPRESSED_SIZE); + uncompressed += TVG_HEADER_UNCOMPRESSED_SIZE; + + //Comprssed Size + memcpy(uncompressed, &compressedSize, TVG_HEADER_COMPRESSED_SIZE); + uncompressed += TVG_HEADER_COMPRESSED_SIZE; + + //Compressed Size Bits + memcpy(uncompressed, &compressedSizeBits, TVG_HEADER_COMPRESSED_SIZE_BITS); + + //Good optimization, flush to file. + auto fp = _fopen(path.c_str(), "w+"); + if (!fp) goto fail; + + //write header + if (fwrite(buffer.data, SIZE(uint8_t), headerSize, fp) == 0) goto fail; + + //write compressed data + if (fwrite(compressed, SIZE(uint8_t), compressedSize, fp) == 0) goto fail; + + fclose(fp); + free(compressed); + + return true; + +fail: + if (fp) fclose(fp); + if (compressed) free(compressed); + return false; +} + + +bool TvgSaver::flushTo(const std::string& path) +{ + auto fp = _fopen(path.c_str(), "w+"); + if (!fp) return false; + + if (fwrite(buffer.data, SIZE(uint8_t), buffer.count, fp) == 0) { + fclose(fp); + return false; + } + fclose(fp); + + return true; +} + + +/* WARNING: Header format shall not changed! */ +bool TvgSaver::writeHeader() +{ + headerSize = TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + SIZE(vsize) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE; + + buffer.grow(headerSize); + + //1. Signature + auto ptr = buffer.ptr(); + memcpy(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH); + ptr += TVG_HEADER_SIGNATURE_LENGTH; + + //2. Version + memcpy(ptr, TVG_HEADER_VERSION, TVG_HEADER_VERSION_LENGTH); + ptr += TVG_HEADER_VERSION_LENGTH; + + buffer.count += (TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH); + + //3. View Size + writeData(vsize, SIZE(vsize)); + ptr += SIZE(vsize); + + //4. Reserved data + Compress size + memset(ptr, 0x00, TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE); + buffer.count += (TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE); + + return true; +} + + +void TvgSaver::writeTag(TvgBinTag tag) +{ + buffer.grow(SIZE(TvgBinTag)); + memcpy(buffer.ptr(), &tag, SIZE(TvgBinTag)); + buffer.count += SIZE(TvgBinTag); +} + + +void TvgSaver::writeCount(TvgBinCounter cnt) +{ + buffer.grow(SIZE(TvgBinCounter)); + memcpy(buffer.ptr(), &cnt, SIZE(TvgBinCounter)); + buffer.count += SIZE(TvgBinCounter); +} + + +void TvgSaver::writeReservedCount(TvgBinCounter cnt) +{ + memcpy(buffer.ptr() - cnt - SIZE(TvgBinCounter), &cnt, SIZE(TvgBinCounter)); +} + + +void TvgSaver::reserveCount() +{ + buffer.grow(SIZE(TvgBinCounter)); + buffer.count += SIZE(TvgBinCounter); +} + + +TvgBinCounter TvgSaver::writeData(const void* data, TvgBinCounter cnt) +{ + buffer.grow(cnt); + memcpy(buffer.ptr(), data, cnt); + buffer.count += cnt; + + return cnt; +} + + +TvgBinCounter TvgSaver::writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data) +{ + auto growCnt = SERIAL_DONE(cnt); + + buffer.grow(growCnt); + + auto ptr = buffer.ptr(); + + *ptr = tag; + ++ptr; + + memcpy(ptr, &cnt, SIZE(TvgBinCounter)); + ptr += SIZE(TvgBinCounter); + + memcpy(ptr, data, cnt); + ptr += cnt; + + buffer.count += growCnt; + + return growCnt; +} + + +TvgBinCounter TvgSaver::writeTransform(const Matrix* transform, TvgBinTag tag) +{ + if (fabs(transform->e11 - 1) > FLT_EPSILON || fabs(transform->e12) > FLT_EPSILON || fabs(transform->e13) > FLT_EPSILON || + fabs(transform->e21) > FLT_EPSILON || fabs(transform->e22 - 1) > FLT_EPSILON || fabs(transform->e23) > FLT_EPSILON || + fabs(transform->e31) > FLT_EPSILON || fabs(transform->e32) > FLT_EPSILON || fabs(transform->e33 - 1) > FLT_EPSILON) { + return writeTagProperty(tag, SIZE(Matrix), transform); + } + return 0; +} + + +TvgBinCounter TvgSaver::serializePaint(const Paint* paint, const Matrix* pTransform) +{ + TvgBinCounter cnt = 0; + + //opacity + auto opacity = paint->opacity(); + if (opacity < 255) { + cnt += writeTagProperty(TVG_TAG_PAINT_OPACITY, SIZE(opacity), &opacity); + } + + //composite + const Paint* cmpTarget = nullptr; + auto cmpMethod = paint->composite(&cmpTarget); + if (cmpMethod != CompositeMethod::None && cmpTarget) { + cnt += serializeComposite(cmpTarget, cmpMethod, pTransform); + } + + return cnt; +} + + +/* Propagate parents properties to the child so that we can skip saving the parent. */ +TvgBinCounter TvgSaver::serializeChild(const Paint* parent, const Paint* child, const Matrix* transform) +{ + const Paint* compTarget = nullptr; + auto compMethod = parent->composite(&compTarget); + + /* If the parent & the only child have composition, we can't skip the parent... + Or if the parent has the transform and composition, we can't skip the parent... */ + if (compMethod != CompositeMethod::None) { + if (transform || child->composite(nullptr) != CompositeMethod::None) return 0; + } + + //propagate opacity + uint32_t opacity = parent->opacity(); + + if (opacity < 255) { + uint32_t tmp = (child->opacity() * opacity); + if (tmp > 0) tmp /= 255; + const_cast(child)->opacity(tmp); + } + + //propagate composition + if (compTarget) const_cast(child)->composite(unique_ptr(compTarget->duplicate()), compMethod); + + return serialize(child, transform); +} + + +TvgBinCounter TvgSaver::serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform) +{ + auto it = this->iterator(scene); + if (it->count() == 0) { + delete(it); + return 0; + } + + //Case - Only Child: Skip saving this scene. + if (it->count() == 1) { + auto cnt = serializeChild(scene, it->next(), cTransform); + if (cnt > 0) { + delete(it); + return cnt; + } + } + + it->begin(); + + //Case - Delegator Scene: This scene is just a delegator, we can skip this: + if (scene->composite(nullptr) == CompositeMethod::None && scene->opacity() == 255) { + auto ret = serializeChildren(it, cTransform, false); + delete(it); + return ret; + } + + //Case - Serialize Scene & its children + writeTag(TVG_TAG_CLASS_SCENE); + reserveCount(); + + auto cnt = serializeChildren(it, cTransform, true) + serializePaint(scene, pTransform); + + delete(it); + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +TvgBinCounter TvgSaver::serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform) +{ + const Fill::ColorStop* stops = nullptr; + auto stopsCnt = fill->colorStops(&stops); + if (!stops || stopsCnt == 0) return 0; + + writeTag(tag); + reserveCount(); + + TvgBinCounter cnt = 0; + + //radial fill + if (fill->identifier() == TVG_CLASS_ID_RADIAL) { + float args[3]; + static_cast(fill)->radial(args, args + 1, args + 2); + cnt += writeTagProperty(TVG_TAG_FILL_RADIAL_GRADIENT, SIZE(args), args); + //linear fill + } else { + float args[4]; + static_cast(fill)->linear(args, args + 1, args + 2, args + 3); + cnt += writeTagProperty(TVG_TAG_FILL_LINEAR_GRADIENT, SIZE(args), args); + } + + if (auto flag = static_cast(fill->spread())) + cnt += writeTagProperty(TVG_TAG_FILL_FILLSPREAD, SIZE(TvgBinFlag), &flag); + cnt += writeTagProperty(TVG_TAG_FILL_COLORSTOPS, stopsCnt * SIZE(Fill::ColorStop), stops); + + auto gTransform = fill->transform(); + if (pTransform) gTransform = _multiply(pTransform, &gTransform); + cnt += writeTransform(&gTransform, TVG_TAG_FILL_TRANSFORM); + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +TvgBinCounter TvgSaver::serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform) +{ + writeTag(TVG_TAG_SHAPE_STROKE); + reserveCount(); + + //width + auto width = shape->strokeWidth(); + if (preTransform) width *= sqrtf(powf(pTransform->e11, 2.0f) + powf(pTransform->e21, 2.0f)); //we know x/y scaling factors are same. + auto cnt = writeTagProperty(TVG_TAG_SHAPE_STROKE_WIDTH, SIZE(width), &width); + + //cap + if (auto flag = static_cast(shape->strokeCap())) + cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_CAP, SIZE(TvgBinFlag), &flag); + + //join + if (auto flag = static_cast(shape->strokeJoin())) + cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_JOIN, SIZE(TvgBinFlag), &flag); + + //fill + if (auto fill = shape->strokeFill()) { + cnt += serializeFill(fill, TVG_TAG_SHAPE_STROKE_FILL, (preTransform ? pTransform : nullptr)); + } else { + uint8_t color[4] = {0, 0, 0, 0}; + shape->strokeColor(color, color + 1, color + 2, color + 3); + cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_COLOR, SIZE(color), &color); + } + + //dash + const float* dashPattern = nullptr; + auto dashCnt = shape->strokeDash(&dashPattern); + if (dashPattern && dashCnt > 0) { + TvgBinCounter dashCntSize = SIZE(dashCnt); + TvgBinCounter dashPtrnSize = dashCnt * SIZE(dashPattern[0]); + + writeTag(TVG_TAG_SHAPE_STROKE_DASHPTRN); + writeCount(dashCntSize + dashPtrnSize); + cnt += writeData(&dashCnt, dashCntSize); + cnt += writeData(dashPattern, dashPtrnSize); + cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter); + } + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +TvgBinCounter TvgSaver::serializePath(const Shape* shape, const Matrix* transform, bool preTransform) +{ + const PathCommand* cmds = nullptr; + auto cmdCnt = shape->pathCommands(&cmds); + const Point* pts = nullptr; + auto ptsCnt = shape->pathCoords(&pts); + + if (!cmds || !pts || cmdCnt == 0 || ptsCnt == 0) return 0; + + writeTag(TVG_TAG_SHAPE_PATH); + reserveCount(); + + /* Reduce the binary size. + Convert PathCommand(4 bytes) to TvgBinFlag(1 byte) */ + TvgBinFlag* outCmds = (TvgBinFlag*)alloca(SIZE(TvgBinFlag) * cmdCnt); + for (uint32_t i = 0; i < cmdCnt; ++i) { + outCmds[i] = static_cast(cmds[i]); + } + + auto cnt = writeData(&cmdCnt, SIZE(cmdCnt)); + cnt += writeData(&ptsCnt, SIZE(ptsCnt)); + cnt += writeData(outCmds, SIZE(TvgBinFlag) * cmdCnt); + + //transform? + if (preTransform) { + if (fabs(transform->e11 - 1) > FLT_EPSILON || fabs(transform->e12) > FLT_EPSILON || fabs(transform->e13) > FLT_EPSILON || + fabs(transform->e21) > FLT_EPSILON || fabs(transform->e22 - 1) > FLT_EPSILON || fabs(transform->e23) > FLT_EPSILON || + fabs(transform->e31) > FLT_EPSILON || fabs(transform->e32) > FLT_EPSILON || fabs(transform->e33 - 1) > FLT_EPSILON) { + auto p = const_cast(pts); + for (uint32_t i = 0; i < ptsCnt; ++i) _multiply(p++, transform); + } + } + + cnt += writeData(pts, ptsCnt * SIZE(pts[0])); + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +TvgBinCounter TvgSaver::serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform) +{ + writeTag(TVG_TAG_CLASS_SHAPE); + reserveCount(); + TvgBinCounter cnt = 0; + + //fill rule + if (auto flag = static_cast(shape->fillRule())) { + cnt = writeTagProperty(TVG_TAG_SHAPE_FILLRULE, SIZE(TvgBinFlag), &flag); + } + + //the pre-transformation can't be applied in the case when the stroke is dashed or irregulary scaled + bool preTransform = true; + + //stroke + if (shape->strokeWidth() > 0) { + uint8_t color[4] = {0, 0, 0, 0}; + shape->strokeColor(color, color + 1, color + 2, color + 3); + auto fill = shape->strokeFill(); + if (fill || color[3] > 0) { + if (fabsf(cTransform->e11 - cTransform->e22) > FLT_EPSILON || (fabsf(cTransform->e11) < FLT_EPSILON && fabsf(cTransform->e12 - cTransform->e21) > FLT_EPSILON) || shape->strokeDash(nullptr) > 0) preTransform = false; + cnt += serializeStroke(shape, cTransform, preTransform); + } + } + + //fill + if (auto fill = shape->fill()) { + cnt += serializeFill(fill, TVG_TAG_SHAPE_FILL, (preTransform ? cTransform : nullptr)); + } else { + uint8_t color[4] = {0, 0, 0, 0}; + shape->fillColor(color, color + 1, color + 2, color + 3); + if (color[3] > 0) cnt += writeTagProperty(TVG_TAG_SHAPE_COLOR, SIZE(color), color); + } + + cnt += serializePath(shape, cTransform, preTransform); + + if (!preTransform) cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM); + cnt += serializePaint(shape, pTransform); + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +/* Picture has either a vector scene or a bitmap. */ +TvgBinCounter TvgSaver::serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform) +{ + auto it = this->iterator(picture); + + //Case - Vector Scene: + if (it->count() == 1) { + auto cnt = serializeChild(picture, it->next(), cTransform); + //Only child, Skip to save Picture... + if (cnt > 0) { + delete(it); + return cnt; + /* Unfortunately, we can't skip the Picture because it might have a compositor, + Serialize Scene(instead of the Picture) & its scene. */ + } else { + writeTag(TVG_TAG_CLASS_SCENE); + reserveCount(); + auto cnt = serializeChildren(it, cTransform, true) + serializePaint(picture, pTransform); + writeReservedCount(cnt); + delete(it); + return SERIAL_DONE(cnt); + } + } + delete(it); + + //Case - Bitmap Image: + uint32_t w, h; + auto pixels = picture->data(&w, &h); + if (!pixels) return 0; + + writeTag(TVG_TAG_CLASS_PICTURE); + reserveCount(); + + TvgBinCounter cnt = 0; + TvgBinCounter sizeCnt = SIZE(w); + TvgBinCounter imgSize = w * h * SIZE(pixels[0]); + + writeTag(TVG_TAG_PICTURE_RAW_IMAGE); + writeCount(2 * sizeCnt + imgSize); + + cnt += writeData(&w, sizeCnt); + cnt += writeData(&h, sizeCnt); + cnt += writeData(pixels, imgSize); + cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter); + + //Bitmap picture needs the transform info. + cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM); + + cnt += serializePaint(picture, pTransform); + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +TvgBinCounter TvgSaver::serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform) +{ + writeTag(TVG_TAG_PAINT_CMP_TARGET); + reserveCount(); + + auto flag = static_cast(cmpMethod); + auto cnt = writeTagProperty(TVG_TAG_PAINT_CMP_METHOD, SIZE(TvgBinFlag), &flag); + + cnt += serialize(cmpTarget, pTransform, true); + + writeReservedCount(cnt); + + return SERIAL_DONE(cnt); +} + + +TvgBinCounter TvgSaver::serializeChildren(Iterator* it, const Matrix* pTransform, bool reserved) +{ + TvgBinCounter cnt = 0; + + //Merging shapes. the result is written in the children. + Array children; + children.reserve(it->count()); + children.push(it->next()); + + while (auto child = it->next()) { + if (child->identifier() == TVG_CLASS_ID_SHAPE) { + //only dosable if the previous child is a shape. + auto target = children.ptr() - 1; + if ((*target)->identifier() == TVG_CLASS_ID_SHAPE) { + if (_merge((Shape*)child, (Shape*)*target)) { + continue; + } + } + } + children.push(child); + } + + //The children of a reserved scene + if (reserved && children.count > 1) { + cnt += writeTagProperty(TVG_TAG_SCENE_RESERVEDCNT, SIZE(children.count), &children.count); + } + + //Serialize merged children. + auto child = children.data; + for (uint32_t i = 0; i < children.count; ++i, ++child) { + cnt += serialize(*child, pTransform); + } + + return cnt; +} + + +TvgBinCounter TvgSaver::serialize(const Paint* paint, const Matrix* pTransform, bool compTarget) +{ + if (!paint) return 0; + + //Invisible paint, no point to save it if the paint is not the composition target... + if (!compTarget && paint->opacity() == 0) return 0; + + auto transform = const_cast(paint)->transform(); + if (pTransform) transform = _multiply(pTransform, &transform); + + switch (paint->identifier()) { + case TVG_CLASS_ID_SHAPE: return serializeShape(static_cast(paint), pTransform, &transform); + case TVG_CLASS_ID_SCENE: return serializeScene(static_cast(paint), pTransform, &transform); + case TVG_CLASS_ID_PICTURE: return serializePicture(static_cast(paint), pTransform, &transform); + } + + return 0; +} + + +void TvgSaver::run(unsigned tid) +{ + if (!writeHeader()) return; + + //Serialize Root Paint, without its transform. + Matrix transform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + + if (paint->opacity() > 0) { + switch (paint->identifier()) { + case TVG_CLASS_ID_SHAPE: { + serializeShape(static_cast(paint), nullptr, &transform); + break; + } + case TVG_CLASS_ID_SCENE: { + serializeScene(static_cast(paint), nullptr, &transform); + break; + } + case TVG_CLASS_ID_PICTURE: { + serializePicture(static_cast(paint), nullptr, &transform); + break; + } + } + } + + if (!saveEncoding(path)) return; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +TvgSaver::~TvgSaver() +{ + close(); +} + + +bool TvgSaver::close() +{ + this->done(); + + if (paint) { + delete(paint); + paint = nullptr; + } + if (path) { + free(path); + path = nullptr; + } + buffer.reset(); + return true; +} + + +bool TvgSaver::save(Paint* paint, const string& path, bool compress) +{ + close(); + + this->path = strdup(path.c_str()); + if (!this->path) return false; + + float x, y; + paint->bounds(&x, &y, &vsize[0], &vsize[1], false); + + //cut off the negative space + if (x < 0) vsize[0] += x; + if (y < 0) vsize[1] += y; + + if (vsize[0] <= FLT_EPSILON || vsize[1] <= FLT_EPSILON) { + TVGLOG("TVG_SAVER", "Saving paint(%p) has zero view size.", paint); + return false; + } + + this->paint = paint; + this->compress = compress; + + TaskScheduler::request(this); + + return true; +} diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h new file mode 100644 index 0000000000..27186b5d4a --- /dev/null +++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _TVG_TVGSAVER_H_ +#define _TVG_TVGSAVER_H_ + +#include "tvgArray.h" +#include "tvgBinaryDesc.h" +#include "tvgTaskScheduler.h" + +namespace tvg +{ + +class TvgSaver : public SaveModule, public Task +{ +private: + Array buffer; + Paint* paint = nullptr; + char *path = nullptr; + uint32_t headerSize; + float vsize[2] = {0.0f, 0.0f}; + bool compress; + + bool flushTo(const std::string& path); + bool saveEncoding(const std::string& path); + void reserveCount(); + + bool writeHeader(); + bool writeViewSize(); + void writeTag(TvgBinTag tag); + void writeCount(TvgBinCounter cnt); + void writeReservedCount(TvgBinCounter cnt); + TvgBinCounter writeData(const void* data, TvgBinCounter cnt); + TvgBinCounter writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data); + TvgBinCounter writeTransform(const Matrix* transform, TvgBinTag tag); + + TvgBinCounter serialize(const Paint* paint, const Matrix* pTransform, bool compTarget = false); + TvgBinCounter serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform); + TvgBinCounter serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform); + TvgBinCounter serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform); + TvgBinCounter serializePaint(const Paint* paint, const Matrix* pTransform); + TvgBinCounter serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform); + TvgBinCounter serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform); + TvgBinCounter serializePath(const Shape* shape, const Matrix* transform, bool preTransform); + TvgBinCounter serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform); + TvgBinCounter serializeChildren(Iterator* it, const Matrix* transform, bool reserved); + TvgBinCounter serializeChild(const Paint* parent, const Paint* child, const Matrix* pTransform); + +public: + ~TvgSaver(); + + bool save(Paint* paint, const string& path, bool compress) override; + bool close() override; + void run(unsigned tid) override; +}; + +} + +#endif //_TVG_SAVE_MODULE_H_ diff --git a/thirdparty/thorvg/tvgcompat/tvgpack_v0.5.tar.gz b/thirdparty/thorvg/tvgcompat/tvgpack_v0.5.tar.gz new file mode 100644 index 0000000000..43333289e9 Binary files /dev/null and b/thirdparty/thorvg/tvgcompat/tvgpack_v0.5.tar.gz differ diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh new file mode 100755 index 0000000000..ec39bd3a11 --- /dev/null +++ b/thirdparty/thorvg/update-thorvg.sh @@ -0,0 +1,58 @@ +rm -rf AUTHORS inc LICENSE src *.zip +curl -L -O https://github.com/Samsung/thorvg/archive/refs/tags/v0.6.0.zip +bsdtar --strip-components=1 -xvf *.zip +rm *.zip +rm -rf .github docs pc res test tools .git* *.md *.txt wasm_build.sh +find . -type f -name 'meson.build' -delete +rm -rf src/bin src/bindings src/examples src/wasm +rm -rf src/lib/gl_engine + +cat << 'EOF' > inc/config.h +/*************************************************************************/ +/* config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 SVG_CONFIG_H +#define SVG_CONFIG_H + +#define THORVG_SW_RASTER_SUPPORT 1 + +#define THORVG_SVG_LOADER_SUPPORT 1 + +#define THORVG_PNG_LOADER_SUPPORT 1 + +#define THORVG_TVG_LOADER_SUPPORT 1 + +#define THORVG_JPG_LOADER_SUPPORT 1 + +#define THORVG_LOG_ENABLED 1 + +#define THORVG_VERSION_STRING "master" +#endif +EOF