feat(thorvg): update thorvg to 0.15.3 (#7103)

Signed-off-by: lhdjply <lhdjply@126.com>
This commit is contained in:
Liu Yi 2024-11-05 20:09:11 +08:00 committed by GitHub
parent 4aa74e15c3
commit 0091be46fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
668 changed files with 6713 additions and 4639 deletions

View File

@ -11,5 +11,5 @@
#define THORVG_LOTTIE_LOADER_SUPPORT LV_USE_LOTTIE
#define THORVG_VERSION_STRING "0.13.5"
#define THORVG_VERSION_STRING "0.15.3"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ namespace tvg
*
* @see Animation
*
* @note Experimental API
* @since 0.15
*/
#include "../../lv_conf_internal.h"
@ -81,13 +81,13 @@ public:
* @note Experimental API
*/
const char* marker(uint32_t idx) noexcept;
/**
* @brief Creates a new LottieAnimation object.
*
* @return A new LottieAnimation object.
*
* @note Experimental API
* @since 0.15
*/
static std::unique_ptr<LottieAnimation> gen() noexcept;
};

View File

@ -24,20 +24,21 @@
#if LV_USE_THORVG_INTERNAL
#include "tvgIteratorAccessor.h"
#include "tvgCompressor.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static bool accessChildren(Iterator* it, function<bool(const Paint* paint)> func)
static bool accessChildren(Iterator* it, function<bool(const Paint* paint, void* data)> func, void* data)
{
while (auto child = it->next()) {
//Access the child
if (!func(child)) return false;
if (!func(child, data)) return false;
//Access the children of the child
if (auto it2 = IteratorAccessor::iterator(child)) {
if (!accessChildren(it2, func)) {
if (!accessChildren(it2, func, data)) {
delete(it2);
return false;
}
@ -47,26 +48,46 @@ static bool accessChildren(Iterator* it, function<bool(const Paint* paint)> func
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
unique_ptr<Picture> Accessor::set(unique_ptr<Picture> picture, function<bool(const Paint* paint)> func) noexcept
TVG_DEPRECATED unique_ptr<Picture> Accessor::set(unique_ptr<Picture> picture, function<bool(const Paint* paint)> func) noexcept
{
auto p = picture.get();
if (!p || !func) return picture;
auto backward = [](const tvg::Paint* paint, void* data) -> bool
{
auto func = reinterpret_cast<function<bool(const Paint* paint)>*>(data);
if (!(*func)(paint)) return false;
return true;
};
set(picture.get(), backward, reinterpret_cast<void*>(&func));
return picture;
}
Result Accessor::set(const Picture* picture, function<bool(const Paint* paint, void* data)> func, void* data) noexcept
{
if (!picture || !func) return Result::InvalidArguments;
//Use the Preorder Tree-Search
//Root
if (!func(p)) return picture;
if (!func(picture, data)) return Result::Success;
//Children
if (auto it = IteratorAccessor::iterator(p)) {
accessChildren(it, func);
if (auto it = IteratorAccessor::iterator(picture)) {
accessChildren(it, func, data);
delete(it);
}
return picture;
return Result::Success;
}
uint32_t Accessor::id(const char* name) noexcept
{
return djb2Encode(name);
}

View File

@ -98,7 +98,7 @@ float Animation::duration() const noexcept
Result Animation::segment(float begin, float end) noexcept
{
if (begin < 0.0 || end > 1.0 || begin >= end) return Result::InvalidArguments;
if (begin < 0.0 || end > 1.0 || begin > end) return Result::InvalidArguments;
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return Result::InsufficientCondition;

View File

@ -62,7 +62,7 @@ struct Array
data[count++] = element;
}
void push(Array<T>& rhs)
void push(const Array<T>& rhs)
{
if (rhs.count == 0) return;
grow(rhs.count);

View File

@ -39,7 +39,7 @@ using TvgBinFlag = TvgBinByte;
#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 "001200" //Major 00, Minor 12, Micro 00
#define TVG_HEADER_VERSION "001500" //Major 00, Minor 15, 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

View File

@ -29,17 +29,15 @@
#include "tvgPaint.h"
enum Status : uint8_t {Synced = 0, Updating, Drawing, Damaged};
struct Canvas::Impl
{
enum Status : uint8_t {Synced = 0, Updating, Drawing};
list<Paint*> paints;
RenderMethod* renderer;
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
Status status = Status::Synced;
bool refresh = false; //if all paints should be updated by force.
Impl(RenderMethod* pRenderer) : renderer(pRenderer)
{
renderer->ref();
@ -90,26 +88,22 @@ struct Canvas::Impl
return Result::Success;
}
void needRefresh()
{
refresh = true;
}
Result update(Paint* paint, bool force)
{
if (paints.empty() || status == Status::Drawing) return Result::InsufficientCondition;
Array<RenderData> clips;
auto flag = RenderUpdateFlag::None;
if (refresh || force) flag = RenderUpdateFlag::All;
if (status == Status::Damaged || force) flag = RenderUpdateFlag::All;
auto m = Matrix{1, 0, 0, 0, 1, 0, 0, 0, 1};
if (paint) {
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
paint->pImpl->update(renderer, m, clips, 255, flag);
} else {
for (auto paint : paints) {
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
paint->pImpl->update(renderer, m, clips, 255, flag);
}
refresh = false;
}
status = Status::Updating;
return Result::Success;
@ -117,6 +111,7 @@ struct Canvas::Impl
Result draw()
{
if (status == Status::Damaged) update(nullptr, false);
if (status == Status::Drawing || paints.empty() || !renderer->preRender()) return Result::InsufficientCondition;
bool rendered = false;
@ -132,19 +127,20 @@ struct Canvas::Impl
Result sync()
{
if (status == Status::Synced) return Result::InsufficientCondition;
if (status == Status::Synced || status == Status::Damaged) return Result::InsufficientCondition;
if (renderer->sync()) {
status = Status::Synced;
return Result::Success;
}
return Result::InsufficientCondition;
return Result::Unknown;
}
Result viewport(int32_t x, int32_t y, int32_t w, int32_t h)
{
if (status != Status::Synced) return Result::InsufficientCondition;
if (status != Status::Damaged && status != Status::Synced) return Result::InsufficientCondition;
RenderRegion val = {x, y, w, h};
//intersect if the target buffer is already set.
auto surface = renderer->mainSurface();
@ -154,7 +150,7 @@ struct Canvas::Impl
if (vport == val) return Result::Success;
renderer->viewport(val);
vport = val;
needRefresh();
status = Status::Damaged;
return Result::Success;
}
};

View File

@ -55,6 +55,12 @@ TVG_API Tvg_Result tvg_engine_term(Tvg_Engine engine_method)
}
TVG_API Tvg_Result tvg_engine_version(uint32_t* major, uint32_t* minor, uint32_t* micro, const char** version)
{
if (version) *version = Initializer::version(major, minor, micro);
return TVG_RESULT_SUCCESS;
}
/************************************************************************/
/* Canvas API */
/************************************************************************/
@ -114,13 +120,6 @@ TVG_API Tvg_Result tvg_canvas_update(Tvg_Canvas* canvas)
}
TVG_API Tvg_Result tvg_canvas_est_viewport(Tvg_Canvas* canvas, int32_t x, int32_t y, int32_t w, int32_t h)
{
if (!canvas) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Canvas*>(canvas)->viewport(x, y, w, h);
}
TVG_API Tvg_Result tvg_canvas_update_paint(Tvg_Canvas* canvas, Tvg_Paint* paint)
{
if (!canvas || !paint) return TVG_RESULT_INVALID_ARGUMENT;
@ -141,6 +140,7 @@ TVG_API Tvg_Result tvg_canvas_sync(Tvg_Canvas* canvas)
return (Tvg_Result) reinterpret_cast<Canvas*>(canvas)->sync();
}
TVG_API Tvg_Result tvg_canvas_set_viewport(Tvg_Canvas* canvas, int32_t x, int32_t y, int32_t w, int32_t h)
{
if (!canvas) return TVG_RESULT_INVALID_ARGUMENT;
@ -240,26 +240,31 @@ TVG_API Tvg_Result tvg_paint_get_composite_method(const Tvg_Paint* paint, const
}
TVG_API Tvg_Result tvg_paint_set_blend_method(const Tvg_Paint* paint, Tvg_Blend_Method method)
TVG_API Tvg_Result tvg_paint_set_blend_method(Tvg_Paint* paint, Tvg_Blend_Method method)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<const Paint*>(paint)->blend((BlendMethod)method);
return (Tvg_Result) reinterpret_cast<Paint*>(paint)->blend((BlendMethod)method);
}
TVG_API Tvg_Result tvg_paint_get_blend_method(const Tvg_Paint* paint, Tvg_Blend_Method* method)
TVG_API Tvg_Result tvg_paint_get_type(const Tvg_Paint* paint, Tvg_Type* type)
{
if (!paint || !method) return TVG_RESULT_INVALID_ARGUMENT;
*method = static_cast<Tvg_Blend_Method>(reinterpret_cast<const Paint*>(paint)->blend());
if (!paint || !type) return TVG_RESULT_INVALID_ARGUMENT;
*type = static_cast<Tvg_Type>(reinterpret_cast<const Paint*>(paint)->type());
return TVG_RESULT_SUCCESS;
}
TVG_API Tvg_Result tvg_paint_get_identifier(const Tvg_Paint* paint, Tvg_Identifier* identifier)
TVG_API Tvg_Result tvg_paint_set_clip(Tvg_Paint* paint, Tvg_Paint* clipper)
{
if (!paint || !identifier) return TVG_RESULT_INVALID_ARGUMENT;
*identifier = static_cast<Tvg_Identifier>(reinterpret_cast<const Paint*>(paint)->identifier());
return TVG_RESULT_SUCCESS;
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Paint*>(paint)->clip(unique_ptr<Paint>((Paint*)(clipper)));
}
TVG_DEPRECATED TVG_API Tvg_Result tvg_paint_get_identifier(const Tvg_Paint* paint, Tvg_Identifier* identifier)
{
return tvg_paint_get_type(paint, (Tvg_Type*) identifier);
}
/************************************************************************/
@ -449,7 +454,6 @@ TVG_API Tvg_Result tvg_shape_get_stroke_join(const Tvg_Paint* paint, Tvg_Stroke_
TVG_API Tvg_Result tvg_shape_set_stroke_miterlimit(Tvg_Paint* paint, float ml)
{
if (ml < 0.0f) return TVG_RESULT_NOT_SUPPORTED;
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Shape*>(paint)->strokeMiterlimit(ml);
}
@ -463,6 +467,13 @@ TVG_API Tvg_Result tvg_shape_get_stroke_miterlimit(const Tvg_Paint* paint, float
}
TVG_API Tvg_Result tvg_shape_set_stroke_trim(Tvg_Paint* paint, float begin, float end, bool simultaneous)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Shape*>(paint)->strokeTrim(begin, end, simultaneous);
}
TVG_API Tvg_Result tvg_shape_set_fill_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
@ -565,6 +576,13 @@ TVG_API Tvg_Result tvg_picture_get_size(const Tvg_Paint* paint, float* w, float*
}
TVG_API const Tvg_Paint* tvg_picture_get_paint(Tvg_Paint* paint, uint32_t id)
{
if (!paint) return nullptr;
return (Tvg_Paint*) reinterpret_cast<Picture*>(paint)->paint(id);
}
/************************************************************************/
/* Gradient API */
/************************************************************************/
@ -669,13 +687,19 @@ TVG_API Tvg_Result tvg_gradient_get_transform(const Tvg_Gradient* grad, Tvg_Matr
}
TVG_API Tvg_Result tvg_gradient_get_identifier(const Tvg_Gradient* grad, Tvg_Identifier* identifier)
TVG_API Tvg_Result tvg_gradient_get_type(const Tvg_Gradient* grad, Tvg_Type* type)
{
if (!grad || !identifier) return TVG_RESULT_INVALID_ARGUMENT;
*identifier = static_cast<Tvg_Identifier>(reinterpret_cast<const Fill*>(grad)->identifier());
if (!grad || !type) return TVG_RESULT_INVALID_ARGUMENT;
*type = static_cast<Tvg_Type>(reinterpret_cast<const Fill*>(grad)->type());
return TVG_RESULT_SUCCESS;
}
TVG_DEPRECATED TVG_API Tvg_Result tvg_gradient_get_identifier(const Tvg_Gradient* grad, Tvg_Identifier* identifier)
{
return tvg_gradient_get_type(grad, (Tvg_Type*) identifier);
}
/************************************************************************/
/* Scene API */
/************************************************************************/
@ -706,6 +730,62 @@ TVG_API Tvg_Result tvg_scene_clear(Tvg_Paint* scene, bool free)
}
/************************************************************************/
/* Text API */
/************************************************************************/
TVG_API Tvg_Paint* tvg_text_new()
{
return (Tvg_Paint*)Text::gen().release();
}
TVG_API Tvg_Result tvg_text_set_font(Tvg_Paint* paint, const char* name, float size, const char* style)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Text*>(paint)->font(name, size, style);
}
TVG_API Tvg_Result tvg_text_set_text(Tvg_Paint* paint, const char* text)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Text*>(paint)->text(text);
}
TVG_API Tvg_Result tvg_text_set_fill_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Text*>(paint)->fill(r, g, b);
}
TVG_API Tvg_Result tvg_text_set_gradient(Tvg_Paint* paint, Tvg_Gradient* gradient)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Text*>(paint)->fill(unique_ptr<Fill>((Fill*)(gradient)));
}
TVG_API Tvg_Result tvg_font_load(const char* path)
{
return (Tvg_Result) Text::load(path);
}
TVG_API Tvg_Result tvg_font_load_data(const char* name, const char* data, uint32_t size, const char *mimetype, bool copy)
{
return (Tvg_Result) Text::load(name, data, size, mimetype ? mimetype : "", copy);
}
TVG_API Tvg_Result tvg_font_unload(const char* path)
{
return (Tvg_Result) Text::unload(path);
}
/************************************************************************/
/* Saver API */
/************************************************************************/
@ -808,6 +888,16 @@ TVG_API Tvg_Result tvg_animation_del(Tvg_Animation* animation)
}
/************************************************************************/
/* Accessor API */
/************************************************************************/
TVG_API uint32_t tvg_accessor_generate_id(const char* name)
{
return Accessor::id(name);
}
/************************************************************************/
/* Lottie Animation API */
/************************************************************************/

View File

@ -57,15 +57,6 @@ using namespace tvg;
#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
#define TVG_CLASS_ID_TEXT 6
enum class FileType { Png = 0, Jpg, Webp, Tvg, Svg, Lottie, Ttf, Raw, Gif, Unknown };
using Size = Point;
@ -90,6 +81,18 @@ uint16_t THORVG_VERSION_NUMBER();
#define P(A) ((A)->pImpl) //Access to pimpl.
#define PP(A) (((Paint*)(A))->pImpl) //Access to pimpl.
//for debugging
#if 0
#include <sys/time.h>
static inline double THORVG_TIMESTAMP()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec + tv.tv_usec / 1000000.0);
}
#endif
#endif //_TVG_COMMON_H_
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -471,10 +471,27 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded)
encoded += 4;
}
*decoded = output;
return reserved;
return idx;
}
/************************************************************************/
/* DJB2 Implementation */
/************************************************************************/
unsigned long djb2Encode(const char* str)
{
if (!str) return 0;
unsigned long hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash;
}
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

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

View File

@ -158,15 +158,14 @@ Fill* Fill::duplicate() const noexcept
}
uint32_t Fill::identifier() const noexcept
TVG_DEPRECATED uint32_t Fill::identifier() const noexcept
{
return pImpl->id;
return (uint32_t) type();
}
RadialGradient::RadialGradient():pImpl(new Impl())
{
Fill::pImpl->id = TVG_CLASS_ID_RADIAL;
Fill::pImpl->method(new FillDup<RadialGradient::Impl>(pImpl));
}
@ -199,15 +198,20 @@ unique_ptr<RadialGradient> RadialGradient::gen() noexcept
}
uint32_t RadialGradient::identifier() noexcept
TVG_DEPRECATED uint32_t RadialGradient::identifier() noexcept
{
return TVG_CLASS_ID_RADIAL;
return (uint32_t) Type::RadialGradient;
}
Type RadialGradient::type() const noexcept
{
return Type::RadialGradient;
}
LinearGradient::LinearGradient():pImpl(new Impl())
{
Fill::pImpl->id = TVG_CLASS_ID_LINEAR;
Fill::pImpl->method(new FillDup<LinearGradient::Impl>(pImpl));
}
@ -246,11 +250,16 @@ unique_ptr<LinearGradient> LinearGradient::gen() noexcept
}
uint32_t LinearGradient::identifier() noexcept
TVG_DEPRECATED uint32_t LinearGradient::identifier() noexcept
{
return TVG_CLASS_ID_LINEAR;
return (uint32_t) Type::LinearGradient;
}
Type LinearGradient::type() const noexcept
{
return Type::LinearGradient;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -58,7 +58,6 @@ struct Fill::Impl
uint32_t cnt = 0;
FillSpread spread;
DuplicateMethod<Fill>* dup = nullptr;
uint8_t id;
~Impl()
{

View File

@ -48,15 +48,14 @@ struct GlCanvas::Impl
/************************************************************************/
#ifdef THORVG_GL_RASTER_SUPPORT
GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(new Impl)
GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(nullptr)
#else
GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(new Impl)
GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(nullptr)
#endif
{
}
GlCanvas::~GlCanvas()
{
delete(pImpl);
@ -66,6 +65,10 @@ GlCanvas::~GlCanvas()
Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
{
#ifdef THORVG_GL_RASTER_SUPPORT
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
return Result::InsufficientCondition;
}
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<GlRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
@ -75,7 +78,7 @@ Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
renderer->viewport(Canvas::pImpl->vport);
//Paints must be updated again with this new target.
Canvas::pImpl->needRefresh();
Canvas::pImpl->status = Status::Damaged;
return Result::Success;
#endif

View File

@ -57,36 +57,30 @@ static constexpr bool operator &(CanvasEngine a, CanvasEngine b)
return int(a) & int(b);
}
static bool _buildVersionInfo()
static bool _buildVersionInfo(uint32_t* major, uint32_t* minor, uint32_t* micro)
{
auto SRC = THORVG_VERSION_STRING; //ex) 0.3.99
auto p = SRC;
auto VER = THORVG_VERSION_STRING;
auto p = VER;
const char* x;
char major[3];
x = strchr(p, '.');
if (!x) return false;
memcpy(major, p, x - p);
major[x - p] = '\0';
if (!(x = strchr(p, '.'))) return false;
uint32_t majorVal = atoi(p);
p = x + 1;
char minor[3];
x = strchr(p, '.');
if (!x) return false;
memcpy(minor, p, x - p);
minor[x - p] = '\0';
if (!(x = strchr(p, '.'))) return false;
uint32_t minorVal = atoi(p);
p = x + 1;
char micro[3];
x = SRC + strlen(THORVG_VERSION_STRING);
memcpy(micro, p, x - p);
micro[x - p] = '\0';
uint32_t microVal = atoi(p);
char sum[7];
snprintf(sum, sizeof(sum), "%s%s%s", major, minor, micro);
snprintf(sum, sizeof(sum), "%d%02d%02d", majorVal, minorVal, microVal);
_version = atoi(sum);
if (major) *major = majorVal;
if (minor) *minor = minorVal;
if (micro) *micro = microVal;
return true;
}
@ -125,7 +119,7 @@ Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept
if (_initCnt++ > 0) return Result::Success;
if (!_buildVersionInfo()) return Result::Unknown;
if (!_buildVersionInfo(nullptr, nullptr, nullptr)) return Result::Unknown;
if (!LoaderMgr::init()) return Result::Unknown;
@ -175,11 +169,17 @@ Result Initializer::term(CanvasEngine engine) noexcept
}
const char* Initializer::version(uint32_t* major, uint32_t* minor, uint32_t* micro) noexcept
{
if ((!major && ! minor && !micro) || _buildVersionInfo(major, minor, micro)) return THORVG_VERSION_STRING;
return nullptr;
}
uint16_t THORVG_VERSION_NUMBER()
{
return _version;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -103,7 +103,7 @@ struct Inlist
if (element == tail) tail = element->prev;
}
bool empty()
bool empty() const
{
return head ? false : true;
}

View File

@ -1,249 +0,0 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include "tvgMath.h"
#include "tvgLines.h"
#define BEZIER_EPSILON 1e-2f
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static float _lineLengthApprox(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 float _lineLength(const Point& pt1, const Point& pt2)
{
Point diff = {pt2.x - pt1.x, pt2.y - pt1.y};
return sqrtf(diff.x * diff.x + diff.y * diff.y);
}
template<typename LengthFunc>
float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc)
{
Bezier left, right;
auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end);
auto chord = lineLengthFunc(cur.start, cur.end);
if (fabsf(len - chord) > BEZIER_EPSILON) {
tvg::bezSplit(cur, left, right);
return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc);
}
return len;
}
template<typename LengthFunc>
float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc)
{
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 >= length) return 1.0f;
while (true) {
auto right = bz;
Bezier left;
bezSplitLeft(right, t, left);
length = _bezLength(left, lineLengthFunc);
if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
break;
}
if (length < at) {
smallest = t;
t = (t + biggest) * 0.5f;
} else {
biggest = t;
t = (smallest + t) * 0.5f;
}
}
return t;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
namespace tvg
{
float lineLength(const Point& pt1, const Point& pt2)
{
return _lineLength(pt1, pt2);
}
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;
}
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)
{
return _bezLength(cur, _lineLength);
}
float bezLengthApprox(const Bezier& cur)
{
return _bezLength(cur, _lineLengthApprox);
}
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, float length)
{
return _bezAt(bz, at, length, _lineLength);
}
float bezAtApprox(const Bezier& bz, float at, float length)
{
return _bezAt(bz, at, length, _lineLengthApprox);
}
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right)
{
right = cur;
auto t = bezAt(right, at, bezLength(right));
bezSplitLeft(right, t, left);
}
Point bezPointAt(const Bezier& bz, float t)
{
Point cur;
auto it = 1.0f - t;
auto ax = bz.start.x * it + bz.ctrl1.x * t;
auto bx = bz.ctrl1.x * it + bz.ctrl2.x * t;
auto cx = bz.ctrl2.x * it + bz.end.x * t;
ax = ax * it + bx * t;
bx = bx * it + cx * t;
cur.x = ax * it + bx * t;
float ay = bz.start.y * it + bz.ctrl1.y * t;
float by = bz.ctrl1.y * it + bz.ctrl2.y * t;
float cy = bz.ctrl2.y * it + bz.end.y * t;
ay = ay * it + by * t;
by = by * it + cy * t;
cur.y = ay * it + by * t;
return cur;
}
float bezAngleAt(const Bezier& bz, float t)
{
if (t < 0 || t > 1) return 0;
//derivate
// p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 *
// t^2) * p2 + t^2 * p3)
float mt = 1.0f - t;
float d = t * t;
float a = -mt * mt;
float b = 1 - 4 * t + 3 * d;
float c = 2 * t - 3 * d;
Point pt ={a * bz.start.x + b * bz.ctrl1.x + c * bz.ctrl2.x + d * bz.end.x, a * bz.start.y + b * bz.ctrl1.y + c * bz.ctrl2.y + d * bz.end.y};
pt.x *= 3;
pt.y *= 3;
return mathRad2Deg(atan2(pt.x, pt.y));
}
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -83,14 +83,14 @@ struct ImageLoader : LoadModule
static ColorSpace cs; //desired value
float w = 0, h = 0; //default image size
Surface surface;
RenderSurface surface;
ImageLoader(FileType type) : LoadModule(type) {}
virtual bool animatable() { return false; } //true if this loader supports animation.
virtual Paint* paint() { return nullptr; }
virtual Surface* bitmap()
virtual RenderSurface* bitmap()
{
if (surface.data) return &surface;
return nullptr;
@ -104,7 +104,8 @@ struct FontLoader : LoadModule
FontLoader(FileType type) : LoadModule(type) {}
virtual bool request(Shape* shape, char* text, bool italic = false) = 0;
virtual bool request(Shape* shape, char* text) = 0;
virtual bool transform(Paint* paint, float fontSize, bool italic) = 0;
};
#endif //_TVG_LOAD_MODULE_H_

View File

@ -297,10 +297,10 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid)
{
*invalid = false;
//TODO: lottie is not sharable.
//TODO: svg & lottie is not sharable.
auto allowCache = true;
auto ext = path.substr(path.find_last_of(".") + 1);
if (!ext.compare("json")) allowCache = false;
if (!ext.compare("svg") || !ext.compare("json")) allowCache = false;
if (allowCache) {
if (auto loader = _findFromCache(path)) return loader;
@ -437,5 +437,29 @@ LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool
return nullptr;
}
//loads fonts from memory - loader is cached (regardless of copy value) in order to access it while setting font
LoadModule* LoaderMgr::loader(const char* name, const char* data, uint32_t size, TVG_UNUSED const string& mimeType, bool copy)
{
#ifdef THORVG_TTF_LOADER_SUPPORT
//TODO: add check for mimetype ?
if (auto loader = _findFromCache(name)) return loader;
//function is dedicated for ttf loader (the only supported font loader)
auto loader = new TtfLoader;
if (loader->open(data, size, copy)) {
loader->hashpath = strdup(name);
loader->pathcache = true;
ScopedLock lock(key);
_activeLoaders.back(loader);
return loader;
}
TVGLOG("LOADER", "The font data \"%s\" could not be loaded.", name);
delete(loader);
#endif
return nullptr;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -35,6 +35,7 @@ struct LoaderMgr
static LoadModule* loader(const string& path, bool* invalid);
static LoadModule* loader(const char* data, uint32_t size, const string& mimeType, bool copy);
static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, bool copy);
static LoadModule* loader(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy);
static LoadModule* loader(const char* key);
static bool retrieve(const string& path);
static bool retrieve(LoadModule* loader);

View File

@ -29,6 +29,7 @@
#ifdef THORVG_THREAD_SUPPORT
#include <mutex>
#include "tvgTaskScheduler.h"
namespace tvg {
@ -43,13 +44,17 @@ namespace tvg {
ScopedLock(Key& k)
{
k.mtx.lock();
key = &k;
if (TaskScheduler::threads() > 0) {
k.mtx.lock();
key = &k;
}
}
~ScopedLock()
{
key->mtx.unlock();
if (TaskScheduler::threads() > 0) {
key->mtx.unlock();
}
}
};

View File

@ -46,9 +46,7 @@ Result LottieAnimation::override(const char* slot) noexcept
{
if (!pImpl->picture->pImpl->loader) return Result::InsufficientCondition;
if (static_cast<LottieLoader*>(pImpl->picture->pImpl->loader)->override(slot)) {
return Result::Success;
}
if (static_cast<LottieLoader*>(pImpl->picture->pImpl->loader)->override(slot)) return Result::Success;
return Result::InvalidArguments;
}
@ -58,7 +56,6 @@ Result LottieAnimation::segment(const char* marker) noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return Result::InsufficientCondition;
if (!loader->animatable()) return Result::NonSupport;
if (!marker) {
static_cast<FrameModule*>(loader)->segment(0.0f, 1.0f);
@ -66,9 +63,8 @@ Result LottieAnimation::segment(const char* marker) noexcept
}
float begin, end;
if (!static_cast<LottieLoader*>(loader)->segment(marker, begin, end)) {
return Result::InvalidArguments;
}
if (!static_cast<LottieLoader*>(loader)->segment(marker, begin, end)) return Result::InvalidArguments;
return static_cast<Animation*>(this)->segment(begin, end);
}
@ -76,7 +72,7 @@ Result LottieAnimation::segment(const char* marker) noexcept
uint32_t LottieAnimation::markersCnt() noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader || !loader->animatable()) return 0;
if (!loader) return 0;
return static_cast<LottieLoader*>(loader)->markersCnt();
}
@ -84,7 +80,7 @@ uint32_t LottieAnimation::markersCnt() noexcept
const char* LottieAnimation::marker(uint32_t idx) noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader || !loader->animatable()) return nullptr;
if (!loader) return nullptr;
return static_cast<LottieLoader*>(loader)->markers(idx);
}

File diff suppressed because it is too large Load Diff

View File

@ -22,18 +22,76 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#ifndef _TVG_LOTTIE_BUILDER_H_
#define _TVG_LOTTIE_BUILDER_H_
#include "tvgCommon.h"
#include "tvgInlist.h"
#include "tvgPaint.h"
#include "tvgShape.h"
#include "tvgLottieExpressions.h"
#include "tvgLottieModifier.h"
struct LottieComposition;
struct RenderRepeater
{
int cnt;
Matrix transform;
float offset;
Point position;
Point anchor;
Point scale;
float rotation;
uint8_t startOpacity;
uint8_t endOpacity;
bool interpOpacity;
bool inorder;
};
struct RenderContext
{
INLIST_ITEM(RenderContext);
Shape* propagator = nullptr; //for propagating the shape properties excluding paths
Shape* merging = nullptr; //merging shapes if possible (if shapes have same properties)
LottieObject** begin = nullptr; //iteration entry point
Array<RenderRepeater> repeaters;
Matrix* transform = nullptr;
LottieRoundnessModifier* roundness = nullptr;
LottieOffsetModifier* offsetPath = nullptr;
bool fragmenting = false; //render context has been fragmented by filling
bool reqFragment = false; //requirement to fragment the render context
RenderContext(Shape* propagator)
{
P(propagator)->reset();
PP(propagator)->ref();
this->propagator = propagator;
}
~RenderContext()
{
PP(propagator)->unref();
free(transform);
delete(roundness);
delete(offsetPath);
}
RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false)
{
if (mergeable) merging = rhs.merging;
PP(propagator)->ref();
this->propagator = propagator;
this->repeaters = rhs.repeaters;
if (rhs.roundness) this->roundness = new LottieRoundnessModifier(rhs.roundness->r);
if (rhs.offsetPath) this->offsetPath = new LottieOffsetModifier(rhs.offsetPath->offset, rhs.offsetPath->miterLimit, rhs.offsetPath->join);
}
};
struct LottieBuilder
{
LottieExpressions* exps = nullptr;
LottieBuilder()
{
exps = LottieExpressions::instance();
@ -46,6 +104,34 @@ struct LottieBuilder
bool update(LottieComposition* comp, float progress);
void build(LottieComposition* comp);
private:
void updateEffect(LottieLayer* layer, float frameNo);
void updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo);
bool updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer);
void updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo);
void updateSolid(LottieLayer* layer);
void updateImage(LottieGroup* layer);
void updateText(LottieLayer* layer, float frameNo);
void updateMaskings(LottieLayer* layer, float frameNo);
void updateTransform(LottieLayer* layer, float frameNo);
void updateChildren(LottieGroup* parent, float frameNo, Inlist<RenderContext>& contexts);
void updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& pcontexts, RenderContext* ctx);
void updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateRect(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updatePath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateTrimpath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateRepeater(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
LottieExpressions* exps;
};
#endif //_TVG_LOTTIE_BUILDER_H

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#ifndef _TVG_LOTTIE_COMMON_
#define _TVG_LOTTIE_COMMON_
#include <cmath>
#include "tvgCommon.h"
#include "tvgArray.h"
struct PathSet
{
Point* pts = nullptr;
PathCommand* cmds = nullptr;
uint16_t ptsCnt = 0;
uint16_t cmdsCnt = 0;
};
struct RGB24
{
int32_t rgb[3];
};
struct ColorStop
{
Fill::ColorStop* data = nullptr;
Array<float>* input = nullptr;
};
struct TextDocument
{
char* text = nullptr;
float height;
float shift;
RGB24 color;
struct {
Point pos;
Point size;
} bbox;
struct {
RGB24 color;
float width;
bool render = false;
} stroke;
char* name = nullptr;
float size;
float tracking = 0.0f;
uint8_t justify;
};
static inline int32_t REMAP255(float val)
{
return (int32_t)nearbyintf(val * 255.0f);
}
static inline RGB24 operator-(const RGB24& lhs, const RGB24& rhs)
{
return {lhs.rgb[0] - rhs.rgb[0], lhs.rgb[1] - rhs.rgb[1], lhs.rgb[2] - rhs.rgb[2]};
}
static inline RGB24 operator+(const RGB24& lhs, const RGB24& rhs)
{
return {lhs.rgb[0] + rhs.rgb[0], lhs.rgb[1] + rhs.rgb[1], lhs.rgb[2] + rhs.rgb[2]};
}
static inline RGB24 operator*(const RGB24& lhs, float rhs)
{
return {(int32_t)nearbyintf(lhs.rgb[0] * rhs), (int32_t)nearbyintf(lhs.rgb[1] * rhs), (int32_t)nearbyintf(lhs.rgb[2] * rhs)};
}
#endif //_TVG_LOTTIE_COMMON_
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -23,7 +23,9 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include "tvgMath.h"
#include "tvgCompressor.h"
#include "tvgLottieModel.h"
#include "tvgLottieExpressions.h"
@ -35,10 +37,12 @@
struct ExpContent
{
LottieExpression* exp;
LottieObject* obj;
float frameNo;
};
static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt);
//reserved expressions specifiers
static const char* EXP_NAME = "name";
@ -57,6 +61,16 @@ static const char* EXP_EFFECT= "effect";
static LottieExpressions* exps = nullptr; //singleton instance engine
static ExpContent* _expcontent(LottieExpression* exp, float frameNo, LottieObject* obj)
{
auto data = (ExpContent*)malloc(sizeof(ExpContent));
data->exp = exp;
data->frameNo = frameNo;
data->obj = obj;
return data;
}
static void contentFree(void *native_p, struct jerry_object_native_info_t *info_p)
{
free(native_p);
@ -78,43 +92,91 @@ static char* _name(jerry_value_t args)
}
static unsigned long _idByName(jerry_value_t args)
{
auto name = _name(args);
auto id = djb2Encode(name);
free(name);
return id;
}
static jerry_value_t _toComp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
TVGERR("LOTTIE", "toComp is not supported in expressions!");
TVGLOG("LOTTIE", "toComp is not supported in expressions!");
return jerry_undefined();
}
static void _buildTransform(jerry_value_t context, LottieTransform* transform)
static jerry_value_t _value(float frameNo, LottieProperty* property)
{
switch (property->type) {
case LottieProperty::Type::Point: {
auto value = jerry_object();
auto pos = (*static_cast<LottiePoint*>(property))(frameNo);
auto val1 = jerry_number(pos.x);
auto val2 = jerry_number(pos.y);
jerry_object_set_index(value, 0, val1);
jerry_object_set_index(value, 1, val2);
jerry_value_free(val1);
jerry_value_free(val2);
return value;
}
case LottieProperty::Type::Float: {
return jerry_number((*static_cast<LottieFloat*>(property))(frameNo));
}
case LottieProperty::Type::Opacity: {
return jerry_number((*static_cast<LottieOpacity*>(property))(frameNo));
}
case LottieProperty::Type::PathSet: {
auto value = jerry_object();
jerry_object_set_native_ptr(value, nullptr, property);
return value;
}
case LottieProperty::Type::Position: {
auto value = jerry_object();
auto pos = (*static_cast<LottiePosition*>(property))(frameNo);
auto val1 = jerry_number(pos.x);
auto val2 = jerry_number(pos.y);
jerry_object_set_index(value, 0, val1);
jerry_object_set_index(value, 1, val2);
jerry_value_free(val1);
jerry_value_free(val2);
return value;
}
default: {
TVGERR("LOTTIE", "Non supported type for value? = %d", (int) property->type);
}
}
return jerry_undefined();
}
static void _buildTransform(jerry_value_t context, float frameNo, LottieTransform* transform)
{
if (!transform) return;
auto obj = jerry_object();
jerry_object_set_sz(context, "transform", obj);
auto anchorPoint = jerry_object();
jerry_object_set_native_ptr(anchorPoint, nullptr, &transform->anchor);
auto anchorPoint = _value(frameNo, &transform->anchor);
jerry_object_set_sz(obj, "anchorPoint", anchorPoint);
jerry_value_free(anchorPoint);
auto position = jerry_object();
jerry_object_set_native_ptr(position, nullptr, &transform->position);
auto position = _value(frameNo, &transform->position);
jerry_object_set_sz(obj, "position", position);
jerry_value_free(position);
auto scale = jerry_object();
jerry_object_set_native_ptr(scale, nullptr, &transform->scale);
auto scale = _value(frameNo, &transform->scale);
jerry_object_set_sz(obj, "scale", scale);
jerry_value_free(scale);
auto rotation = jerry_object();
jerry_object_set_native_ptr(rotation, nullptr, &transform->rotation);
auto rotation = _value(frameNo, &transform->rotation);
jerry_object_set_sz(obj, "rotation", rotation);
jerry_value_free(rotation);
auto opacity = jerry_object();
jerry_object_set_native_ptr(opacity, nullptr, &transform->opacity);
auto opacity = _value(frameNo, &transform->opacity);
jerry_object_set_sz(obj, "opacity", opacity);
jerry_value_free(opacity);
@ -122,7 +184,71 @@ static void _buildTransform(jerry_value_t context, LottieTransform* transform)
}
static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposition* comp)
static jerry_value_t _buildGroup(LottieGroup* group, float frameNo)
{
auto obj = jerry_function_external(_content);
//attach a transform
for (auto c = group->children.begin(); c < group->children.end(); ++c) {
if ((*c)->type == LottieObject::Type::Transform) {
_buildTransform(obj, frameNo, static_cast<LottieTransform*>(*c));
break;
}
}
jerry_object_set_native_ptr(obj, &freeCb, _expcontent(nullptr, frameNo, group));
jerry_object_set_sz(obj, EXP_CONTENT, obj);
return obj;
}
static jerry_value_t _buildPolystar(LottiePolyStar* polystar, float frameNo)
{
auto obj = jerry_object();
auto position = jerry_object();
jerry_object_set_native_ptr(position, nullptr, &polystar->position);
jerry_object_set_sz(obj, "position", position);
jerry_value_free(position);
auto innerRadius = jerry_number(polystar->innerRadius(frameNo));
jerry_object_set_sz(obj, "innerRadius", innerRadius);
jerry_value_free(innerRadius);
auto outerRadius = jerry_number(polystar->outerRadius(frameNo));
jerry_object_set_sz(obj, "outerRadius", outerRadius);
jerry_value_free(outerRadius);
auto innerRoundness = jerry_number(polystar->innerRoundness(frameNo));
jerry_object_set_sz(obj, "innerRoundness", innerRoundness);
jerry_value_free(innerRoundness);
auto outerRoundness = jerry_number(polystar->outerRoundness(frameNo));
jerry_object_set_sz(obj, "outerRoundness", outerRoundness);
jerry_value_free(outerRoundness);
auto rotation = jerry_number(polystar->rotation(frameNo));
jerry_object_set_sz(obj, "rotation", rotation);
jerry_value_free(rotation);
auto ptsCnt = jerry_number(polystar->ptsCnt(frameNo));
jerry_object_set_sz(obj, "points", ptsCnt);
jerry_value_free(ptsCnt);
return obj;
}
static jerry_value_t _buildTrimpath(LottieTrimpath* trimpath, float frameNo)
{
jerry_value_t obj = jerry_object();
auto start = jerry_number(trimpath->start(frameNo));
jerry_object_set_sz(obj, "start", start);
jerry_value_free(start);
auto end = jerry_number(trimpath->end(frameNo));
jerry_object_set_sz(obj, "end", end);
jerry_value_free(end);
auto offset = jerry_number(trimpath->offset(frameNo));
jerry_object_set_sz(obj, "offset", end);
jerry_value_free(offset);
return obj;
}
static void _buildLayer(jerry_value_t context, float frameNo, LottieLayer* layer, LottieLayer* comp, LottieExpression* exp)
{
auto width = jerry_number(layer->w);
jerry_object_set_sz(context, EXP_WIDTH, width);
@ -132,7 +258,7 @@ static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposi
jerry_object_set_sz(context, EXP_HEIGHT, height);
jerry_value_free(height);
auto index = jerry_number(layer->id);
auto index = jerry_number(layer->idx);
jerry_object_set_sz(context, EXP_INDEX, index);
jerry_value_free(index);
@ -153,7 +279,8 @@ static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposi
jerry_object_set_sz(context, "outPoint", outPoint);
jerry_value_free(outPoint);
auto startTime = jerry_number(comp->timeAtFrame(layer->startFrame));
//TODO: Confirm exp->layer->comp->timeAtFrame() ?
auto startTime = jerry_number(exp->comp->timeAtFrame(layer->startFrame));
jerry_object_set_sz(context, "startTime", startTime);
jerry_value_free(startTime);
@ -177,7 +304,7 @@ static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposi
//sampleImage(point, radius = [.5, .5], postEffect=true, t=time)
_buildTransform(context, layer->transform);
_buildTransform(context, frameNo, layer->transform);
//audioLevels, #the value of the Audio Levels property of the layer in decibels
@ -199,70 +326,45 @@ static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposi
jerry_object_set_sz(context, "toComp", toComp);
jerry_object_set_native_ptr(toComp, nullptr, comp);
jerry_value_free(toComp);
}
static jerry_value_t _value(float frameNo, LottieExpression* exp)
{
switch (exp->type) {
case LottieProperty::Type::Point: {
auto value = jerry_object();
auto pos = (*static_cast<LottiePoint*>(exp->property))(frameNo);
auto val1 = jerry_number(pos.x);
auto val2 = jerry_number(pos.y);
jerry_object_set_index(value, 0, val1);
jerry_object_set_index(value, 1, val2);
jerry_value_free(val1);
jerry_value_free(val2);
return value;
}
case LottieProperty::Type::Float: {
return jerry_number((*static_cast<LottieFloat*>(exp->property))(frameNo));
}
case LottieProperty::Type::Opacity: {
return jerry_number((*static_cast<LottieOpacity*>(exp->property))(frameNo));
}
case LottieProperty::Type::PathSet: {
auto value = jerry_object();
jerry_object_set_native_ptr(value, nullptr, exp->property);
return value;
}
case LottieProperty::Type::Position: {
auto value = jerry_object();
auto pos = (*static_cast<LottiePosition*>(exp->property))(frameNo);
auto val1 = jerry_number(pos.x);
auto val2 = jerry_number(pos.y);
jerry_object_set_index(value, 0, val1);
jerry_object_set_index(value, 1, val2);
jerry_value_free(val1);
jerry_value_free(val2);
return value;
}
default: {
TVGERR("LOTTIE", "Non supported type for value? = %d", (int) exp->type);
}
}
return jerry_undefined();
//content("name"), #look for the named property from a layer
auto content = jerry_function_external(_content);
jerry_object_set_sz(context, EXP_CONTENT, content);
jerry_object_set_native_ptr(content, &freeCb, _expcontent(exp, frameNo, layer));
jerry_value_free(content);
}
static jerry_value_t _addsub(const jerry_value_t args[], float addsub)
{
//1d
if (jerry_value_is_number(args[0])) return jerry_number(jerry_value_as_number(args[0]) + addsub * jerry_value_as_number(args[1]));
auto n1 = jerry_value_is_number(args[0]);
auto n2 = jerry_value_is_number(args[1]);
//2d
auto val1 = jerry_object_get_index(args[0], 0);
auto val2 = jerry_object_get_index(args[0], 1);
auto val3 = jerry_object_get_index(args[1], 0);
auto val4 = jerry_object_get_index(args[1], 1);
auto x = jerry_value_as_number(val1) + addsub * jerry_value_as_number(val3);
auto y = jerry_value_as_number(val2) + addsub * jerry_value_as_number(val4);
//1d + 1d
if (n1 && n2) return jerry_number(jerry_value_as_number(args[0]) + addsub * jerry_value_as_number(args[1]));
auto val1 = jerry_object_get_index(args[n1 ? 1 : 0], 0);
auto val2 = jerry_object_get_index(args[n1 ? 1 : 0], 1);
auto x = jerry_value_as_number(val1);
auto y = jerry_value_as_number(val2);
jerry_value_free(val1);
jerry_value_free(val2);
jerry_value_free(val3);
jerry_value_free(val4);
//2d + 1d
if (n1 || n2) {
auto secondary = n1 ? 0 : 1;
auto val3 = jerry_value_as_number(args[secondary]);
if (secondary == 0) x = (x * addsub) + val3;
else x += (addsub * val3);
//2d + 2d
} else {
auto val3 = jerry_object_get_index(args[1], 0);
auto val4 = jerry_object_get_index(args[1], 1);
x += (addsub * jerry_value_as_number(val3));
y += (addsub * jerry_value_as_number(val4));
jerry_value_free(val3);
jerry_value_free(val4);
}
auto obj = jerry_object();
val1 = jerry_number(x);
@ -332,25 +434,25 @@ static jerry_value_t _interp(float t, const jerry_value_t args[], int argsCnt)
auto tMax = 1.0f;
int idx = 0;
if (argsCnt > 3) {
tMin = jerry_value_as_number(args[1]);
tMax = jerry_value_as_number(args[2]);
idx += 2;
}
tMin = jerry_value_as_number(args[1]);
tMax = jerry_value_as_number(args[2]);
idx += 2;
t = (t - tMin) / (tMax - tMin);
if (t < 0) t = 0.0f;
else if (t > 1) t = 1.0f;
//2d
if (jerry_value_is_object(args[idx + 1]) && jerry_value_is_object(args[idx + 2])) {
auto val1 = jerry_object_get_index(args[0], 0);
auto val2 = jerry_object_get_index(args[0], 1);
auto val3 = jerry_object_get_index(args[1], 0);
auto val4 = jerry_object_get_index(args[1], 1);
auto val1 = jerry_object_get_index(args[idx + 1], 0);
auto val2 = jerry_object_get_index(args[idx + 1], 1);
auto val3 = jerry_object_get_index(args[idx + 2], 0);
auto val4 = jerry_object_get_index(args[idx + 2], 1);
Point pt1 = {(float)jerry_value_as_number(val1), (float)jerry_value_as_number(val2)};
Point pt2 = {(float)jerry_value_as_number(val3), (float)jerry_value_as_number(val4)};
Point ret;
if (t <= tMin) ret = pt1;
else if (t >= tMax) ret = pt2;
else ret = mathLerp(pt1, pt2, t);
ret = lerp(pt1, pt2, t);
jerry_value_free(val1);
jerry_value_free(val2);
@ -370,10 +472,8 @@ static jerry_value_t _interp(float t, const jerry_value_t args[], int argsCnt)
//1d
auto val1 = (float) jerry_value_as_number(args[idx + 1]);
if (t <= tMin) jerry_number(val1);
auto val2 = (float) jerry_value_as_number(args[idx + 2]);
if (t >= tMax) jerry_number(val2);
return jerry_number(mathLerp(val1, val2, t));
return jerry_number(lerp(val1, val2, t));
}
@ -511,19 +611,19 @@ static jerry_value_t _random(const jerry_call_info_t* info, const jerry_value_t
static jerry_value_t _deg2rad(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
return jerry_number(mathDeg2Rad((float)jerry_value_as_number(args[0])));
return jerry_number(deg2rad((float)jerry_value_as_number(args[0])));
}
static jerry_value_t _rad2deg(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
return jerry_number(mathRad2Deg((float)jerry_value_as_number(args[0])));
return jerry_number(rad2deg((float)jerry_value_as_number(args[0])));
}
static jerry_value_t _effect(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
TVGERR("LOTTIE", "effect is not supported in expressions!");
TVGLOG("LOTTIE", "effect is not supported in expressions!");
return jerry_undefined();
}
@ -531,7 +631,7 @@ static jerry_value_t _effect(const jerry_call_info_t* info, const jerry_value_t
static jerry_value_t _fromCompToSurface(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
TVGERR("LOTTIE", "fromCompToSurface is not supported in expressions!");
TVGLOG("LOTTIE", "fromCompToSurface is not supported in expressions!");
return jerry_undefined();
}
@ -539,53 +639,22 @@ static jerry_value_t _fromCompToSurface(const jerry_call_info_t* info, const jer
static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
auto name = _name(args[0]);
auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(info->function, &freeCb));
auto group = static_cast<LottieGroup*>(data->obj);
auto target = group->content((char*)name);
free(name);
auto target = group->content(_idByName(args[0]));
if (!target) return jerry_undefined();
//find the a path property(sh) in the group layer?
switch (target->type) {
case LottieObject::Group: {
auto group = static_cast<LottieGroup*>(target);
auto obj = jerry_function_external(_content);
//attach a transform
for (auto c = group->children.begin(); c < group->children.end(); ++c) {
if ((*c)->type == LottieObject::Type::Transform) {
_buildTransform(obj, static_cast<LottieTransform*>(*c));
break;
}
}
auto data2 = (ExpContent*)malloc(sizeof(ExpContent));
data2->obj = group;
data2->frameNo = data->frameNo;
jerry_object_set_native_ptr(obj, &freeCb, data2);
jerry_object_set_sz(obj, EXP_CONTENT, obj);
return obj;
}
case LottieObject::Group: return _buildGroup(static_cast<LottieGroup*>(target), data->frameNo);
case LottieObject::Path: {
jerry_value_t obj = jerry_object();
jerry_object_set_native_ptr(obj, nullptr, &static_cast<LottiePath*>(target)->pathset);
jerry_object_set_sz(obj, "path", obj);
return obj;
}
case LottieObject::Trimpath: {
auto trimpath = static_cast<LottieTrimpath*>(target);
jerry_value_t obj = jerry_object();
auto start = jerry_number(trimpath->start(data->frameNo));
jerry_object_set_sz(obj, "start", start);
jerry_value_free(start);
auto end = jerry_number(trimpath->end(data->frameNo));
jerry_object_set_sz(obj, "end", end);
jerry_value_free(end);
auto offset = jerry_number(trimpath->offset(data->frameNo));
jerry_object_set_sz(obj, "offset", end);
jerry_value_free(offset);
return obj;
}
case LottieObject::Polystar: return _buildPolystar(static_cast<LottiePolyStar*>(target), data->frameNo);
case LottieObject::Trimpath: return _buildTrimpath(static_cast<LottieTrimpath*>(target), data->frameNo);
default: break;
}
return jerry_undefined();
@ -594,26 +663,25 @@ static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t
static jerry_value_t _layer(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
auto comp = static_cast<LottieComposition*>(jerry_object_get_native_ptr(info->function, nullptr));
auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(info->function, &freeCb));
auto comp = static_cast<LottieLayer*>(data->obj);
LottieLayer* layer;
//layer index
if (jerry_value_is_number(args[0])) {
auto idx = (uint16_t)jerry_value_as_int32(args[0]);
layer = comp->layer(idx);
layer = comp->layerByIdx(idx);
jerry_value_free(idx);
//layer name
} else {
auto name = _name(args[0]);
layer = comp->layer((char*)name);
free(name);
layer = comp->layerById(_idByName(args[0]));
}
if (!layer) return jerry_undefined();
auto obj = jerry_object();
jerry_object_set_native_ptr(obj, nullptr, layer);
_buildLayer(obj, layer, comp);
_buildLayer(obj, data->frameNo, layer, comp, data->exp);
return obj;
}
@ -633,13 +701,54 @@ static jerry_value_t _nearestKey(const jerry_call_info_t* info, const jerry_valu
return obj;
}
static jerry_value_t _property(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(info->function, &freeCb));
auto property = data->obj->property(jerry_value_as_int32(args[0]));
if (!property) return jerry_undefined();
return _value(data->frameNo, property);
}
static jerry_value_t _propertyGroup(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(info->function, &freeCb));
auto level = jerry_value_as_int32(args[0]);
//intermediate group
if (level == 1) {
auto group = jerry_function_external(_property);
jerry_object_set_native_ptr(group, &freeCb, _expcontent(data->exp, data->frameNo, data->obj));
jerry_object_set_sz(group, "", group);
return group;
}
TVGLOG("LOTTIE", "propertyGroup(%d)?", level);
return jerry_undefined();
}
static jerry_value_t _valueAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
auto exp = static_cast<LottieExpression*>(jerry_object_get_native_ptr(info->function, nullptr));
auto time = jerry_value_as_number(args[0]);
auto frameNo = exp->comp->frameAtTime(time);
return _value(frameNo, exp);
return _value(frameNo, exp->property);
}
static jerry_value_t _velocity(float px, float cx, float py, float cy, float elapsed)
{
float velocity[] = {(cx - px) / elapsed, (cy - py) / elapsed};
auto obj = jerry_object();
auto val1 = jerry_number(velocity[0]);
auto val2 = jerry_number(velocity[1]);
jerry_object_set_index(obj, 0, val1);
jerry_object_set_index(obj, 1, val2);
jerry_value_free(val1);
jerry_value_free(val2);
return obj;
}
@ -653,37 +762,27 @@ static jerry_value_t _velocityAtTime(const jerry_call_info_t* info, const jerry_
auto cframe = exp->property->frameNo(key);
auto elapsed = (cframe - pframe) / (exp->comp->frameRate);
Point cur, prv;
//compute the velocity
switch (exp->type) {
switch (exp->property->type) {
case LottieProperty::Type::Point: {
prv = (*static_cast<LottiePoint*>(exp->property))(pframe);
cur = (*static_cast<LottiePoint*>(exp->property))(cframe);
break;
auto prv = (*static_cast<LottiePoint*>(exp->property))(pframe);
auto cur = (*static_cast<LottiePoint*>(exp->property))(cframe);
return _velocity(prv.x, cur.x, prv.y, cur.y, elapsed);
}
case LottieProperty::Type::Position: {
prv = (*static_cast<LottiePosition*>(exp->property))(pframe);
cur = (*static_cast<LottiePosition*>(exp->property))(cframe);
break;
auto prv = (*static_cast<LottiePosition*>(exp->property))(pframe);
auto cur = (*static_cast<LottiePosition*>(exp->property))(cframe);
return _velocity(prv.x, cur.x, prv.y, cur.y, elapsed);
}
default: {
TVGERR("LOTTIE", "Non supported type for velocityAtTime?");
return jerry_undefined();
case LottieProperty::Type::Float: {
auto prv = (*static_cast<LottieFloat*>(exp->property))(pframe);
auto cur = (*static_cast<LottieFloat*>(exp->property))(cframe);
auto velocity = (cur - prv) / elapsed;
return jerry_number(velocity);
}
default: TVGLOG("LOTTIE", "Non supported type for velocityAtTime?");
}
float velocity[] = {(cur.x - prv.x) / elapsed, (cur.y - prv.y) / elapsed};
auto obj = jerry_object();
auto val1 = jerry_number(velocity[0]);
auto val2 = jerry_number(velocity[1]);
jerry_object_set_index(obj, 0, val1);
jerry_object_set_index(obj, 1, val2);
jerry_value_free(val1);
jerry_value_free(val2);
return obj;
return jerry_undefined();
}
@ -700,7 +799,7 @@ static jerry_value_t _speedAtTime(const jerry_call_info_t* info, const jerry_val
Point cur, prv;
//compute the velocity
switch (exp->type) {
switch (exp->property->type) {
case LottieProperty::Type::Point: {
prv = (*static_cast<LottiePoint*>(exp->property))(pframe);
cur = (*static_cast<LottiePoint*>(exp->property))(cframe);
@ -712,7 +811,7 @@ static jerry_value_t _speedAtTime(const jerry_call_info_t* info, const jerry_val
break;
}
default: {
TVGERR("LOTTIE", "Non supported type for speedAtTime?");
TVGLOG("LOTTIE", "Non supported type for speedAtTime?");
return jerry_undefined();
}
}
@ -736,8 +835,8 @@ static bool _loopOutCommon(LottieExpression* exp, const jerry_value_t args[], co
free(name);
}
if (exp->loop.mode != LottieExpression::LoopMode::OutCycle) {
TVGERR("hermet", "Not supported loopOut type = %d", exp->loop.mode);
if (exp->loop.mode != LottieExpression::LoopMode::OutCycle && exp->loop.mode != LottieExpression::LoopMode::OutPingPong) {
TVGLOG("LOTTIE", "Not supported loopOut type = %d", exp->loop.mode);
return false;
}
@ -788,8 +887,8 @@ static bool _loopInCommon(LottieExpression* exp, const jerry_value_t args[], con
free(name);
}
if (exp->loop.mode != LottieExpression::LoopMode::InCycle) {
TVGERR("hermet", "Not supported loopOut type = %d", exp->loop.mode);
if (exp->loop.mode != LottieExpression::LoopMode::InCycle && exp->loop.mode != LottieExpression::LoopMode::InPingPong) {
TVGLOG("LOTTIE", "Not supported loopIn type = %d", exp->loop.mode);
return false;
}
@ -802,9 +901,7 @@ static jerry_value_t _loopIn(const jerry_call_info_t* info, const jerry_value_t
if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined();
if (argsCnt > 1) {
exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1]));
}
if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]);
auto obj = jerry_object();
jerry_object_set_native_ptr(obj, nullptr, exp->property);
@ -834,13 +931,21 @@ static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t arg
auto key = jerry_value_as_int32(args[0]);
auto frameNo = exp->property->frameNo(key);
auto time = jerry_number(exp->comp->timeAtFrame(frameNo));
auto value = _value(frameNo, exp);
auto value = _value(frameNo, exp->property);
auto obj = jerry_object();
jerry_object_set_sz(obj, EXP_TIME, time);
jerry_object_set_sz(obj, EXP_INDEX, args[0]);
jerry_object_set_sz(obj, EXP_VALUE, value);
//direct access, key[0], key[1]
if (exp->property->type == LottieProperty::Type::Float) {
jerry_object_set_index(obj, 0, value);
} else if (exp->property->type == LottieProperty::Type::Point || exp->property->type == LottieProperty::Type::Position) {
jerry_object_set_index(obj, 0, jerry_object_get_index(value, 0));
jerry_object_set_index(obj, 1, jerry_object_get_index(value, 1));
}
jerry_value_free(time);
jerry_value_free(value);
@ -848,7 +953,6 @@ static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t arg
}
static jerry_value_t _createPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
//TODO: arg1: points, arg2: inTangents, arg3: outTangents, arg4: isClosed
@ -914,7 +1018,7 @@ static void _buildPath(jerry_value_t context, LottieExpression* exp)
static void _buildProperty(float frameNo, jerry_value_t context, LottieExpression* exp)
{
auto value = _value(frameNo, exp);
auto value = _value(frameNo, exp->property);
jerry_object_set_sz(context, EXP_VALUE, value);
jerry_value_free(value);
@ -981,42 +1085,37 @@ static void _buildProperty(float frameNo, jerry_value_t context, LottieExpressio
jerry_object_set_sz(context, "numKeys", numKeys);
jerry_value_free(numKeys);
//propertyGroup(countUp = 1)
auto propertyGroup = jerry_function_external(_propertyGroup);
jerry_object_set_native_ptr(propertyGroup, &freeCb, _expcontent(exp, frameNo, exp->object));
jerry_object_set_sz(context, "propertyGroup", propertyGroup);
jerry_value_free(propertyGroup);
//propertyIndex
//name
//content("name"), #look for the named property from a layer
auto data = (ExpContent*)malloc(sizeof(ExpContent));
data->obj = exp->layer;
data->frameNo = frameNo;
auto content = jerry_function_external(_content);
jerry_object_set_sz(context, EXP_CONTENT, content);
jerry_object_set_native_ptr(content, &freeCb, data);
jerry_object_set_native_ptr(content, &freeCb, _expcontent(exp, frameNo, exp->layer));
jerry_value_free(content);
//expansions per types
if (exp->property->type == LottieProperty::Type::PathSet) _buildPath(context, exp);
}
static jerry_value_t _comp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt)
{
auto comp = static_cast<LottieComposition*>(jerry_object_get_native_ptr(info->function, nullptr));
LottieLayer* layer;
auto arg0 = jerry_value_to_string(args[0]);
auto len = jerry_string_length(arg0);
auto name = (jerry_char_t*)alloca(len * sizeof(jerry_char_t) + 1);
jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len);
name[len] = '\0';
jerry_value_free(arg0);
layer = comp->asset((char*)name);
auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(info->function, &freeCb));
auto comp = static_cast<LottieLayer*>(data->obj);
auto layer = comp->layerById(_idByName(args[0]));
if (!layer) return jerry_undefined();
auto obj = jerry_object();
jerry_object_set_native_ptr(obj, nullptr, layer);
_buildLayer(obj, layer, comp);
_buildLayer(obj, data->frameNo, layer, comp, data->exp);
return obj;
}
@ -1116,11 +1215,37 @@ static void _buildMath(jerry_value_t context)
}
void LottieExpressions::buildComp(LottieComposition* comp)
void LottieExpressions::buildGlobal(LottieExpression* exp)
{
jerry_object_set_native_ptr(this->comp, nullptr, comp);
jerry_object_set_native_ptr(thisComp, nullptr, comp);
jerry_object_set_native_ptr(layer, nullptr, comp);
auto index = jerry_number(exp->layer->idx);
jerry_object_set_sz(global, EXP_INDEX, index);
jerry_value_free(index);
}
void LottieExpressions::buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp)
{
auto data = static_cast<ExpContent*>(jerry_object_get_native_ptr(context, &freeCb));
data->exp = exp;
data->frameNo = frameNo;
data->obj = comp;
//layer(index) / layer(name) / layer(otherLayer, reIndex)
auto layer = jerry_function_external(_layer);
jerry_object_set_sz(context, "layer", layer);
jerry_object_set_native_ptr(layer, &freeCb, _expcontent(exp, frameNo, comp));
jerry_value_free(layer);
auto numLayers = jerry_number(comp->children.count);
jerry_object_set_sz(context, "numLayers", numLayers);
jerry_value_free(numLayers);
}
void LottieExpressions::buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp)
{
buildComp(this->comp, frameNo, comp->root, exp);
//marker
//marker.key(index)
@ -1128,10 +1253,6 @@ void LottieExpressions::buildComp(LottieComposition* comp)
//marker.nearestKey(t)
//marker.numKeys
auto numLayers = jerry_number(comp->root->children.count);
jerry_object_set_sz(thisComp, "numLayers", numLayers);
jerry_value_free(numLayers);
//activeCamera
auto width = jerry_number(comp->w);
@ -1170,17 +1291,15 @@ jerry_value_t LottieExpressions::buildGlobal()
//comp(name)
comp = jerry_function_external(_comp);
jerry_object_set_native_ptr(comp, &freeCb, _expcontent(nullptr, 0.0f, nullptr));
jerry_object_set_sz(global, "comp", comp);
//footage(name)
thisComp = jerry_object();
jerry_object_set_native_ptr(thisComp, &freeCb, _expcontent(nullptr, 0.0f, nullptr));
jerry_object_set_sz(global, "thisComp", thisComp);
//layer(index) / layer(name) / layer(otherLayer, reIndex)
layer = jerry_function_external(_layer);
jerry_object_set_sz(thisComp, "layer", layer);
thisLayer = jerry_object();
jerry_object_set_sz(global, "thisLayer", thisLayer);
@ -1208,23 +1327,36 @@ jerry_value_t LottieExpressions::buildGlobal()
jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp)
{
buildComp(exp->comp);
if (exp->disabled) return jerry_undefined();
buildGlobal(exp);
//main composition
buildComp(exp->comp, frameNo, exp);
//this composition
buildComp(thisComp, frameNo, exp->layer->comp, exp);
//update global context values
jerry_object_set_native_ptr(thisLayer, nullptr, exp->layer);
_buildLayer(thisLayer, exp->layer, exp->comp);
jerry_object_set_native_ptr(thisProperty, nullptr, exp->property);
_buildProperty(frameNo, global, exp);
if (exp->type == LottieProperty::Type::PathSet) _buildPath(thisProperty, exp);
if (exp->object->type == LottieObject::Transform) _buildTransform(global, static_cast<LottieTransform*>(exp->object));
//this layer
jerry_object_set_native_ptr(thisLayer, nullptr, exp->layer);
_buildLayer(thisLayer, frameNo, exp->layer, exp->comp->root, exp);
//this property
jerry_object_set_native_ptr(thisProperty, nullptr, exp->property);
_buildProperty(frameNo, thisProperty, exp);
//expansions per object type
if (exp->object->type == LottieObject::Transform) _buildTransform(global, frameNo, static_cast<LottieTransform*>(exp->object));
//evaluate the code
auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS);
if (jerry_value_is_exception(eval) || jerry_value_is_undefined(eval)) {
exp->enabled = false; // The feature is experimental, it will be forcefully turned off if it's incompatible.
TVGERR("LOTTIE", "Failed to dispatch the expressions!");
exp->disabled = true;
return jerry_undefined();
}
@ -1242,7 +1374,6 @@ LottieExpressions::~LottieExpressions()
{
jerry_value_free(thisProperty);
jerry_value_free(thisLayer);
jerry_value_free(layer);
jerry_value_free(thisComp);
jerry_value_free(comp);
jerry_value_free(global);

View File

@ -27,10 +27,13 @@
#define _TVG_LOTTIE_EXPRESSIONS_H_
#include "tvgCommon.h"
#include "tvgLottieCommon.h"
struct LottieExpression;
struct LottieComposition;
struct RGB24;
struct LottieLayer;
struct LottieRoundnessModifier;
struct LottieOffsetModifier;
#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT
@ -44,14 +47,12 @@ public:
bool result(float frameNo, NumType& out, LottieExpression* exp)
{
auto bm_rt = evaluate(frameNo, exp);
if (jerry_value_is_undefined(bm_rt)) return false;
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*prop)(frameNo);
} else if (jerry_value_is_number(bm_rt)) {
if (jerry_value_is_number(bm_rt)) {
out = (NumType) jerry_value_as_number(bm_rt);
} else {
TVGERR("LOTTIE", "Failed dispatching a Value!");
return false;
} else if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*prop)(frameNo);
}
jerry_value_free(bm_rt);
return true;
@ -61,21 +62,17 @@ public:
bool result(float frameNo, Point& out, LottieExpression* exp)
{
auto bm_rt = evaluate(frameNo, exp);
if (jerry_value_is_undefined(bm_rt)) return false;
if (jerry_value_is_object(bm_rt)) {
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*prop)(frameNo);
} else {
auto x = jerry_object_get_index(bm_rt, 0);
auto y = jerry_object_get_index(bm_rt, 1);
out.x = jerry_value_as_number(x);
out.y = jerry_value_as_number(y);
jerry_value_free(x);
jerry_value_free(y);
}
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*prop)(frameNo);
} else {
TVGERR("LOTTIE", "Failed dispatching Point!");
return false;
auto x = jerry_object_get_index(bm_rt, 0);
auto y = jerry_object_get_index(bm_rt, 1);
out.x = jerry_value_as_number(x);
out.y = jerry_value_as_number(y);
jerry_value_free(x);
jerry_value_free(y);
}
jerry_value_free(bm_rt);
return true;
@ -85,12 +82,20 @@ public:
bool result(float frameNo, RGB24& out, LottieExpression* exp)
{
auto bm_rt = evaluate(frameNo, exp);
if (jerry_value_is_undefined(bm_rt)) return false;
if (auto color = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
out = (*color)(frameNo);
} else {
TVGERR("LOTTIE", "Failed dispatching Color!");
return false;
auto r = jerry_object_get_index(bm_rt, 0);
auto g = jerry_object_get_index(bm_rt, 1);
auto b = jerry_object_get_index(bm_rt, 2);
out.rgb[0] = REMAP255(jerry_value_as_number(r));
out.rgb[1] = REMAP255(jerry_value_as_number(g));
out.rgb[2] = REMAP255(jerry_value_as_number(b));
jerry_value_free(r);
jerry_value_free(g);
jerry_value_free(b);
}
jerry_value_free(bm_rt);
return true;
@ -100,28 +105,24 @@ public:
bool result(float frameNo, Fill* fill, LottieExpression* exp)
{
auto bm_rt = evaluate(frameNo, exp);
if (jerry_value_is_undefined(bm_rt)) return false;
if (auto colorStop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
(*colorStop)(frameNo, fill, this);
} else {
TVGERR("LOTTIE", "Failed dispatching ColorStop!");
return false;
}
jerry_value_free(bm_rt);
return true;
}
template<typename Property>
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness, LottieExpression* exp)
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpression* exp)
{
auto bm_rt = evaluate(frameNo, exp);
if (jerry_value_is_undefined(bm_rt)) return false;
if (auto pathset = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
(*pathset)(frameNo, cmds, pts, transform, roundness);
} else {
TVGERR("LOTTIE", "Failed dispatching PathSet!");
return false;
}
(*pathset)(frameNo, cmds, pts, transform, roundness, offsetPath);
}
jerry_value_free(bm_rt);
return true;
}
@ -138,12 +139,14 @@ private:
jerry_value_t evaluate(float frameNo, LottieExpression* exp);
jerry_value_t buildGlobal();
void buildComp(LottieComposition* comp);
void buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp);
void buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp);
void buildGlobal(LottieExpression* exp);
//global object, attributes, methods
jerry_value_t global;
jerry_value_t comp;
jerry_value_t layer;
jerry_value_t thisComp;
jerry_value_t thisLayer;
jerry_value_t thisProperty;
@ -157,7 +160,7 @@ struct LottieExpressions
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Point&, LottieExpression*) { return false; }
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED RGB24&, TVG_UNUSED LottieExpression*) { return false; }
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Fill*, TVG_UNUSED LottieExpression*) { return false; }
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Array<PathCommand>&, TVG_UNUSED Array<Point>&, TVG_UNUSED Matrix* transform, TVG_UNUSED float, TVG_UNUSED LottieExpression*) { return false; }
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Array<PathCommand>&, TVG_UNUSED Array<Point>&, TVG_UNUSED Matrix* transform, TVG_UNUSED const LottieRoundnessModifier*, TVG_UNUSED const LottieOffsetModifier*, TVG_UNUSED LottieExpression*) { return false; }
void update(TVG_UNUSED float) {}
static LottieExpressions* instance() { return nullptr; }
static void retrieve(TVG_UNUSED LottieExpressions* instance) {}

View File

@ -42,13 +42,29 @@ void LottieLoader::run(unsigned tid)
} else {
LottieParser parser(content, dirName);
if (!parser.parse()) return;
comp = parser.comp;
{
ScopedLock lock(key);
comp = parser.comp;
}
builder->build(comp);
release();
}
rebuild = false;
}
void LottieLoader::release()
{
if (copy) {
free((char*)content);
content = nullptr;
}
free(dirName);
dirName = nullptr;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
@ -61,10 +77,9 @@ LottieLoader::LottieLoader() : FrameModule(FileType::Lottie), builder(new Lottie
LottieLoader::~LottieLoader()
{
this->done();
done();
if (copy) free((char*)content);
free(dirName);
release();
//TODO: correct position?
delete(comp);
@ -76,12 +91,14 @@ bool LottieLoader::header()
{
//A single thread doesn't need to perform intensive tasks.
if (TaskScheduler::threads() == 0) {
LoadModule::read();
run(0);
if (comp) {
w = static_cast<float>(comp->w);
h = static_cast<float>(comp->h);
frameDuration = comp->duration();
frameCnt = comp->frameCnt();
frameRate = comp->frameRate;
return true;
} else {
return false;
@ -91,7 +108,6 @@ bool LottieLoader::header()
//Quickly validate the given Lottie file without parsing in order to get the animation info.
auto startFrame = 0.0f;
auto endFrame = 0.0f;
auto frameRate = 0.0f;
uint32_t depth = 0;
auto p = content;
@ -185,11 +201,14 @@ bool LottieLoader::header()
bool LottieLoader::open(const char* data, uint32_t size, bool copy)
{
if (copy) {
content = (char*)malloc(size);
content = (char*)malloc(size + 1);
if (!content) return false;
memcpy((char*)content, data, size);
const_cast<char*>(content)[size] = '\0';
} else content = data;
this->dirName = strdup(".");
this->size = size;
this->copy = copy;
@ -249,10 +268,10 @@ bool LottieLoader::resize(Paint* paint, float w, float h)
bool LottieLoader::read()
{
if (!content || size == 0) return false;
//the loading has been already completed
if (comp || !LoadModule::read()) return true;
if (!LoadModule::read()) return true;
if (!content || size == 0) return false;
TaskScheduler::request(this);
@ -272,9 +291,7 @@ Paint* LottieLoader::paint()
bool LottieLoader::override(const char* slot)
{
if (!comp) done();
if (!comp || comp->slots.count == 0) return false;
if (!ready() || comp->slots.count == 0) return false;
auto success = true;
@ -285,6 +302,7 @@ bool LottieLoader::override(const char* slot)
//parsing slot json
LottieParser parser(temp, dirName);
parser.comp = comp;
auto idx = 0;
while (auto sid = parser.sid(idx == 0)) {
@ -317,7 +335,7 @@ bool LottieLoader::frame(float no)
//This ensures that the target frame number is reached.
frameNo *= 10000.0f;
frameNo = roundf(frameNo);
frameNo = nearbyintf(frameNo);
frameNo *= 0.0001f;
//Skip update if frame diff is too small.
@ -354,17 +372,13 @@ float LottieLoader::curFrame()
float LottieLoader::duration()
{
if (segmentBegin == 0.0f && segmentEnd == 1.0f) return frameDuration;
if (!comp) done();
if (!comp) return 0.0f;
return frameCnt * (segmentEnd - segmentBegin) / comp->frameRate;
return frameCnt * (segmentEnd - segmentBegin) / frameRate;
}
void LottieLoader::sync()
{
this->done();
done();
if (rebuild) run(0);
}
@ -372,16 +386,13 @@ void LottieLoader::sync()
uint32_t LottieLoader::markersCnt()
{
if (!comp) done();
if (!comp) return 0;
return comp->markers.count;
return ready() ? comp->markers.count : 0;
}
const char* LottieLoader::markers(uint32_t index)
{
if (!comp) done();
if (!comp || index >= comp->markers.count) return nullptr;
if (!ready() || index >= comp->markers.count) return nullptr;
auto marker = comp->markers.begin() + index;
return (*marker)->name;
}
@ -389,9 +400,8 @@ const char* LottieLoader::markers(uint32_t index)
bool LottieLoader::segment(const char* marker, float& begin, float& end)
{
if (!comp) done();
if (!comp) return false;
if (!ready() || comp->markers.count == 0) return false;
for (auto m = comp->markers.begin(); m < comp->markers.end(); ++m) {
if (!strcmp(marker, (*m)->name)) {
begin = (*m)->time / frameCnt;
@ -403,5 +413,16 @@ bool LottieLoader::segment(const char* marker, float& begin, float& end)
}
bool LottieLoader::ready()
{
{
ScopedLock lock(key);
if (comp) return true;
}
done();
if (comp) return true;
return false;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -41,10 +41,12 @@ public:
float frameNo = 0.0f; //current frame number
float frameCnt = 0.0f;
float frameDuration = 0.0f;
float frameRate = 0.0f;
LottieBuilder* builder;
LottieComposition* comp = nullptr;
Key key;
char* dirName = nullptr; //base resource directory
bool copy = false; //"content" is owned by this loader
bool overridden = false; //overridden properties with slots
@ -73,10 +75,12 @@ public:
bool segment(const char* marker, float& begin, float& end);
private:
bool ready();
bool header();
void clear();
float startFrame();
void run(unsigned tid) override;
void release();
};

View File

@ -23,8 +23,10 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include "tvgMath.h"
#include "tvgPaint.h"
#include "tvgFill.h"
#include "tvgTaskScheduler.h"
#include "tvgLottieModel.h"
@ -111,69 +113,87 @@ void LottieSlot::assign(LottieObject* target)
}
void LottieTextRange::range(float frameNo, float totalLen, float& start, float& end)
{
auto divisor = (rangeUnit == Unit::Percent) ? (100.0f / totalLen) : 1.0f;
auto offset = this->offset(frameNo) / divisor;
start = nearbyintf(this->start(frameNo) / divisor) + offset;
end = nearbyintf(this->end(frameNo) / divisor) + offset;
if (start > end) std::swap(start, end);
if (random == 0) return;
auto range = end - start;
auto len = (rangeUnit == Unit::Percent) ? 100.0f : totalLen;
start = static_cast<float>(random % int(len - range));
end = start + range;
}
LottieImage::~LottieImage()
{
free(b64Data);
free(mimeType);
}
if (picture && PP(picture)->unref() == 0) {
delete(picture);
}
void LottieImage::prepare()
{
LottieObject::type = LottieObject::Image;
auto picture = Picture::gen().release();
//force to load a picture on the same thread
TaskScheduler::async(false);
if (size > 0) picture->load((const char*)b64Data, size, mimeType, false);
else picture->load(path);
TaskScheduler::async(true);
picture->size(width, height);
PP(picture)->ref();
pooler.push(picture);
}
void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps)
{
auto s = this->start(frameNo, exps) * 0.01f;
auto e = this->end(frameNo, exps) * 0.01f;
start = this->start(frameNo, exps) * 0.01f;
tvg::clamp(start, 0.0f, 1.0f);
end = this->end(frameNo, exps) * 0.01f;
tvg::clamp(end, 0.0f, 1.0f);
auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1
auto diff = fabs(s - e);
if (mathZero(diff)) {
auto diff = fabs(start - end);
if (tvg::zero(diff)) {
start = 0.0f;
end = 0.0f;
return;
}
if (mathEqual(diff, 1.0f) || mathEqual(diff, 2.0f)) {
if (tvg::equal(diff, 1.0f) || tvg::equal(diff, 2.0f)) {
start = 0.0f;
end = 1.0f;
return;
}
s += o;
e += o;
auto loop = true;
//no loop
if (s > 1.0f && e > 1.0f) loop = false;
if (s < 0.0f && e < 0.0f) loop = false;
if (s >= 0.0f && s <= 1.0f && e >= 0.0f && e <= 1.0f) loop = false;
if (s > 1.0f) s -= 1.0f;
if (s < 0.0f) s += 1.0f;
if (e > 1.0f) e -= 1.0f;
if (e < 0.0f) e += 1.0f;
if (loop) {
start = s > e ? s : e;
end = s < e ? s : e;
} else {
start = s < e ? s : e;
end = s > e ? s : e;
}
if (start > end) std::swap(start, end);
start += o;
end += o;
}
uint32_t LottieGradient::populate(ColorStop& color)
uint32_t LottieGradient::populate(ColorStop& color, size_t count)
{
colorStops.populated = true;
if (!color.input) return 0;
uint32_t alphaCnt = (color.input->count - (colorStops.count * 4)) / 2;
Array<Fill::ColorStop> output(colorStops.count + alphaCnt);
uint32_t alphaCnt = (color.input->count - (count * 4)) / 2;
Array<Fill::ColorStop> output(count + alphaCnt);
uint32_t cidx = 0; //color count
uint32_t clast = colorStops.count * 4;
uint32_t clast = count * 4;
if (clast > color.input->count) clast = color.input->count;
uint32_t aidx = clast; //alpha count
Fill::ColorStop cs;
@ -183,33 +203,37 @@ uint32_t LottieGradient::populate(ColorStop& color)
if (cidx == clast || aidx == color.input->count) break;
if ((*color.input)[cidx] == (*color.input)[aidx]) {
cs.offset = (*color.input)[cidx];
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
cidx += 4;
aidx += 2;
} else if ((*color.input)[cidx] < (*color.input)[aidx]) {
cs.offset = (*color.input)[cidx];
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
//generate alpha value
if (output.count > 0) {
auto p = ((*color.input)[cidx] - output.last().offset) / ((*color.input)[aidx] - output.last().offset);
cs.a = mathLerp<uint8_t>(output.last().a, lroundf((*color.input)[aidx + 1] * 255.0f), p);
} else cs.a = 255;
cs.a = lerp<uint8_t>(output.last().a, (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f), p);
} else cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
cidx += 4;
} else {
cs.offset = (*color.input)[aidx];
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
//generate color value
if (output.count > 0) {
auto p = ((*color.input)[aidx] - output.last().offset) / ((*color.input)[cidx] - output.last().offset);
cs.r = mathLerp<uint8_t>(output.last().r, lroundf((*color.input)[cidx + 1] * 255.0f), p);
cs.g = mathLerp<uint8_t>(output.last().g, lroundf((*color.input)[cidx + 2] * 255.0f), p);
cs.b = mathLerp<uint8_t>(output.last().b, lroundf((*color.input)[cidx + 3] * 255.0f), p);
} else cs.r = cs.g = cs.b = 255;
cs.r = lerp<uint8_t>(output.last().r, (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f), p);
cs.g = lerp<uint8_t>(output.last().g, (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f), p);
cs.b = lerp<uint8_t>(output.last().b, (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f), p);
} else {
cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
}
aidx += 2;
}
output.push(cs);
@ -218,9 +242,9 @@ uint32_t LottieGradient::populate(ColorStop& color)
//color remains
while (cidx + 3 < clast) {
cs.offset = (*color.input)[cidx];
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f);
cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f);
cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f);
cs.a = (output.count > 0) ? output.last().a : 255;
output.push(cs);
cidx += 4;
@ -229,7 +253,7 @@ uint32_t LottieGradient::populate(ColorStop& color)
//alpha remains
while (aidx < color.input->count) {
cs.offset = (*color.input)[aidx];
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f);
if (output.count > 0) {
cs.r = output.last().r;
cs.g = output.last().g;
@ -251,11 +275,14 @@ uint32_t LottieGradient::populate(ColorStop& color)
Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
{
auto opacity = this->opacity(frameNo);
if (opacity == 0) return nullptr;
Fill* fill = nullptr;
auto s = start(frameNo, exps);
auto e = end(frameNo, exps);
//Linear Gradient
//Linear Graident
if (id == 1) {
fill = LinearGradient::gen().release();
static_cast<LinearGradient*>(fill)->linear(s.x, s.y, e.x, e.y);
@ -269,12 +296,12 @@ Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w);
auto progress = this->height(frameNo, exps) * 0.01f;
if (mathZero(progress)) {
if (tvg::zero(progress)) {
P(static_cast<RadialGradient*>(fill))->radial(s.x, s.y, r, s.x, s.y, 0.0f);
} else {
if (mathEqual(progress, 1.0f)) progress = 0.99f;
auto startAngle = mathRad2Deg(atan2(e.y - s.y, e.x - s.x));
auto angle = mathDeg2Rad((startAngle + this->angle(frameNo, exps)));
if (tvg::equal(progress, 1.0f)) progress = 0.99f;
auto startAngle = rad2deg(tvg::atan2(e.y - s.y, e.x - s.x));
auto angle = deg2rad((startAngle + this->angle(frameNo, exps)));
auto fx = s.x + cos(angle) * progress * r;
auto fy = s.y + sin(angle) * progress * r;
// Lottie doesn't have any focal radius concept
@ -286,10 +313,29 @@ Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
colorStops(frameNo, fill, exps);
//multiply the current opacity with the fill
if (opacity < 255) {
const Fill::ColorStop* colorStops;
auto cnt = fill->colorStops(&colorStops);
for (uint32_t i = 0; i < cnt; ++i) {
const_cast<Fill::ColorStop*>(&colorStops[i])->a = MULTIPLY(colorStops[i].a, opacity);
}
}
return fill;
}
LottieGroup::LottieGroup()
{
reqFragment = false;
buildDone = false;
trimpath = false;
visible = false;
allowMerge = true;
}
void LottieGroup::prepare(LottieObject::Type type)
{
LottieObject::type = type;
@ -308,6 +354,24 @@ void LottieGroup::prepare(LottieObject::Type type)
In that case, the rendering context can be sharable with the parent's. */
if (allowMerge && (child->type == LottieObject::Group || !child->mergeable())) allowMerge = false;
//Figure out this group has visible contents
switch (child->type) {
case LottieObject::Group: {
visible |= static_cast<LottieGroup*>(child)->visible;
break;
}
case LottieObject::Rect:
case LottieObject::Ellipse:
case LottieObject::Path:
case LottieObject::Polystar:
case LottieObject::Image:
case LottieObject::Text: {
visible = true;
break;
}
default: break;
}
if (reqFragment) continue;
/* Figure out if the rendering context should be fragmented.
@ -347,21 +411,23 @@ void LottieGroup::prepare(LottieObject::Type type)
LottieLayer::~LottieLayer()
{
if (refId) {
//No need to free assets children because the Composition owns them.
children.clear();
free(refId);
}
//No need to free assets children because the Composition owns them.
if (rid) children.clear();
for (auto m = masks.begin(); m < masks.end(); ++m) {
delete(*m);
}
delete(matte.target);
for (auto e = effects.begin(); e < effects.end(); ++e) {
delete(*e);
}
delete(transform);
free(name);
}
void LottieLayer::prepare()
void LottieLayer::prepare(RGB24* color)
{
/* if layer is hidden, only useful data is its transform matrix.
so force it to be a Null Layer and release all resource. */
@ -371,11 +437,27 @@ void LottieLayer::prepare()
children.reset();
return;
}
//prepare the viewport clipper
if (type == LottieLayer::Precomp) {
auto clipper = Shape::gen().release();
clipper->appendRect(0.0f, 0.0f, w, h);
PP(clipper)->ref();
statical.pooler.push(clipper);
//prepare solid fill in advance if it is a layer type.
} else if (color && type == LottieLayer::Solid) {
auto solidFill = Shape::gen().release();
solidFill->appendRect(0, 0, static_cast<float>(w), static_cast<float>(h));
solidFill->fill(color->rgb[0], color->rgb[1], color->rgb[2]);
PP(solidFill)->ref();
statical.pooler.push(solidFill);
}
LottieGroup::prepare(LottieObject::Layer);
}
float LottieLayer::remap(float frameNo, LottieExpressions* exp)
float LottieLayer::remap(LottieComposition* comp, float frameNo, LottieExpressions* exp)
{
if (timeRemap.frames || timeRemap.value) {
frameNo = comp->frameAtTime(timeRemap(frameNo, exp));

View File

@ -31,6 +31,7 @@
#include "tvgCommon.h"
#include "tvgRender.h"
#include "tvgLottieProperty.h"
#include "tvgLottieRenderPooler.h"
struct LottieComposition;
@ -79,9 +80,37 @@ struct LottieStroke
};
struct LottieEffect
{
enum Type : uint8_t
{
GaussianBlur = 0,
};
virtual ~LottieEffect() {}
Type type;
bool enable = false;
};
struct LottieGaussianBlur : LottieEffect
{
LottieSlider blurness = 0.0f;
LottieCheckbox direction = 0;
LottieCheckbox wrap = 0;
LottieGaussianBlur()
{
type = GaussianBlur;
}
};
struct LottieMask
{
LottiePathSet pathset;
LottieFloat expand = 0.0f;
LottieOpacity opacity = 255;
CompositeMethod method;
bool inverse = false;
@ -108,12 +137,12 @@ struct LottieObject
Trimpath,
Text,
Repeater,
RoundedCorner
RoundedCorner,
OffsetPath
};
virtual ~LottieObject()
{
free(name);
}
virtual void override(LottieProperty* prop)
@ -122,8 +151,9 @@ struct LottieObject
}
virtual bool mergeable() { return false; }
virtual LottieProperty* property(uint16_t ix) { return nullptr; }
char* name = nullptr;
unsigned long id = 0;
Type type;
bool hidden = false; //remove?
};
@ -152,6 +182,46 @@ struct LottieGlyph
};
struct LottieTextStyle
{
LottieColor fillColor = RGB24{255, 255, 255};
LottieColor strokeColor = RGB24{255, 255, 255};
LottiePosition position = Point{0, 0};
LottiePoint scale = Point{100, 100};
LottieFloat letterSpacing = 0.0f;
LottieFloat lineSpacing = 0.0f;
LottieFloat strokeWidth = 0.0f;
LottieFloat rotation = 0.0f;
LottieOpacity fillOpacity = 255;
LottieOpacity strokeOpacity = 255;
LottieOpacity opacity = 255;
};
struct LottieTextRange
{
enum Based : uint8_t { Chars = 1, CharsExcludingSpaces, Words, Lines };
enum Shape : uint8_t { Square = 1, RampUp, RampDown, Triangle, Round, Smooth };
enum Unit : uint8_t { Percent = 1, Index };
LottieTextStyle style;
LottieFloat offset = 0.0f;
LottieFloat maxEase = 0.0f;
LottieFloat minEase = 0.0f;
LottieFloat maxAmount = 0.0f;
LottieFloat smoothness = 0.0f;
LottieFloat start = 0.0f;
LottieFloat end = FLT_MAX;
Based based = Chars;
Shape shape = Square;
Unit rangeUnit = Percent;
uint8_t random = 0;
bool expressible = false;
void range(float frameNo, float totalLen, float& start, float& end);
};
struct LottieFont
{
enum Origin : uint8_t { Local = 0, CssURL, ScriptURL, FontURL, Embedded };
@ -184,7 +254,7 @@ struct LottieMarker
}
};
struct LottieText : LottieObject
struct LottieText : LottieObject, LottieRenderPooler<tvg::Shape>
{
void prepare()
{
@ -197,9 +267,20 @@ struct LottieText : LottieObject
this->prepare();
}
LottieProperty* property(uint16_t ix) override
{
if (doc.ix == ix) return &doc;
return nullptr;
}
LottieTextDoc doc;
LottieFont* font;
LottieFloat spacing = 0.0f; //letter spacing
Array<LottieTextRange*> ranges;
~LottieText()
{
for (auto r = ranges.begin(); r < ranges.end(); ++r) delete(*r);
}
};
@ -218,6 +299,14 @@ struct LottieTrimpath : LottieObject
return false;
}
LottieProperty* property(uint16_t ix) override
{
if (start.ix == ix) return &start;
if (end.ix == ix) return &end;
if (offset.ix == ix) return &offset;
return nullptr;
}
void segment(float frameNo, float& start, float& end, LottieExpressions* exps);
LottieFloat start = 0.0f;
@ -227,15 +316,21 @@ struct LottieTrimpath : LottieObject
};
struct LottieShape : LottieObject
struct LottieShape : LottieObject, LottieRenderPooler<tvg::Shape>
{
bool clockwise = true; //clockwise or counter-clockwise
virtual ~LottieShape() {}
uint8_t direction = 0; //0: clockwise, 2: counter-clockwise, 3: xor(?)
bool mergeable() override
{
return true;
}
void prepare(LottieObject::Type type)
{
LottieObject::type = type;
}
};
@ -245,6 +340,13 @@ struct LottieRoundedCorner : LottieObject
{
LottieObject::type = LottieObject::RoundedCorner;
}
LottieProperty* property(uint16_t ix) override
{
if (radius.ix == ix) return &radius;
return nullptr;
}
LottieFloat radius = 0.0f;
};
@ -253,7 +355,13 @@ struct LottiePath : LottieShape
{
void prepare()
{
LottieObject::type = LottieObject::Path;
LottieShape::prepare(LottieObject::Path);
}
LottieProperty* property(uint16_t ix) override
{
if (pathset.ix == ix) return &pathset;
return nullptr;
}
LottiePathSet pathset;
@ -264,7 +372,15 @@ struct LottieRect : LottieShape
{
void prepare()
{
LottieObject::type = LottieObject::Rect;
LottieShape::prepare(LottieObject::Rect);
}
LottieProperty* property(uint16_t ix) override
{
if (position.ix == ix) return &position;
if (size.ix == ix) return &size;
if (radius.ix == ix) return &radius;
return nullptr;
}
LottiePosition position = Point{0.0f, 0.0f};
@ -279,7 +395,19 @@ struct LottiePolyStar : LottieShape
void prepare()
{
LottieObject::type = LottieObject::Polystar;
LottieShape::prepare(LottieObject::Polystar);
}
LottieProperty* property(uint16_t ix) override
{
if (position.ix == ix) return &position;
if (innerRadius.ix == ix) return &innerRadius;
if (outerRadius.ix == ix) return &outerRadius;
if (innerRoundness.ix == ix) return &innerRoundness;
if (outerRoundness.ix == ix) return &outerRoundness;
if (rotation.ix == ix) return &rotation;
if (ptsCnt.ix == ix) return &ptsCnt;
return nullptr;
}
LottiePosition position = Point{0.0f, 0.0f};
@ -297,7 +425,14 @@ struct LottieEllipse : LottieShape
{
void prepare()
{
LottieObject::type = LottieObject::Ellipse;
LottieShape::prepare(LottieObject::Ellipse);
}
LottieProperty* property(uint16_t ix) override
{
if (position.ix == ix) return &position;
if (size.ix == ix) return &size;
return nullptr;
}
LottiePosition position = Point{0.0f, 0.0f};
@ -336,6 +471,22 @@ struct LottieTransform : LottieObject
return false;
}
LottieProperty* property(uint16_t ix) override
{
if (position.ix == ix) return &position;
if (rotation.ix == ix) return &rotation;
if (scale.ix == ix) return &scale;
if (anchor.ix == ix) return &anchor;
if (opacity.ix == ix) return &opacity;
if (skewAngle.ix == ix) return &skewAngle;
if (skewAxis.ix == ix) return &skewAxis;
if (coords) {
if (coords->x.ix == ix) return &coords->x;
if (coords->y.ix == ix) return &coords->y;
}
return nullptr;
}
LottiePosition position = Point{0.0f, 0.0f};
LottieFloat rotation = 0.0f; //z rotation
LottiePoint scale = Point{100.0f, 100.0f};
@ -353,6 +504,13 @@ struct LottieSolid : LottieObject
{
LottieColor color = RGB24{255, 255, 255};
LottieOpacity opacity = 255;
LottieProperty* property(uint16_t ix) override
{
if (color.ix == ix) return &color;
if (opacity.ix == ix) return &opacity;
return nullptr;
}
};
@ -363,6 +521,17 @@ struct LottieSolidStroke : LottieSolid, LottieStroke
LottieObject::type = LottieObject::SolidStroke;
}
LottieProperty* property(uint16_t ix) override
{
if (width.ix == ix) return &width;
if (dashattr) {
if (dashattr->value[0].ix == ix) return &dashattr->value[0];
if (dashattr->value[1].ix == ix) return &dashattr->value[1];
if (dashattr->value[2].ix == ix) return &dashattr->value[2];
}
return LottieSolid::property(ix);
}
void override(LottieProperty* prop) override
{
this->color = *static_cast<LottieColor*>(prop);
@ -393,19 +562,33 @@ struct LottieGradient : LottieObject
bool prepare()
{
if (!colorStops.populated) {
auto count = colorStops.count; //colorstop count can be modified after population
if (colorStops.frames) {
for (auto v = colorStops.frames->begin(); v < colorStops.frames->end(); ++v) {
colorStops.count = populate(v->value);
colorStops.count = populate(v->value, count);
}
} else {
colorStops.count = populate(colorStops.value);
colorStops.count = populate(colorStops.value, count);
}
colorStops.populated = true;
}
if (start.frames || end.frames || height.frames || angle.frames || opacity.frames || colorStops.frames) return true;
return false;
}
uint32_t populate(ColorStop& color);
LottieProperty* property(uint16_t ix) override
{
if (start.ix == ix) return &start;
if (end.ix == ix) return &end;
if (height.ix == ix) return &height;
if (angle.ix == ix) return &angle;
if (opacity.ix == ix) return &opacity;
if (colorStops.ix == ix) return &colorStops;
return nullptr;
}
uint32_t populate(ColorStop& color, size_t count);
Fill* fill(float frameNo, LottieExpressions* exps);
LottiePoint start = Point{0.0f, 0.0f};
@ -444,6 +627,17 @@ struct LottieGradientStroke : LottieGradient, LottieStroke
LottieGradient::prepare();
}
LottieProperty* property(uint16_t ix) override
{
if (width.ix == ix) return &width;
if (dashattr) {
if (dashattr->value[0].ix == ix) return &dashattr->value[0];
if (dashattr->value[1].ix == ix) return &dashattr->value[1];
if (dashattr->value[2].ix == ix) return &dashattr->value[2];
}
return LottieGradient::property(ix);
}
void override(LottieProperty* prop) override
{
this->colorStops = *static_cast<LottieColorStop*>(prop);
@ -452,7 +646,7 @@ struct LottieGradientStroke : LottieGradient, LottieStroke
};
struct LottieImage : LottieObject
struct LottieImage : LottieObject, LottieRenderPooler<tvg::Picture>
{
union {
char* b64Data = nullptr;
@ -460,15 +654,11 @@ struct LottieImage : LottieObject
};
char* mimeType = nullptr;
uint32_t size = 0;
Picture* picture = nullptr; //tvg render data
float width = 0.0f;
float height = 0.0f;
~LottieImage();
void prepare()
{
LottieObject::type = LottieObject::Image;
}
void prepare();
};
@ -479,6 +669,19 @@ struct LottieRepeater : LottieObject
LottieObject::type = LottieObject::Repeater;
}
LottieProperty* property(uint16_t ix) override
{
if (copies.ix == ix) return &copies;
if (offset.ix == ix) return &offset;
if (position.ix == ix) return &position;
if (rotation.ix == ix) return &rotation;
if (scale.ix == ix) return &scale;
if (anchor.ix == ix) return &anchor;
if (startOpacity.ix == ix) return &startOpacity;
if (endOpacity.ix == ix) return &endOpacity;
return nullptr;
}
LottieFloat copies = 0.0f;
LottieFloat offset = 0.0f;
@ -493,8 +696,23 @@ struct LottieRepeater : LottieObject
};
struct LottieGroup : LottieObject
struct LottieOffsetPath : LottieObject
{
void prepare()
{
LottieObject::type = LottieObject::OffsetPath;
}
LottieFloat offset = 0.0f;
LottieFloat miterLimit = 4.0f;
StrokeJoin join = StrokeJoin::Miter;
};
struct LottieGroup : LottieObject, LottieRenderPooler<tvg::Shape>
{
LottieGroup();
virtual ~LottieGroup()
{
for (auto p = children.begin(); p < children.end(); ++p) delete(*p);
@ -503,27 +721,28 @@ struct LottieGroup : LottieObject
void prepare(LottieObject::Type type = LottieObject::Group);
bool mergeable() override { return allowMerge; }
LottieObject* content(const char* id)
LottieObject* content(unsigned long id)
{
if (name && !strcmp(name, id)) return this;
if (this->id == id) return this;
//source has children, find recursively.
for (auto c = children.begin(); c < children.end(); ++c) {
auto child = *c;
if (child->type == LottieObject::Type::Group || child->type == LottieObject::Type::Layer) {
if (auto ret = static_cast<LottieGroup*>(child)->content(id)) return ret;
} else if (child->name && !strcmp(child->name, id)) return child;
} else if (child->id == id) return child;
}
return nullptr;
}
Scene* scene = nullptr; //tvg render data
Scene* scene = nullptr;
Array<LottieObject*> children;
bool reqFragment = false; //requirement to fragment the render context
bool buildDone = false; //completed in building the composition.
bool allowMerge = true; //if this group is consisted of simple (transformed) shapes.
bool trimpath = false; //this group has a trimpath.
bool reqFragment : 1; //requirement to fragment the render context
bool buildDone : 1; //completed in building the composition.
bool trimpath : 1; //this group has a trimpath.
bool visible : 1; //this group has visible contents.
bool allowMerge : 1; //if this group is consisted of simple (transformed) shapes.
};
@ -542,41 +761,61 @@ struct LottieLayer : LottieGroup
bool mergeable() override { return false; }
void prepare();
float remap(float frameNo, LottieExpressions* exp);
void prepare(RGB24* color = nullptr);
float remap(LottieComposition* comp, float frameNo, LottieExpressions* exp);
struct {
CompositeMethod type = CompositeMethod::None;
LottieLayer* target = nullptr;
} matte;
BlendMethod blendMethod = BlendMethod::Normal;
char* name = nullptr;
LottieLayer* parent = nullptr;
LottieFloat timeRemap = 0.0f;
LottieComposition* comp = nullptr;
LottieLayer* comp = nullptr; //Precompositor, current layer is belonges.
LottieTransform* transform = nullptr;
Array<LottieMask*> masks;
RGB24 color; //used by Solid layer
Array<LottieEffect*> effects;
LottieLayer* matteTarget = nullptr;
LottieRenderPooler<tvg::Shape> statical; //static pooler for solid fill and clipper
float timeStretch = 1.0f;
float w = 0.0f, h = 0.0f;
float inFrame = 0.0f;
float outFrame = 0.0f;
float startFrame = 0.0f;
char* refId = nullptr; //pre-composition reference.
int16_t pid = -1; //id of the parent layer.
int16_t id = -1; //id of the current layer.
unsigned long rid = 0; //pre-composition reference id.
int16_t mid = -1; //id of the matte layer.
int16_t pidx = -1; //index of the parent layer.
int16_t idx = -1; //index of the current layer.
//cached data
struct {
float frameNo = -1.0f;
Matrix matrix;
uint8_t opacity;
} cache;
CompositeMethod matteType = CompositeMethod::None;
BlendMethod blendMethod = BlendMethod::Normal;
Type type = Null;
bool autoOrient = false;
bool matteSrc = false;
LottieLayer* layerById(unsigned long id)
{
for (auto child = children.begin(); child < children.end(); ++child) {
if ((*child)->type != LottieObject::Type::Layer) continue;
auto layer = static_cast<LottieLayer*>(*child);
if (layer->id == id) return layer;
}
return nullptr;
}
LottieLayer* layerByIdx(int16_t idx)
{
for (auto child = children.begin(); child < children.end(); ++child) {
if ((*child)->type != LottieObject::Type::Layer) continue;
auto layer = static_cast<LottieLayer*>(*child);
if (layer->idx == idx) return layer;
}
return nullptr;
}
};
@ -629,37 +868,19 @@ struct LottieComposition
float timeAtFrame(float frameNo)
{
return (frameNo - startFrame) / frameRate;
return (frameNo - root->inFrame) / frameRate;
}
float frameCnt() const
{
return endFrame - startFrame;
return root->outFrame - root->inFrame;
}
LottieLayer* layer(const char* name)
{
for (auto child = root->children.begin(); child < root->children.end(); ++child) {
auto layer = static_cast<LottieLayer*>(*child);
if (layer->name && !strcmp(layer->name, name)) return layer;
}
return nullptr;
}
LottieLayer* layer(int16_t id)
{
for (auto child = root->children.begin(); child < root->children.end(); ++child) {
auto layer = static_cast<LottieLayer*>(*child);
if (layer->id == id) return layer;
}
return nullptr;
}
LottieLayer* asset(const char* name)
LottieLayer* asset(unsigned long id)
{
for (auto asset = assets.begin(); asset < assets.end(); ++asset) {
auto layer = static_cast<LottieLayer*>(*asset);
if (layer->name && !strcmp(layer->name, name)) return layer;
if (layer->id == id) return layer;
}
return nullptr;
}
@ -668,7 +889,6 @@ struct LottieComposition
char* version = nullptr;
char* name = nullptr;
float w, h;
float startFrame, endFrame;
float frameRate;
Array<LottieObject*> assets;
Array<LottieInterpolator*> interpolators;

View File

@ -0,0 +1,401 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include "tvgLottieModifier.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, const Point& prev, const Point& curr, const Point& next, float r)
{
auto lenPrev = length(prev - curr);
auto rPrev = lenPrev > 0.0f ? 0.5f * std::min(lenPrev * 0.5f, r) / lenPrev : 0.0f;
auto lenNext = length(next - curr);
auto rNext = lenNext > 0.0f ? 0.5f * std::min(lenNext * 0.5f, r) / lenNext : 0.0f;
auto dPrev = rPrev * (curr - prev);
auto dNext = rNext * (curr - next);
pts.push(curr - 2.0f * dPrev);
pts.push(curr - dPrev);
pts.push(curr - dNext);
pts.push(curr - 2.0f * dNext);
cmds.push(PathCommand::LineTo);
cmds.push(PathCommand::CubicTo);
}
static bool _zero(const Point& p1, const Point& p2)
{
constexpr float epsilon = 1e-3f;
return fabsf(p1.x / p2.x - 1.0f) < epsilon && fabsf(p1.y / p2.y - 1.0f) < epsilon;
}
static bool _intersect(const Line& line1, const Line& line2, Point& intersection, bool& inside)
{
if (_zero(line1.pt2, line2.pt1)) {
intersection = line1.pt2;
inside = true;
return true;
}
constexpr float epsilon = 1e-3f;
float denom = (line1.pt2.x - line1.pt1.x) * (line2.pt2.y - line2.pt1.y) - (line1.pt2.y - line1.pt1.y) * (line2.pt2.x - line2.pt1.x);
if (fabsf(denom) < epsilon) return false;
float t = ((line2.pt1.x - line1.pt1.x) * (line2.pt2.y - line2.pt1.y) - (line2.pt1.y - line1.pt1.y) * (line2.pt2.x - line2.pt1.x)) / denom;
float u = ((line2.pt1.x - line1.pt1.x) * (line1.pt2.y - line1.pt1.y) - (line2.pt1.y - line1.pt1.y) * (line1.pt2.x - line1.pt1.x)) / denom;
intersection.x = line1.pt1.x + t * (line1.pt2.x - line1.pt1.x);
intersection.y = line1.pt1.y + t * (line1.pt2.y - line1.pt1.y);
inside = t >= -epsilon && t <= 1.0f + epsilon && u >= -epsilon && u <= 1.0f + epsilon;
return true;
}
static Line _offset(const Point& p1, const Point& p2, float offset)
{
auto scaledNormal = normal(p1, p2) * offset;
return {p1 + scaledNormal, p2 + scaledNormal};
}
static bool _clockwise(const Point* pts, uint32_t n)
{
auto area = 0.0f;
for (uint32_t i = 0; i < n - 1; i++) {
area += cross(pts[i], pts[i + 1]);
}
area += cross(pts[n - 1], pts[0]);;
return area < 0.0f;
}
void LottieOffsetModifier::corner(const Line& line, const Line& nextLine, uint32_t movetoOutIndex, bool nextClose, Array<PathCommand>& outCmds, Array<Point>& outPts) const
{
bool inside{};
Point intersect{};
if (_intersect(line, nextLine, intersect, inside)) {
if (inside) {
if (nextClose) outPts[movetoOutIndex] = intersect;
outPts.push(intersect);
} else {
outPts.push(line.pt2);
if (join == StrokeJoin::Round) {
outCmds.push(PathCommand::CubicTo);
outPts.push((line.pt2 + intersect) * 0.5f);
outPts.push((nextLine.pt1 + intersect) * 0.5f);
outPts.push(nextLine.pt1);
} else if (join == StrokeJoin::Miter) {
auto norm = normal(line.pt1, line.pt2);
auto nextNorm = normal(nextLine.pt1, nextLine.pt2);
auto miterDirection = (norm + nextNorm) / length(norm + nextNorm);
outCmds.push(PathCommand::LineTo);
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) outPts.push(intersect);
else outPts.push(nextLine.pt1);
} else {
outCmds.push(PathCommand::LineTo);
outPts.push(nextLine.pt1);
}
}
} else outPts.push(line.pt2);
}
void LottieOffsetModifier::line(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t& currentPt, uint32_t currentCmd, State& state, bool degenerated, Array<PathCommand>& outCmds, Array<Point>& outPts, float offset) const
{
if (tvg::zero(inPts[currentPt - 1] - inPts[currentPt])) {
++currentPt;
return;
}
if (inCmds[currentCmd - 1] != PathCommand::LineTo) state.line = _offset(inPts[currentPt - 1], inPts[currentPt], offset);
if (state.moveto) {
outCmds.push(PathCommand::MoveTo);
state.movetoOutIndex = outPts.count;
outPts.push(state.line.pt1);
state.firstLine = state.line;
state.moveto = false;
}
auto nonDegeneratedCubic = [&](uint32_t cmd, uint32_t pt) {
return inCmds[cmd] == PathCommand::CubicTo && !tvg::zero(inPts[pt] - inPts[pt + 1]) && !tvg::zero(inPts[pt + 2] - inPts[pt + 3]);
};
outCmds.push(PathCommand::LineTo);
if (currentCmd + 1 == inCmdsCnt || inCmds[currentCmd + 1] == PathCommand::MoveTo || nonDegeneratedCubic(currentCmd + 1, currentPt + degenerated)) {
outPts.push(state.line.pt2);
++currentPt;
return;
}
Line nextLine = state.firstLine;
if (inCmds[currentCmd + 1] == PathCommand::LineTo) nextLine = _offset(inPts[currentPt + degenerated], inPts[currentPt + 1 + degenerated], offset);
else if (inCmds[currentCmd + 1] == PathCommand::CubicTo) nextLine = _offset(inPts[currentPt + 1 + degenerated], inPts[currentPt + 2 + degenerated], offset);
else if (inCmds[currentCmd + 1] == PathCommand::Close && !_zero(inPts[currentPt + degenerated], inPts[state.movetoInIndex + degenerated]))
nextLine = _offset(inPts[currentPt + degenerated], inPts[state.movetoInIndex + degenerated], offset);
corner(state.line, nextLine, state.movetoOutIndex, inCmds[currentCmd + 1] == PathCommand::Close, outCmds, outPts);
state.line = nextLine;
++currentPt;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
bool LottieRoundnessModifier::modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts, Matrix* transform) const
{
outCmds.reserve(inCmdsCnt * 2);
outPts.reserve((uint32_t)(inPtsCnt * 1.5));
auto ptsCnt = outPts.count;
uint32_t startIndex = 0;
for (uint32_t iCmds = 0, iPts = 0; iCmds < inCmdsCnt; ++iCmds) {
switch (inCmds[iCmds]) {
case PathCommand::MoveTo: {
startIndex = outPts.count;
outCmds.push(PathCommand::MoveTo);
outPts.push(inPts[iPts++]);
break;
}
case PathCommand::CubicTo: {
auto& prev = inPts[iPts - 1];
auto& curr = inPts[iPts + 2];
if (iCmds < inCmdsCnt - 1 &&
tvg::zero(inPts[iPts - 1] - inPts[iPts]) &&
tvg::zero(inPts[iPts + 1] - inPts[iPts + 2])) {
if (inCmds[iCmds + 1] == PathCommand::CubicTo &&
tvg::zero(inPts[iPts + 2] - inPts[iPts + 3]) &&
tvg::zero(inPts[iPts + 4] - inPts[iPts + 5])) {
_roundCorner(outCmds, outPts, prev, curr, inPts[iPts + 5], r);
iPts += 3;
break;
} else if (inCmds[iCmds + 1] == PathCommand::Close) {
_roundCorner(outCmds, outPts, prev, curr, inPts[2], r);
outPts[startIndex] = outPts.last();
iPts += 3;
break;
}
}
outCmds.push(PathCommand::CubicTo);
outPts.push(inPts[iPts++]);
outPts.push(inPts[iPts++]);
outPts.push(inPts[iPts++]);
break;
}
case PathCommand::Close: {
outCmds.push(PathCommand::Close);
break;
}
default: break;
}
}
if (transform) {
for (auto i = ptsCnt; i < outPts.count; ++i) {
outPts[i] *= *transform;
}
}
return true;
}
bool LottieRoundnessModifier::modifyPolystar(TVG_UNUSED const Array<PathCommand>& inCmds, const Array<Point>& inPts, Array<PathCommand>& outCmds, Array<Point>& outPts, float outerRoundness, bool hasRoundness) const
{
static constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f;
auto len = length(inPts[1] - inPts[2]);
auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, this->r) / len : 0.0f;
if (hasRoundness) {
outCmds.grow((uint32_t)(1.5 * inCmds.count));
outPts.grow((uint32_t)(4.5 * inCmds.count));
int start = 3 * tvg::zero(outerRoundness);
outCmds.push(PathCommand::MoveTo);
outPts.push(inPts[start]);
for (uint32_t i = 1 + start; i < inPts.count; i += 6) {
auto& prev = inPts[i];
auto& curr = inPts[i + 2];
auto& next = (i < inPts.count - start) ? inPts[i + 4] : inPts[2];
auto& nextCtrl = (i < inPts.count - start) ? inPts[i + 5] : inPts[3];
auto dNext = r * (curr - next);
auto dPrev = r * (curr - prev);
auto p0 = curr - 2.0f * dPrev;
auto p1 = curr - dPrev;
auto p2 = curr - dNext;
auto p3 = curr - 2.0f * dNext;
outCmds.push(PathCommand::CubicTo);
outPts.push(prev); outPts.push(p0); outPts.push(p0);
outCmds.push(PathCommand::CubicTo);
outPts.push(p1); outPts.push(p2); outPts.push(p3);
outCmds.push(PathCommand::CubicTo);
outPts.push(p3); outPts.push(next); outPts.push(nextCtrl);
}
} else {
outCmds.grow(2 * inCmds.count);
outPts.grow(4 * inCmds.count);
auto dPrev = r * (inPts[1] - inPts[0]);
auto p = inPts[0] + 2.0f * dPrev;
outCmds.push(PathCommand::MoveTo);
outPts.push(p);
for (uint32_t i = 1; i < inPts.count; ++i) {
auto& curr = inPts[i];
auto& next = (i == inPts.count - 1) ? inPts[1] : inPts[i + 1];
auto dNext = r * (curr - next);
auto p0 = curr - 2.0f * dPrev;
auto p1 = curr - dPrev;
auto p2 = curr - dNext;
auto p3 = curr - 2.0f * dNext;
outCmds.push(PathCommand::LineTo);
outPts.push(p0);
outCmds.push(PathCommand::CubicTo);
outPts.push(p1); outPts.push(p2); outPts.push(p3);
dPrev = -1.0f * dNext;
}
}
outCmds.push(PathCommand::Close);
return true;
}
bool LottieRoundnessModifier::modifyRect(const Point& size, float& r) const
{
r = std::min(this->r, std::max(size.x, size.y) * 0.5f);
return true;
}
bool LottieOffsetModifier::modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts) const
{
outCmds.reserve(inCmdsCnt * 2);
outPts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2));
Array<Bezier> stack{5};
State state;
auto offset = _clockwise(inPts, inPtsCnt) ? this->offset : -this->offset;
auto threshold = 1.0f / fabsf(offset) + 1.0f;
for (uint32_t iCmd = 0, iPt = 0; iCmd < inCmdsCnt; ++iCmd) {
if (inCmds[iCmd] == PathCommand::MoveTo) {
state.moveto = true;
state.movetoInIndex = iPt++;
} else if (inCmds[iCmd] == PathCommand::LineTo) {
line(inCmds, inCmdsCnt, inPts, iPt, iCmd, state, false, outCmds, outPts, offset);
} else if (inCmds[iCmd] == PathCommand::CubicTo) {
//cubic degenerated to a line
if (tvg::zero(inPts[iPt - 1] - inPts[iPt]) || tvg::zero(inPts[iPt + 1] - inPts[iPt + 2])) {
++iPt;
line(inCmds, inCmdsCnt, inPts, iPt, iCmd, state, true, outCmds, outPts, offset);
++iPt;
continue;
}
stack.push({inPts[iPt - 1], inPts[iPt], inPts[iPt + 1], inPts[iPt + 2]});
while (!stack.empty()) {
auto& bezier = stack.last();
auto len = tvg::length(bezier.start - bezier.ctrl1) + tvg::length(bezier.ctrl1 - bezier.ctrl2) + tvg::length(bezier.ctrl2 - bezier.end);
if (len > threshold * bezier.length()) {
Bezier next;
bezier.split(0.5f, next);
stack.push(next);
continue;
}
stack.pop();
auto line1 = _offset(bezier.start, bezier.ctrl1, offset);
auto line2 = _offset(bezier.ctrl1, bezier.ctrl2, offset);
auto line3 = _offset(bezier.ctrl2, bezier.end, offset);
if (state.moveto) {
outCmds.push(PathCommand::MoveTo);
state.movetoOutIndex = outPts.count;
outPts.push(line1.pt1);
state.firstLine = line1;
state.moveto = false;
}
bool inside{};
Point intersect{};
_intersect(line1, line2, intersect, inside);
outPts.push(intersect);
_intersect(line2, line3, intersect, inside);
outPts.push(intersect);
outPts.push(line3.pt2);
outCmds.push(PathCommand::CubicTo);
}
iPt += 3;
}
else {
if (!_zero(inPts[iPt - 1], inPts[state.movetoInIndex])) {
outCmds.push(PathCommand::LineTo);
corner(state.line, state.firstLine, state.movetoOutIndex, true, outCmds, outPts);
}
outCmds.push(PathCommand::Close);
}
}
return true;
}
bool LottieOffsetModifier::modifyPolystar(const Array<PathCommand>& inCmds, const Array<Point>& inPts, Array<PathCommand>& outCmds, Array<Point>& outPts) const {
return modifyPath(inCmds.data, inCmds.count, inPts.data, inPts.count, outCmds, outPts);
}
bool LottieOffsetModifier::modifyRect(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts) const
{
return modifyPath(inCmds, inCmdsCnt, inPts, inPtsCnt, outCmds, outPts);
}
bool LottieOffsetModifier::modifyEllipse(float& rx, float& ry) const
{
rx += offset;
ry += offset;
return true;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#ifndef _TVG_LOTTIE_MODIFIER_H_
#define _TVG_LOTTIE_MODIFIER_H_
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgMath.h"
struct LottieRoundnessModifier
{
static constexpr float ROUNDNESS_EPSILON = 1.0f;
float r;
LottieRoundnessModifier(float r) : r(r) {};
bool modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts, Matrix* transform) const;
bool modifyPolystar(const Array<PathCommand>& inCmds, const Array<Point>& inPts, Array<PathCommand>& outCmds, Array<Point>& outPts, float outerRoundness, bool hasRoundness) const;
bool modifyRect(const Point& size, float& r) const;
};
struct LottieOffsetModifier
{
float offset;
float miterLimit;
StrokeJoin join;
LottieOffsetModifier(float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : offset(offset), miterLimit(miter), join(join) {};
bool modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts) const;
bool modifyPolystar(const Array<PathCommand>& inCmds, const Array<Point>& inPts, Array<PathCommand>& outCmds, Array<Point>& outPts) const;
bool modifyRect(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array<PathCommand>& outCmds, Array<Point>& outPts) const;
bool modifyEllipse(float& rx, float& ry) const;
private:
struct State
{
Line line{};
Line firstLine{};
bool moveto = false;
uint32_t movetoOutIndex = 0;
uint32_t movetoInIndex = 0;
};
void line(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t& currentPt, uint32_t currentCmd, State& state, bool degenerated, Array<PathCommand>& cmds, Array<Point>& pts, float offset) const;
void corner(const Line& line, const Line& nextLine, uint32_t movetoIndex, bool nextClose, Array<PathCommand>& cmds, Array<Point>& pts) const;
};
#endif
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -37,7 +37,7 @@
#define KEY_AS(name) !strcmp(key, name)
static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property, LottieProperty::Type type)
static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property)
{
if (!comp->expressions) comp->expressions = true;
@ -47,18 +47,25 @@ static LottieExpression* _expression(char* code, LottieComposition* comp, Lottie
inst->layer = layer;
inst->object = object;
inst->property = property;
inst->type = type;
inst->enabled = true;
return inst;
}
static char* _int2str(int num)
static unsigned long _int2str(int num)
{
char str[20];
snprintf(str, 20, "%d", num);
return strdup(str);
return djb2Encode(str);
}
LottieEffect* LottieParser::getEffect(int type)
{
switch (type) {
case 29: return new LottieGaussianBlur;
default: return nullptr;
}
}
@ -75,40 +82,13 @@ CompositeMethod LottieParser::getMaskMethod(bool inversed)
case 's': return CompositeMethod::SubtractMask;
case 'i': return CompositeMethod::IntersectMask;
case 'f': return CompositeMethod::DifferenceMask;
case 'l': return CompositeMethod::LightenMask;
case 'd': return CompositeMethod::DarkenMask;
default: return CompositeMethod::None;
}
}
BlendMethod LottieParser::getBlendMethod()
{
switch (getInt()) {
case 0: return BlendMethod::Normal;
case 1: return BlendMethod::Multiply;
case 2: return BlendMethod::Screen;
case 3: return BlendMethod::Overlay;
case 4: return BlendMethod::Darken;
case 5: return BlendMethod::Lighten;
case 6: return BlendMethod::ColorDodge;
case 7: return BlendMethod::ColorBurn;
case 8: return BlendMethod::HardLight;
case 9: return BlendMethod::SoftLight;
case 10: return BlendMethod::Difference;
case 11: return BlendMethod::Exclusion;
//case 12: return BlendMethod::Hue:
//case 13: return BlendMethod::Saturation:
//case 14: return BlendMethod::Color:
//case 15: return BlendMethod::Luminosity:
case 16: return BlendMethod::Add;
//case 17: return BlendMethod::HardMix:
default: {
TVGERR("LOTTIE", "Non-Supported Blend Mode");
return BlendMethod::Normal;
}
}
}
RGB24 LottieParser::getColor(const char *str)
{
RGB24 color = {0, 0, 0};
@ -182,7 +162,7 @@ void LottieParser::getValue(TextDocument& doc)
{
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("s")) doc.size = getFloat();
if (KEY_AS("s")) doc.size = getFloat() * 0.01f;
else if (KEY_AS("f")) doc.name = getStringCopy();
else if (KEY_AS("t")) doc.text = getStringCopy();
else if (KEY_AS("j")) doc.justify = getInt();
@ -277,7 +257,8 @@ void LottieParser::getValue(ColorStop& color)
{
if (peekType() == kArrayType) enterArray();
color.input = new Array<float>(static_cast<LottieGradient*>(context.parent)->colorStops.count);
if (!color.input) color.input = new Array<float>(static_cast<LottieGradient*>(context.parent)->colorStops.count * 6);
else color.input->clear();
while (nextArrayValue()) color.input->push(getFloat());
}
@ -295,6 +276,19 @@ void LottieParser::getValue(Array<Point>& pts)
}
void LottieParser::getValue(int8_t& val)
{
if (peekType() == kArrayType) {
enterArray();
if (nextArrayValue()) val = getInt();
//discard rest
while (nextArrayValue()) getInt();
} else {
val = getFloat();
}
}
void LottieParser::getValue(uint8_t& val)
{
if (peekType() == kArrayType) {
@ -321,17 +315,22 @@ void LottieParser::getValue(float& val)
}
void LottieParser::getValue(Point& pt)
bool LottieParser::getValue(Point& pt)
{
auto type = peekType();
if (type == kNullType) return false;
int i = 0;
auto ptr = (float*)(&pt);
if (peekType() == kArrayType) enterArray();
if (type == kArrayType) enterArray();
while (nextArrayValue()) {
auto val = getFloat();
if (i < 2) ptr[i++] = val;
}
return true;
}
@ -343,7 +342,7 @@ void LottieParser::getValue(RGB24& color)
while (nextArrayValue()) {
auto val = getFloat();
if (i < 3) color.rgb[i++] = int32_t(lroundf(val * 255.0f));
if (i < 3) color.rgb[i++] = REMAP255(val);
}
//TODO: color filter?
@ -373,14 +372,11 @@ void LottieParser::parseSlotProperty(T& prop)
template<typename T>
bool LottieParser::parseTangent(const char *key, LottieVectorFrame<T>& value)
{
if (KEY_AS("ti")) {
value.hasTangent = true;
getValue(value.inTangent);
} else if (KEY_AS("to")) {
value.hasTangent = true;
getValue(value.outTangent);
} else return false;
if (KEY_AS("ti") && getValue(value.inTangent)) ;
else if (KEY_AS("to") && getValue(value.outTangent)) ;
else return false;
value.hasTangent = true;
return true;
}
@ -504,30 +500,55 @@ void LottieParser::parseProperty(T& prop, LottieObject* obj)
for (auto slot = comp->slots.begin(); slot < comp->slots.end(); ++slot) {
if (strcmp((*slot)->sid, sid)) continue;
(*slot)->pairs.push({obj, 0});
return;
break;
}
comp->slots.push(new LottieSlot(sid, obj, type));
} else if (!strcmp(key, "x")) {
prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop, type);
}
else skip(key);
} else if (KEY_AS("x")) {
prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop);
} else if (KEY_AS("ix")) {
prop.ix = getInt();
} else skip(key);
}
prop.type = type;
}
bool LottieParser::parseCommon(LottieObject* obj, const char* key)
{
if (KEY_AS("nm")) {
obj->id = djb2Encode(getString());
return true;
} else if (KEY_AS("hd")) {
obj->hidden = getBool();
return true;
} else return false;
}
bool LottieParser::parseDirection(LottieShape* shape, const char* key)
{
if (KEY_AS("d")) {
if (getInt() == 3) {
shape->clockwise = false; //default is true
}
return true;
}
return false;
}
LottieRect* LottieParser::parseRect()
{
auto rect = new LottieRect;
if (!rect) return nullptr;
context.parent = rect;
while (auto key = nextObjectKey()) {
if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(rect->size);
else if (KEY_AS("p"))parseProperty<LottieProperty::Type::Position>(rect->position);
if (parseCommon(rect, key)) continue;
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(rect->size);
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(rect->position);
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(rect->radius);
else if (KEY_AS("nm")) rect->name = getStringCopy();
else if (KEY_AS("hd")) rect->hidden = getBool();
else if (parseDirection(rect, key)) continue;
else skip(key);
}
rect->prepare();
@ -538,15 +559,14 @@ LottieRect* LottieParser::parseRect()
LottieEllipse* LottieParser::parseEllipse()
{
auto ellipse = new LottieEllipse;
if (!ellipse) return nullptr;
context.parent = ellipse;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) ellipse->name = getStringCopy();
if (parseCommon(ellipse, key)) continue;
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(ellipse->position);
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(ellipse->size);
else if (KEY_AS("hd")) ellipse->hidden = getBool();
else if (parseDirection(ellipse, key)) continue;
else skip(key);
}
ellipse->prepare();
@ -557,7 +577,6 @@ LottieEllipse* LottieParser::parseEllipse()
LottieTransform* LottieParser::parseTransform(bool ddd)
{
auto transform = new LottieTransform;
if (!transform) return nullptr;
context.parent = transform;
@ -567,7 +586,8 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
}
while (auto key = nextObjectKey()) {
if (KEY_AS("p"))
if (parseCommon(transform, key)) continue;
else if (KEY_AS("p"))
{
enterObject();
while (auto key = nextObjectKey()) {
@ -576,9 +596,10 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
//check separateCoord to figure out whether "x(expression)" / "x(coord)"
else if (transform->coords && KEY_AS("x")) parseProperty<LottieProperty::Type::Float>(transform->coords->x);
else if (transform->coords && KEY_AS("y")) parseProperty<LottieProperty::Type::Float>(transform->coords->y);
else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position, LottieProperty::Type::Position);
else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position);
else skip(key);
}
transform->position.type = LottieProperty::Type::Position;
}
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Point>(transform->anchor);
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(transform->scale);
@ -587,7 +608,6 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
else if (transform->rotationEx && KEY_AS("rx")) parseProperty<LottieProperty::Type::Float>(transform->rotationEx->x);
else if (transform->rotationEx && KEY_AS("ry")) parseProperty<LottieProperty::Type::Float>(transform->rotationEx->y);
else if (transform->rotationEx && KEY_AS("rz")) parseProperty<LottieProperty::Type::Float>(transform->rotation);
else if (KEY_AS("nm")) transform->name = getStringCopy();
else if (KEY_AS("sk")) parseProperty<LottieProperty::Type::Float>(transform->skewAngle);
else if (KEY_AS("sa")) parseProperty<LottieProperty::Type::Float>(transform->skewAxis);
else skip(key);
@ -600,17 +620,15 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
LottieSolidFill* LottieParser::parseSolidFill()
{
auto fill = new LottieSolidFill;
if (!fill) return nullptr;
context.parent = fill;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) fill->name = getStringCopy();
if (parseCommon(fill, key)) continue;
else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(fill->color, fill);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(fill->opacity, fill);
else if (KEY_AS("fillEnabled")) fill->hidden |= !getBool();
else if (KEY_AS("r")) fill->rule = getFillRule();
else if (KEY_AS("hd")) fill->hidden = getBool();
else skip(key);
}
fill->prepare();
@ -641,19 +659,17 @@ void LottieParser::parseStrokeDash(LottieStroke* stroke)
LottieSolidStroke* LottieParser::parseSolidStroke()
{
auto stroke = new LottieSolidStroke;
if (!stroke) return nullptr;
context.parent = stroke;
while (auto key = nextObjectKey()) {
if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
if (parseCommon(stroke, key)) continue;
else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(stroke->opacity, stroke);
else if (KEY_AS("w")) parseProperty<LottieProperty::Type::Float>(stroke->width, stroke);
else if (KEY_AS("lc")) stroke->cap = getStrokeCap();
else if (KEY_AS("lj")) stroke->join = getStrokeJoin();
else if (KEY_AS("ml")) stroke->miterLimit = getFloat();
else if (KEY_AS("nm")) stroke->name = getStringCopy();
else if (KEY_AS("hd")) stroke->hidden = getBool();
else if (KEY_AS("fillEnabled")) stroke->hidden |= !getBool();
else if (KEY_AS("d")) parseStrokeDash(stroke);
else skip(key);
@ -674,22 +690,22 @@ void LottieParser::getPathSet(LottiePathSet& path)
} else {
getValue(path.value);
}
} else if (!strcmp(key, "x")) {
path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path, LottieProperty::Type::PathSet);
} else if (KEY_AS("x")) {
path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path);
} else skip(key);
}
path.type = LottieProperty::Type::PathSet;
}
LottiePath* LottieParser::parsePath()
{
auto path = new LottiePath;
if (!path) return nullptr;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) path->name = getStringCopy();
if (parseCommon(path, key)) continue;
else if (KEY_AS("ks")) getPathSet(path->pathset);
else if (KEY_AS("hd")) path->hidden = getBool();
else if (parseDirection(path, key)) continue;
else skip(key);
}
path->prepare();
@ -700,12 +716,11 @@ LottiePath* LottieParser::parsePath()
LottiePolyStar* LottieParser::parsePolyStar()
{
auto star = new LottiePolyStar;
if (!star) return nullptr;
context.parent = star;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) star->name = getStringCopy();
if (parseCommon(star, key)) continue;
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(star->position);
else if (KEY_AS("pt")) parseProperty<LottieProperty::Type::Float>(star->ptsCnt);
else if (KEY_AS("ir")) parseProperty<LottieProperty::Type::Float>(star->innerRadius);
@ -714,7 +729,7 @@ LottiePolyStar* LottieParser::parsePolyStar()
else if (KEY_AS("os")) parseProperty<LottieProperty::Type::Float>(star->outerRoundness);
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(star->rotation);
else if (KEY_AS("sy")) star->type = (LottiePolyStar::Type) getInt();
else if (KEY_AS("hd")) star->hidden = getBool();
else if (parseDirection(star, key)) continue;
else skip(key);
}
star->prepare();
@ -725,14 +740,12 @@ LottiePolyStar* LottieParser::parsePolyStar()
LottieRoundedCorner* LottieParser::parseRoundedCorner()
{
auto corner = new LottieRoundedCorner;
if (!corner) return nullptr;
context.parent = corner;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) corner->name = getStringCopy();
if (parseCommon(corner, key)) continue;
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(corner->radius);
else if (KEY_AS("hd")) corner->hidden = getBool();
else skip(key);
}
corner->prepare();
@ -764,14 +777,12 @@ void LottieParser::parseGradient(LottieGradient* gradient, const char* key)
LottieGradientFill* LottieParser::parseGradientFill()
{
auto fill = new LottieGradientFill;
if (!fill) return nullptr;
context.parent = fill;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) fill->name = getStringCopy();
if (parseCommon(fill, key)) continue;
else if (KEY_AS("r")) fill->rule = getFillRule();
else if (KEY_AS("hd")) fill->hidden = getBool();
else parseGradient(fill, key);
}
@ -784,16 +795,14 @@ LottieGradientFill* LottieParser::parseGradientFill()
LottieGradientStroke* LottieParser::parseGradientStroke()
{
auto stroke = new LottieGradientStroke;
if (!stroke) return nullptr;
context.parent = stroke;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) stroke->name = getStringCopy();
if (parseCommon(stroke, key)) continue;
else if (KEY_AS("lc")) stroke->cap = getStrokeCap();
else if (KEY_AS("lj")) stroke->join = getStrokeJoin();
else if (KEY_AS("ml")) stroke->miterLimit = getFloat();
else if (KEY_AS("hd")) stroke->hidden = getBool();
else if (KEY_AS("w")) parseProperty<LottieProperty::Type::Float>(stroke->width);
else if (KEY_AS("d")) parseStrokeDash(stroke);
else parseGradient(stroke, key);
@ -807,17 +816,15 @@ LottieGradientStroke* LottieParser::parseGradientStroke()
LottieTrimpath* LottieParser::parseTrimpath()
{
auto trim = new LottieTrimpath;
if (!trim) return nullptr;
context.parent = trim;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) trim->name = getStringCopy();
if (parseCommon(trim, key)) continue;
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Float>(trim->start);
else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Float>(trim->end);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(trim->offset);
else if (KEY_AS("m")) trim->type = static_cast<LottieTrimpath::Type>(getInt());
else if (KEY_AS("hd")) trim->hidden = getBool();
else skip(key);
}
trim->prepare();
@ -829,15 +836,14 @@ LottieTrimpath* LottieParser::parseTrimpath()
LottieRepeater* LottieParser::parseRepeater()
{
auto repeater = new LottieRepeater;
if (!repeater) return nullptr;
context.parent = repeater;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) repeater->name = getStringCopy();
if (parseCommon(repeater, key)) continue;
else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Float>(repeater->copies);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(repeater->offset);
else if (KEY_AS("m")) repeater->inorder = getInt();
else if (KEY_AS("m")) repeater->inorder = getInt() == 2;
else if (KEY_AS("tr"))
{
enterObject();
@ -851,7 +857,6 @@ LottieRepeater* LottieParser::parseRepeater()
else skip(key);
}
}
else if (KEY_AS("hd")) repeater->hidden = getBool();
else skip(key);
}
repeater->prepare();
@ -860,6 +865,25 @@ LottieRepeater* LottieParser::parseRepeater()
}
LottieOffsetPath* LottieParser::parseOffsetPath()
{
auto offsetPath = new LottieOffsetPath;
context.parent = offsetPath;
while (auto key = nextObjectKey()) {
if (parseCommon(offsetPath, key)) continue;
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(offsetPath->offset);
else if (KEY_AS("lj")) offsetPath->join = getStrokeJoin();
else if (KEY_AS("ml")) parseProperty<LottieProperty::Type::Float>(offsetPath->miterLimit);
else skip(key);
}
offsetPath->prepare();
return offsetPath;
}
LottieObject* LottieParser::parseObject()
{
auto type = getString();
@ -878,11 +902,11 @@ LottieObject* LottieParser::parseObject()
else if (!strcmp(type, "gs")) return parseGradientStroke();
else if (!strcmp(type, "tm")) return parseTrimpath();
else if (!strcmp(type, "rp")) return parseRepeater();
else if (!strcmp(type, "mm")) TVGERR("LOTTIE", "MergePath(mm) is not supported yet");
else if (!strcmp(type, "pb")) TVGERR("LOTTIE", "Puker/Bloat(pb) is not supported yet");
else if (!strcmp(type, "tw")) TVGERR("LOTTIE", "Twist(tw) is not supported yet");
else if (!strcmp(type, "op")) TVGERR("LOTTIE", "Offset Path(op) is not supported yet");
else if (!strcmp(type, "zz")) TVGERR("LOTTIE", "Zig Zag(zz) is not supported yet");
else if (!strcmp(type, "mm")) TVGLOG("LOTTIE", "MergePath(mm) is not supported yet");
else if (!strcmp(type, "pb")) TVGLOG("LOTTIE", "Puker/Bloat(pb) is not supported yet");
else if (!strcmp(type, "tw")) TVGLOG("LOTTIE", "Twist(tw) is not supported yet");
else if (!strcmp(type, "op")) return parseOffsetPath();
else if (!strcmp(type, "zz")) TVGLOG("LOTTIE", "ZigZag(zz) is not supported yet");
return nullptr;
}
@ -901,7 +925,7 @@ void LottieParser::parseObject(Array<LottieObject*>& parent)
}
LottieImage* LottieParser::parseImage(const char* data, const char* subPath, bool embedded)
LottieImage* LottieParser::parseImage(const char* data, const char* subPath, bool embedded, float width, float height)
{
//Used for Image Asset
auto image = new LottieImage;
@ -924,6 +948,8 @@ LottieImage* LottieParser::parseImage(const char* data, const char* subPath, boo
snprintf(image->path, len, "%s%s%s", dirName, subPath, data);
}
image->width = width;
image->height = height;
image->prepare();
return image;
@ -935,31 +961,34 @@ LottieObject* LottieParser::parseAsset()
enterObject();
LottieObject* obj = nullptr;
char *id = nullptr;
unsigned long id = 0;
//Used for Image Asset
const char* data = nullptr;
const char* subPath = nullptr;
float width = 0.0f;
float height = 0.0f;
auto embedded = false;
while (auto key = nextObjectKey()) {
if (KEY_AS("id"))
{
if (peekType() == kStringType) {
id = getStringCopy();
id = djb2Encode(getString());
} else {
id = _int2str(getInt());
}
}
else if (KEY_AS("layers")) obj = parseLayers();
else if (KEY_AS("layers")) obj = parseLayers(comp->root);
else if (KEY_AS("u")) subPath = getString();
else if (KEY_AS("p")) data = getString();
else if (KEY_AS("w")) width = getFloat();
else if (KEY_AS("h")) height = getFloat();
else if (KEY_AS("e")) embedded = getInt();
else skip(key);
}
if (data) obj = parseImage(data, subPath, embedded);
if (obj) obj->name = id;
else free(id);
if (data) obj = parseImage(data, subPath, embedded, width, height);
if (obj) obj->id = id;
return obj;
}
@ -1059,12 +1088,10 @@ void LottieParser::parseFonts()
LottieObject* LottieParser::parseGroup()
{
auto group = new LottieGroup;
if (!group) return nullptr;
while (auto key = nextObjectKey()) {
if (KEY_AS("nm")) {
group->name = getStringCopy();
} else if (KEY_AS("it")) {
if (parseCommon(group, key)) continue;
else if (KEY_AS("it")) {
enterArray();
while (nextArrayValue()) parseObject(group->children);
} else skip(key);
@ -1085,34 +1112,19 @@ void LottieParser::parseTimeRemap(LottieLayer* layer)
}
uint8_t LottieParser::getDirection()
{
auto v = getInt();
if (v == 1) return 0;
if (v == 2) return 3;
if (v == 3) return 2;
return 0;
}
void LottieParser::parseShapes(Array<LottieObject*>& parent)
{
uint8_t direction;
enterArray();
while (nextArrayValue()) {
direction = 0;
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("it")) {
enterArray();
while (nextArrayValue()) parseObject(parent);
} else if (KEY_AS("d")) {
direction = getDirection();
} else if (KEY_AS("ty")) {
if (auto child = parseObject()) {
if (child->hidden) delete(child);
else parent.push(child);
if (direction > 0) static_cast<LottieShape*>(child)->direction = direction;
}
} else skip(key);
}
@ -1125,15 +1137,47 @@ void LottieParser::parseTextRange(LottieText* text)
enterArray();
while (nextArrayValue()) {
enterObject();
auto selector = new LottieTextRange;
while (auto key = nextObjectKey()) {
if (KEY_AS("a")) { //text style
if (KEY_AS("s")) { // text range selector
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("t")) parseProperty<LottieProperty::Type::Float>(text->spacing);
if (KEY_AS("t")) selector->expressible = (bool) getInt();
else if (KEY_AS("xe")) parseProperty<LottieProperty::Type::Float>(selector->maxEase);
else if (KEY_AS("ne")) parseProperty<LottieProperty::Type::Float>(selector->minEase);
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(selector->maxAmount);
else if (KEY_AS("b")) selector->based = (LottieTextRange::Based) getInt();
else if (KEY_AS("rn")) selector->random = getInt() ? rand() : 0;
else if (KEY_AS("sh")) selector->shape = (LottieTextRange::Shape) getInt();
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(selector->offset);
else if (KEY_AS("r")) selector->rangeUnit = (LottieTextRange::Unit) getInt();
else if (KEY_AS("sm")) parseProperty<LottieProperty::Type::Float>(selector->smoothness);
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Float>(selector->start);
else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Float>(selector->end);
else skip(key);
}
} else if (KEY_AS("a")) { // text style
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("t")) parseProperty<LottieProperty::Type::Float>(selector->style.letterSpacing);
else if (KEY_AS("ls")) parseProperty<LottieProperty::Type::Color>(selector->style.lineSpacing);
else if (KEY_AS("fc")) parseProperty<LottieProperty::Type::Color>(selector->style.fillColor);
else if (KEY_AS("fo")) parseProperty<LottieProperty::Type::Color>(selector->style.fillOpacity);
else if (KEY_AS("sw")) parseProperty<LottieProperty::Type::Float>(selector->style.strokeWidth);
else if (KEY_AS("sc")) parseProperty<LottieProperty::Type::Color>(selector->style.strokeColor);
else if (KEY_AS("so")) parseProperty<LottieProperty::Type::Opacity>(selector->style.strokeOpacity);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(selector->style.opacity);
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(selector->style.position);
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Position>(selector->style.scale);
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(selector->style.rotation);
else skip(key);
}
} else skip(key);
}
text->ranges.push(selector);
}
}
@ -1147,8 +1191,16 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
while (auto key = nextObjectKey()) {
if (KEY_AS("d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
else if (KEY_AS("a")) parseTextRange(text);
//else if (KEY_AS("p")) TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
//else if (KEY_AS("m")) TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported");
else if (KEY_AS("p"))
{
TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
skip(key);
}
else if (KEY_AS("m"))
{
TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported");
skip(key);
}
else skip(key);
}
@ -1172,17 +1224,27 @@ void LottieParser::getLayerSize(float& val)
LottieMask* LottieParser::parseMask()
{
auto mask = new LottieMask;
if (!mask) return nullptr;
auto valid = true; //skip if the mask mode is none.
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("inv")) mask->inverse = getBool();
else if (KEY_AS("mode")) mask->method = getMaskMethod(mask->inverse);
else if (KEY_AS("pt")) getPathSet(mask->pathset);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(mask->opacity);
else if (KEY_AS("mode"))
{
mask->method = getMaskMethod(mask->inverse);
if (mask->method == CompositeMethod::None) valid = false;
}
else if (valid && KEY_AS("pt")) getPathSet(mask->pathset);
else if (valid && KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(mask->opacity);
else if (valid && KEY_AS("x")) parseProperty<LottieProperty::Type::Float>(mask->expand);
else skip(key);
}
if (!valid) {
delete(mask);
return nullptr;
}
return mask;
}
@ -1191,29 +1253,99 @@ void LottieParser::parseMasks(LottieLayer* layer)
{
enterArray();
while (nextArrayValue()) {
auto mask = parseMask();
layer->masks.push(mask);
if (auto mask = parseMask()) {
layer->masks.push(mask);
}
}
}
LottieLayer* LottieParser::parseLayer()
void LottieParser::parseGaussianBlur(LottieGaussianBlur* effect)
{
int idx = 0; //blurness -> direction -> wrap
enterArray();
while (nextArrayValue()) {
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("v")) {
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("k")) {
if (idx == 0) parsePropertyInternal(effect->blurness);
else if (idx == 1) parsePropertyInternal(effect->direction);
else if (idx == 2) parsePropertyInternal(effect->wrap);
else skip(key);
++idx;
} else skip(key);
}
} else skip(key);
}
}
}
void LottieParser::parseEffect(LottieEffect* effect)
{
switch (effect->type) {
case LottieEffect::GaussianBlur: {
parseGaussianBlur(static_cast<LottieGaussianBlur*>(effect));
break;
}
default: break;
}
}
void LottieParser::parseEffects(LottieLayer* layer)
{
auto invalid = true;
enterArray();
while (nextArrayValue()) {
LottieEffect* effect = nullptr;
enterObject();
while (auto key = nextObjectKey()) {
//type must be priortized.
if (KEY_AS("ty"))
{
effect = getEffect(getInt());
if (!effect) break;
else invalid = false;
}
else if (effect && KEY_AS("en")) effect->enable = getInt();
else if (effect && KEY_AS("ef")) parseEffect(effect);
else skip(key);
}
//TODO: remove when all effects were guaranteed.
if (invalid) {
TVGLOG("LOTTIE", "Not supported Layer Effect = %d", effect ? (int)effect->type : -1);
while (auto key = nextObjectKey()) skip(key);
} else layer->effects.push(effect);
}
}
LottieLayer* LottieParser::parseLayer(LottieLayer* precomp)
{
auto layer = new LottieLayer;
if (!layer) return nullptr;
layer->comp = comp;
layer->comp = precomp;
context.layer = layer;
auto ddd = false;
RGB24 color;
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("ddd")) ddd = getInt(); //3d layer
else if (KEY_AS("ind")) layer->id = getInt();
if (KEY_AS("nm"))
{
layer->name = getStringCopy();
layer->id = djb2Encode(layer->name);
}
else if (KEY_AS("ddd")) ddd = getInt(); //3d layer
else if (KEY_AS("ind")) layer->idx = getInt();
else if (KEY_AS("ty")) layer->type = (LottieLayer::Type) getInt();
else if (KEY_AS("nm")) layer->name = getStringCopy();
else if (KEY_AS("sr")) layer->timeStretch = getFloat();
else if (KEY_AS("ks"))
{
@ -1225,65 +1357,43 @@ LottieLayer* LottieParser::parseLayer()
else if (KEY_AS("ip")) layer->inFrame = getFloat();
else if (KEY_AS("op")) layer->outFrame = getFloat();
else if (KEY_AS("st")) layer->startFrame = getFloat();
else if (KEY_AS("bm")) layer->blendMethod = getBlendMethod();
else if (KEY_AS("parent")) layer->pid = getInt();
else if (KEY_AS("bm")) layer->blendMethod = (BlendMethod) getInt();
else if (KEY_AS("parent")) layer->pidx = getInt();
else if (KEY_AS("tm")) parseTimeRemap(layer);
else if (KEY_AS("w") || KEY_AS("sw")) getLayerSize(layer->w);
else if (KEY_AS("h") || KEY_AS("sh")) getLayerSize(layer->h);
else if (KEY_AS("sc")) layer->color = getColor(getString());
else if (KEY_AS("tt")) layer->matte.type = getMatteType();
else if (KEY_AS("sc")) color = getColor(getString());
else if (KEY_AS("tt")) layer->matteType = getMatteType();
else if (KEY_AS("tp")) layer->mid = getInt();
else if (KEY_AS("masksProperties")) parseMasks(layer);
else if (KEY_AS("hd")) layer->hidden = getBool();
else if (KEY_AS("refId")) layer->refId = getStringCopy();
else if (KEY_AS("refId")) layer->rid = djb2Encode(getString());
else if (KEY_AS("td")) layer->matteSrc = getInt(); //used for matte layer
else if (KEY_AS("t")) parseText(layer->children);
else if (KEY_AS("ef"))
{
TVGERR("LOTTIE", "layer effect(ef) is not supported!");
skip(key);
}
else if (KEY_AS("ef")) parseEffects(layer);
else skip(key);
}
//Not a valid layer
if (!layer->transform) {
delete(layer);
return nullptr;
}
layer->prepare();
layer->prepare(&color);
return layer;
}
LottieLayer* LottieParser::parseLayers()
LottieLayer* LottieParser::parseLayers(LottieLayer* root)
{
auto root = new LottieLayer;
if (!root) return nullptr;
auto precomp = new LottieLayer;
root->type = LottieLayer::Precomp;
root->comp = comp;
precomp->type = LottieLayer::Precomp;
precomp->comp = root;
enterArray();
while (nextArrayValue()) {
if (auto layer = parseLayer()) {
if (layer->matte.type == CompositeMethod::None) {
root->children.push(layer);
} else {
//matte source must be located in the right previous.
auto matte = static_cast<LottieLayer*>(root->children.last());
if (matte->matteSrc) {
layer->matte.target = matte;
} else {
TVGLOG("LOTTIE", "Matte Source(%s) is not designated?", matte->name);
}
root->children.last() = layer;
}
}
precomp->children.push(parseLayer(precomp));
}
root->prepare();
return root;
precomp->prepare();
return precomp;
}
@ -1337,7 +1447,7 @@ bool LottieParser::apply(LottieSlot* slot)
case LottieProperty::Type::Color: {
obj = new LottieSolid;
context.parent = obj;
parseSlotProperty<LottieProperty::Type::Color>(static_cast<LottieSolid*>(obj)->color);
parseSlotProperty<LottieProperty::Type::Color>(static_cast<LottieSolid*>(obj)->color);
break;
}
case LottieProperty::Type::TextDoc: {
@ -1368,20 +1478,22 @@ bool LottieParser::parse()
if (comp) delete(comp);
comp = new LottieComposition;
if (!comp) return false;
Array<LottieGlyph*> glyphs;
auto startFrame = 0.0f;
auto endFrame = 0.0f;
while (auto key = nextObjectKey()) {
if (KEY_AS("v")) comp->version = getStringCopy();
else if (KEY_AS("fr")) comp->frameRate = getFloat();
else if (KEY_AS("ip")) comp->startFrame = getFloat();
else if (KEY_AS("op")) comp->endFrame = getFloat();
else if (KEY_AS("ip")) startFrame = getFloat();
else if (KEY_AS("op")) endFrame = getFloat();
else if (KEY_AS("w")) comp->w = getFloat();
else if (KEY_AS("h")) comp->h = getFloat();
else if (KEY_AS("nm")) comp->name = getStringCopy();
else if (KEY_AS("assets")) parseAssets();
else if (KEY_AS("layers")) comp->root = parseLayers();
else if (KEY_AS("layers")) comp->root = parseLayers(comp->root);
else if (KEY_AS("fonts")) parseFonts();
else if (KEY_AS("chars")) parseChars(glyphs);
else if (KEY_AS("markers")) parseMarkers();
@ -1393,8 +1505,8 @@ bool LottieParser::parse()
return false;
}
comp->root->inFrame = comp->startFrame;
comp->root->outFrame = comp->endFrame;
comp->root->inFrame = startFrame;
comp->root->outFrame = endFrame;
postProcess(glyphs);

View File

@ -46,7 +46,6 @@ public:
const char* dirName = nullptr; //base resource directory
private:
BlendMethod getBlendMethod();
RGB24 getColor(const char *str);
CompositeMethod getMatteType();
FillRule getFillRule();
@ -54,7 +53,7 @@ private:
StrokeJoin getStrokeJoin();
CompositeMethod getMaskMethod(bool inversed);
LottieInterpolator* getInterpolator(const char* key, Point& in, Point& out);
uint8_t getDirection();
LottieEffect* getEffect(int type);
void getInterpolatorPoint(Point& pt);
void getPathSet(LottiePathSet& path);
@ -65,8 +64,9 @@ private:
void getValue(ColorStop& color);
void getValue(float& val);
void getValue(uint8_t& val);
void getValue(Point& pt);
void getValue(int8_t& val);
void getValue(RGB24& color);
bool getValue(Point& pt);
template<typename T> bool parseTangent(const char *key, LottieVectorFrame<T>& value);
template<typename T> bool parseTangent(const char *key, LottieScalarFrame<T>& value);
@ -77,8 +77,8 @@ private:
LottieObject* parseObject();
LottieObject* parseAsset();
LottieImage* parseImage(const char* data, const char* subPath, bool embedded);
LottieLayer* parseLayer();
LottieImage* parseImage(const char* data, const char* subPath, bool embedded, float width, float height);
LottieLayer* parseLayer(LottieLayer* precomp);
LottieObject* parseGroup();
LottieRect* parseRect();
LottieEllipse* parseEllipse();
@ -90,17 +90,23 @@ private:
LottiePolyStar* parsePolyStar();
LottieRoundedCorner* parseRoundedCorner();
LottieGradientFill* parseGradientFill();
LottieLayer* parseLayers();
LottieLayer* parseLayers(LottieLayer* root);
LottieMask* parseMask();
LottieTrimpath* parseTrimpath();
LottieRepeater* parseRepeater();
LottieOffsetPath* parseOffsetPath();
LottieFont* parseFont();
LottieMarker* parseMarker();
void parseGaussianBlur(LottieGaussianBlur* effect);
bool parseDirection(LottieShape* shape, const char* key);
bool parseCommon(LottieObject* obj, const char* key);
void parseObject(Array<LottieObject*>& parent);
void parseShapes(Array<LottieObject*>& parent);
void parseText(Array<LottieObject*>& parent);
void parseMasks(LottieLayer* layer);
void parseEffects(LottieLayer* layer);
void parseTimeRemap(LottieLayer* layer);
void parseStrokeDash(LottieStroke* stroke);
void parseGradient(LottieGradient* gradient, const char* key);
@ -109,6 +115,7 @@ private:
void parseFonts();
void parseChars(Array<LottieGlyph*>& glyphs);
void parseMarkers();
void parseEffect(LottieEffect* effect);
void postProcess(Array<LottieGlyph*>& glyphs);
//Current parsing context

View File

@ -89,7 +89,7 @@ bool LookaheadParserHandler::nextArrayValue()
int LookaheadParserHandler::getInt()
{
if (state != kHasNumber || !val.IsInt()) {
if (state != kHasNumber) {
Error();
return 0;
}

View File

@ -26,82 +26,19 @@
#ifndef _TVG_LOTTIE_PROPERTY_H_
#define _TVG_LOTTIE_PROPERTY_H_
#include "tvgCommon.h"
#include "tvgArray.h"
#include <algorithm>
#include "tvgMath.h"
#include "tvgLines.h"
#include "tvgLottieCommon.h"
#include "tvgLottieInterpolator.h"
#include "tvgLottieExpressions.h"
#include "tvgLottieModifier.h"
#define ROUNDNESS_EPSILON 1.0f
struct LottieFont;
struct LottieLayer;
struct LottieObject;
struct PathSet
{
Point* pts = nullptr;
PathCommand* cmds = nullptr;
uint16_t ptsCnt = 0;
uint16_t cmdsCnt = 0;
};
struct RGB24
{
int32_t rgb[3];
};
struct ColorStop
{
Fill::ColorStop* data = nullptr;
Array<float>* input = nullptr;
};
struct TextDocument
{
char* text = nullptr;
float height;
float shift;
RGB24 color;
struct {
Point pos;
Point size;
} bbox;
struct {
RGB24 color;
float width;
bool render = false;
} stroke;
char* name = nullptr;
float size;
float tracking = 0.0f;
uint8_t justify;
};
static inline RGB24 operator-(const RGB24& lhs, const RGB24& rhs)
{
return {lhs.rgb[0] - rhs.rgb[0], lhs.rgb[1] - rhs.rgb[1], lhs.rgb[2] - rhs.rgb[2]};
}
static inline RGB24 operator+(const RGB24& lhs, const RGB24& rhs)
{
return {lhs.rgb[0] + rhs.rgb[0], lhs.rgb[1] + rhs.rgb[1], lhs.rgb[2] + rhs.rgb[2]};
}
static inline RGB24 operator*(const RGB24& lhs, float rhs)
{
return {(int32_t)lroundf(lhs.rgb[0] * rhs), (int32_t)lroundf(lhs.rgb[1] * rhs), (int32_t)lroundf(lhs.rgb[2] * rhs)};
}
template<typename T>
struct LottieScalarFrame
{
@ -119,7 +56,7 @@ struct LottieScalarFrame
if (t < 1.0f) return value;
else return next->value;
}
return mathLerp(value, next->value, t);
return lerp(value, next->value, t);
}
};
@ -147,27 +84,30 @@ struct LottieVectorFrame
if (hasTangent) {
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
t = bezAtApprox(bz, t * length, length);
return bezPointAt(bz, t);
return bz.at(bz.atApprox(t * length, length));
} else {
return mathLerp(value, next->value, t);
return lerp(value, next->value, t);
}
}
float angle(LottieVectorFrame* next, float frameNo)
{
if (!hasTangent) return 0;
if (!hasTangent) {
Point dp = next->value - value;
return rad2deg(tvg::atan2(dp.y, dp.x));
}
auto t = (frameNo - no) / (next->no - no);
if (interpolator) t = interpolator->progress(t);
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
t = bezAtApprox(bz, t * length, length);
return -bezAngleAt(bz, t);
t = bz.atApprox(t * length, length);
return bz.angle(t >= 1.0f ? 0.99f : (t <= 0.0f ? 0.01f : t));
}
void prepare(LottieVectorFrame* next)
{
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
length = bezLengthApprox(bz);
length = bz.lengthApprox();
}
};
@ -176,11 +116,13 @@ struct LottieVectorFrame
struct LottieProperty
{
enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid };
virtual ~LottieProperty() {}
LottieExpression* exp = nullptr;
Type type;
uint8_t ix; //property index
//TODO: Apply common bodies?
virtual ~LottieProperty() {}
virtual uint32_t frameCnt() = 0;
virtual uint32_t nearest(float time) = 0;
virtual float frameNo(int32_t key) = 0;
@ -196,16 +138,14 @@ struct LottieExpression
LottieLayer* layer;
LottieObject* object;
LottieProperty* property;
LottieProperty::Type type;
bool enabled;
bool disabled = false;
struct {
uint32_t key = 0; //the keyframe number repeating to
float in = FLT_MAX; //looping duration in frame number
LoopMode mode = None;
} loop;
;
~LottieExpression()
{
free(code);
@ -220,7 +160,7 @@ static void _copy(PathSet* pathset, Array<Point>& outPts, Matrix* transform)
if (transform) {
for (int i = 0; i < pathset->ptsCnt; ++i) {
Point pt = pathset->pts[i];
mathMultiply(&pt, transform);
pt *= *transform;
outPts.push(pt);
}
} else {
@ -242,80 +182,6 @@ static void _copy(PathSet* pathset, Array<PathCommand>& outCmds)
}
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, const Point& prev, const Point& curr, const Point& next, float roundness)
{
auto lenPrev = mathLength(prev - curr);
auto rPrev = lenPrev > 0.0f ? 0.5f * mathMin(lenPrev * 0.5f, roundness) / lenPrev : 0.0f;
auto lenNext = mathLength(next - curr);
auto rNext = lenNext > 0.0f ? 0.5f * mathMin(lenNext * 0.5f, roundness) / lenNext : 0.0f;
auto dPrev = rPrev * (curr - prev);
auto dNext = rNext * (curr - next);
pts.push(curr - 2.0f * dPrev);
pts.push(curr - dPrev);
pts.push(curr - dNext);
pts.push(curr - 2.0f * dNext);
cmds.push(PathCommand::LineTo);
cmds.push(PathCommand::CubicTo);
}
static bool _modifier(Point* inputPts, uint32_t inputPtsCnt, PathCommand* inputCmds, uint32_t inputCmdsCnt, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
{
cmds.reserve(inputCmdsCnt * 2);
pts.reserve((uint16_t)(inputPtsCnt * 1.5));
auto ptsCnt = pts.count;
auto startIndex = 0;
for (uint32_t iCmds = 0, iPts = 0; iCmds < inputCmdsCnt; ++iCmds) {
switch (inputCmds[iCmds]) {
case PathCommand::MoveTo: {
startIndex = pts.count;
cmds.push(PathCommand::MoveTo);
pts.push(inputPts[iPts++]);
break;
}
case PathCommand::CubicTo: {
auto& prev = inputPts[iPts - 1];
auto& curr = inputPts[iPts + 2];
if (iCmds < inputCmdsCnt - 1 &&
mathZero(inputPts[iPts - 1] - inputPts[iPts]) &&
mathZero(inputPts[iPts + 1] - inputPts[iPts + 2])) {
if (inputCmds[iCmds + 1] == PathCommand::CubicTo &&
mathZero(inputPts[iPts + 2] - inputPts[iPts + 3]) &&
mathZero(inputPts[iPts + 4] - inputPts[iPts + 5])) {
_roundCorner(cmds, pts, prev, curr, inputPts[iPts + 5], roundness);
iPts += 3;
break;
} else if (inputCmds[iCmds + 1] == PathCommand::Close) {
_roundCorner(cmds, pts, prev, curr, inputPts[2], roundness);
pts[startIndex] = pts.last();
iPts += 3;
break;
}
}
cmds.push(PathCommand::CubicTo);
pts.push(inputPts[iPts++]);
pts.push(inputPts[iPts++]);
pts.push(inputPts[iPts++]);
break;
}
case PathCommand::Close: {
cmds.push(PathCommand::Close);
break;
}
default: break;
}
}
if (transform) {
for (auto i = ptsCnt; i < pts.count; ++i)
mathTransform(transform, &pts[i]);
}
return true;
}
template<typename T>
uint32_t _bsearch(T* frames, float frameNo)
{
@ -359,17 +225,29 @@ float _frameNo(T* frames, int32_t key)
template<typename T>
float _loop(T* frames, float frameNo, LottieExpression* exp)
{
if (frameNo >= exp->loop.in || frameNo < frames->first().no ||frameNo < frames->last().no) return frameNo;
if (frameNo >= exp->loop.in || frameNo < frames->first().no || frameNo < frames->last().no) return frameNo;
frameNo -= frames->first().no;
switch (exp->loop.mode) {
case LottieExpression::LoopMode::InCycle: {
frameNo -= frames->first().no;
return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no;
}
case LottieExpression::LoopMode::InPingPong: {
auto range = frames->last().no - (*frames)[exp->loop.key].no;
auto forward = (static_cast<int>(frameNo / range) % 2) == 0 ? true : false;
frameNo = fmodf(frameNo, range);
return (forward ? frameNo : (range - frameNo)) + (*frames)[exp->loop.key].no;
}
case LottieExpression::LoopMode::OutCycle: {
frameNo -= frames->first().no;
return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no;
}
case LottieExpression::LoopMode::OutPingPong: {
auto range = (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no;
auto forward = (static_cast<int>(frameNo / range) % 2) == 0 ? true : false;
frameNo = fmodf(frameNo, range);
return (forward ? frameNo : (range - frameNo)) + frames->first().no;
}
default: break;
}
return frameNo;
@ -440,13 +318,13 @@ struct LottieGenericProperty : LottieProperty
if (frameNo >= frames->last().no) return frames->last().value;
auto frame = frames->data + _bsearch(frames, frameNo);
if (mathEqual(frame->no, frameNo)) return frame->value;
if (tvg::equal(frame->no, frameNo)) return frame->value;
return frame->interpolate(frame + 1, frameNo);
}
T operator()(float frameNo, LottieExpressions* exps)
{
if (exps && (exp && exp->enabled)) {
if (exps && exp) {
T out{};
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottieGenericProperty<T>>(frameNo, out, exp)) return out;
@ -454,12 +332,12 @@ struct LottieGenericProperty : LottieProperty
return operator()(frameNo);
}
T& operator=(const T& other)
LottieGenericProperty<T>& operator=(const LottieGenericProperty<T>& other)
{
//shallow copy, used for slot overriding
if (other.frames) {
frames = other.frames;
const_cast<T&>(other).frames = nullptr;
const_cast<LottieGenericProperty<T>&>(other).frames = nullptr;
} else value = other.value;
return *this;
}
@ -533,7 +411,7 @@ struct LottiePathSet : LottieProperty
return (*frames)[frames->count];
}
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath)
{
PathSet* path = nullptr;
LottieScalarFrame<PathSet>* frame = nullptr;
@ -545,8 +423,11 @@ struct LottiePathSet : LottieProperty
else if (frameNo >= frames->last().no) path = &frames->last().value;
else {
frame = frames->data + _bsearch(frames, frameNo);
if (mathEqual(frame->no, frameNo)) path = &frame->value;
else {
if (tvg::equal(frame->no, frameNo)) path = &frame->value;
else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) {
path = &frame->value;
TVGLOG("LOTTIE", "Different numbers of points in consecutive frames - interpolation omitted.");
} else {
t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
if (frame->interpolator) t = frame->interpolator->progress(t);
if (frame->hold) path = &(frame + ((t < 1.0f) ? 0 : 1))->value;
@ -555,7 +436,17 @@ struct LottiePathSet : LottieProperty
}
if (!interpolate) {
if (roundness > ROUNDNESS_EPSILON) return _modifier(path->pts, path->ptsCnt, path->cmds, path->cmdsCnt, cmds, pts, transform, roundness);
if (roundness) {
if (offsetPath) {
Array<PathCommand> cmds1(path->cmdsCnt);
Array<Point> pts1(path->ptsCnt);
roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds1, pts1, transform);
return offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts);
}
return roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, transform);
}
if (offsetPath) return offsetPath->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts);
_copy(path, cmds);
_copy(path, pts, transform);
return true;
@ -564,35 +455,45 @@ struct LottiePathSet : LottieProperty
auto s = frame->value.pts;
auto e = (frame + 1)->value.pts;
if (roundness > ROUNDNESS_EPSILON) {
auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
auto p = interpPts;
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
*p = mathLerp(*s, *e, t);
if (transform) mathMultiply(p, transform);
}
_modifier(interpPts, frame->value.ptsCnt, frame->value.cmds, frame->value.cmdsCnt, cmds, pts, nullptr, roundness);
free(interpPts);
return true;
} else {
if (!roundness && !offsetPath) {
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
auto pt = mathLerp(*s, *e, t);
if (transform) mathMultiply(&pt, transform);
auto pt = lerp(*s, *e, t);
if (transform) pt *= *transform;
pts.push(pt);
}
_copy(&frame->value, cmds);
return true;
}
auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
auto p = interpPts;
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
*p = lerp(*s, *e, t);
if (transform) *p *= *transform;
}
if (roundness) {
if (offsetPath) {
Array<PathCommand> cmds1;
Array<Point> pts1;
roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds1, pts1, nullptr);
offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts);
} else roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, nullptr);
} else if (offsetPath) offsetPath->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts);
free(interpPts);
return true;
}
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness, LottieExpressions* exps)
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpressions* exps)
{
if (exps && (exp && exp->enabled)) {
if (exps && exp) {
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, exp)) return true;
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, offsetPath, exp)) return true;
}
return operator()(frameNo, cmds, pts, transform, roundness);
return operator()(frameNo, cmds, pts, transform, roundness, offsetPath);
}
void prepare() {}
@ -669,7 +570,7 @@ struct LottieColorStop : LottieProperty
Result operator()(float frameNo, Fill* fill, LottieExpressions* exps)
{
if (exps && (exp && exp->enabled)) {
if (exps && exp) {
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottieColorStop>(frameNo, fill, exp)) return Result::Success;
}
@ -680,10 +581,12 @@ struct LottieColorStop : LottieProperty
return fill->colorStops(frames->first().value.data, count);
}
if (frameNo >= frames->last().no) return fill->colorStops(frames->last().value.data, count);
if (frameNo >= frames->last().no) {
return fill->colorStops(frames->last().value.data, count);
}
auto frame = frames->data + _bsearch(frames, frameNo);
if (mathEqual(frame->no, frameNo)) return fill->colorStops(frame->value.data, count);
if (tvg::equal(frame->no, frameNo)) return fill->colorStops(frame->value.data, count);
//interpolate
auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
@ -700,11 +603,11 @@ struct LottieColorStop : LottieProperty
Array<Fill::ColorStop> result;
for (auto i = 0; i < count; ++i, ++s, ++e) {
auto offset = mathLerp(s->offset, e->offset, t);
auto r = mathLerp(s->r, e->r, t);
auto g = mathLerp(s->g, e->g, t);
auto b = mathLerp(s->b, e->b, t);
auto a = mathLerp(s->a, e->a, t);
auto offset = lerp(s->offset, e->offset, t);
auto r = lerp(s->r, e->r, t);
auto g = lerp(s->g, e->g, t);
auto b = lerp(s->b, e->b, t);
auto a = lerp(s->a, e->a, t);
result.push({offset, r, g, b, a});
}
return fill->colorStops(result.data, count);
@ -794,14 +697,14 @@ struct LottiePosition : LottieProperty
if (frameNo >= frames->last().no) return frames->last().value;
auto frame = frames->data + _bsearch(frames, frameNo);
if (mathEqual(frame->no, frameNo)) return frame->value;
if (tvg::equal(frame->no, frameNo)) return frame->value;
return frame->interpolate(frame + 1, frameNo);
}
Point operator()(float frameNo, LottieExpressions* exps)
{
Point out{};
if (exps && (exp && exp->enabled)) {
if (exps && exp) {
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
if (exps->result<LottiePosition>(frameNo, out, exp)) return out;
}
@ -810,9 +713,13 @@ struct LottiePosition : LottieProperty
float angle(float frameNo)
{
if (!frames) return 0;
if (frames->count == 1 || frameNo <= frames->first().no) return 0;
if (frameNo >= frames->last().no) return 0;
if (!frames || frames->count == 1) return 0;
if (frameNo <= frames->first().no) return frames->first().angle(frames->data + 1, frames->first().no);
if (frameNo >= frames->last().no) {
auto frame = frames->data + frames->count - 2;
return frame->angle(frame + 1, frames->last().no);
}
auto frame = frames->data + _bsearch(frames, frameNo);
return frame->angle(frame + 1, frameNo);
@ -928,6 +835,8 @@ using LottiePoint = LottieGenericProperty<Point>;
using LottieFloat = LottieGenericProperty<float>;
using LottieOpacity = LottieGenericProperty<uint8_t>;
using LottieColor = LottieGenericProperty<RGB24>;
using LottieSlider = LottieFloat;
using LottieCheckbox = LottieGenericProperty<int8_t>;
#endif //_TVG_LOTTIE_PROPERTY_H_

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -23,44 +23,42 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#ifndef _TVG_LINES_H_
#define _TVG_LINES_H_
#ifndef _TVG_LOTTIE_RENDER_POOLER_H_
#define _TVG_LOTTIE_RENDER_POOLER_H_
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgPaint.h"
namespace tvg
{
struct Line
template<typename T>
struct LottieRenderPooler
{
Point pt1;
Point pt2;
Array<T*> pooler;
~LottieRenderPooler()
{
for (auto p = pooler.begin(); p < pooler.end(); ++p) {
if (PP(*p)->unref() == 0) delete(*p);
}
}
T* pooling(bool copy = false)
{
//return available one.
for (auto p = pooler.begin(); p < pooler.end(); ++p) {
if (PP(*p)->refCnt == 1) return *p;
}
//no empty, generate a new one.
auto p = copy ? static_cast<T*>(pooler[0]->duplicate()) : T::gen().release();
PP(p)->ref();
pooler.push(p);
return p;
}
};
float lineLength(const Point& pt1, const Point& pt2);
void lineSplitAt(const Line& cur, float at, Line& left, Line& right);
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, float length);
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right);
Point bezPointAt(const Bezier& bz, float t);
float bezAngleAt(const Bezier& bz, float t);
float bezLengthApprox(const Bezier& cur);
float bezAtApprox(const Bezier& bz, float at, float length);
}
#endif //_TVG_LINES_H_
#endif //_TVG_LOTTIE_RENDER_POOLER_H_
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -25,16 +25,106 @@
#include "tvgMath.h"
#define BEZIER_EPSILON 1e-2f
bool mathInverse(const Matrix* m, Matrix* out)
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static float _lineLengthApprox(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 float _lineLength(const Point& pt1, const Point& pt2)
{
Point diff = {pt2.x - pt1.x, pt2.y - pt1.y};
return sqrtf(diff.x * diff.x + diff.y * diff.y);
}
template<typename LengthFunc>
float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc)
{
Bezier left, right;
auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end);
auto chord = lineLengthFunc(cur.start, cur.end);
if (fabsf(len - chord) > BEZIER_EPSILON) {
cur.split(left, right);
return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc);
}
return len;
}
template<typename LengthFunc>
float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc)
{
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 >= length) return 1.0f;
while (true) {
auto right = bz;
Bezier left;
right.split(t, left);
length = _bezLength(left, lineLengthFunc);
if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
break;
}
if (length < at) {
smallest = t;
t = (t + biggest) * 0.5f;
} else {
biggest = t;
t = (smallest + t) * 0.5f;
}
}
return t;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
namespace tvg {
//https://en.wikipedia.org/wiki/Remez_algorithm
float atan2(float y, float x)
{
if (y == 0.0f && x == 0.0f) return 0.0f;
auto a = std::min(fabsf(x), fabsf(y)) / std::max(fabsf(x), fabsf(y));
auto s = a * a;
auto r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a;
if (fabsf(y) > fabsf(x)) r = 1.57079637f - r;
if (x < 0) r = 3.14159274f - r;
if (y < 0) return -r;
return r;
}
bool inverse(const Matrix* m, Matrix* out)
{
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 (mathZero(det)) return false;
auto invDet = 1 / det;
auto invDet = 1.0f / det;
if (std::isinf(invDet)) return false;
out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet;
out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet;
@ -50,27 +140,18 @@ bool mathInverse(const Matrix* m, Matrix* out)
}
Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs)
bool identity(const Matrix* m)
{
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;
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;
}
void mathRotate(Matrix* m, float degree)
void rotate(Matrix* m, float degree)
{
if (degree == 0.0f) return;
@ -85,24 +166,215 @@ void mathRotate(Matrix* m, float degree)
}
bool mathIdentity(const Matrix* m)
Matrix operator*(const Matrix& lhs, const Matrix& rhs)
{
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;
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;
}
bool operator==(const Matrix& lhs, const Matrix& rhs)
{
if (!tvg::equal(lhs.e11, rhs.e11) || !tvg::equal(lhs.e12, rhs.e12) || !tvg::equal(lhs.e13, rhs.e13) ||
!tvg::equal(lhs.e21, rhs.e21) || !tvg::equal(lhs.e22, rhs.e22) || !tvg::equal(lhs.e23, rhs.e23) ||
!tvg::equal(lhs.e31, rhs.e31) || !tvg::equal(lhs.e32, rhs.e32) || !tvg::equal(lhs.e33, rhs.e33)) {
return false;
}
return true;
}
void mathMultiply(Point* pt, const Matrix* transform)
void operator*=(Point& pt, const Matrix& m)
{
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;
auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13;
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
pt.x = tx;
pt.y = ty;
}
Point operator*(const Point& pt, const Matrix& m)
{
auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13;
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
return {tx, ty};
}
Point normal(const Point& p1, const Point& p2)
{
auto dir = p2 - p1;
auto len = length(dir);
if (tvg::zero(len)) return {};
auto unitDir = dir / len;
return {-unitDir.y, unitDir.x};
}
float Line::length() const
{
return _lineLength(pt1, pt2);
}
void Line::split(float at, Line& left, Line& right) const
{
auto len = length();
auto dx = ((pt2.x - pt1.x) / len) * at;
auto dy = ((pt2.y - pt1.y) / len) * at;
left.pt1 = pt1;
left.pt2.x = left.pt1.x + dx;
left.pt2.y = left.pt1.y + dy;
right.pt1 = left.pt2;
right.pt2 = pt2;
}
void Bezier::split(Bezier& left, Bezier& right) const
{
auto c = (ctrl1.x + ctrl2.x) * 0.5f;
left.ctrl1.x = (start.x + ctrl1.x) * 0.5f;
right.ctrl2.x = (ctrl2.x + end.x) * 0.5f;
left.start.x = start.x;
right.end.x = 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 = (ctrl1.y + ctrl2.y) * 0.5f;
left.ctrl1.y = (start.y + ctrl1.y) * 0.5f;
right.ctrl2.y = (ctrl2.y + end.y) * 0.5f;
left.start.y = start.y;
right.end.y = 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;
}
void Bezier::split(float at, Bezier& left, Bezier& right) const
{
right = *this;
auto t = right.at(at, right.length());
right.split(t, left);
}
float Bezier::length() const
{
return _bezLength(*this, _lineLength);
}
float Bezier::lengthApprox() const
{
return _bezLength(*this, _lineLengthApprox);
}
void Bezier::split(float t, Bezier& left)
{
left.start = start;
left.ctrl1.x = start.x + t * (ctrl1.x - start.x);
left.ctrl1.y = start.y + t * (ctrl1.y - start.y);
left.ctrl2.x = ctrl1.x + t * (ctrl2.x - ctrl1.x); //temporary holding spot
left.ctrl2.y = ctrl1.y + t * (ctrl2.y - ctrl1.y); //temporary holding spot
ctrl2.x = ctrl2.x + t * (end.x - ctrl2.x);
ctrl2.y = ctrl2.y + t * (end.y - ctrl2.y);
ctrl1.x = left.ctrl2.x + t * (ctrl2.x - left.ctrl2.x);
ctrl1.y = left.ctrl2.y + t * (ctrl2.y - left.ctrl2.y);
left.ctrl2.x = left.ctrl1.x + t * (left.ctrl2.x - left.ctrl1.x);
left.ctrl2.y = left.ctrl1.y + t * (left.ctrl2.y - left.ctrl1.y);
left.end.x = start.x = left.ctrl2.x + t * (ctrl1.x - left.ctrl2.x);
left.end.y = start.y = left.ctrl2.y + t * (ctrl1.y - left.ctrl2.y);
}
float Bezier::at(float at, float length) const
{
return _bezAt(*this, at, length, _lineLength);
}
float Bezier::atApprox(float at, float length) const
{
return _bezAt(*this, at, length, _lineLengthApprox);
}
Point Bezier::at(float t) const
{
Point cur;
auto it = 1.0f - t;
auto ax = start.x * it + ctrl1.x * t;
auto bx = ctrl1.x * it + ctrl2.x * t;
auto cx = ctrl2.x * it + end.x * t;
ax = ax * it + bx * t;
bx = bx * it + cx * t;
cur.x = ax * it + bx * t;
float ay = start.y * it + ctrl1.y * t;
float by = ctrl1.y * it + ctrl2.y * t;
float cy = ctrl2.y * it + end.y * t;
ay = ay * it + by * t;
by = by * it + cy * t;
cur.y = ay * it + by * t;
return cur;
}
float Bezier::angle(float t) const
{
if (t < 0 || t > 1) return 0;
//derivate
// p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 *
// t^2) * p2 + t^2 * p3)
float mt = 1.0f - t;
float d = t * t;
float a = -mt * mt;
float b = 1 - 4 * t + 3 * d;
float c = 2 * t - 3 * d;
Point pt ={a * start.x + b * ctrl1.x + c * ctrl2.x + d * end.x, a * start.y + b * ctrl1.y + c * ctrl2.y + d * end.y};
pt.x *= 3;
pt.y *= 3;
return rad2deg(tvg::atan2(pt.y, pt.x));
}
uint8_t lerp(const uint8_t &start, const uint8_t &end, float t)
{
auto result = static_cast<int>(start + (end - start) * t);
tvg::clamp(result, 0, 255);
return static_cast<uint8_t>(result);
}
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -29,81 +29,80 @@
#define _USE_MATH_DEFINES
#include <float.h>
#include <math.h>
#include <cmath>
#include "tvgCommon.h"
namespace tvg
{
#define MATH_PI 3.14159265358979323846f
#define MATH_PI2 1.57079632679489661923f
#define FLOAT_EPSILON 1.0e-06f //1.192092896e-07f
#define PATH_KAPPA 0.552284f
#define mathMin(x, y) (((x) < (y)) ? (x) : (y))
#define mathMax(x, y) (((x) > (y)) ? (x) : (y))
/************************************************************************/
/* General functions */
/************************************************************************/
float atan2(float y, float x);
bool mathInverse(const Matrix* m, Matrix* out);
Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs);
void mathRotate(Matrix* m, float degree);
bool mathIdentity(const Matrix* m);
void mathMultiply(Point* pt, const Matrix* transform);
static inline float mathDeg2Rad(float degree)
static inline float deg2rad(float degree)
{
return degree * (MATH_PI / 180.0f);
}
static inline float mathRad2Deg(float radian)
static inline float rad2deg(float radian)
{
return radian * (180.0f / MATH_PI);
}
static inline bool mathZero(float a)
static inline bool zero(float a)
{
return (fabsf(a) <= FLOAT_EPSILON) ? true : false;
}
static inline bool mathZero(const Point& p)
static inline bool equal(float a, float b)
{
return mathZero(p.x) && mathZero(p.y);
return tvg::zero(a - b);
}
static inline bool mathEqual(float a, float b)
template <typename T>
static inline void clamp(T& v, const T& min, const T& max)
{
return mathZero(a - b);
if (v < min) v = min;
else if (v > max) v = max;
}
/************************************************************************/
/* Matrix functions */
/************************************************************************/
static inline bool mathEqual(const Matrix& a, const Matrix& b)
void rotate(Matrix* m, float degree);
bool inverse(const Matrix* m, Matrix* out);
bool identity(const Matrix* m);
Matrix operator*(const Matrix& lhs, const Matrix& rhs);
bool operator==(const Matrix& lhs, const Matrix& rhs);
static inline bool rightAngle(const Matrix& m)
{
if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) ||
!mathEqual(a.e21, b.e21) || !mathEqual(a.e22, b.e22) || !mathEqual(a.e23, b.e23) ||
!mathEqual(a.e31, b.e31) || !mathEqual(a.e32, b.e32) || !mathEqual(a.e33, b.e33)) {
return false;
}
return true;
}
static inline bool mathRightAngle(const Matrix* m)
{
auto radian = fabsf(atan2f(m->e21, m->e11));
if (radian < FLOAT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true;
auto radian = fabsf(tvg::atan2(m.e21, m.e11));
if (radian < FLOAT_EPSILON || tvg::equal(radian, MATH_PI2) || tvg::equal(radian, MATH_PI)) return true;
return false;
}
static inline bool mathSkewed(const Matrix* m)
static inline bool skewed(const Matrix& m)
{
return !mathZero(m->e21 + m->e12);
return !tvg::zero(m.e21 + m.e12);
}
static inline void mathIdentity(Matrix* m)
static inline void identity(Matrix* m)
{
m->e11 = 1.0f;
m->e12 = 0.0f;
@ -117,23 +116,14 @@ static inline void mathIdentity(Matrix* m)
}
static inline void mathTransform(Matrix* transform, Point* coord)
{
auto x = coord->x;
auto y = coord->y;
coord->x = x * transform->e11 + y * transform->e12 + transform->e13;
coord->y = x * transform->e21 + y * transform->e22 + transform->e23;
}
static inline void mathScale(Matrix* m, float sx, float sy)
static inline void scale(Matrix* m, float sx, float sy)
{
m->e11 *= sx;
m->e22 *= sy;
}
static inline void mathScaleR(Matrix* m, float x, float y)
static inline void scaleR(Matrix* m, float x, float y)
{
if (x != 1.0f) {
m->e11 *= x;
@ -146,14 +136,14 @@ static inline void mathScaleR(Matrix* m, float x, float y)
}
static inline void mathTranslate(Matrix* m, float x, float y)
static inline void translate(Matrix* m, float x, float y)
{
m->e13 += x;
m->e23 += y;
}
static inline void mathTranslateR(Matrix* m, float x, float y)
static inline void translateR(Matrix* m, float x, float y)
{
if (x == 0.0f && y == 0.0f) return;
m->e13 += (x * m->e11 + y * m->e12);
@ -161,13 +151,45 @@ static inline void mathTranslateR(Matrix* m, float x, float y)
}
static inline void mathLog(Matrix* m)
static inline bool operator!=(const Matrix& lhs, const Matrix& rhs)
{
TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33);
return !(lhs == rhs);
}
static inline float mathLength(const Point* a, const Point* b)
static inline void operator*=(Matrix& lhs, const Matrix& rhs)
{
lhs = lhs * rhs;
}
static inline void log(const Matrix& m)
{
TVGLOG("COMMON", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m.e11, m.e12, m.e13, m.e21, m.e22, m.e23, m.e31, m.e32, m.e33);
}
/************************************************************************/
/* Point functions */
/************************************************************************/
void operator*=(Point& pt, const Matrix& m);
Point operator*(const Point& pt, const Matrix& m);
Point normal(const Point& p1, const Point& p2);
static inline float cross(const Point& lhs, const Point& rhs)
{
return lhs.x * rhs.y - rhs.x * lhs.y;
}
static inline bool zero(const Point& p)
{
return tvg::zero(p.x) && tvg::zero(p.y);
}
static inline float length(const Point* a, const Point* b)
{
auto x = b->x - a->x;
auto y = b->y - a->y;
@ -179,12 +201,24 @@ static inline float mathLength(const Point* a, const Point* b)
}
static inline float mathLength(const Point& a)
static inline float length(const Point& a)
{
return sqrtf(a.x * a.x + a.y * a.y);
}
static inline bool operator==(const Point& lhs, const Point& rhs)
{
return tvg::equal(lhs.x, rhs.x) && tvg::equal(lhs.y, rhs.y);
}
static inline bool operator!=(const Point& lhs, const Point& rhs)
{
return !(lhs == rhs);
}
static inline Point operator-(const Point& lhs, const Point& rhs)
{
return {lhs.x - rhs.x, lhs.y - rhs.y};
@ -215,12 +249,62 @@ static inline Point operator/(const Point& lhs, const float rhs)
}
static inline void log(const Point& pt)
{
TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y);
}
/************************************************************************/
/* Line functions */
/************************************************************************/
struct Line
{
Point pt1;
Point pt2;
void split(float at, Line& left, Line& right) const;
float length() const;
};
/************************************************************************/
/* Bezier functions */
/************************************************************************/
struct Bezier
{
Point start;
Point ctrl1;
Point ctrl2;
Point end;
void split(float t, Bezier& left);
void split(Bezier& left, Bezier& right) const;
void split(float at, Bezier& left, Bezier& right) const;
float length() const;
float lengthApprox() const;
float at(float at, float length) const;
float atApprox(float at, float length) const;
Point at(float t) const;
float angle(float t) const;
};
/************************************************************************/
/* Interpolation functions */
/************************************************************************/
template <typename T>
static inline T mathLerp(const T &start, const T &end, float t)
static inline T lerp(const T &start, const T &end, float t)
{
return static_cast<T>(start + (end - start) * t);
}
uint8_t lerp(const uint8_t &start, const uint8_t &end, float t);
}
#endif //_TVG_MATH_H_

View File

@ -35,17 +35,48 @@
/************************************************************************/
#define PAINT_METHOD(ret, METHOD) \
switch (id) { \
case TVG_CLASS_ID_SHAPE: ret = P((Shape*)paint)->METHOD; break; \
case TVG_CLASS_ID_SCENE: ret = P((Scene*)paint)->METHOD; break; \
case TVG_CLASS_ID_PICTURE: ret = P((Picture*)paint)->METHOD; break; \
case TVG_CLASS_ID_TEXT: ret = P((Text*)paint)->METHOD; break; \
switch (paint->type()) { \
case Type::Shape: ret = P((Shape*)paint)->METHOD; break; \
case Type::Scene: ret = P((Scene*)paint)->METHOD; break; \
case Type::Picture: ret = P((Picture*)paint)->METHOD; break; \
case Type::Text: ret = P((Text*)paint)->METHOD; break; \
default: ret = {}; \
}
static Result _clipRect(RenderMethod* renderer, const Point* pts, const Matrix& pm, const Matrix& rm, RenderRegion& before)
{
//sorting
Point tmp[4];
Point min = {FLT_MAX, FLT_MAX};
Point max = {0.0f, 0.0f};
static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport)
for (int i = 0; i < 4; ++i) {
tmp[i] = pts[i];
tmp[i] *= rm;
tmp[i] *= pm;
if (tmp[i].x < min.x) min.x = tmp[i].x;
if (tmp[i].x > max.x) max.x = tmp[i].x;
if (tmp[i].y < min.y) min.y = tmp[i].y;
if (tmp[i].y > max.y) max.y = tmp[i].y;
}
float region[4] = {float(before.x), float(before.x + before.w), float(before.y), float(before.y + before.h)};
//figure out if the clipper is a superset of the current viewport(before) region
if (min.x <= region[0] && max.x >= region[1] && min.y <= region[2] && max.y >= region[3]) {
//viewport region is same, nothing to do.
return Result::Success;
//figure out if the clipper is totally outside of the viewport
} else if (max.x <= region[0] || min.x >= region[1] || max.y <= region[2] || min.y >= region[3]) {
renderer->viewport({0, 0, 0, 0});
return Result::Success;
}
return Result::InsufficientCondition;
}
static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Matrix& pm, RenderRegion& before)
{
/* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
auto shape = static_cast<Shape*>(cmpTarget);
@ -56,14 +87,17 @@ static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform
//nothing to clip
if (ptsCnt == 0) return Result::InvalidArguments;
if (ptsCnt != 4) return Result::InsufficientCondition;
if (rTransform) rTransform->update();
auto& rm = P(cmpTarget)->transform();
//No rotation and no skewing
if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return Result::InsufficientCondition;
if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return Result::InsufficientCondition;
//No rotation and no skewing, still can try out clipping the rect region.
auto tryClip = false;
if ((!rightAngle(pm) || skewed(pm))) tryClip = true;
if ((!rightAngle(rm) || skewed(rm))) tryClip = true;
if (tryClip) return _clipRect(renderer, pts, pm, rm, before);
//Perpendicular Rectangle?
auto pt1 = pts + 0;
@ -71,42 +105,32 @@ static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform
auto pt3 = pts + 2;
auto pt4 = pts + 3;
if ((mathEqual(pt1->x, pt2->x) && mathEqual(pt2->y, pt3->y) && mathEqual(pt3->x, pt4->x) && mathEqual(pt1->y, pt4->y)) ||
(mathEqual(pt2->x, pt3->x) && mathEqual(pt1->y, pt2->y) && mathEqual(pt1->x, pt4->x) && mathEqual(pt3->y, pt4->y))) {
if ((tvg::equal(pt1->x, pt2->x) && tvg::equal(pt2->y, pt3->y) && tvg::equal(pt3->x, pt4->x) && tvg::equal(pt1->y, pt4->y)) ||
(tvg::equal(pt2->x, pt3->x) && tvg::equal(pt1->y, pt2->y) && tvg::equal(pt1->x, pt4->x) && tvg::equal(pt3->y, pt4->y))) {
RenderRegion after;
auto v1 = *pt1;
auto v2 = *pt3;
if (rTransform) {
mathMultiply(&v1, &rTransform->m);
mathMultiply(&v2, &rTransform->m);
}
if (pTransform) {
mathMultiply(&v1, &pTransform->m);
mathMultiply(&v2, &pTransform->m);
}
v1 *= rm;
v2 *= rm;
v1 *= pm;
v2 *= pm;
//sorting
if (v1.x > v2.x) {
auto tmp = v2.x;
v2.x = v1.x;
v1.x = tmp;
}
if (v1.x > v2.x) std::swap(v1.x, v2.x);
if (v1.y > v2.y) std::swap(v1.y, v2.y);
if (v1.y > v2.y) {
auto tmp = v2.y;
v2.y = v1.y;
v1.y = tmp;
}
after.x = static_cast<int32_t>(v1.x);
after.y = static_cast<int32_t>(v1.y);
after.w = static_cast<int32_t>(ceil(v2.x - after.x));
after.h = static_cast<int32_t>(ceil(v2.y - after.y));
viewport.x = static_cast<int32_t>(v1.x);
viewport.y = static_cast<int32_t>(v1.y);
viewport.w = static_cast<int32_t>(ceil(v2.x - viewport.x));
viewport.h = static_cast<int32_t>(ceil(v2.y - viewport.y));
if (after.w < 0) after.w = 0;
if (after.h < 0) after.h = 0;
if (viewport.w < 0) viewport.w = 0;
if (viewport.h < 0) viewport.h = 0;
after.intersect(before);
renderer->viewport(after);
return Result::Success;
}
@ -130,21 +154,20 @@ Iterator* Paint::Impl::iterator()
}
Paint* Paint::Impl::duplicate()
Paint* Paint::Impl::duplicate(Paint* ret)
{
Paint* ret;
PAINT_METHOD(ret, duplicate());
if (ret) ret->composite(nullptr, CompositeMethod::None);
PAINT_METHOD(ret, duplicate(ret));
//duplicate Transform
if (rTransform) {
ret->pImpl->rTransform = new RenderTransform();
*ret->pImpl->rTransform = *rTransform;
ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
}
ret->pImpl->tr = tr;
ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
ret->pImpl->opacity = opacity;
if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
if (clipper) ret->pImpl->clip(clipper->duplicate());
return ret;
}
@ -152,14 +175,10 @@ Paint* Paint::Impl::duplicate()
bool Paint::Impl::rotate(float degree)
{
if (rTransform) {
if (mathEqual(degree, rTransform->degree)) return true;
} else {
if (mathZero(degree)) return true;
rTransform = new RenderTransform();
}
rTransform->degree = degree;
if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
if (tr.overriding) return false;
if (tvg::equal(degree, tr.degree)) return true;
tr.degree = degree;
renderFlag |= RenderUpdateFlag::Transform;
return true;
}
@ -167,14 +186,10 @@ bool Paint::Impl::rotate(float degree)
bool Paint::Impl::scale(float factor)
{
if (rTransform) {
if (mathEqual(factor, rTransform->scale)) return true;
} else {
if (mathEqual(factor, 1.0f)) return true;
rTransform = new RenderTransform();
}
rTransform->scale = factor;
if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
if (tr.overriding) return false;
if (tvg::equal(factor, tr.scale)) return true;
tr.scale = factor;
renderFlag |= RenderUpdateFlag::Transform;
return true;
}
@ -182,15 +197,11 @@ bool Paint::Impl::scale(float factor)
bool Paint::Impl::translate(float x, float y)
{
if (rTransform) {
if (mathEqual(x, rTransform->x) && mathEqual(y, rTransform->y)) return true;
} else {
if (mathZero(x) && mathZero(y)) return true;
rTransform = new RenderTransform();
}
rTransform->x = x;
rTransform->y = y;
if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
if (tr.overriding) return false;
if (tvg::equal(x, tr.m.e13) && tvg::equal(y, tr.m.e23)) return true;
tr.m.e13 = x;
tr.m.e23 = y;
renderFlag |= RenderUpdateFlag::Transform;
return true;
}
@ -198,11 +209,11 @@ bool Paint::Impl::translate(float x, float y)
bool Paint::Impl::render(RenderMethod* renderer)
{
Compositor* cmp = nullptr;
if (opacity == 0) return true;
/* Note: only ClipPath is processed in update() step.
Create a composition image. */
if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
RenderCompositor* cmp = nullptr;
if (compData && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
RenderRegion region;
PAINT_METHOD(region, bounds(renderer));
@ -216,8 +227,6 @@ bool Paint::Impl::render(RenderMethod* renderer)
if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
renderer->blend(blendMethod);
bool ret;
PAINT_METHOD(ret, render(renderer));
@ -227,7 +236,7 @@ bool Paint::Impl::render(RenderMethod* renderer)
}
RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (this->renderer != renderer) {
if (this->renderer) TVGERR("RENDERER", "paint's renderer has been changed!");
@ -235,79 +244,79 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
this->renderer = renderer;
}
if (renderFlag & RenderUpdateFlag::Transform) {
if (!rTransform) return nullptr;
rTransform->update();
}
if (renderFlag & RenderUpdateFlag::Transform) tr.update();
/* 1. Composition Pre Processing */
RenderData trd = nullptr; //composite target render data
RenderRegion viewport;
Result compFastTrack = Result::InsufficientCondition;
bool childClipper = false;
if (compData) {
auto target = compData->target;
auto method = compData->method;
target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset
P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset
/* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle,
we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */
auto tryFastTrack = false;
if (target->identifier() == TVG_CLASS_ID_SHAPE) {
if (method == CompositeMethod::ClipPath) tryFastTrack = true;
else {
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
//no gradient fill & no compositions of the composition target.
if (!shape->fill() && !(PP(shape)->compData)) {
if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true;
else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true;
}
}
if (tryFastTrack) {
RenderRegion viewport2;
if (target->composite(nullptr) != CompositeMethod::ClipPath &&
(compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2)) == Result::Success) {
/* If the transformation has no rotational factors and the Alpha(InvAlpha)Masking involves a simple rectangle,
we can optimize by using the viewport instead of the regular AlphaMasking sequence for improved performance. */
if (target->type() == Type::Shape) {
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
//no gradient fill & no compositions of the composition target.
if (!shape->fill() && !(PP(shape)->compData)) {
if ((method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) {
viewport = renderer->viewport();
viewport2.intersect(viewport);
renderer->viewport(viewport2);
target->pImpl->ctxFlag |= ContextFlag::FastTrack;
if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
P(target)->ctxFlag |= ContextFlag::FastTrack;
}
}
}
}
if (compFastTrack == Result::InsufficientCondition) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper);
if (childClipper) clips.push(trd);
trd = P(target)->update(renderer, pm, clips, 255, pFlag, false);
}
}
/* 2. Main Update */
/* 2. Clipping */
if (this->clipper) {
P(this->clipper)->ctxFlag &= ~ContextFlag::FastTrack; //reset
viewport = renderer->viewport();
/* TODO: Intersect the clipper's clipper, if both are FastTrack.
Update the subsequent clipper first and check its ctxFlag. */
if (!P(this->clipper)->clipper && (compFastTrack = _compFastTrack(renderer, this->clipper, pm, viewport)) == Result::Success) {
P(this->clipper)->ctxFlag |= ContextFlag::FastTrack;
}
if (compFastTrack == Result::InsufficientCondition) {
trd = P(this->clipper)->update(renderer, pm, clips, 255, pFlag, true);
clips.push(trd);
}
}
/* 3. Main Update */
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
renderFlag = RenderUpdateFlag::None;
opacity = MULTIPLY(opacity, this->opacity);
RenderData rd = nullptr;
RenderTransform outTransform(pTransform, rTransform);
PAINT_METHOD(rd, update(renderer, &outTransform, clips, opacity, newFlag, clipper));
/* 3. Composition Post Processing */
tr.cm = pm * tr.m;
PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
/* 4. Composition Post Processing */
if (compFastTrack == Result::Success) renderer->viewport(viewport);
else if (childClipper) clips.pop();
else if (this->clipper) clips.pop();
return rd;
}
bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking)
bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin)
{
Matrix* m = nullptr;
bool ret;
const auto& m = this->transform(origin);
//Case: No transformed, quick return!
if (!transformed || !(m = this->transform())) {
if (!transformed || identity(&m)) {
PAINT_METHOD(ret, bounds(x, y, w, h, stroking));
return ret;
}
@ -331,7 +340,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
//Compute the AABB after transformation
for (int i = 0; i < 4; i++) {
mathMultiply(&pt[i], m);
pt[i] *= m;
if (pt[i].x < x1) x1 = pt[i].x;
if (pt[i].x > x2) x2 = pt[i].x;
@ -348,6 +357,32 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
}
void Paint::Impl::reset()
{
if (clipper) {
delete(clipper);
clipper = nullptr;
}
if (compData) {
if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData);
compData = nullptr;
}
tvg::identity(&tr.m);
tr.degree = 0.0f;
tr.scale = 1.0f;
tr.overriding = false;
blendMethod = BlendMethod::Normal;
renderFlag = RenderUpdateFlag::None;
ctxFlag = ContextFlag::Invalid;
opacity = 255;
paint->id = 0;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
@ -366,36 +401,34 @@ Paint :: ~Paint()
Result Paint::rotate(float degree) noexcept
{
if (pImpl->rotate(degree)) return Result::Success;
return Result::FailedAllocation;
return Result::InsufficientCondition;
}
Result Paint::scale(float factor) noexcept
{
if (pImpl->scale(factor)) return Result::Success;
return Result::FailedAllocation;
return Result::InsufficientCondition;
}
Result Paint::translate(float x, float y) noexcept
{
if (pImpl->translate(x, y)) return Result::Success;
return Result::FailedAllocation;
return Result::InsufficientCondition;
}
Result Paint::transform(const Matrix& m) noexcept
{
if (pImpl->transform(m)) return Result::Success;
return Result::FailedAllocation;
return Result::InsufficientCondition;
}
Matrix Paint::transform() noexcept
{
auto pTransform = pImpl->transform();
if (pTransform) return *pTransform;
return {1, 0, 0, 0, 1, 0, 0, 0, 1};
return pImpl->transform();
}
@ -405,9 +438,9 @@ TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) cons
}
Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept
Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept
{
if (pImpl->bounds(x, y, w, h, transform, true)) return Result::Success;
if (pImpl->bounds(x, y, w, h, transformed, true, transformed)) return Result::Success;
return Result::InsufficientCondition;
}
@ -418,10 +451,27 @@ Paint* Paint::duplicate() const noexcept
}
Result Paint::clip(std::unique_ptr<Paint> clipper) noexcept
{
auto p = clipper.release();
if (p && p->type() != Type::Shape) {
TVGERR("RENDERER", "Clipping only supports the Shape!");
return Result::NonSupport;
}
pImpl->clip(p);
return Result::Success;
}
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
{
//TODO: remove. Keep this for the backward compatibility
if (target && method == CompositeMethod::ClipPath) return clip(std::move(target));
auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success;
delete(p);
return Result::InvalidArguments;
}
@ -433,6 +483,11 @@ CompositeMethod Paint::composite(const Paint** target) const noexcept
if (target) *target = pImpl->compData->target;
return pImpl->compData->method;
} else {
//TODO: remove. Keep this for the backward compatibility
if (pImpl->clipper) {
if (target) *target = pImpl->clipper;
return CompositeMethod::ClipPath;
}
if (target) *target = nullptr;
return CompositeMethod::None;
}
@ -456,14 +511,17 @@ uint8_t Paint::opacity() const noexcept
}
uint32_t Paint::identifier() const noexcept
TVG_DEPRECATED uint32_t Paint::identifier() const noexcept
{
return pImpl->id;
return (uint32_t) type();
}
Result Paint::blend(BlendMethod method) const noexcept
Result Paint::blend(BlendMethod method) noexcept
{
//TODO: Remove later
if (method == BlendMethod::Hue || method == BlendMethod::Saturation || method == BlendMethod::Color || method == BlendMethod::Luminosity || method == BlendMethod::HardMix) return Result::NonSupport;
if (pImpl->blendMethod != method) {
pImpl->blendMethod = method;
pImpl->renderFlag |= RenderUpdateFlag::Blend;
@ -472,11 +530,5 @@ Result Paint::blend(BlendMethod method) const noexcept
return Result::Success;
}
BlendMethod Paint::blend() const noexcept
{
return pImpl->blendMethod;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -51,17 +51,40 @@ namespace tvg
struct Paint::Impl
{
Paint* paint = nullptr;
RenderTransform* rTransform = nullptr;
Composite* compData = nullptr;
Paint* clipper = nullptr;
RenderMethod* renderer = nullptr;
BlendMethod blendMethod = BlendMethod::Normal; //uint8_t
uint8_t renderFlag = RenderUpdateFlag::None;
uint8_t ctxFlag = ContextFlag::Invalid;
uint8_t id;
uint8_t opacity = 255;
struct {
Matrix m; //input matrix
Matrix cm; //multipled parents matrix
float degree; //rotation degree
float scale; //scale factor
bool overriding; //user transform?
void update()
{
if (overriding) return;
m.e11 = 1.0f;
m.e12 = 0.0f;
m.e21 = 0.0f;
m.e22 = 1.0f;
m.e31 = 0.0f;
m.e32 = 0.0f;
m.e33 = 1.0f;
tvg::scale(&m, scale, scale);
tvg::rotate(&m, degree);
}
} tr;
BlendMethod blendMethod;
uint8_t renderFlag;
uint8_t ctxFlag;
uint8_t opacity;
uint8_t refCnt = 0; //reference count
Impl(Paint* pnt) : paint(pnt) {}
Impl(Paint* pnt) : paint(pnt)
{
reset();
}
~Impl()
{
@ -69,7 +92,7 @@ namespace tvg
if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData);
}
delete(rTransform);
if (clipper && P(clipper)->unref() == 0) delete(clipper);
if (renderer && (renderer->unref() == 0)) delete(renderer);
}
@ -87,24 +110,33 @@ namespace tvg
bool transform(const Matrix& m)
{
if (!rTransform) {
if (mathIdentity(&m)) return true;
rTransform = new RenderTransform();
if (!rTransform) return false;
}
rTransform->override(m);
if (&tr.m != &m) tr.m = m;
tr.overriding = true;
renderFlag |= RenderUpdateFlag::Transform;
return true;
}
Matrix* transform()
Matrix& transform(bool origin = false)
{
if (rTransform) {
rTransform->update();
return &rTransform->m;
//update transform
if (renderFlag & RenderUpdateFlag::Transform) tr.update();
if (origin) return tr.cm;
return tr.m;
}
void clip(Paint* clp)
{
if (this->clipper) {
P(this->clipper)->unref();
if (this->clipper != clp && P(this->clipper)->refCnt == 0) {
delete(this->clipper);
}
}
return nullptr;
this->clipper = clp;
if (!clp) return;
P(clipper)->ref();
}
bool composite(Paint* source, Paint* target, CompositeMethod method)
@ -139,10 +171,11 @@ namespace tvg
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, bool stroking);
RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin = false);
RenderData update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod* renderer);
Paint* duplicate();
Paint* duplicate(Paint* ret = nullptr);
void reset();
};
}

View File

@ -23,6 +23,7 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include "tvgPaint.h"
#include "tvgPicture.h"
/************************************************************************/
@ -76,9 +77,11 @@ bool Picture::Impl::needComposition(uint8_t opacity)
bool Picture::Impl::render(RenderMethod* renderer)
{
bool ret = false;
renderer->blend(PP(picture)->blendMethod);
if (surface) return renderer->renderImage(rd);
else if (paint) {
Compositor* cmp = nullptr;
RenderCompositor* cmp = nullptr;
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, 255);
@ -107,21 +110,6 @@ RenderRegion Picture::Impl::bounds(RenderMethod* renderer)
}
RenderTransform Picture::Impl::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);
}
Result Picture::Impl::load(ImageLoader* loader)
{
//Same resource has been loaded.
@ -150,7 +138,6 @@ Result Picture::Impl::load(ImageLoader* loader)
Picture::Picture() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_PICTURE;
}
@ -166,9 +153,15 @@ unique_ptr<Picture> Picture::gen() noexcept
}
uint32_t Picture::identifier() noexcept
TVG_DEPRECATED uint32_t Picture::identifier() noexcept
{
return TVG_CLASS_ID_PICTURE;
return (uint32_t) Type::Picture;
}
Type Picture::type() const noexcept
{
return Type::Picture;
}
@ -218,20 +211,26 @@ Result Picture::size(float* w, float* h) const noexcept
}
Result Picture::mesh(const Polygon* triangles, uint32_t triangleCnt) noexcept
const Paint* Picture::paint(uint32_t id) noexcept
{
if (!triangles && triangleCnt > 0) return Result::InvalidArguments;
if (triangles && triangleCnt == 0) return Result::InvalidArguments;
struct Value
{
uint32_t id;
const Paint* ret;
} value = {id, nullptr};
pImpl->mesh(triangles, triangleCnt);
return Result::Success;
}
auto cb = [](const tvg::Paint* paint, void* data) -> bool
{
auto p = static_cast<Value*>(data);
if (p->id == paint->id) {
p->ret = paint;
return false;
}
return true;
};
uint32_t Picture::mesh(const Polygon** triangles) const noexcept
{
if (triangles) *triangles = pImpl->rm.triangles;
return pImpl->rm.triangleCnt;
tvg::Accessor::gen()->set(this, cb, &value);
return value.ret;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -63,15 +63,13 @@ struct Picture::Impl
ImageLoader* loader = nullptr;
Paint* paint = nullptr; //vector picture uses
Surface* surface = nullptr; //bitmap picture uses
RenderSurface* surface = nullptr; //bitmap picture uses
RenderData rd = nullptr; //engine data
float w = 0, h = 0;
RenderMesh rm; //mesh data
Picture* picture = nullptr;
bool resizing = false;
bool needComp = false; //need composition
RenderTransform resizeTransform(const RenderTransform* pTransform);
bool needComposition(uint8_t opacity);
bool render(RenderMethod* renderer);
bool size(float w, float h);
@ -93,57 +91,37 @@ struct Picture::Impl
delete(paint);
}
RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{
auto flag = load();
auto flag = static_cast<RenderUpdateFlag>(pFlag | load());
if (surface) {
auto transform = resizeTransform(pTransform);
rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag));
if (flag == RenderUpdateFlag::None) return rd;
//Overriding Transformation by the desired image size
auto sx = w / loader->w;
auto sy = h / loader->h;
auto scale = sx < sy ? sx : sy;
auto m = transform * Matrix{scale, 0, 0, 0, scale, 0, 0, 0, 1};
rd = renderer->prepare(surface, rd, m, clips, opacity, flag);
} else if (paint) {
if (resizing) {
loader->resize(paint, w, h);
resizing = false;
}
needComp = needComposition(opacity) ? true : false;
rd = paint->pImpl->update(renderer, pTransform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return rd;
}
bool bounds(float* x, float* y, float* w, float* h, bool stroking)
{
if (rm.triangleCnt > 0) {
auto triangles = rm.triangles;
auto min = triangles[0].vertex[0].pt;
auto max = triangles[0].vertex[0].pt;
for (uint32_t i = 0; i < rm.triangleCnt; ++i) {
if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x;
else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x;
if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y;
else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y;
if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x;
else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x;
if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y;
else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y;
if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x;
else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x;
if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y;
else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.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;
} else {
if (x) *x = 0;
if (y) *y = 0;
if (w) *w = this->w;
if (h) *h = this->h;
}
if (x) *x = 0;
if (y) *y = 0;
if (w) *w = this->w;
if (h) *h = this->h;
return true;
}
@ -178,31 +156,21 @@ struct Picture::Impl
return load(loader);
}
void mesh(const Polygon* triangles, const uint32_t triangleCnt)
Paint* duplicate(Paint* ret)
{
if (triangles && triangleCnt > 0) {
this->rm.triangleCnt = triangleCnt;
this->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * triangleCnt);
memcpy(this->rm.triangles, triangles, sizeof(Polygon) * triangleCnt);
} else {
free(this->rm.triangles);
this->rm.triangles = nullptr;
this->rm.triangleCnt = 0;
}
}
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
Paint* duplicate()
{
load();
auto ret = Picture::gen().release();
auto dup = ret->pImpl;
auto picture = Picture::gen().release();
auto dup = picture->pImpl;
if (paint) dup->paint = paint->duplicate();
if (loader) {
dup->loader = loader;
++dup->loader->sharing;
PP(picture)->renderFlag |= RenderUpdateFlag::Image;
}
dup->surface = surface;
@ -210,13 +178,7 @@ struct Picture::Impl
dup->h = h;
dup->resizing = resizing;
if (rm.triangleCnt > 0) {
dup->rm.triangleCnt = rm.triangleCnt;
dup->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * rm.triangleCnt);
memcpy(dup->rm.triangles, rm.triangles, sizeof(Polygon) * rm.triangleCnt);
}
return ret;
return picture;
}
Iterator* iterator()

View File

@ -35,33 +35,17 @@
/* External Class Implementation */
/************************************************************************/
void RenderTransform::override(const Matrix& m)
uint32_t RenderMethod::ref()
{
this->m = m;
overriding = true;
ScopedLock lock(key);
return (++refCnt);
}
void RenderTransform::update()
uint32_t RenderMethod::unref()
{
if (overriding) return;
mathIdentity(&m);
mathScale(&m, scale, scale);
if (!mathZero(degree)) mathRotate(&m, degree);
mathTranslate(&m, x, y);
}
RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs)
{
if (lhs && rhs) m = mathMultiply(&lhs->m, &rhs->m);
else if (lhs) m = lhs->m;
else if (rhs) m = rhs->m;
else mathIdentity(&m);
ScopedLock lock(key);
return (--refCnt);
}

View File

@ -26,6 +26,8 @@
#ifndef _TVG_RENDER_H_
#define _TVG_RENDER_H_
#include <math.h>
#include <cstdarg>
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgLock.h"
@ -38,9 +40,8 @@ using pixel_t = uint32_t;
enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255};
struct Surface;
enum ColorSpace
//TODO: Move this in public header unifying with SwCanvas::Colorspace
enum ColorSpace : uint8_t
{
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.
@ -50,7 +51,7 @@ enum ColorSpace
Unsupported //TODO: Change to the default, At the moment, we put it in the last to align with SwCanvas::Colorspace.
};
struct Surface
struct RenderSurface
{
union {
pixel_t* data = nullptr; //system based data pointer
@ -64,11 +65,11 @@ struct Surface
uint8_t channelSize = 0;
bool premultiplied = false; //Alpha-premultiplied
Surface()
RenderSurface()
{
}
Surface(const Surface* rhs)
RenderSurface(const RenderSurface* rhs)
{
data = rhs->data;
stride = rhs->stride;
@ -82,21 +83,10 @@ struct Surface
};
struct Compositor
struct RenderCompositor
{
CompositeMethod method;
uint8_t opacity;
};
struct RenderMesh
{
Polygon* triangles = nullptr;
uint32_t triangleCnt = 0;
~RenderMesh()
{
free(triangles);
}
uint8_t opacity;
};
struct RenderRegion
@ -106,29 +96,13 @@ struct RenderRegion
void intersect(const RenderRegion& rhs);
void add(const RenderRegion& rhs);
bool operator==(const RenderRegion& rhs)
bool operator==(const RenderRegion& rhs) const
{
if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true;
return false;
}
};
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?
void update();
void override(const Matrix& m);
RenderTransform() {}
RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs);
};
struct RenderStroke
{
float width = 0.0f;
@ -145,9 +119,60 @@ struct RenderStroke
struct {
float begin = 0.0f;
float end = 1.0f;
bool individual = false;
bool simultaneous = true;
} trim;
void operator=(const RenderStroke& rhs)
{
width = rhs.width;
memcpy(color, rhs.color, sizeof(color));
delete(fill);
if (rhs.fill) fill = rhs.fill->duplicate();
else fill = nullptr;
free(dashPattern);
if (rhs.dashCnt > 0) {
dashPattern = static_cast<float*>(malloc(sizeof(float) * rhs.dashCnt));
memcpy(dashPattern, rhs.dashPattern, sizeof(float) * rhs.dashCnt);
} else {
dashPattern = nullptr;
}
dashCnt = rhs.dashCnt;
miterlimit = rhs.miterlimit;
cap = rhs.cap;
join = rhs.join;
strokeFirst = rhs.strokeFirst;
trim = rhs.trim;
}
bool strokeTrim(float& begin, float& end) const
{
begin = trim.begin;
end = trim.end;
if (fabsf(end - begin) >= 1.0f) {
begin = 0.0f;
end = 1.0f;
return false;
}
auto loop = true;
if (begin > 1.0f && end > 1.0f) loop = false;
if (begin < 0.0f && end < 0.0f) loop = false;
if (begin >= 0.0f && begin <= 1.0f && end >= 0.0f && end <= 1.0f) loop = false;
if (begin > 1.0f) begin -= 1.0f;
if (begin < 0.0f) begin += 1.0f;
if (end > 1.0f) end -= 1.0f;
if (end < 0.0f) end += 1.0f;
if ((loop && begin < end) || (!loop && begin > end)) std::swap(begin, end);
return true;
}
~RenderStroke()
{
free(dashPattern);
@ -192,7 +217,7 @@ struct RenderShape
{
if (!stroke) return false;
if (stroke->trim.begin == 0.0f && stroke->trim.end == 1.0f) return false;
if (stroke->trim.begin == 1.0f && stroke->trim.end == 0.0f) return false;
if (fabsf(stroke->trim.end - stroke->trim.begin) >= 1.0f) return false;
return true;
}
@ -237,11 +262,45 @@ struct RenderShape
float strokeMiterlimit() const
{
if (!stroke) return 4.0f;
return stroke->miterlimit;;
}
};
struct RenderEffect
{
RenderData rd = nullptr;
RenderRegion extend = {0, 0, 0, 0};
SceneEffect type;
bool invalid = false;
virtual ~RenderEffect()
{
free(rd);
}
};
struct RenderEffectGaussian : RenderEffect
{
float sigma;
uint8_t direction; //0: both, 1: horizontal, 2: vertical
uint8_t border; //0: duplicate, 1: wrap
uint8_t quality; //0 ~ 100 (optional)
static RenderEffectGaussian* gen(va_list& args)
{
auto sigma = (float) va_arg(args, double);
if (sigma <= 0) return nullptr;
auto inst = new RenderEffectGaussian;
inst->sigma = sigma;
inst->direction = std::min(va_arg(args, int), 2);
inst->border = std::min(va_arg(args, int), 1);
inst->quality = std::min(va_arg(args, int), 100);
inst->type = SceneEffect::GaussianBlur;
return inst;
}
};
class RenderMethod
{
private:
@ -249,22 +308,12 @@ private:
Key key;
public:
uint32_t ref()
{
ScopedLock lock(key);
return (++refCnt);
}
uint32_t unref()
{
ScopedLock lock(key);
return (--refCnt);
}
uint32_t ref();
uint32_t unref();
virtual ~RenderMethod() {}
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
virtual RenderData prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual bool preRender() = 0;
virtual bool renderShape(RenderData data) = 0;
virtual bool renderImage(RenderData data) = 0;
@ -275,14 +324,17 @@ public:
virtual bool viewport(const RenderRegion& vp) = 0;
virtual bool blend(BlendMethod method) = 0;
virtual ColorSpace colorSpace() = 0;
virtual const Surface* mainSurface() = 0;
virtual const RenderSurface* mainSurface() = 0;
virtual bool clear() = 0;
virtual bool sync() = 0;
virtual Compositor* target(const RenderRegion& region, ColorSpace cs) = 0;
virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
virtual bool endComposite(Compositor* cmp) = 0;
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0;
virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
virtual bool endComposite(RenderCompositor* cmp) = 0;
virtual bool prepare(RenderEffect* effect) = 0;
virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect) = 0;
};
static inline bool MASK_REGION_MERGING(CompositeMethod method)
@ -298,6 +350,8 @@ static inline bool MASK_REGION_MERGING(CompositeMethod method)
//these might expand the rendering region
case CompositeMethod::AddMask:
case CompositeMethod::DifferenceMask:
case CompositeMethod::LightenMask:
case CompositeMethod::DarkenMask:
return true;
default:
TVGERR("RENDERER", "Unsupported Composite Method! = %d", (int)method);
@ -331,6 +385,8 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod* renderer, Composi
case CompositeMethod::DifferenceMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
case CompositeMethod::LightenMask:
case CompositeMethod::DarkenMask:
return ColorSpace::Grayscale8;
//TODO: Optimize Luma/InvLuma colorspace to Grayscale8
case CompositeMethod::LumaMask:
@ -347,7 +403,6 @@ static inline uint8_t MULTIPLY(uint8_t c, uint8_t a)
return (((c) * (a) + 0xff) >> 8);
}
}
#endif //_TVG_RENDER_H_

View File

@ -163,7 +163,7 @@ Result Saver::save(unique_ptr<Animation> animation, const string& path, uint32_t
//animation holds the picture, it must be 1 at the bottom.
auto remove = PP(a->picture())->refCnt <= 1 ? true : false;
if (mathZero(a->totalFrame())) {
if (tvg::zero(a->totalFrame())) {
if (remove) delete(a);
return Result::InsufficientCondition;
}

View File

@ -23,15 +23,32 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include <cstdarg>
#include "tvgScene.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
Result Scene::Impl::resetEffects()
{
if (effects) {
for (auto e = effects->begin(); e < effects->end(); ++e) {
delete(*e);
}
delete(effects);
effects = nullptr;
}
return Result::Success;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Scene::Scene() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_SCENE;
}
@ -47,9 +64,15 @@ unique_ptr<Scene> Scene::gen() noexcept
}
uint32_t Scene::identifier() noexcept
TVG_DEPRECATED uint32_t Scene::identifier() noexcept
{
return TVG_CLASS_ID_SCENE;
return (uint32_t) Type::Scene;
}
Type Scene::type() const noexcept
{
return Type::Scene;
}
@ -83,5 +106,32 @@ list<Paint*>& Scene::paints() noexcept
return pImpl->paints;
}
Result Scene::push(SceneEffect effect, ...) noexcept
{
if (effect == SceneEffect::ClearAll) return pImpl->resetEffects();
if (!pImpl->effects) pImpl->effects = new Array<RenderEffect*>;
va_list args;
va_start(args, effect);
RenderEffect* re = nullptr;
switch (effect) {
case SceneEffect::GaussianBlur: {
re = RenderEffectGaussian::gen(args);
break;
}
default: break;
}
if (!re) return Result::InvalidArguments;
pImpl->effects->push(re);
return Result::Success;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -26,10 +26,9 @@
#ifndef _TVG_SCENE_H_
#define _TVG_SCENE_H_
#include <float.h>
#include "tvgMath.h"
#include "tvgPaint.h"
struct SceneIterator : Iterator
{
list<Paint*>* paints;
@ -64,8 +63,10 @@ struct Scene::Impl
list<Paint*> paints;
RenderData rd = nullptr;
Scene* scene = nullptr;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
Array<RenderEffect*>* effects = nullptr;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Impl(Scene* s) : scene(s)
{
@ -73,6 +74,8 @@ struct Scene::Impl
~Impl()
{
resetEffects();
for (auto paint : paints) {
if (P(paint)->unref() == 0) delete(paint);
}
@ -86,12 +89,15 @@ struct Scene::Impl
{
if (opacity == 0 || paints.empty()) return false;
//post effects requires composition
if (effects) return true;
//Masking may require composition (even if opacity == 255)
auto compMethod = scene->composite(nullptr);
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true;
//Blending may require composition (even if opacity == 255)
if (scene->blend() != BlendMethod::Normal) return true;
if (PP(scene)->blendMethod != BlendMethod::Normal) return true;
//Half translucent requires intermediate composition.
if (opacity == 255) return false;
@ -99,40 +105,35 @@ struct Scene::Impl
//If scene has several children or only scene, it may require composition.
//OPTIMIZE: the bitmap type of the picture would not need the composition.
//OPTIMIZE: a single paint of a scene would not need the composition.
if (paints.size() == 1 && paints.front()->identifier() == TVG_CLASS_ID_SHAPE) return false;
if (paints.size() == 1 && paints.front()->type() == Type::Shape) return false;
return true;
}
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper)
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper)
{
this->vport = renderer->viewport();
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermediate composition with that opacity value. */
this->opacity = opacity;
opacity = 255;
}
if (clipper) {
Array<RenderData> rds(paints.size());
for (auto paint : paints) {
rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true));
}
rd = renderer->prepare(rds, rd, transform, clips, opacity, flag);
return rd;
} else {
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return nullptr;
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return nullptr;
}
bool render(RenderMethod* renderer)
{
Compositor* cmp = nullptr;
RenderCompositor* cmp = nullptr;
auto ret = true;
renderer->blend(PP(scene)->blendMethod);
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
@ -142,7 +143,15 @@ struct Scene::Impl
ret &= paint->pImpl->render(renderer);
}
if (cmp) renderer->endComposite(cmp);
if (cmp) {
//Apply post effects if any.
if (effects) {
for (auto e = effects->begin(); e < effects->end(); ++e) {
renderer->effect(cmp, *e);
}
}
renderer->endComposite(cmp);
}
return ret;
}
@ -166,7 +175,23 @@ struct Scene::Impl
if (y2 < region.y + region.h) y2 = (region.y + region.h);
}
return {x1, y1, (x2 - x1), (y2 - y1)};
//Extends the render region if post effects require
int32_t ex = 0, ey = 0, ew = 0, eh = 0;
if (effects) {
for (auto e = effects->begin(); e < effects->end(); ++e) {
auto effect = *e;
if (effect->rd || renderer->prepare(effect)) {
ex = std::min(ex, effect->extend.x);
ey = std::min(ey, effect->extend.y);
ew = std::max(ew, effect->extend.w);
eh = std::max(eh, effect->extend.h);
}
}
}
auto ret = RenderRegion{x1 + ex, y1 + ey, (x2 - x1) + ew, (y2 - y1) + eh};
ret.intersect(this->vport);
return ret;
}
bool bounds(float* px, float* py, float* pw, float* ph, bool stroking)
@ -201,10 +226,12 @@ struct Scene::Impl
return true;
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
auto ret = Scene::gen().release();
auto dup = ret->pImpl;
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
auto scene = Scene::gen().release();
auto dup = scene->pImpl;
for (auto paint : paints) {
auto cdup = paint->duplicate();
@ -212,7 +239,9 @@ struct Scene::Impl
dup->paints.push_back(cdup);
}
return ret;
if (effects) TVGERR("RENDERER", "TODO: Duplicate Effects?");
return scene;
}
void clear(bool free)
@ -227,6 +256,8 @@ struct Scene::Impl
{
return new SceneIterator(&paints);
}
Result resetEffects();
};
#endif //_TVG_SCENE_H_

View File

@ -37,7 +37,6 @@
Shape :: Shape() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_SHAPE;
}
@ -55,7 +54,13 @@ unique_ptr<Shape> Shape::gen() noexcept
uint32_t Shape::identifier() noexcept
{
return TVG_CLASS_ID_SHAPE;
return (uint32_t) Type::Shape;
}
Type Shape::type() const noexcept
{
return Type::Shape;
}
@ -91,6 +96,8 @@ Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point*
pImpl->grow(cmdCnt, ptsCnt);
pImpl->append(cmds, cmdCnt, pts, ptsCnt);
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -107,6 +114,8 @@ Result Shape::lineTo(float x, float y) noexcept
{
pImpl->lineTo(x, y);
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -115,6 +124,8 @@ Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float
{
pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y);
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -123,6 +134,8 @@ Result Shape::close() noexcept
{
pImpl->close();
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -140,17 +153,20 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept
pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy);
pImpl->close();
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept
{
//just circle
if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius);
const float arcPrecision = 1e-5f;
startAngle = mathDeg2Rad(startAngle);
sweep = mathDeg2Rad(sweep);
startAngle = deg2rad(startAngle);
sweep = deg2rad(sweep);
auto nCurves = static_cast<int>(fabsf(sweep / MATH_PI2));
if (fabsf(sweep / MATH_PI2) - nCurves > arcPrecision) ++nCurves;
@ -199,6 +215,8 @@ Result Shape::appendArc(float cx, float cy, float radius, float startAngle, floa
if (pie) pImpl->close();
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -237,6 +255,8 @@ Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry)
pImpl->close();
}
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -290,16 +310,14 @@ const Fill* Shape::fill() const noexcept
Result Shape::order(bool strokeFirst) noexcept
{
if (!pImpl->strokeFirst(strokeFirst)) return Result::FailedAllocation;
pImpl->strokeFirst(strokeFirst);
return Result::Success;
}
Result Shape::stroke(float width) noexcept
{
if (!pImpl->strokeWidth(width)) return Result::FailedAllocation;
pImpl->strokeWidth(width);
return Result::Success;
}
@ -312,8 +330,7 @@ float Shape::strokeWidth() const noexcept
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;
pImpl->strokeColor(r, g, b, a);
return Result::Success;
}
@ -352,27 +369,25 @@ uint32_t Shape::strokeDash(const float** dashPattern) const noexcept
Result Shape::stroke(StrokeCap cap) noexcept
{
if (!pImpl->strokeCap(cap)) return Result::FailedAllocation;
pImpl->strokeCap(cap);
return Result::Success;
}
Result Shape::stroke(StrokeJoin join) noexcept
{
if (!pImpl->strokeJoin(join)) return Result::FailedAllocation;
pImpl->strokeJoin(join);
return Result::Success;
}
Result Shape::strokeMiterlimit(float miterlimit) noexcept
{
// https://www.w3.org/TR/SVG2/painting.html#LineJoin
// - A negative value for stroke-miterlimit must be treated as an illegal value.
if (miterlimit < 0.0f) return Result::NonSupport;
if (miterlimit < 0.0f) return Result::InvalidArguments;
// TODO Find out a reasonable max value.
if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation;
pImpl->strokeMiterlimit(miterlimit);
return Result::Success;
}
@ -388,12 +403,20 @@ StrokeJoin Shape::strokeJoin() const noexcept
return pImpl->rs.strokeJoin();
}
float Shape::strokeMiterlimit() const noexcept
{
return pImpl->rs.strokeMiterlimit();
}
Result Shape::strokeTrim(float begin, float end, bool simultaneous) noexcept
{
pImpl->strokeTrim(begin, end, simultaneous);
return Result::Success;
}
Result Shape::fill(FillRule r) noexcept
{
pImpl->rs.rule = r;

View File

@ -37,6 +37,7 @@ struct Shape::Impl
RenderData rd = nullptr; //engine data
Shape* shape;
uint8_t flag = RenderUpdateFlag::None;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
@ -53,14 +54,18 @@ struct Shape::Impl
bool render(RenderMethod* renderer)
{
Compositor* cmp = nullptr;
bool ret;
if (!rd) return false;
RenderCompositor* cmp = nullptr;
renderer->blend(PP(shape)->blendMethod);
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
}
ret = renderer->renderShape(rd);
auto ret = renderer->renderShape(rd);
if (cmp) renderer->endComposite(cmp);
return ret;
}
@ -81,7 +86,7 @@ struct Shape::Impl
auto method = shape->composite(&target);
if (!target || method == CompositeMethod::ClipPath) return false;
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) {
if (target->identifier() == TVG_CLASS_ID_SHAPE) {
if (target->type() == Type::Shape) {
auto shape = static_cast<const Shape*>(target);
if (!shape->fill()) {
uint8_t r, g, b, a;
@ -98,8 +103,10 @@ struct Shape::Impl
return true;
}
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (static_cast<RenderUpdateFlag>(pFlag | flag) == RenderUpdateFlag::None) return rd;
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermediate composition with that opacity value. */
@ -114,6 +121,7 @@ struct Shape::Impl
RenderRegion bounds(RenderMethod* renderer)
{
if (!rd) return {0, 0, 0, 0};
return renderer->region(rd);
}
@ -170,24 +178,18 @@ struct Shape::Impl
memcpy(rs.path.pts.end(), pts, sizeof(Point) * ptsCnt);
rs.path.cmds.count += cmdCnt;
rs.path.pts.count += ptsCnt;
flag |= RenderUpdateFlag::Path;
}
void moveTo(float x, float y)
{
rs.path.cmds.push(PathCommand::MoveTo);
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void lineTo(float x, float y)
{
rs.path.cmds.push(PathCommand::LineTo);
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
@ -196,8 +198,6 @@ struct Shape::Impl
rs.path.pts.push({cx1, cy1});
rs.path.pts.push({cx2, cy2});
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void close()
@ -206,64 +206,66 @@ struct Shape::Impl
if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return;
rs.path.cmds.push(PathCommand::Close);
flag |= RenderUpdateFlag::Path;
}
bool strokeWidth(float width)
void strokeWidth(float width)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->width = width;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeTrim(float begin, float end, bool individual)
void strokeTrim(float begin, float end, bool simultaneous)
{
if (!rs.stroke) {
if (begin == 0.0f && end == 1.0f) return true;
if (begin == 0.0f && end == 1.0f) return;
rs.stroke = new RenderStroke();
}
if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end)) return true;
if (tvg::equal(rs.stroke->trim.begin, begin) && tvg::equal(rs.stroke->trim.end, end) &&
rs.stroke->trim.simultaneous == simultaneous) return;
rs.stroke->trim.begin = begin;
rs.stroke->trim.end = end;
rs.stroke->trim.individual = individual;
rs.stroke->trim.simultaneous = simultaneous;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeCap(StrokeCap cap)
bool strokeTrim(float* begin, float* end)
{
if (rs.stroke) {
if (begin) *begin = rs.stroke->trim.begin;
if (end) *end = rs.stroke->trim.end;
return rs.stroke->trim.simultaneous;
} else {
if (begin) *begin = 0.0f;
if (end) *end = 1.0f;
return false;
}
}
void strokeCap(StrokeCap cap)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->cap = cap;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeJoin(StrokeJoin join)
void strokeJoin(StrokeJoin join)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->join = join;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeMiterlimit(float miterlimit)
void strokeMiterlimit(float miterlimit)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->miterlimit = miterlimit;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
void strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->fill) {
@ -278,8 +280,6 @@ struct Shape::Impl
rs.stroke->color[3] = a;
flag |= RenderUpdateFlag::Stroke;
return true;
}
Result strokeFill(unique_ptr<Fill> f)
@ -290,6 +290,7 @@ struct Shape::Impl
if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill);
rs.stroke->fill = p;
rs.stroke->color[3] = 0;
flag |= RenderUpdateFlag::Stroke;
flag |= RenderUpdateFlag::GradientStroke;
@ -338,13 +339,11 @@ struct Shape::Impl
return rs.stroke->strokeFirst;
}
bool strokeFirst(bool strokeFirst)
void strokeFirst(bool strokeFirst)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->strokeFirst = strokeFirst;
flag |= RenderUpdateFlag::Stroke;
return true;
}
void update(RenderUpdateFlag flag)
@ -352,47 +351,56 @@ struct Shape::Impl
this->flag |= flag;
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
auto ret = Shape::gen().release();
auto dup = ret->pImpl;
auto shape = static_cast<Shape*>(ret);
if (shape) shape->reset();
else shape = Shape::gen().release();
auto dup = shape->pImpl;
delete(dup->rs.fill);
//Default Properties
dup->flag = RenderUpdateFlag::All;
dup->rs.rule = rs.rule;
//Color
memcpy(dup->rs.color, rs.color, sizeof(rs.color));
dup->flag = RenderUpdateFlag::Color;
//Path
if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) {
dup->rs.path.cmds = rs.path.cmds;
dup->rs.path.pts = rs.path.pts;
dup->flag |= RenderUpdateFlag::Path;
}
dup->rs.path.cmds.push(rs.path.cmds);
dup->rs.path.pts.push(rs.path.pts);
//Stroke
if (rs.stroke) {
dup->rs.stroke = new RenderStroke();
if (!dup->rs.stroke) dup->rs.stroke = new RenderStroke;
*dup->rs.stroke = *rs.stroke;
memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color));
if (rs.stroke->dashCnt > 0) {
dup->rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * rs.stroke->dashCnt));
memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt);
}
if (rs.stroke->fill) {
dup->rs.stroke->fill = rs.stroke->fill->duplicate();
dup->flag |= RenderUpdateFlag::GradientStroke;
}
dup->flag |= RenderUpdateFlag::Stroke;
} else {
delete(dup->rs.stroke);
dup->rs.stroke = nullptr;
}
//Fill
if (rs.fill) {
dup->rs.fill = rs.fill->duplicate();
dup->flag |= RenderUpdateFlag::Gradient;
}
if (rs.fill) dup->rs.fill = rs.fill->duplicate();
else dup->rs.fill = nullptr;
return ret;
return shape;
}
void reset()
{
PP(shape)->reset();
rs.path.cmds.clear();
rs.path.pts.clear();
rs.color[3] = 0;
rs.rule = FillRule::Winding;
delete(rs.stroke);
rs.stroke = nullptr;
delete(rs.fill);
rs.fill = nullptr;
}
Iterator* iterator()

View File

@ -186,7 +186,7 @@ float strToFloat(const char *nPtr, char **endPtr)
auto scale = 1.0f;
while (exponentPart >= 8U) {
scale *= 1E8f;
scale *= 1E8;
exponentPart -= 8U;
}
while (exponentPart > 0U) {
@ -210,16 +210,6 @@ error:
return 0.0f;
}
int str2int(const char* str, size_t n)
{
int ret = 0;
for(size_t i = 0; i < n; ++i) {
ret = ret * 10 + (str[i] - '0');
}
return ret;
}
char* strDuplicate(const char *str, size_t n)
{
auto len = strlen(str);
@ -232,6 +222,14 @@ char* strDuplicate(const char *str, size_t n)
return (char *) memcpy(ret, str, n);
}
char* strAppend(char* lhs, const char* rhs, size_t n)
{
if (!rhs) return lhs;
if (!lhs) return strDuplicate(rhs, n);
lhs = (char*)realloc(lhs, strlen(lhs) + n + 1);
return strncat(lhs, rhs, n);
}
char* strDirname(const char* path)
{
const char *ptr = strrchr(path, '/');

View File

@ -32,8 +32,8 @@ namespace tvg
{
float strToFloat(const char *nPtr, char **endPtr); //convert to float
int str2int(const char* str, size_t n); //convert to integer
char* strDuplicate(const char *str, size_t n); //copy the string
char* strAppend(char* lhs, const char* rhs, size_t n); //append the rhs to the lhs
char* strDirname(const char* path); //return the full directory name
}

View File

@ -180,7 +180,8 @@ static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengt
else if (strstr(str, "%")) {
if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0f) * svgParse->global.h;
else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0f) * svgParse->global.w;
else //if other then it's radius
else if (type == SvgParserLengthType::Diagonal) parsedValue = (sqrtf(powf(svgParse->global.w, 2) + powf(svgParse->global.h, 2)) / sqrtf(2.0f)) * (parsedValue / 100.0f);
else //if other than it's radius
{
float max = svgParse->global.w;
if (max < svgParse->global.h)
@ -202,7 +203,7 @@ static float _gradientToFloat(const SvgParser* svgParse, const char* str, bool&
isPercentage = false;
if (strstr(str, "%")) {
parsedValue = parsedValue / 100.0f;
parsedValue = parsedValue / 100.0;
isPercentage = true;
}
else if (strstr(str, "cm")) parsedValue *= PX_PER_CM;
@ -227,7 +228,7 @@ static float _toOffset(const char* str)
auto ptr = strstr(str, "%");
if (ptr) {
parsedValue = parsedValue / 100.0f;
parsedValue = parsedValue / 100.0;
if (end != ptr || (end + 1) != strEnd) return 0;
} else if (end != strEnd) return 0;
@ -591,15 +592,20 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re
float _red = 0, _green = 0, _blue = 0;
uint32_t i = 0;
if (mathZero(saturation)) _red = _green = _blue = brightness;
while (hue < 0) hue += 360.0f;
hue = fmod(hue, 360.0f);
saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f;
brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f;
if (tvg::zero(saturation)) _red = _green = _blue = brightness;
else {
if (mathEqual(hue, 360.0)) hue = 0.0f;
if (tvg::equal(hue, 360.0)) hue = 0.0f;
hue /= 60.0f;
v = (brightness <= 0.5f) ? (brightness * (1.0f + saturation)) : (brightness + saturation - (brightness * saturation));
p = brightness + brightness - v;
if (!mathZero(v)) sv = (v - p) / v;
if (!tvg::zero(v)) sv = (v - p) / v;
else sv = 0;
i = static_cast<uint8_t>(hue);
@ -650,9 +656,9 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re
}
}
*red = static_cast<uint8_t>(roundf(_red * 255.0f));
*green = static_cast<uint8_t>(roundf(_green * 255.0f));
*blue = static_cast<uint8_t>(roundf(_blue * 255.0f));
*red = (uint8_t)nearbyint(_red * 255.0f);
*green = (uint8_t)nearbyint(_green * 255.0f);
*blue = (uint8_t)nearbyint(_blue * 255.0f);
return true;
}
@ -713,15 +719,15 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
return true;
} else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') {
float th, ts, tb;
const char *content, *hue, *saturation, *brightness;
content = str + 4;
content = _skipSpace(content, nullptr);
const char* content = _skipSpace(str + 4, nullptr);
const char* hue = nullptr;
if (_parseNumber(&content, &hue, &th) && hue) {
th = float(uint32_t(th) % 360);
const char* saturation = nullptr;
hue = _skipSpace(hue, nullptr);
hue = (char*)_skipComma(hue);
hue = _skipSpace(hue, nullptr);
if (_parseNumber(&hue, &saturation, &ts) && saturation && *saturation == '%') {
const char* brightness = nullptr;
ts /= 100.0f;
saturation = _skipSpace(saturation + 1, nullptr);
saturation = (char*)_skipComma(saturation);
@ -843,31 +849,31 @@ static Matrix* _parseTransformationMatrix(const char* value)
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};
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else if (state == MatrixState::Translate) {
if (ptCount == 1) {
Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1};
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else if (ptCount == 2) {
Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1};
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else goto error;
} else if (state == MatrixState::Rotate) {
//Transform to signed.
points[0] = fmodf(points[0], 360.0f);
if (points[0] < 0) points[0] += 360.0f;
auto c = cosf(mathDeg2Rad(points[0]));
auto s = sinf(mathDeg2Rad(points[0]));
auto c = cosf(deg2rad(points[0]));
auto s = sinf(deg2rad(points[0]));
if (ptCount == 1) {
Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else if (ptCount == 3) {
Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else {
goto error;
}
@ -877,17 +883,17 @@ static Matrix* _parseTransformationMatrix(const char* value)
auto sy = sx;
if (ptCount == 2) sy = points[1];
Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else if (state == MatrixState::SkewX) {
if (ptCount != 1) goto error;
auto deg = tanf(mathDeg2Rad(points[0]));
auto deg = tanf(deg2rad(points[0]));
Matrix tmp = { 1, deg, 0, 0, 1, 0, 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
} else if (state == MatrixState::SkewY) {
if (ptCount != 1) goto error;
auto deg = tanf(mathDeg2Rad(points[0]));
auto deg = tanf(deg2rad(points[0]));
Matrix tmp = { 1, 0, 0, deg, 1, 0, 0, 0, 1 };
*matrix = mathMultiply(matrix, &tmp);
*matrix *= tmp;
}
}
return matrix;
@ -1064,7 +1070,7 @@ static void _handleStrokeDashOffsetAttr(SvgLoaderData* loader, SvgNode* node, co
static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
{
node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Width);
node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Diagonal);
}
@ -1612,7 +1618,7 @@ static constexpr struct
} 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)}
{"r", SvgParserLengthType::Diagonal, sizeof("r"), offsetof(SvgCircleNode, r)}
};
@ -2122,7 +2128,73 @@ static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const cha
}
//TODO: Implement 'text' primitive
static constexpr struct
{
const char* tag;
SvgParserLengthType type;
int sz;
size_t offset;
} textTags[] = {
{"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgTextNode, x)},
{"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgTextNode, y)},
{"font-size", SvgParserLengthType::Vertical, sizeof("font-size"), offsetof(SvgTextNode, fontSize)}
};
static bool _attrParseTextNode(void* data, const char* key, const char* value)
{
SvgLoaderData* loader = (SvgLoaderData*)data;
SvgNode* node = loader->svgParse->node;
SvgTextNode* text = &(node->node.text);
unsigned char* array;
int sz = strlen(key);
array = (unsigned char*)text;
for (unsigned int i = 0; i < sizeof(textTags) / sizeof(textTags[0]); i++) {
if (textTags[i].sz - 1 == sz && !strncmp(textTags[i].tag, key, sz)) {
*((float*)(array + textTags[i].offset)) = _toFloat(loader->svgParse, value, textTags[i].type);
return true;
}
}
if (!strcmp(key, "font-family")) {
if (text->fontFamily && value) free(text->fontFamily);
text->fontFamily = strdup(value);
} else if (!strcmp(key, "style")) {
return simpleXmlParseW3CAttribute(value, strlen(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) free(node->id);
node->id = _copyId(value);
} else if (!strcmp(key, "class")) {
_handleCssClassAttr(loader, node, value);
} else {
return _parseStyleAttr(loader, key, value, false);
}
return true;
}
static SvgNode* _createTextNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func)
{
loader->svgParse->node = _createNode(parent, SvgNodeType::Text);
if (!loader->svgParse->node) return nullptr;
//TODO: support the def font and size as used in a system?
loader->svgParse->node->node.text.fontSize = 10.0f;
loader->svgParse->node->node.text.fontFamily = nullptr;
loader->svgParse->node->node.text.text = nullptr;
func(buf, bufLength, _attrParseTextNode, loader);
return loader->svgParse->node;
}
static constexpr struct
{
const char* tag;
@ -2137,7 +2209,8 @@ static constexpr struct
{"rect", sizeof("rect"), _createRectNode},
{"polyline", sizeof("polyline"), _createPolylineNode},
{"line", sizeof("line"), _createLineNode},
{"image", sizeof("image"), _createImageNode}
{"image", sizeof("image"), _createImageNode},
{"text", sizeof("text"), _createTextNode}
};
@ -3125,6 +3198,20 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
to->node.use.symbol = from->node.use.symbol;
break;
}
case SvgNodeType::Text: {
to->node.text.x = from->node.text.x;
to->node.text.y = from->node.text.y;
to->node.text.fontSize = from->node.text.fontSize;
if (from->node.text.text) {
if (to->node.text.text) free(to->node.text.text);
to->node.text.text = strdup(from->node.text.text);
}
if (from->node.text.fontFamily) {
if (to->node.text.fontFamily) free(to->node.text.fontFamily);
to->node.text.fontFamily = strdup(from->node.text.fontFamily);
}
break;
}
default: {
break;
}
@ -3177,35 +3264,36 @@ static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc)
}
static constexpr struct
static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content, unsigned int length)
{
const char* tag;
size_t sz;
} popArray[] = {
{"g", sizeof("g")},
{"svg", sizeof("svg")},
{"defs", sizeof("defs")},
{"mask", sizeof("mask")},
{"clipPath", sizeof("clipPath")},
{"style", sizeof("style")},
{"symbol", sizeof("symbol")}
};
const char* itr = nullptr;
int sz = length;
char tagName[20] = "";
static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content)
{
content = _skipSpace(content, nullptr);
itr = content;
while ((itr != nullptr) && *itr != '>') itr++;
for (unsigned int i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) {
if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) {
if (itr) {
sz = itr - content;
while ((sz > 0) && (isspace(content[sz - 1]))) sz--;
if ((unsigned int)sz >= sizeof(tagName)) sz = sizeof(tagName) - 1;
strncpy(tagName, content, sz);
tagName[sz] = '\0';
}
else return;
for (unsigned int i = 0; i < sizeof(groupTags) / sizeof(groupTags[0]); i++) {
if (!strncmp(tagName, groupTags[i].tag, sz)) {
loader->stack.pop();
break;
}
}
for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) {
if (!strncmp(content, graphicsTags[i].tag, graphicsTags[i].sz - 1)) {
if (!strncmp(tagName, graphicsTags[i].tag, sz)) {
loader->currentGraphicsNode = nullptr;
if (!strncmp(tagName, "text", 4)) loader->openedTag = OpenedTagType::Other;
loader->stack.pop();
break;
}
@ -3262,7 +3350,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
node = method(loader, nullptr, attrs, attrsLength, simpleXmlParseAttributes);
loader->cssStyle = node;
loader->doc->node.doc.style = node;
loader->style = true;
loader->openedTag = OpenedTagType::Style;
}
} else {
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
@ -3278,6 +3366,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
else parent = loader->doc;
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
if (node && !empty) {
if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text;
auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
loader->stack.push(defs);
loader->currentGraphicsNode = node;
@ -3313,6 +3402,13 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
}
static void _svgLoaderParserText(SvgLoaderData* loader, const char* content, unsigned int length)
{
auto text = &loader->svgParse->node->node.text;
text->text = strAppend(text->text, content, length);
}
static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* content, unsigned int length)
{
char* tag;
@ -3345,7 +3441,7 @@ static void _svgLoaderParserXmlCssStyle(SvgLoaderData* loader, const char* conte
free(tag);
free(name);
}
loader->style = false;
loader->openedTag = OpenedTagType::Other;
}
@ -3363,12 +3459,13 @@ static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content
break;
}
case SimpleXMLType::Close: {
_svgLoaderParserXmlClose(loader, content);
_svgLoaderParserXmlClose(loader, content, length);
break;
}
case SimpleXMLType::Data:
case SimpleXMLType::CData: {
if (loader->style) _svgLoaderParserXmlCssStyle(loader, content, length);
if (loader->openedTag == OpenedTagType::Style) _svgLoaderParserXmlCssStyle(loader, content, length);
else if (loader->openedTag == OpenedTagType::Text) _svgLoaderParserText(loader, content, length);
break;
}
case SimpleXMLType::DoctypeChild: {
@ -3590,6 +3687,11 @@ static void _freeNode(SvgNode* node)
free(node->node.image.href);
break;
}
case SvgNodeType::Text: {
free(node->node.text.text);
free(node->node.text.fontFamily);
break;
}
default: {
break;
}

View File

@ -218,6 +218,7 @@ enum class SvgParserLengthType
{
Vertical,
Horizontal,
Diagonal,
//In case of, for example, radius of radial gradient
Other
};
@ -377,6 +378,14 @@ struct SvgCssStyleNode
{
};
struct SvgTextNode
{
char* text;
char* fontFamily;
float x, y;
float fontSize;
};
struct SvgLinearGradient
{
float x1;
@ -521,6 +530,7 @@ struct SvgNode
SvgClipNode clip;
SvgCssStyleNode cssStyle;
SvgSymbolNode symbol;
SvgTextNode text;
} node;
~SvgNode();
};
@ -548,11 +558,18 @@ struct SvgNodeIdPair
char *id;
};
enum class OpenedTagType : uint8_t
{
Other = 0,
Style,
Text
};
struct SvgLoaderData
{
Array<SvgNode*> stack;
SvgNode* doc = nullptr;
SvgNode* def = nullptr;
SvgNode* def = nullptr; //also used to store nested graphic nodes
SvgNode* cssStyle = nullptr;
Array<SvgStyleGradient*> gradients;
SvgStyleGradient* latestGradient = nullptr; //For stops
@ -562,7 +579,7 @@ struct SvgLoaderData
Array<char*> images; //embedded images
int level = 0;
bool result = false;
bool style = false;
OpenedTagType openedTag = OpenedTagType::Other;
SvgNode* currentGraphicsNode = nullptr;
};

View File

@ -129,7 +129,7 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
rx = fabsf(rx);
ry = fabsf(ry);
angle = mathDeg2Rad(angle);
angle = deg2rad(angle);
cosPhi = cosf(angle);
sinPhi = sinf(angle);
dx2 = (sx - x) / 2.0f;
@ -197,10 +197,10 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
//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));
at = tvg::atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
nat = tvg::atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
if (sweep) {
@ -405,10 +405,10 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
case 'q':
case 'Q': {
Point p[3];
float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0f / 3.0f);
float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0f / 3.0f);
float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0f / 3.0f);
float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0f / 3.0f);
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};
@ -431,10 +431,10 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
} else {
ctrl = *cur;
}
float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0f / 3.0f);
float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0f / 3.0f);
float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0f / 3.0f);
float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0f / 3.0f);
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};
@ -472,12 +472,12 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
}
case 'a':
case 'A': {
if (mathZero(arr[0]) || mathZero(arr[1])) {
if (tvg::zero(arr[0]) || tvg::zero(arr[1])) {
Point p = {arr[5], arr[6]};
cmds->push(PathCommand::LineTo);
pts->push(p);
*cur = {arr[5], arr[6]};
} else if (!mathEqual(cur->x, arr[5]) || !mathEqual(cur->y, arr[6])) {
} else if (!tvg::equal(cur->x, arr[5]) || !tvg::equal(cur->y, arr[6])) {
_pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]);
*cur = *curCtl = {arr[5], arr[6]};
*isQuadratic = false;

View File

@ -70,6 +70,14 @@ static Box _boundingBox(const Shape* shape)
}
static Box _boundingBox(const Text* text)
{
float x, y, w, h;
text->bounds(&x, &y, &w, &h, false);
return {x, y, w, h};
}
static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
{
gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
@ -82,7 +90,7 @@ static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
}
static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
{
Fill::ColorStop* stops;
int stopCount = 0;
@ -137,7 +145,7 @@ static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient*
}
static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, const Box& vBox, int opacity)
static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
{
Fill::ColorStop *stops;
int stopCount = 0;
@ -206,11 +214,11 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape*
if (node->transform) finalTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
finalTransform = mathMultiply(&finalTransform, &m);
finalTransform *= m;
}
if (child->transform) finalTransform = mathMultiply(child->transform, &finalTransform);
if (child->transform) finalTransform = *child->transform * finalTransform;
return _appendClipShape(loaderData, child, shape, vBox, svgPath, mathIdentity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform);
return _appendClipShape(loaderData, child, shape, vBox, svgPath, identity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform);
}
@ -231,13 +239,13 @@ static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const Svg
m = *node->transform;
}
if (compNode->transform) {
m = mathMultiply(&m, compNode->transform);
m *= *compNode->transform;
}
if (!compNode->node.clip.userSpace) {
float x, y, w, h;
P(paint)->bounds(&x, &y, &w, &h, false, false);
Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1};
m = mathMultiply(&m, &mBBox);
m *= mBBox;
}
return m;
}
@ -267,8 +275,7 @@ static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const Svg
if (valid) {
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath);
comp->transform(finalTransform);
paint->composite(std::move(comp), CompositeMethod::ClipPath);
paint->clip(std::move(comp));
}
node->style->clipPath.applying = false;
@ -323,14 +330,15 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
vg->fill(std::move(linear));
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
vg->fill(std::move(linear));
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
vg->fill(std::move(radial));
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
vg->fill(std::move(radial));
}
} else if (style->fill.paint.url) {
//TODO: Apply the color pointed by url
TVGLOG("SVG", "The fill's url not supported.");
} 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);
@ -366,14 +374,15 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
vg->stroke(std::move(linear));
} else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
vg->stroke(std::move(radial));
}
} else if (style->stroke.paint.url) {
//TODO: Apply the color pointed by url
TVGLOG("SVG", "The stroke's url not supported.");
} 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);
@ -477,7 +486,10 @@ static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* sh
auto ptsCnt = shape->pathCoords(&pts);
auto p = const_cast<Point*>(pts) + currentPtsCnt;
while (currentPtsCnt++ < ptsCnt) mathMultiply(p++, m);
while (currentPtsCnt++ < ptsCnt) {
*p *= *m;
++p;
}
}
_applyProperty(loaderData, node, shape, vBox, svgPath, true);
@ -508,6 +520,7 @@ static constexpr struct
} imageMimeTypes[] = {
{"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
{"png", sizeof("png"), imageMimeTypeEncoding::base64},
{"webp", sizeof("webp"), imageMimeTypeEncoding::base64},
{"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
};
@ -618,7 +631,7 @@ static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode*
auto sy = node->node.image.h / h;
m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
}
if (node->transform) m = mathMultiply(node->transform, &m);
if (node->transform) m = *node->transform * m;
picture->transform(m);
_applyComposition(loaderData, picture.get(), node, vBox, svgPath);
@ -703,7 +716,6 @@ static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMee
static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
{
unique_ptr<Scene> finalScene;
auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite);
// mUseTransform = mUseTransform * mTranslate
@ -711,7 +723,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
if (node->transform) mUseTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
mUseTransform = mathMultiply(&mUseTransform, &mTranslate);
mUseTransform *= mTranslate;
}
if (node->node.use.symbol) {
@ -725,49 +737,94 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
auto vh = (symbol.hasViewBox ? symbol.vh : height);
Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if ((!mathEqual(width, vw) || !mathEqual(height, vh)) && vw > 0 && vh > 0) {
if ((!tvg::equal(width, vw) || !tvg::equal(height, vh)) && vw > 0 && vh > 0) {
Box box = {symbol.vx, symbol.vy, vw, vh};
mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
} else if (!mathZero(symbol.vx) || !mathZero(symbol.vy)) {
} else if (!tvg::zero(symbol.vx) || !tvg::zero(symbol.vy)) {
mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
}
// mSceneTransform = mUseTransform * mSymbolTransform * mViewBox
Matrix mSceneTransform = mViewBox;
if (node->node.use.symbol->transform) {
mSceneTransform = mathMultiply(node->node.use.symbol->transform, &mViewBox);
mSceneTransform = *node->node.use.symbol->transform * mViewBox;
}
mSceneTransform = mathMultiply(&mUseTransform, &mSceneTransform);
mSceneTransform = mUseTransform * mSceneTransform;
scene->transform(mSceneTransform);
if (node->node.use.symbol->node.symbol.overflowVisible) {
finalScene = std::move(scene);
} else {
if (!node->node.use.symbol->node.symbol.overflowVisible) {
auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0);
// mClipTransform = mUseTransform * mSymbolTransform
Matrix mClipTransform = mUseTransform;
if (node->node.use.symbol->transform) {
mClipTransform = mathMultiply(&mUseTransform, node->node.use.symbol->transform);
mClipTransform = mUseTransform * *node->node.use.symbol->transform;
}
viewBoxClip->transform(mClipTransform);
auto compositeLayer = Scene::gen();
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(std::move(scene));
auto root = Scene::gen();
root->push(std::move(compositeLayer));
finalScene = std::move(root);
scene->clip(std::move(viewBoxClip));
}
} else {
scene->transform(mUseTransform);
finalScene = std::move(scene);
}
return finalScene;
return scene;
}
static void _applyTextFill(SvgStyleProperty* style, Text* text, const Box& vBox)
{
//If fill property is nullptr then do nothing
if (style->fill.paint.none) {
//Do nothing
} else if (style->fill.paint.gradient) {
Box bBox = vBox;
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(text);
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
text->fill(std::move(linear));
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
text->fill(std::move(radial));
}
} else if (style->fill.paint.url) {
//TODO: Apply the color pointed by url
TVGLOG("SVG", "The fill's url not supported.");
} else if (style->fill.paint.curColor) {
//Apply the current style color
text->fill(style->color.r, style->color.g, style->color.b);
text->opacity(style->fill.opacity);
} else {
//Apply the fill color
text->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b);
text->opacity(style->fill.opacity);
}
}
static unique_ptr<Text> _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath)
{
auto textNode = &node->node.text;
if (!textNode->text) return nullptr;
auto text = Text::gen();
Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (node->transform) textTransform = *node->transform;
translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize);
text->transform(textTransform);
//TODO: handle def values of font and size as used in a system?
const float ptPerPx = 0.75f; //1 pt = 1/72; 1 in = 96 px; -> 72/96 = 0.75
auto fontSizePt = textNode->fontSize * ptPerPx;
if (textNode->fontFamily) text->font(textNode->fontFamily, fontSizePt);
text->text(textNode->text);
_applyTextFill(node->style, text.get(), vBox);
_applyComposition(loaderData, text.get(), node, vBox, svgPath);
return text;
}
@ -799,6 +856,9 @@ static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgN
scene->push(std::move(image));
if (isMaskWhite) *isMaskWhite = false;
}
} else if ((*child)->type == SvgNodeType::Text) {
auto text = _textBuildHelper(loaderData, *child, vBox, svgPath);
if (text) scene->push(std::move(text));
} else if ((*child)->type != SvgNodeType::Mask) {
auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath);
if (shape) {
@ -857,10 +917,10 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe
if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode.get(), vBox, w, h, viewFlag);
if (!mathEqual(w, vBox.w) || !mathEqual(h, vBox.h)) {
if (!tvg::equal(w, vBox.w) || !tvg::equal(h, vBox.h)) {
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
docNode->transform(m);
} else if (!mathZero(vBox.x) || !mathZero(vBox.y)) {
} else if (!tvg::zero(vBox.x) || !tvg::zero(vBox.y)) {
docNode->translate(-vBox.x, -vBox.y);
}
@ -868,7 +928,7 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe
viewBoxClip->appendRect(0, 0, w, h);
auto compositeLayer = Scene::gen();
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->clip(std::move(viewBoxClip));
compositeLayer->push(std::move(docNode));
auto root = Scene::gen();

View File

@ -72,5 +72,6 @@ size_t svgUtilURLDecode(const char *src, char** dst)
return idx + 1;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -49,9 +49,9 @@ struct SwCanvas::Impl
/************************************************************************/
#ifdef THORVG_SW_RASTER_SUPPORT
SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(new Impl)
SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(nullptr)
#else
SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(new Impl)
SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(nullptr)
#endif
{
}
@ -85,6 +85,10 @@ Result SwCanvas::mempool(MempoolPolicy policy) noexcept
Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept
{
#ifdef THORVG_SW_RASTER_SUPPORT
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
return Result::InsufficientCondition;
}
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<SwRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
@ -93,12 +97,12 @@ Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
renderer->viewport(Canvas::pImpl->vport);
//Paints must be updated again with this new target.
Canvas::pImpl->needRefresh();
//FIXME: The value must be associated with an individual canvas instance.
ImageLoader::cs = static_cast<ColorSpace>(cs);
//Paints must be updated again with this new target.
Canvas::pImpl->status = Status::Damaged;
return Result::Success;
#endif
return Result::NonSupport;

View File

@ -26,21 +26,10 @@
#ifndef _TVG_SW_COMMON_H_
#define _TVG_SW_COMMON_H_
#include <algorithm>
#include "tvgCommon.h"
#include "tvgRender.h"
#include <algorithm>
#if 0
#include <sys/time.h>
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)
@ -127,7 +116,7 @@ struct SwSpan
uint8_t coverage;
};
struct SwRleData
struct SwRle
{
SwSpan *spans;
uint32_t alloc;
@ -148,7 +137,6 @@ struct SwFill
{
struct SwLinear {
float dx, dy;
float len;
float offset;
};
@ -168,6 +156,7 @@ struct SwFill
uint32_t* ctable;
FillSpread spread;
bool solid = false; //solid color fill with the last color from colorStops
bool translucent;
};
@ -225,8 +214,8 @@ struct SwShape
SwOutline* outline = nullptr;
SwStroke* stroke = nullptr;
SwFill* fill = nullptr;
SwRleData* rle = nullptr;
SwRleData* strokeRle = nullptr;
SwRle* rle = nullptr;
SwRle* 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?
@ -235,7 +224,7 @@ struct SwShape
struct SwImage
{
SwOutline* outline = nullptr;
SwRleData* rle = nullptr;
SwRle* rle = nullptr;
union {
pixel_t* data; //system based data pointer
uint32_t* buf32; //for explicit 32bits channels
@ -258,13 +247,13 @@ typedef uint8_t(*SwAlpha)(uint8_t*); //bl
struct SwCompositor;
struct SwSurface : Surface
struct SwSurface : RenderSurface
{
SwJoin join;
SwAlpha alphas[4]; //Alpha:2, InvAlpha:3, Luma:4, InvLuma:5
SwBlender blender = nullptr; //blender (optional)
SwCompositor* compositor = nullptr; //compositor (optional)
BlendMethod blendMethod; //blending method (uint8_t)
BlendMethod blendMethod = BlendMethod::Normal;
SwAlpha alpha(CompositeMethod method)
{
@ -276,7 +265,7 @@ struct SwSurface : Surface
{
}
SwSurface(const SwSurface* rhs) : Surface(rhs)
SwSurface(const SwSurface* rhs) : RenderSurface(rhs)
{
join = rhs->join;
memcpy(alphas, rhs->alphas, sizeof(alphas));
@ -286,7 +275,7 @@ struct SwSurface : Surface
}
};
struct SwCompositor : Compositor
struct SwCompositor : RenderCompositor
{
SwSurface* recoverSfc; //Recover surface when composition is started
SwCompositor* recoverCmp; //Recover compositor when composition is done
@ -315,8 +304,8 @@ static inline uint32_t JOIN(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)
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));
++a;
return (((((c >> 8) & 0x00ff00ff) * a) & 0xff00ff00) + ((((c & 0x00ff00ff) * a) >> 8) & 0x00ff00ff));
}
static inline uint32_t INTERPOLATE(uint32_t s, uint32_t d, uint8_t a)
@ -418,7 +407,6 @@ static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s * d
@ -428,7 +416,6 @@ static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// if (2 * d < da) => 2 * s * d,
@ -504,42 +491,44 @@ SwFixed mathAtan(const SwPoint& pt);
SwFixed mathCos(SwFixed angle);
SwFixed mathSin(SwFixed angle);
void mathSplitCubic(SwPoint* base);
void mathSplitLine(SwPoint* base);
SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
SwFixed mathLength(const SwPoint& pt);
bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
int mathCubicAngle(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
SwFixed mathMean(SwFixed angle1, SwFixed angle2);
SwPoint mathTransform(const Point* to, const Matrix* transform);
SwPoint mathTransform(const Point* to, const Matrix& transform);
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee);
bool mathClipBBox(const SwBBox& clipper, SwBBox& clippee);
void shapeReset(SwShape* shape);
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
bool shapePrepared(const SwShape* shape);
bool shapeGenRle(SwShape* shape, const RenderShape* rshape, bool antiAlias);
void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid);
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform);
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& transform);
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, 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, uint8_t opacity, bool ctable);
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable);
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_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 RenderShape* shape, const Matrix* transform);
void strokeReset(SwStroke* stroke, const RenderShape* 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 RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
bool imageGenRle(SwImage* image, 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, uint8_t opacity, bool ctable);
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable);
const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata);
void fillReset(SwFill* fill);
void fillFree(SwFill* fill);
@ -556,13 +545,13 @@ void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver.
SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRleData* rleRender(const SwBBox* bbox);
void rleFree(SwRleData* rle);
void rleReset(SwRleData* rle);
void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2);
void rleClipPath(SwRleData* rle, const SwRleData* clip);
void rleClipRect(SwRleData* rle, const SwBBox* clip);
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRle* rleRender(const SwBBox* bbox);
void rleFree(SwRle* rle);
void rleReset(SwRle* rle);
void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2);
void rleClip(SwRle* rle, const SwRle* clip);
void rleClip(SwRle* rle, const SwBBox* clip);
SwMpool* mpoolInit(uint32_t threads);
bool mpoolTerm(SwMpool* mpool);
@ -575,17 +564,21 @@ SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx);
void mpoolRetDashOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface);
bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id);
bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity);
bool rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, const SwBBox& bbox, uint8_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, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0);
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
void rasterUnpremultiply(Surface* surface);
void rasterPremultiply(Surface* surface);
bool rasterConvertCS(Surface* surface, ColorSpace to);
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped);
void rasterUnpremultiply(RenderSurface* surface);
void rasterPremultiply(RenderSurface* surface);
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params);
bool effectGaussianPrepare(RenderEffectGaussian* effect);
#endif /* _TVG_SW_COMMON_H_ */

View File

@ -61,13 +61,75 @@ static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, f
auto deltaDeltaRr = 2.0f * (radial->a11 * radial->a11 + radial->a21 * radial->a21) * radial->invA;
det = b * b + (rr - radial->fr * radial->fr) * radial->invA;
deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr;
deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr * 0.5f;
deltaDeltaDet = 2.0f * deltaB * deltaB + deltaDeltaRr;
}
static uint32_t _estimateAAMargin(const Fill* fdata)
{
constexpr float marginScalingFactor = 800.0f;
if (fdata->type() == Type::RadialGradient) {
auto radius = P(static_cast<const RadialGradient*>(fdata))->r;
return tvg::zero(radius) ? 0 : static_cast<uint32_t>(marginScalingFactor / radius);
}
auto grad = P(static_cast<const LinearGradient*>(fdata));
Point p1 {grad->x1, grad->y1};
Point p2 {grad->x2, grad->y2};
auto len = length(&p1, &p2);
return tvg::zero(len) ? 0 : static_cast<uint32_t>(marginScalingFactor / len);
}
static void _adjustAAMargin(uint32_t& iMargin, uint32_t index)
{
constexpr float threshold = 0.1f;
constexpr uint32_t iMarginMax = 40;
auto iThreshold = static_cast<uint32_t>(index * threshold);
if (iMargin > iThreshold) iMargin = iThreshold;
if (iMargin > iMarginMax) iMargin = iMarginMax;
}
static inline uint32_t _alphaUnblend(uint32_t c)
{
auto a = (c >> 24);
if (a == 255 || a == 0) return c;
auto invA = 255.0f / static_cast<float>(a);
auto c0 = static_cast<uint8_t>(static_cast<float>((c >> 16) & 0xFF) * invA);
auto c1 = static_cast<uint8_t>(static_cast<float>((c >> 8) & 0xFF) * invA);
auto c2 = static_cast<uint8_t>(static_cast<float>(c & 0xFF) * invA);
return (a << 24) | (c0 << 16) | (c1 << 8) | c2;
}
static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end)
{
if (begin == 0 || end == 0) return;
auto i = GRADIENT_STOP_SIZE - end;
auto rgbaEnd = _alphaUnblend(fill->ctable[i]);
auto rgbaBegin = _alphaUnblend(fill->ctable[begin]);
auto dt = 1.0f / (begin + end + 1.0f);
float t = dt;
while (i != begin) {
auto dist = 255 - static_cast<int32_t>(255 * t);
auto color = INTERPOLATE(rgbaEnd, rgbaBegin, dist);
fill->ctable[i++] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
if (i == GRADIENT_STOP_SIZE) i = 0;
t += dt;
}
}
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
{
if (fill->solid) return true;
if (!fill->ctable) {
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
if (!fill->ctable) return false;
@ -91,6 +153,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
auto pos = 1.5f * inc;
uint32_t i = 0;
//If repeat is true, anti-aliasing must be applied between the last and the first colors.
auto repeat = fill->spread == FillSpread::Repeat;
uint32_t iAABegin = repeat ? _estimateAAMargin(fdata) : 0;
uint32_t iAAEnd = 0;
fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a);
while (pos <= pColors->offset) {
@ -100,6 +167,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
}
for (uint32_t j = 0; j < cnt - 1; ++j) {
if (repeat && j == cnt - 2 && iAAEnd == 0) {
iAAEnd = iAABegin;
_adjustAAMargin(iAAEnd, GRADIENT_STOP_SIZE - i);
}
auto curr = colors + j;
auto next = curr + 1;
auto delta = 1.0f / (next->offset - curr->offset);
@ -121,62 +193,69 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
}
rgba = rgba2;
a = a2;
if (repeat && j == 0) _adjustAAMargin(iAABegin, i - 1);
}
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;
//For repeat fill spread apply anti-aliasing between the last and first colors,
//othewise make sure the last color stop is represented at the end of the table.
if (repeat) _applyAA(fill, iAABegin, iAAEnd);
else fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
return true;
}
bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform)
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;
auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
if (fill->linear.len < FLOAT_EPSILON) return true;
if (len < FLOAT_EPSILON) {
if (tvg::zero(fill->linear.dx) && tvg::zero(fill->linear.dy)) {
fill->solid = true;
}
return true;
}
fill->linear.dx /= fill->linear.len;
fill->linear.dy /= fill->linear.len;
fill->linear.dx /= len;
fill->linear.dy /= len;
fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;
auto gradTransform = linear->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
bool isTransformation = !identity((const Matrix*)(&gradTransform));
if (isTransformation) {
if (transform) gradTransform = mathMultiply(transform, &gradTransform);
} else if (transform) {
gradTransform = *transform;
gradTransform = transform * gradTransform;
} else {
gradTransform = transform;
isTransformation = true;
}
if (isTransformation) {
Matrix invTransform;
if (!mathInverse(&gradTransform, &invTransform)) return false;
if (!inverse(&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;
}
return true;
}
bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform)
bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& transform)
{
auto cx = P(radial)->cx;
auto cy = P(radial)->cy;
@ -185,7 +264,10 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
auto fy = P(radial)->fy;
auto fr = P(radial)->fr;
if (r < FLOAT_EPSILON) return true;
if (tvg::zero(r)) {
fill->solid = true;
return true;
}
fill->radial.dr = r - fr;
fill->radial.dx = cx - fx;
@ -216,19 +298,17 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a;
auto gradTransform = radial->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
bool isTransformation = !identity((const Matrix*)(&gradTransform));
if (transform) {
if (isTransformation) gradTransform = mathMultiply(transform, &gradTransform);
else {
gradTransform = *transform;
isTransformation = true;
}
if (isTransformation) gradTransform = transform * gradTransform;
else {
gradTransform = transform;
isTransformation = true;
}
if (isTransformation) {
Matrix invTransform;
if (!mathInverse(&gradTransform, &invTransform)) return false;
if (!inverse(&gradTransform, &invTransform)) return false;
fill->radial.a11 = invTransform.e11;
fill->radial.a12 = invTransform.e12;
fill->radial.a13 = invTransform.e13;
@ -476,7 +556,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (opacity == 255) {
if (mathZero(inc)) {
if (tvg::zero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(color, *dst, alpha(cmp));
@ -507,7 +587,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
}
}
} else {
if (mathZero(inc)) {
if (tvg::zero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(color, *dst, MULTIPLY(alpha(cmp), opacity));
@ -549,7 +629,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32
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 (mathZero(inc)) {
if (tvg::zero(inc)) {
auto src = MULTIPLY(a, A(_fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE))));
for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = maskOp(src, *dst, ~src);
@ -566,7 +646,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
auto src = MULTIPLY(_fixedPixel(fill, t2), a);
auto src = MULTIPLY(A(_fixedPixel(fill, t2)), a);
*dst = maskOp(src, *dst, ~src);
t2 += inc2;
}
@ -574,7 +654,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto src = MULTIPLY(_pixel(fill, t / GRADIENT_STOP_SIZE), a);
auto src = MULTIPLY(A(_pixel(fill, t / GRADIENT_STOP_SIZE)), a);
*dst = maskOp(src, *dst, ~src);
++dst;
t += inc;
@ -591,7 +671,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32
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 (mathZero(inc)) {
if (tvg::zero(inc)) {
auto src = A(_fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE)));
src = MULTIPLY(src, a);
for (uint32_t i = 0; i < len; ++i, ++dst, ++cmp) {
@ -638,7 +718,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
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 (mathZero(inc)) {
if (tvg::zero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = op(color, *dst, a);
@ -678,7 +758,7 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
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 (mathZero(inc)) {
if (tvg::zero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
if (a == 255) {
for (uint32_t i = 0; i < len; ++i, ++dst) {
@ -745,25 +825,32 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
}
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
if (!fill) return false;
fill->spread = fdata->spread();
if (ctable) {
if (!_updateColorTable(fill, fdata, surface, opacity)) return false;
if (fdata->type() == Type::LinearGradient) {
if (!_prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform)) return false;
} else if (fdata->type() == Type::RadialGradient) {
if (!_prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform)) return false;
}
if (fdata->identifier() == TVG_CLASS_ID_LINEAR) {
return _prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform);
} else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
return _prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform);
}
if (ctable) return _updateColorTable(fill, fdata, surface, opacity);
return true;
}
//LOG: What type of gradient?!
return false;
const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata)
{
if (!fill->solid) return nullptr;
const Fill::ColorStop* colors;
auto cnt = fdata->colorStops(&colors);
if (cnt == 0 || !colors) return nullptr;
return colors + cnt - 1;
}
@ -774,6 +861,7 @@ void fillReset(SwFill* fill)
fill->ctable = nullptr;
}
fill->translucent = false;
fill->solid = false;
}

View File

@ -30,14 +30,14 @@
/* Internal Class Implementation */
/************************************************************************/
static inline bool _onlyShifted(const Matrix* m)
static inline bool _onlyShifted(const Matrix& m)
{
if (mathEqual(m->e11, 1.0f) && mathEqual(m->e22, 1.0f) && mathZero(m->e12) && mathZero(m->e21)) return true;
if (tvg::equal(m.e11, 1.0f) && tvg::equal(m.e22, 1.0f) && tvg::zero(m.e12) && tvg::zero(m.e21)) return true;
return false;
}
static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* transform, SwMpool* mpool, unsigned tid)
static bool _genOutline(SwImage* image, const Matrix& transform, SwMpool* mpool, unsigned tid)
{
image->outline = mpoolReqOutline(mpool, tid);
auto outline = image->outline;
@ -48,48 +48,12 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr
outline->closed.reserve(1);
Point to[4];
if (mesh->triangleCnt > 0) {
// TODO: Optimise me. We appear to calculate this exact min/max bounding area in multiple
// places. We should be able to re-use one we have already done? Also see:
// tvgPicture.h --> bounds
// tvgSwRasterTexmap.h --> _rasterTexmapPolygonMesh
//
// TODO: Should we calculate the exact path(s) of the triangle mesh instead?
// i.e. copy tvgSwShape.capp -> _genOutline?
//
// TODO: Cntrs?
auto triangles = mesh->triangles;
auto min = triangles[0].vertex[0].pt;
auto max = triangles[0].vertex[0].pt;
for (uint32_t i = 0; i < mesh->triangleCnt; ++i) {
if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x;
else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x;
if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y;
else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y;
if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x;
else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x;
if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y;
else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y;
if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x;
else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x;
if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y;
else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y;
}
to[0] = {min.x, min.y};
to[1] = {max.x, min.y};
to[2] = {max.x, max.y};
to[3] = {min.x, max.y};
} else {
auto w = static_cast<float>(image->w);
auto h = static_cast<float>(image->h);
to[0] = {0, 0};
to[1] = {w, 0};
to[2] = {w, h};
to[3] = {0, h};
}
auto w = static_cast<float>(image->w);
auto h = static_cast<float>(image->h);
to[0] = {0, 0};
to[1] = {w, 0};
to[2] = {w, h};
to[3] = {0, h};
for (int i = 0; i < 4; i++) {
outline->pts.push(mathTransform(&to[i], transform));
@ -111,25 +75,25 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr
/* External Class Implementation */
/************************************************************************/
bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
{
image->direct = _onlyShifted(transform);
//Fast track: Non-transformed image but just shifted.
if (image->direct) {
image->ox = -static_cast<int32_t>(round(transform->e13));
image->oy = -static_cast<int32_t>(round(transform->e23));
image->ox = -static_cast<int32_t>(nearbyint(transform.e13));
image->oy = -static_cast<int32_t>(nearbyint(transform.e23));
//Figure out the scale factor by transform matrix
} else {
auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21));
auto scaleY = sqrtf((transform->e22 * transform->e22) + (transform->e12 * transform->e12));
auto scaleX = sqrtf((transform.e11 * transform.e11) + (transform.e21 * transform.e21));
auto scaleY = sqrtf((transform.e22 * transform.e22) + (transform.e12 * transform.e12));
image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX;
if (mathZero(transform->e12) && mathZero(transform->e21)) image->scaled = true;
if (tvg::zero(transform.e12) && tvg::zero(transform.e21)) image->scaled = true;
else image->scaled = false;
}
if (!_genOutline(image, mesh, transform, mpool, tid)) return false;
if (!_genOutline(image, transform, mpool, tid)) return false;
return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, image->direct);
}

View File

@ -47,7 +47,7 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2)
}
bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
int mathCubicAngle(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
{
auto d1 = base[2] - base[3];
auto d2 = base[1] - base[2];
@ -57,7 +57,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw
if (d2.small()) {
if (d3.small()) {
angleIn = angleMid = angleOut = 0;
return true;
return -1; //ignoreable
} else {
angleIn = angleMid = angleOut = mathAtan(d3);
}
@ -93,8 +93,8 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw
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;
if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return 0; //small size
return 1;
}
@ -167,8 +167,8 @@ void mathRotate(SwPoint& pt, SwFixed angle)
auto cosv = cosf(radian);
auto sinv = sinf(radian);
pt.x = SwCoord(roundf((v.x * cosv - v.y * sinv) * 64.0f));
pt.y = SwCoord(roundf((v.x * sinv + v.y * cosv) * 64.0f));
pt.x = SwCoord(nearbyint((v.x * cosv - v.y * sinv) * 64.0f));
pt.y = SwCoord(nearbyint((v.x * sinv + v.y * cosv) * 64.0f));
}
@ -182,7 +182,7 @@ SwFixed mathTan(SwFixed angle)
SwFixed mathAtan(const SwPoint& pt)
{
if (pt.zero()) return 0;
return SwFixed(atan2f(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f);
return SwFixed(tvg::atan2(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f);
}
@ -245,6 +245,15 @@ void mathSplitCubic(SwPoint* base)
}
void mathSplitLine(SwPoint* base)
{
base[2] = base[1];
base[1].x = (base[0].x + base[1].x) >> 1;
base[1].y = (base[0].y + base[1].y) >> 1;
}
SwFixed mathDiff(SwFixed angle1, SwFixed angle2)
{
auto delta = angle2 - angle1;
@ -257,30 +266,28 @@ SwFixed mathDiff(SwFixed angle1, SwFixed angle2)
}
SwPoint mathTransform(const Point* to, const Matrix* transform)
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;
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 mathClipBBox(const SwBBox& clipper, SwBBox& clipee)
bool mathClipBBox(const SwBBox& clipper, SwBBox& clippee)
{
clipee.max.x = (clipee.max.x < clipper.max.x) ? clipee.max.x : clipper.max.x;
clipee.max.y = (clipee.max.y < clipper.max.y) ? clipee.max.y : clipper.max.y;
clipee.min.x = (clipee.min.x > clipper.min.x) ? clipee.min.x : clipper.min.x;
clipee.min.y = (clipee.min.y > clipper.min.y) ? clipee.min.y : clipper.min.y;
clippee.max.x = (clippee.max.x < clipper.max.x) ? clippee.max.x : clipper.max.x;
clippee.max.y = (clippee.max.y < clipper.max.y) ? clippee.max.y : clipper.max.y;
clippee.min.x = (clippee.min.x > clipper.min.x) ? clippee.min.x : clipper.min.x;
clippee.min.y = (clippee.min.y > clipper.min.y) ? clippee.min.y : clipper.min.y;
//Check valid region
if (clipee.max.x - clipee.min.x < 1 && clipee.max.y - clipee.min.y < 1) return false;
if (clippee.max.x - clippee.min.x < 1 && clippee.max.y - clippee.min.y < 1) return false;
//Check boundary
if (clipee.min.x >= clipper.max.x || clipee.min.y >= clipper.max.y ||
clipee.max.x <= clipper.min.x || clipee.max.y <= clipper.min.y) return false;
if (clippee.min.x >= clipper.max.x || clippee.min.y >= clipper.max.y ||
clippee.max.x <= clipper.min.x || clippee.max.y <= clipper.min.y) return false;
return true;
}
@ -308,14 +315,12 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S
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<SwCoord>(round(xMin / 64.0f));
renderRegion.max.x = static_cast<SwCoord>(round(xMax / 64.0f));
renderRegion.min.y = static_cast<SwCoord>(round(yMin / 64.0f));
renderRegion.max.y = static_cast<SwCoord>(round(yMax / 64.0f));
renderRegion.min.x = static_cast<SwCoord>(nearbyint(xMin / 64.0f));
renderRegion.max.x = static_cast<SwCoord>(nearbyint(xMax / 64.0f));
renderRegion.min.y = static_cast<SwCoord>(nearbyint(yMin / 64.0f));
renderRegion.max.y = static_cast<SwCoord>(nearbyint(yMax / 64.0f));
} else {
renderRegion.min.x = xMin >> 6;
renderRegion.max.x = (xMax + 63) >> 6;

View File

@ -84,7 +84,7 @@ SwMpool* mpoolInit(uint32_t threads)
{
auto allocSize = threads + 1;
auto mpool = static_cast<SwMpool*>(calloc(sizeof(SwMpool), 1));
auto mpool = static_cast<SwMpool*>(calloc(1, sizeof(SwMpool)));
mpool->outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
mpool->strokeOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
mpool->dashOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));

View File

@ -0,0 +1,214 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#include "tvgSwCommon.h"
/************************************************************************/
/* Gaussian Filter Implementation */
/************************************************************************/
struct SwGaussianBlur
{
static constexpr int MAX_LEVEL = 3;
int level;
int kernel[MAX_LEVEL];
};
static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direction)
{
//bbox region expansion for feathering
if (direction != 2) {
region.x = -extra;
region.w = extra * 2;
}
if (direction != 1) {
region.y = -extra;
region.h = extra * 2;
}
}
static int _gaussianRemap(int end, int idx, int border)
{
//wrap
if (border == 1) return idx % end;
//duplicate
if (idx < 0) return 0;
else if (idx >= end) return end - 1;
return idx;
}
//TODO: SIMD OPTIMIZATION?
static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped)
{
if (flipped) {
src += ((bbox.min.x * stride) + bbox.min.y) << 2;
dst += ((bbox.min.x * stride) + bbox.min.y) << 2;
} else {
src += ((bbox.min.y * stride) + bbox.min.x) << 2;
dst += ((bbox.min.y * stride) + bbox.min.x) << 2;
}
auto iarr = 1.0f / (dimension + dimension + 1);
for (int x = 0; x < h; x++) {
auto p = x * stride;
auto i = p * 4; //current index
auto l = -(dimension + 1); //left index
auto r = dimension; //right index
int acc[4] = {0, 0, 0, 0}; //sliding accumulator
//initial acucmulation
for (int x2 = l; x2 < r; ++x2) {
auto id = (_gaussianRemap(w, x2, border) + p) * 4;
acc[0] += src[id++];
acc[1] += src[id++];
acc[2] += src[id++];
acc[3] += src[id];
}
//perform filtering
for (int x2 = 0; x2 < w; ++x2, ++r, ++l) {
auto rid = (_gaussianRemap(w, r, border) + p) * 4;
auto lid = (_gaussianRemap(w, l, border) + p) * 4;
acc[0] += src[rid++] - src[lid++];
acc[1] += src[rid++] - src[lid++];
acc[2] += src[rid++] - src[lid++];
acc[3] += src[rid] - src[lid];
dst[i++] = static_cast<uint8_t>(acc[0] * iarr + 0.5f);
dst[i++] = static_cast<uint8_t>(acc[1] * iarr + 0.5f);
dst[i++] = static_cast<uint8_t>(acc[2] * iarr + 0.5f);
dst[i++] = static_cast<uint8_t>(acc[3] * iarr + 0.5f);
}
}
}
static int _gaussianInit(int* kernel, float sigma, int level)
{
const auto MAX_LEVEL = SwGaussianBlur::MAX_LEVEL;
//compute the kernel
auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1);
if (wl % 2 == 0) --wl;
auto wu = wl + 2;
auto mi = (12 * sigma - MAX_LEVEL * wl * wl - 4 * MAX_LEVEL * wl - 3 * MAX_LEVEL) / (-4 * wl - 4);
auto m = int(mi + 0.5f);
auto extends = 0;
for (int i = 0; i < level; i++) {
kernel[i] = ((i < m ? wl : wu) - 1) / 2;
extends += kernel[i];
}
return extends;
}
bool effectGaussianPrepare(RenderEffectGaussian* params)
{
auto data = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur));
//compute box kernel sizes
data->level = int(SwGaussianBlur::MAX_LEVEL * ((params->quality - 1) * 0.01f)) + 1;
auto extends = _gaussianInit(data->kernel, params->sigma * params->sigma, data->level);
//skip, if the parameters are invalid.
if (extends == 0) {
params->invalid = true;
free(data);
return false;
}
_gaussianExtendRegion(params->extend, extends, params->direction);
params->rd = data;
return true;
}
/* It is best to take advantage of the Gaussian blurs separable property
by dividing the process into two passes. horizontal and vertical.
We can expect fewer calculations. */
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params)
{
if (params->invalid) return false;
if (image.channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
return false;
}
auto data = static_cast<SwGaussianBlur*>(params->rd);
auto w = (bbox.max.x - bbox.min.x);
auto h = (bbox.max.y - bbox.min.y);
auto stride = image.stride;
auto front = image.buf8;
auto back = buffer.buf8;
auto swapped = false;
//fine-tuning for low-quality (experimental)
auto threshold = (std::min(w, h) < 300) ? 2 : 1;
TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level);
//horizontal
if (params->direction == 0 || params->direction == 1) {
for (int i = 0; i < data->level; ++i) {
auto k = data->kernel[i] / threshold;
if (k == 0) continue;
_gaussianBlur(front, back, stride, w, h, bbox, k, params->border, false);
std::swap(front, back);
swapped = !swapped;
}
}
//vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
if (params->direction == 0 || params->direction == 2) {
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, w, h, bbox, false);
std::swap(front, back);
for (int i = 0; i < data->level; ++i) {
auto k = data->kernel[i] / threshold;
if (k == 0) continue;
_gaussianBlur(front, back, stride, h, w, bbox, k, params->border, true);
std::swap(front, back);
swapped = !swapped;
}
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, h, w, bbox, true);
std::swap(front, back);
}
if (swapped) std::swap(image.buf8, buffer.buf8);
return true;
}
#endif /* LV_USE_THORVG_INTERNAL */

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
@ -65,9 +65,9 @@ static inline __m128i ALPHA_BLEND(__m128i c, __m128i a)
}
static void avxRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
static void avxRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
{
dst += offset;
dst += offset;
__m256i vecVal = _mm256_set1_epi8(val);
@ -104,105 +104,127 @@ static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32
static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
uint32_t ialpha = 255 - a;
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto avxColor = _mm_set1_epi32(color);
auto avxIalpha = _mm_set1_epi8(ialpha);
uint32_t ialpha = 255 - a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
auto avxColor = _mm_set1_epi32(color);
auto avxIalpha = _mm_set1_epi8(ialpha);
//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) {
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++;
}
}
//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++;
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = a + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto span = rle->spans;
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
uint32_t src;
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
for (uint32_t i = 0; i < rle->size; ++i) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
auto ialpha = IA(src);
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
//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 > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
auto ialpha = IA(src);
//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 > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
*dst = src + ALPHA_BLEND(*dst, ialpha);
}
}
//2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
//In order to avoid unnecessary avx variables declarations a check is made whether there are any iterations at all
uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
uint32_t avxFilled = 0;
if (iterations > 0) {
auto avxSrc = _mm_set1_epi32(src);
auto avxIalpha = _mm_set1_epi8(ialpha);
avxFilled = iterations * N_32BITS_IN_128REG;
auto avxDst = (__m128i*)dst;
for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
*avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
}
}
//3. fill the remaining pixels
int32_t leftovers = span->len - notAligned - avxFilled;
dst += avxFilled;
while (leftovers--) {
*dst = src + ALPHA_BLEND(*dst, ialpha);
dst++;
}
++span;
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + MULTIPLY(*dst, ialpha);
}
}
//2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
//In order to avoid unnecessary avx variables declarations a check is made whether there are any iterations at all
uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
uint32_t avxFilled = 0;
if (iterations > 0) {
auto avxSrc = _mm_set1_epi32(src);
auto avxIalpha = _mm_set1_epi8(ialpha);
avxFilled = iterations * N_32BITS_IN_128REG;
auto avxDst = (__m128i*)dst;
for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
*avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
}
}
//3. fill the remaining pixels
int32_t leftovers = span->len - notAligned - avxFilled;
dst += avxFilled;
while (leftovers--) {
*dst = src + ALPHA_BLEND(*dst, ialpha);
dst++;
}
++span;
}
return true;
}

View File

@ -63,7 +63,7 @@ static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int
}
static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto span = rle->spans;
@ -128,7 +128,7 @@ static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& regi
}
static bool inline cRasterABGRtoARGB(Surface* surface)
static bool inline cRasterABGRtoARGB(RenderSurface* surface)
{
TVGLOG("SW_ENGINE", "Convert ColorSpace ABGR - ARGB [Size: %d x %d]", surface->w, surface->h);
@ -159,7 +159,7 @@ static bool inline cRasterABGRtoARGB(Surface* surface)
}
static bool inline cRasterARGBtoABGR(Surface* surface)
static bool inline cRasterARGBtoABGR(RenderSurface* surface)
{
//exactly same with ABGRtoARGB
return cRasterABGRtoARGB(surface);

View File

@ -92,46 +92,58 @@ static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int3
}
static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
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;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
uint32_t src;
uint8x8_t *vDst = nullptr;
uint16_t align;
auto dst = &surface->buf32[span->y * surface->stride + span->x];
auto ialpha = IA(src);
for (uint32_t i = 0; i < rle->size; ++i) {
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
if ((((uintptr_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;
auto dst = &surface->buf32[span->y * surface->stride + span->x];
auto ialpha = IA(src);
if ((((uintptr_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(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;
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + MULTIPLY(*dst, ialpha);
}
}
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(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;
}
@ -139,41 +151,51 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, u
static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
auto ialpha = 255 - a;
auto vColor = vdup_n_u32(color);
auto vIalpha = vdup_n_u8((uint8_t) ialpha);
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = 255 - a;
uint8x8_t* vDst = nullptr;
uint32_t align;
auto vColor = vdup_n_u32(color);
auto vIalpha = vdup_n_u8((uint8_t) ialpha);
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
uint8x8_t* vDst = nullptr;
uint32_t align;
if ((((uintptr_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 y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
if ((((uintptr_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(vDst[x], vIalpha));
auto leftovers = (w - align) % 2;
if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = a + MULTIPLY(*dst, ialpha);
}
}
for (uint32_t x = 0; x < (w - align) / 2; ++x)
vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha));
auto leftovers = (w - align) % 2;
if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
}
return true;
}

View File

@ -23,6 +23,17 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
struct Vertex
{
Point pt;
Point uv;
};
struct Polygon
{
Vertex vertex[3];
};
struct AALine
{
int32_t x[2];
@ -37,14 +48,6 @@ struct AASpans
int32_t yEnd;
};
static inline void _swap(float& a, float& b, float& tmp)
{
tmp = a;
a = b;
b = tmp;
}
//Careful! Shared resource, No support threading
static float dudx, dvdx;
static float dxdya, dxdyb, dudya, dvdya;
@ -653,28 +656,27 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
float off_y;
float dxdy[3] = {0.0f, 0.0f, 0.0f};
float tmp;
auto upper = false;
//Sort the vertices in ascending Y order
if (y[0] > y[1]) {
_swap(x[0], x[1], tmp);
_swap(y[0], y[1], tmp);
_swap(u[0], u[1], tmp);
_swap(v[0], v[1], tmp);
std::swap(x[0], x[1]);
std::swap(y[0], y[1]);
std::swap(u[0], u[1]);
std::swap(v[0], v[1]);
}
if (y[0] > y[2]) {
_swap(x[0], x[2], tmp);
_swap(y[0], y[2], tmp);
_swap(u[0], u[2], tmp);
_swap(v[0], v[2], tmp);
std::swap(x[0], x[2]);
std::swap(y[0], y[2]);
std::swap(u[0], u[2]);
std::swap(v[0], v[2]);
}
if (y[1] > y[2]) {
_swap(x[1], x[2], tmp);
_swap(y[1], y[2], tmp);
_swap(u[1], u[2], tmp);
_swap(v[1], v[2], tmp);
std::swap(x[1], x[2]);
std::swap(y[1], y[2]);
std::swap(u[1], u[2]);
std::swap(v[1], v[2]);
}
//Y indexes
@ -687,7 +689,7 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
auto denom = ((x[2] - x[0]) * (y[1] - y[0]) - (x[1] - x[0]) * (y[2] - y[0]));
//Skip poly if it's an infinitely thin line
if (mathZero(denom)) return;
if (tvg::zero(denom)) return;
denom = 1 / denom; //Reciprocal for speeding up
dudx = ((u[2] - u[0]) * (y[1] - y[0]) - (u[1] - u[0]) * (y[2] - y[0])) * denom;
@ -703,8 +705,8 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
//Determine which side of the polygon the longer edge is on
auto side = (dxdy[1] > dxdy[0]) ? true : false;
if (mathEqual(y[0], y[1])) side = x[0] > x[1];
if (mathEqual(y[1], y[2])) side = x[2] > x[1];
if (tvg::equal(y[0], y[1])) side = x[0] > x[1];
if (tvg::equal(y[1], y[2])) side = x[2] > x[1];
auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image?
auto compositing = _compositing(surface); //Composition required
@ -880,17 +882,15 @@ static void _calcVertCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t re
static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x, int32_t x2)
{
if (lines[y].length[eidx] < abs(x - x2)) {
lines[y].length[eidx] = abs(x - x2);
lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
}
lines[y].length[eidx] = abs(x - x2);
lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
}
/*
* This Anti-Aliasing mechanism is originated from Hermet Park's idea.
* To understand this AA logic, you can refer this page:
* www.hermet.pe.kr/122 (hermetpark@gmail.com)
* https://uigraphics.tistory.com/1
*/
static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
{
@ -909,9 +909,14 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
ptx[1] = tx[1]; \
} while (0)
struct Point
{
int32_t x, y;
};
int32_t y = 0;
SwPoint pEdge = {-1, -1}; //previous edge point
SwPoint edgeDiff = {0, 0}; //temporary used for point distance
Point pEdge = {-1, -1}; //previous edge point
Point edgeDiff = {0, 0}; //temporary used for point distance
/* store bigger to tx[0] between prev and current edge's x positions. */
int32_t tx[2] = {0, 0};
@ -936,6 +941,9 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
//Calculates AA Edges
for (y++; y < yEnd; y++) {
if (lines[y].x[0] == INT32_MAX) continue;
//Ready tx
if (eidx == 0) {
tx[0] = pEdge.x;
@ -1033,6 +1041,7 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
static bool _apply(SwSurface* surface, AASpans* aaSpans)
{
auto end = surface->buf32 + surface->h * surface->stride;
auto y = aaSpans->yStart;
uint32_t pixel;
uint32_t* dst;
@ -1053,8 +1062,13 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
dst = surface->buf32 + (offset + line->x[0]);
if (line->x[0] > 1) pixel = *(dst - 1);
else pixel = *dst;
pos = 1;
//exceptional handling. out of memory bound.
if (dst + line->length[0] >= end) {
pos += (dst + line->length[0] - end);
}
while (pos <= line->length[0]) {
*dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos);
++dst;
@ -1062,17 +1076,21 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
}
//Right edge
dst = surface->buf32 + (offset + line->x[1] - 1);
dst = surface->buf32 + offset + line->x[1] - 1;
if (line->x[1] < (int32_t)(surface->w - 1)) pixel = *(dst + 1);
else pixel = *dst;
pos = line->length[1];
pos = width;
while ((int32_t)(width - line->length[1]) < pos) {
*dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * (line->length[1] - (width - pos))));
//exceptional handling. out of memory bound.
if (dst - pos < surface->buf32) --pos;
while (pos > 0) {
*dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * pos));
--dst;
--pos;
}
}
}
y++;
}
@ -1093,7 +1111,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
| / |
3 -- 2
*/
static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint8_t opacity)
static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix& transform, const SwBBox* region, uint8_t opacity)
{
if (surface->channelSize == sizeof(uint8_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon!");
@ -1101,7 +1119,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
}
//Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false;
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return true;
/* Prepare vertices.
shift XY coordinates to match the sub-pixeling technique. */
@ -1113,7 +1131,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
float ys = FLT_MAX, ye = -1.0f;
for (int i = 0; i < 4; i++) {
if (transform) mathMultiply(&vertices[i].pt, transform);
vertices[i].pt *= transform;
if (vertices[i].pt.y < ys) ys = vertices[i].pt.y;
if (vertices[i].pt.y > ye) ye = vertices[i].pt.y;
}
@ -1145,70 +1163,5 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
return _apply(surface, aaSpans);
}
/*
Provide any number of triangles to draw a mesh using the supplied image.
Indexes are not used, so each triangle (Polygon) vertex has to be defined, even if they copy the previous one.
Example:
0 -- 1 0 -- 1 0
| / | --> | / / |
| / | | / / |
2 -- 3 2 1 -- 2
Should provide two Polygons, one for each triangle.
// TODO: region?
*/
static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint8_t opacity)
{
if (surface->channelSize == sizeof(uint8_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!");
return false;
}
//Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false;
// Step polygons once to transform
auto transformedTris = (Polygon*)malloc(sizeof(Polygon) * mesh->triangleCnt);
float ys = FLT_MAX, ye = -1.0f;
for (uint32_t i = 0; i < mesh->triangleCnt; i++) {
transformedTris[i] = mesh->triangles[i];
mathMultiply(&transformedTris[i].vertex[0].pt, transform);
mathMultiply(&transformedTris[i].vertex[1].pt, transform);
mathMultiply(&transformedTris[i].vertex[2].pt, transform);
if (transformedTris[i].vertex[0].pt.y < ys) ys = transformedTris[i].vertex[0].pt.y;
else if (transformedTris[i].vertex[0].pt.y > ye) ye = transformedTris[i].vertex[0].pt.y;
if (transformedTris[i].vertex[1].pt.y < ys) ys = transformedTris[i].vertex[1].pt.y;
else if (transformedTris[i].vertex[1].pt.y > ye) ye = transformedTris[i].vertex[1].pt.y;
if (transformedTris[i].vertex[2].pt.y < ys) ys = transformedTris[i].vertex[2].pt.y;
else if (transformedTris[i].vertex[2].pt.y > ye) ye = transformedTris[i].vertex[2].pt.y;
// Convert normalized UV coordinates to image coordinates
transformedTris[i].vertex[0].uv.x *= (float)image->w;
transformedTris[i].vertex[0].uv.y *= (float)image->h;
transformedTris[i].vertex[1].uv.x *= (float)image->w;
transformedTris[i].vertex[1].uv.y *= (float)image->h;
transformedTris[i].vertex[2].uv.x *= (float)image->w;
transformedTris[i].vertex[2].uv.y *= (float)image->h;
}
// Get AA spans and step polygons again to draw
if (auto aaSpans = _AASpans(ys, ye, image, region)) {
for (uint32_t i = 0; i < mesh->triangleCnt; i++) {
_rasterPolygonImage(surface, image, region, transformedTris[i], aaSpans, opacity);
}
#if 0
if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) {
_compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox);
}
#endif
_apply(surface, aaSpans);
}
free(transformedTris);
return true;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -23,6 +23,10 @@
#include "../../lv_conf_internal.h"
#if LV_USE_THORVG_INTERNAL
#ifdef THORVG_SW_OPENMP_SUPPORT
#include <omp.h>
#endif
#include <algorithm>
#include "tvgMath.h"
#include "tvgSwCommon.h"
#include "tvgTaskScheduler.h"
@ -40,8 +44,8 @@ struct SwTask : Task
{
SwSurface* surface = nullptr;
SwMpool* mpool = nullptr;
SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region
Matrix* transform = nullptr;
SwBBox bbox; //Rendering Region
Matrix transform;
Array<RenderData> clips;
RenderUpdateFlag flags = RenderUpdateFlag::None;
uint8_t opacity;
@ -67,13 +71,8 @@ struct SwTask : Task
}
virtual void dispose() = 0;
virtual bool clip(SwRleData* target) = 0;
virtual SwRleData* rle() = 0;
virtual ~SwTask()
{
free(transform);
}
virtual bool clip(SwRle* target) = 0;
virtual ~SwTask() {}
};
@ -81,7 +80,6 @@ struct SwShapeTask : SwTask
{
SwShape shape;
const RenderShape* rshape = nullptr;
bool cmpStroking = false;
bool clipper = false;
/* We assume that if the stroke width is greater than 2,
@ -90,7 +88,7 @@ struct SwShapeTask : SwTask
Additionally, the stroke style should not be dashed. */
bool antialiasing(float strokeWidth)
{
return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst;
return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;;
}
float validStrokeWidth()
@ -98,40 +96,34 @@ struct SwShapeTask : SwTask
if (!rshape->stroke) return 0.0f;
auto width = rshape->stroke->width;
if (mathZero(width)) return 0.0f;
if (tvg::zero(width)) return 0.0f;
if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f;
if (mathZero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f;
if (tvg::zero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f;
if (transform) return (width * sqrt(transform->e11 * transform->e11 + transform->e12 * transform->e12));
else return width;
return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12));
}
bool clip(SwRleData* target) override
bool clip(SwRle* target) override
{
if (shape.fastTrack) rleClipRect(target, &bbox);
else if (shape.rle) rleClipPath(target, shape.rle);
if (shape.fastTrack) rleClip(target, &bbox);
else if (shape.rle) rleClip(target, shape.rle);
else return false;
return true;
}
SwRleData* rle() override
{
if (!shape.rle && shape.fastTrack) {
shape.rle = rleRender(&shape.bbox);
}
return shape.rle;
}
void run(unsigned tid) override
{
if (opacity == 0 && !clipper) return; //Invisible
//Invisible
if (opacity == 0 && !clipper) {
bbox.reset();
return;
}
auto strokeWidth = validStrokeWidth();
bool visibleFill = false;
auto clipRegion = bbox;
SwBBox renderRegion{};
auto visibleFill = false;
//This checks also for the case, if the invisible shape turned to visible by alpha.
auto prepareShape = false;
@ -143,15 +135,16 @@ struct SwShapeTask : SwTask
rshape->fillColor(nullptr, nullptr, nullptr, &alpha);
alpha = MULTIPLY(alpha, opacity);
visibleFill = (alpha > 0 || rshape->fill);
shapeReset(&shape);
if (visibleFill || clipper) {
shapeReset(&shape);
if (!shapePrepare(&shape, rshape, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) {
if (!shapePrepare(&shape, rshape, transform, bbox, renderRegion, mpool, tid, clips.count > 0 ? true : false)) {
visibleFill = false;
renderRegion.reset();
}
}
}
//Fill
if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
if (flags & (RenderUpdateFlag::Path |RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
if (visibleFill || clipper) {
if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
}
@ -164,11 +157,11 @@ struct SwShapeTask : SwTask
}
}
//Stroke
if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
if (strokeWidth > 0.0f) {
shapeResetStroke(&shape, rshape, transform);
if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err;
if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderRegion, mpool, tid)) goto err;
if (auto fill = rshape->strokeFill()) {
auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
if (ctable) shapeResetStrokeFill(&shape);
@ -192,9 +185,13 @@ struct SwShapeTask : SwTask
//Clip stroke rle
if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err;
}
bbox = renderRegion; //sync
return;
err:
bbox.reset();
shapeReset(&shape);
shapeDelOutline(&shape, mpool, tid);
}
@ -206,77 +203,17 @@ struct SwShapeTask : SwTask
};
struct SwSceneTask : SwTask
{
Array<RenderData> scene; //list of paints render data (SwTask)
SwRleData* sceneRle = nullptr;
bool clip(SwRleData* target) override
{
//Only one shape
if (scene.count == 1) {
return static_cast<SwTask*>(*scene.data)->clip(target);
}
//More than one shapes
if (sceneRle) rleClipPath(target, sceneRle);
else TVGLOG("SW_ENGINE", "No clippers in a scene?");
return true;
}
SwRleData* rle() override
{
return sceneRle;
}
void run(unsigned tid) override
{
//TODO: Skip the run if the scene hasn't changed.
if (!sceneRle) sceneRle = static_cast<SwRleData*>(calloc(1, sizeof(SwRleData)));
else rleReset(sceneRle);
//Merge shapes if it has more than one shapes
if (scene.count > 1) {
//Merge first two clippers
auto clipper1 = static_cast<SwTask*>(*scene.data);
auto clipper2 = static_cast<SwTask*>(*(scene.data + 1));
rleMerge(sceneRle, clipper1->rle(), clipper2->rle());
//Unify the remained clippers
for (auto rd = scene.begin() + 2; rd < scene.end(); ++rd) {
auto clipper = static_cast<SwTask*>(*rd);
rleMerge(sceneRle, sceneRle, clipper->rle());
}
}
}
void dispose() override
{
rleFree(sceneRle);
}
};
struct SwImageTask : SwTask
{
SwImage image;
Surface* source; //Image source
const RenderMesh* mesh = nullptr; //Should be valid ptr in action
RenderSurface* source; //Image source
bool clip(SwRleData* target) override
bool clip(SwRle* target) override
{
TVGERR("SW_ENGINE", "Image is used as ClipPath?");
return true;
}
SwRleData* rle() override
{
TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?");
return nullptr;
}
void run(unsigned tid) override
{
auto clipRegion = bbox;
@ -296,10 +233,9 @@ struct SwImageTask : SwTask
imageReset(&image);
if (!image.data || image.w == 0 || image.h == 0) goto end;
if (!imagePrepare(&image, mesh, transform, clipRegion, bbox, mpool, tid)) goto end;
if (!imagePrepare(&image, transform, clipRegion, bbox, mpool, tid)) goto end;
// TODO: How do we clip the triangle mesh? Only clip non-meshed images for now
if (mesh->triangleCnt == 0 && clips.count > 0) {
if (clips.count > 0) {
if (!imageGenRle(&image, bbox, false)) goto end;
if (image.rle) {
//Clear current task memorypool here if the clippers would use the same memory pool
@ -339,7 +275,7 @@ static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{
uint8_t r, g, b, a;
if (auto fill = task->rshape->fill) {
rasterGradientShape(surface, &task->shape, fill->identifier());
rasterGradientShape(surface, &task->shape, fill, opacity);
} else {
task->rshape->fillColor(&r, &g, &b, &a);
a = MULTIPLY(opacity, a);
@ -351,7 +287,7 @@ static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity
{
uint8_t r, g, b, a;
if (auto strokeFill = task->rshape->strokeFill()) {
rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
rasterGradientStroke(surface, &task->shape, strokeFill, opacity);
} else {
if (task->rshape->strokeColor(&r, &g, &b, &a)) {
a = MULTIPLY(opacity, a);
@ -483,7 +419,7 @@ bool SwRenderer::renderImage(RenderData data)
if (task->opacity == 0) return true;
return rasterImage(surface, &task->image, task->mesh, task->transform, task->bbox, task->opacity);
return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity);
}
@ -515,27 +451,18 @@ bool SwRenderer::blend(BlendMethod method)
surface->blendMethod = method;
switch (method) {
case BlendMethod::Add:
surface->blender = opBlendAdd;
break;
case BlendMethod::Screen:
surface->blender = opBlendScreen;
case BlendMethod::Normal:
surface->blender = nullptr;
break;
case BlendMethod::Multiply:
surface->blender = opBlendMultiply;
break;
case BlendMethod::Screen:
surface->blender = opBlendScreen;
break;
case BlendMethod::Overlay:
surface->blender = opBlendOverlay;
break;
case BlendMethod::Difference:
surface->blender = opBlendDifference;
break;
case BlendMethod::Exclusion:
surface->blender = opBlendExclusion;
break;
case BlendMethod::SrcOver:
surface->blender = opBlendSrcOver;
break;
case BlendMethod::Darken:
surface->blender = opBlendDarken;
break;
@ -554,7 +481,17 @@ bool SwRenderer::blend(BlendMethod method)
case BlendMethod::SoftLight:
surface->blender = opBlendSoftLight;
break;
case BlendMethod::Difference:
surface->blender = opBlendDifference;
break;
case BlendMethod::Exclusion:
surface->blender = opBlendExclusion;
break;
case BlendMethod::Add:
surface->blender = opBlendAdd;
break;
default:
TVGLOG("SW_ENGINE", "Non supported blending option = %d", (int) method);
surface->blender = nullptr;
break;
}
@ -568,7 +505,7 @@ RenderRegion SwRenderer::region(RenderData data)
}
bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity)
bool SwRenderer::beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity)
{
if (!cmp) return false;
auto p = static_cast<SwCompositor*>(cmp);
@ -606,13 +543,50 @@ bool SwRenderer::mempool(bool shared)
}
const Surface* SwRenderer::mainSurface()
const RenderSurface* SwRenderer::mainSurface()
{
return surface;
}
Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
SwSurface* SwRenderer::request(int channelSize)
{
SwSurface* cmp = nullptr;
//Use cached data
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == channelSize) {
cmp = *p;
break;
}
}
//New Composition
if (!cmp) {
//Inherits attributes from main surface
cmp = new SwSurface(surface);
cmp->compositor = new SwCompositor;
cmp->compositor->image.data = (pixel_t*)malloc(channelSize * surface->stride * surface->h);
cmp->compositor->image.w = surface->w;
cmp->compositor->image.h = surface->h;
cmp->compositor->image.stride = surface->stride;
cmp->compositor->image.direct = true;
cmp->compositor->valid = true;
cmp->channelSize = cmp->compositor->image.channelSize = channelSize;
cmp->w = cmp->compositor->image.w;
cmp->h = cmp->compositor->image.h;
compositors.push(cmp);
}
//Sync. This may have been modified by post-processing.
cmp->data = cmp->compositor->image.data;
return cmp;
}
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
{
auto x = region.x;
auto y = region.y;
@ -624,32 +598,11 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
//Out of boundary
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
SwSurface* cmp = nullptr;
auto reqChannelSize = CHANNEL_SIZE(cs);
//Use cached data
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) {
cmp = *p;
break;
}
}
//New Composition
if (!cmp) {
//Inherits attributes from main surface
cmp = new SwSurface(surface);
cmp->compositor = new SwCompositor;
//TODO: We can optimize compositor surface size from (surface->stride x surface->h) to Parameter(w x h)
cmp->compositor->image.data = (pixel_t*)malloc(reqChannelSize * surface->stride * surface->h);
cmp->channelSize = cmp->compositor->image.channelSize = reqChannelSize;
compositors.push(cmp);
}
auto cmp = request(CHANNEL_SIZE(cs));
//Boundary Check
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + w > sw) w = (sw - x);
if (y + h > sh) h = (sh - y);
@ -660,16 +613,11 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
cmp->compositor->bbox.min.y = y;
cmp->compositor->bbox.max.x = x + w;
cmp->compositor->bbox.max.y = y + h;
cmp->compositor->image.stride = surface->stride;
cmp->compositor->image.w = surface->w;
cmp->compositor->image.h = surface->h;
cmp->compositor->image.direct = true;
cmp->data = cmp->compositor->image.data;
cmp->w = cmp->compositor->image.w;
cmp->h = cmp->compositor->image.h;
rasterClear(cmp, x, y, w, h);
/* TODO: Currently, only blending might work.
Blending and composition must be handled together. */
auto color = (surface->blender && !surface->compositor) ? 0x00ffffff : 0x00000000;
rasterClear(cmp, x, y, w, h, color);
//Switch render target
surface = cmp;
@ -678,7 +626,7 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
}
bool SwRenderer::endComposite(Compositor* cmp)
bool SwRenderer::endComposite(RenderCompositor* cmp)
{
if (!cmp) return false;
@ -691,13 +639,35 @@ bool SwRenderer::endComposite(Compositor* cmp)
//Default is alpha blending
if (p->method == CompositeMethod::None) {
return rasterImage(surface, &p->image, nullptr, nullptr, p->bbox, p->opacity);
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
return rasterImage(surface, &p->image, m, p->bbox, p->opacity);
}
return true;
}
bool SwRenderer::prepare(RenderEffect* effect)
{
switch (effect->type) {
case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast<RenderEffectGaussian*>(effect));
default: return false;
}
}
bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect)
{
auto p = static_cast<SwCompositor*>(cmp);
auto& buffer = request(surface->channelSize)->compositor->image;
switch (effect->type) {
case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast<const RenderEffectGaussian*>(effect));
default: return false;
}
}
ColorSpace SwRenderer::colorSpace()
{
if (surface) return surface->cs;
@ -717,14 +687,11 @@ void SwRenderer::dispose(RenderData data)
}
void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
if (!surface) return task;
if (flags == RenderUpdateFlag::None) return task;
//Finish previous task if it has duplicated request.
task->done();
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
@ -733,29 +700,20 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
}
task->clips = clips;
if (transform) {
if (!task->transform) task->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
*task->transform = transform->m;
} else {
if (task->transform) free(task->transform);
task->transform = nullptr;
}
task->transform = transform;
//zero size?
if (task->transform) {
if (task->transform->e11 == 0.0f && task->transform->e12 == 0.0f) return task; //zero width
if (task->transform->e21 == 0.0f && task->transform->e22 == 0.0f) return task; //zero height
}
if (task->transform.e11 == 0.0f && task->transform.e12 == 0.0f) return task; //zero width
if (task->transform.e21 == 0.0f && task->transform.e22 == 0.0f) return task; //zero height
task->opacity = opacity;
task->surface = surface;
task->mpool = mpool;
task->flags = flags;
task->bbox.min.x = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
task->bbox.min.y = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
task->bbox.max.x = mathMin(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
task->bbox.max.y = mathMin(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
task->bbox.min.x = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
task->bbox.min.y = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
task->bbox.max.x = std::min(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
task->bbox.max.y = std::min(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
if (!task->pushed) {
task->pushed = true;
@ -768,42 +726,27 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
}
RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
RenderData SwRenderer::prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwImageTask*>(data);
if (!task) task = new SwImageTask;
else task->done();
task->source = surface;
task->mesh = mesh;
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwSceneTask*>(data);
if (!task) task = new SwSceneTask;
task->scene = scene;
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto task = scene.begin(); task < scene.end(); ++task) {
static_cast<SwTask*>(*task)->done();
}
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
//prepare task
auto task = static_cast<SwShapeTask*>(data);
if (!task) {
task = new SwShapeTask;
task->rshape = &rshape;
}
if (!task) task = new SwShapeTask;
else task->done();
task->rshape = &rshape;
task->clipper = clipper;
return prepareCommon(task, transform, clips, opacity, flags);
@ -834,6 +777,10 @@ bool SwRenderer::init(uint32_t threads)
int32_t SwRenderer::init()
{
#ifdef THORVG_SW_OPENMP_SUPPORT
omp_set_num_threads(TaskScheduler::threads());
#endif
return initEngineCnt;
}

View File

@ -39,9 +39,8 @@ namespace tvg
class SwRenderer : public RenderMethod
{
public:
RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;
bool renderImage(RenderData data) override;
@ -52,18 +51,21 @@ public:
bool viewport(const RenderRegion& vp) override;
bool blend(BlendMethod method) override;
ColorSpace colorSpace() override;
const Surface* mainSurface() override;
const RenderSurface* mainSurface() override;
bool clear() override;
bool sync() override;
bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs);
bool mempool(bool shared);
Compositor* target(const RenderRegion& region, ColorSpace cs) override;
bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) override;
bool endComposite(Compositor* cmp) override;
RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override;
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;
void clearCompositors();
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
static SwRenderer* gen();
static bool init(uint32_t threads);
static int32_t init();
@ -80,7 +82,8 @@ private:
SwRenderer();
~SwRenderer();
RenderData prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
SwSurface* request(int channelSize);
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};
}

View File

@ -200,7 +200,6 @@
/* 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);
@ -221,7 +220,7 @@ struct Cell
struct RleWorker
{
SwRleData* rle;
SwRle* rle;
SwPoint cellPos;
SwPoint cellMin;
@ -239,14 +238,11 @@ struct RleWorker
SwPoint pos;
SwPoint bezStack[32 * 3 + 1];
SwPoint lineStack[32 + 1];
int levStack[32];
SwOutline* outline;
SwSpan spans[MAX_SPANS];
int spansCnt;
int ySpan;
int bandSize;
int bandShoot;
@ -304,26 +300,6 @@ static inline SwCoord HYPOT(SwPoint pt)
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<SwSpan*>(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)
{
@ -347,25 +323,26 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor
if (coverage > 255) coverage = 255;
}
if (coverage == 0) return;
//span has ushort coordinates. check limit overflow
if (x >= SHRT_MAX) {
TVGERR("SW_ENGINE", "X-coordinate overflow!");
x = SHRT_MAX;
return;
}
if (y >= SHRT_MAX) {
TVGERR("SW_ENGINE", "Y Coordinate overflow!");
y = SHRT_MAX;
TVGERR("SW_ENGINE", "Y-coordinate overflow!");
return;
}
if (coverage > 0) {
if (!rw.antiAlias) coverage = 255;
auto count = rw.spansCnt;
auto span = rw.spans + count - 1;
auto rle = rw.rle;
//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)) {
if (!rw.antiAlias) coverage = 255;
//see whether we can add this span to the current list
if (rle->size > 0) {
auto span = rle->spans + rle->size - 1;
if ((span->coverage == coverage) && (span->y == y) && (span->x + span->len == x)) {
//Clip x range
SwCoord xOver = 0;
if (x + aCount >= rw.cellMax.x) xOver -= (x + aCount - rw.cellMax.x);
@ -375,35 +352,35 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor
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;
}
//span pool is full, grow it.
if (rle->size >= rle->alloc) {
auto newSize = (rle->size > 0) ? (rle->size * 2) : 256;
if (rle->alloc < newSize) {
rle->alloc = newSize;
rle->spans = static_cast<SwSpan*>(realloc(rle->spans, rle->alloc * sizeof(SwSpan)));
}
}
//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
auto span = rle->spans + rle->size;
span->x = x;
span->y = y;
span->len = (aCount + xOver);
span->coverage = coverage;
rle->size++;
}
@ -411,9 +388,6 @@ 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;
@ -430,8 +404,6 @@ static void _sweep(RleWorker& rw)
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);
}
@ -545,98 +517,116 @@ static void _lineTo(RleWorker& rw, const SwPoint& to)
return;
}
auto diff = to - rw.pos;
auto f1 = rw.pos - SUBPIXELS(e1);
SwPoint f2;
auto line = rw.lineStack;
line[0] = to;
line[1] = rw.pos;
//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);
while (true) {
auto diff = line[0] - line[1];
auto L = HYPOT(diff);
if (L > SHRT_MAX) {
mathSplitLine(line);
++line;
continue;
}
//any other line
} else {
Area prod = diff.x * f1.y - diff.y * f1.x;
e1 = TRUNC(line[1]);
e2 = TRUNC(line[0]);
/* These macros speed up repetitive divisions by replacing them
with multiplications and right shifts. */
auto dx_r = static_cast<long>(ULONG_MAX >> PIXEL_BITS) / (diff.x);
auto dy_r = static_cast<long>(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;
}
auto f1 = line[1] - 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;
} while(e1 != e2);
/* These macros speed up repetitive divisions by replacing them
with multiplications and right shifts. */
auto dx_r = static_cast<long>(ULONG_MAX >> PIXEL_BITS) / (diff.x);
auto dy_r = static_cast<long>(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 = {line[0].x - SUBPIXELS(e2.x), line[0].y - SUBPIXELS(e2.y)};
rw.cover += (f2.y - f1.y);
rw.area += (f2.y - f1.y) * (f1.x + f2.x);
rw.pos = line[0];
if (line-- == rw.lineStack) return;
}
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;
}
@ -722,31 +712,27 @@ static void _decomposeOutline(RleWorker& rw)
auto start = UPSCALE(outline->pts[first]);
auto pt = outline->pts.data + first;
auto types = outline->types.data + first;
++types;
_moveTo(rw, UPSCALE(outline->pts[first]));
while (pt < limit) {
++pt;
++types;
//emit a single line_to
if (types[0] == SW_CURVE_TYPE_POINT) {
++pt;
++types;
_lineTo(rw, UPSCALE(*pt));
//types cubic
} else {
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;
pt += 3;
types += 3;
if (pt <= limit) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0]));
else if (pt - 1 == limit) _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start);
else goto close;
}
}
_lineTo(rw, start);
close:
_lineTo(rw, start);
first = last + 1;
}
}
@ -763,7 +749,7 @@ static int _genRle(RleWorker& rw)
}
static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *target, SwSpan *outSpans, uint32_t outSpansCnt)
static SwSpan* _intersectSpansRegion(const SwRle *clip, const SwRle *target, SwSpan *outSpans, uint32_t outSpansCnt)
{
auto out = outSpans;
auto spans = target->spans;
@ -772,7 +758,7 @@ static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *tar
auto clipEnd = clip->spans + clip->size;
while (spans < end && clipSpans < clipEnd) {
//align y coordinates.
//align y-coordinates.
if (clipSpans->y > spans->y) {
++spans;
continue;
@ -782,7 +768,7 @@ static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *tar
continue;
}
//Try clipping with all clip spans which have a same y coordinate.
//Try clipping with all clip spans which have a same y-coordinate.
auto temp = clipSpans;
while(temp < clipEnd && outSpansCnt > 0 && temp->y == clipSpans->y) {
auto sx1 = spans->x;
@ -815,7 +801,7 @@ static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *tar
}
static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t outSpansCnt)
static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRle *targetRle, SwSpan *outSpans, uint32_t outSpansCnt)
{
auto out = outSpans;
auto spans = targetRle->spans;
@ -854,47 +840,7 @@ static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRl
}
static SwSpan* _mergeSpansRegion(const SwRleData *clip1, const SwRleData *clip2, SwSpan *outSpans)
{
auto out = outSpans;
auto spans1 = clip1->spans;
auto end1 = clip1->spans + clip1->size;
auto spans2 = clip2->spans;
auto end2 = clip2->spans + clip2->size;
//list two spans up in y order
//TODO: Remove duplicated regions?
while (spans1 < end1 && spans2 < end2) {
while (spans1 < end1 && spans1->y <= spans2->y) {
*out = *spans1;
++spans1;
++out;
}
if (spans1 >= end1) break;
while (spans2 < end2 && spans2->y <= spans1->y) {
*out = *spans2;
++spans2;
++out;
}
}
//Leftovers
while (spans1 < end1) {
*out = *spans1;
++spans1;
++out;
}
while (spans2 < end2) {
*out = *spans2;
++spans2;
++out;
}
return out;
}
void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size)
void _replaceClipSpan(SwRle *rle, SwSpan* clippedSpans, uint32_t size)
{
free(rle->spans);
rle->spans = clippedSpans;
@ -906,7 +852,7 @@ void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size)
/* External Class Implementation */
/************************************************************************/
SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias)
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias)
{
constexpr auto RENDER_POOL_SIZE = 16384L;
constexpr auto BAND_SIZE = 40;
@ -929,13 +875,12 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren
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<SwOutline*>(outline);
rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64
rw.bandSize = rw.bufferSize / (sizeof(Cell) * 2); //bandSize: 256
rw.bandShoot = 0;
rw.antiAlias = antiAlias;
if (!rle) rw.rle = reinterpret_cast<SwRleData*>(calloc(1, sizeof(SwRleData)));
if (!rle) rw.rle = reinterpret_cast<SwRle*>(calloc(1, sizeof(SwRle)));
else rw.rle = rle;
//Generate RLE
@ -969,10 +914,7 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren
if (cellMod > 0) cellStart += sizeof(Cell) - cellMod;
auto cellEnd = rw.bufferSize;
cellEnd -= cellEnd % sizeof(Cell);
auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + cellEnd);
auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + rw.bufferSize);
rw.cells = reinterpret_cast<Cell*>((char*)rw.buffer + cellStart);
if (rw.cells >= cellsMax) goto reduce_bands;
@ -1025,17 +967,16 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren
error:
free(rw.rle);
rw.rle = nullptr;
return nullptr;
}
SwRleData* rleRender(const SwBBox* bbox)
SwRle* rleRender(const SwBBox* bbox)
{
auto width = static_cast<uint16_t>(bbox->max.x - bbox->min.x);
auto height = static_cast<uint16_t>(bbox->max.y - bbox->min.y);
auto rle = static_cast<SwRleData*>(malloc(sizeof(SwRleData)));
auto rle = static_cast<SwRle*>(malloc(sizeof(SwRle)));
rle->spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * height));
rle->size = height;
rle->alloc = height;
@ -1052,14 +993,14 @@ SwRleData* rleRender(const SwBBox* bbox)
}
void rleReset(SwRleData* rle)
void rleReset(SwRle* rle)
{
if (!rle) return;
rle->size = 0;
}
void rleFree(SwRleData* rle)
void rleFree(SwRle* rle)
{
if (!rle) return;
if (rle->spans) free(rle->spans);
@ -1067,46 +1008,7 @@ void rleFree(SwRleData* rle)
}
void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2)
{
if (!rle || (!clip1 && !clip2)) return;
if (clip1 && clip1->size == 0 && clip2 && clip2->size == 0) return;
TVGLOG("SW_ENGINE", "Unifying Rle!");
//clip1 is empty, just copy clip2
if (!clip1 || clip1->size == 0) {
if (clip2) {
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (clip2->size)));
memcpy(spans, clip2->spans, clip2->size);
_replaceClipSpan(rle, spans, clip2->size);
} else {
_replaceClipSpan(rle, nullptr, 0);
}
return;
}
//clip2 is empty, just copy clip1
if (!clip2 || clip2->size == 0) {
if (clip1) {
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (clip1->size)));
memcpy(spans, clip1->spans, clip1->size);
_replaceClipSpan(rle, spans, clip1->size);
} else {
_replaceClipSpan(rle, nullptr, 0);
}
return;
}
auto spanCnt = clip1->size + clip2->size;
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * spanCnt));
auto spansEnd = _mergeSpansRegion(clip1, clip2, spans);
_replaceClipSpan(rle, spans, spansEnd - spans);
}
void rleClipPath(SwRleData *rle, const SwRleData *clip)
void rleClip(SwRle *rle, const SwRle *clip)
{
if (rle->size == 0 || clip->size == 0) return;
auto spanCnt = rle->size > clip->size ? rle->size : clip->size;
@ -1115,11 +1017,11 @@ void rleClipPath(SwRleData *rle, const SwRleData *clip)
_replaceClipSpan(rle, spans, spansEnd - spans);
TVGLOG("SW_ENGINE", "Using ClipPath!");
TVGLOG("SW_ENGINE", "Using Path Clipping!");
}
void rleClipRect(SwRleData *rle, const SwBBox* clip)
void rleClip(SwRle *rle, const SwBBox* clip)
{
if (rle->size == 0) return;
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (rle->size)));
@ -1127,7 +1029,7 @@ void rleClipRect(SwRleData *rle, const SwBBox* clip)
_replaceClipSpan(rle, spans, spansEnd - spans);
TVGLOG("SW_ENGINE", "Using ClipRect!");
TVGLOG("SW_ENGINE", "Using Box Clipping!");
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -25,7 +25,6 @@
#include "tvgSwCommon.h"
#include "tvgMath.h"
#include "tvgLines.h"
/************************************************************************/
/* Internal Class Implementation */
@ -52,7 +51,7 @@ static bool _outlineEnd(SwOutline& outline)
}
static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform, bool closed = false)
static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix& transform, bool closed = false)
{
//make it a contour, if the last contour is not closed yet.
if (!closed) _outlineEnd(outline);
@ -63,14 +62,14 @@ static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* tr
}
static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform)
static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix& transform)
{
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix& transform)
{
outline.pts.push(mathTransform(ctrl1, transform));
outline.types.push(SW_CURVE_TYPE_CUBIC);
@ -102,15 +101,15 @@ static bool _outlineClose(SwOutline& outline)
}
static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform)
static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix& transform)
{
Line cur = {dash.ptCur, *to};
auto len = lineLength(cur.pt1, cur.pt2);
auto len = cur.length();
if (mathZero(len)) {
if (tvg::zero(len)) {
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
//draw the current line fully
} else if (len < dash.curLen) {
} else if (len <= dash.curLen) {
dash.curLen -= len;
if (!dash.curOpGap) {
if (dash.move) {
@ -125,7 +124,7 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans
Line left, right;
if (dash.curLen > 0) {
len -= dash.curLen;
lineSplitAt(cur, dash.curLen, left, right);
cur.split(dash.curLen, left, right);
if (!dash.curOpGap) {
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
_outlineMoveTo(*dash.outline, &left.pt1, transform);
@ -163,15 +162,15 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans
}
static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix& transform)
{
Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
auto len = bezLength(cur);
auto len = cur.length();
//draw the current line fully
if (mathZero(len)) {
if (tvg::zero(len)) {
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
} else if (len < dash.curLen) {
} else if (len <= dash.curLen) {
dash.curLen -= len;
if (!dash.curOpGap) {
if (dash.move) {
@ -186,7 +185,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
Bezier left, right;
if (dash.curLen > 0) {
len -= dash.curLen;
bezSplitAt(cur, dash.curLen, left, right);
cur.split(dash.curLen, left, right);
if (!dash.curOpGap) {
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
_outlineMoveTo(*dash.outline, &left.start, transform);
@ -213,7 +212,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
}
_outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform);
}
if (dash.curLen < 1 && TO_SWCOORD(len) > 1) {
if (dash.curLen < 0.1f && TO_SWCOORD(len) > 1) {
//move to next dash
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
@ -224,7 +223,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
}
static void _dashClose(SwDashStroke& dash, const Matrix* transform)
static void _dashClose(SwDashStroke& dash, const Matrix& transform)
{
_dashLineTo(dash, &dash.ptStart, transform);
}
@ -248,7 +247,86 @@ static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const
}
static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, float length, SwMpool* mpool, unsigned tid)
static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length, float trimBegin, float trimEnd)
{
auto begin = length * trimBegin;
auto end = length * trimEnd;
//default
if (end > begin) {
if (begin > 0.0f) dash->cnt = 4;
else dash->cnt = 2;
//looping
} else dash->cnt = 3;
if (dash->cnt == 2) {
dash->pattern[0] = end - begin;
dash->pattern[1] = length - (end - begin);
} else if (dash->cnt == 3) {
dash->pattern[0] = end;
dash->pattern[1] = (begin - end);
dash->pattern[2] = length - begin;
} else {
dash->pattern[0] = 0; //zero dash to start with a space.
dash->pattern[1] = begin;
dash->pattern[2] = end - begin;
dash->pattern[3] = length - end;
}
}
static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32_t shiftCmds, bool subpath)
{
const PathCommand* cmds = rshape->path.cmds.data + shiftCmds;
auto cmdCnt = rshape->path.cmds.count - shiftCmds;
const Point* pts = rshape->path.pts.data + shiftPts;
auto ptsCnt = rshape->path.pts.count - shiftPts;
//No actual shape data
if (cmdCnt <= 0 || ptsCnt <= 0) return 0.0f;
const Point* close = nullptr;
auto len = 0.0f;
//must begin with moveTo
if (cmds[0] == PathCommand::MoveTo) {
close = pts;
cmds++;
pts++;
cmdCnt--;
}
while (cmdCnt-- > 0) {
switch (*cmds) {
case PathCommand::Close: {
len += length(pts - 1, close);
if (subpath) return len;
break;
}
case PathCommand::MoveTo: {
if (subpath) return len;
close = pts;
++pts;
break;
}
case PathCommand::LineTo: {
len += length(pts - 1, pts);
++pts;
break;
}
case PathCommand::CubicTo: {
len += Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.length();
pts += 3;
break;
}
}
++cmds;
}
return len;
}
static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix& transform, bool trimmed, SwMpool* mpool, unsigned tid)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
@ -258,52 +336,28 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return nullptr;
auto startPts = pts;
auto startCmds = cmds;
SwDashStroke dash;
auto offset = 0.0f;
auto trimmed = false;
dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset);
auto simultaneous = rshape->stroke->trim.simultaneous;
float trimBegin = 0.0f, trimEnd = 1.0f;
if (trimmed) rshape->stroke->strokeTrim(trimBegin, trimEnd);
//dash by trimming.
if (length > 0.0f && dash.cnt == 0) {
auto begin = length * rshape->stroke->trim.begin;
auto end = length * rshape->stroke->trim.end;
//TODO: mix trimming + dash style
//default
if (end > begin) {
if (begin > 0.0f) dash.cnt += 4;
else dash.cnt += 2;
//looping
} else dash.cnt += 3;
dash.pattern = (float*)malloc(sizeof(float) * dash.cnt);
if (dash.cnt == 2) {
dash.pattern[0] = end - begin;
dash.pattern[1] = length - (end - begin);
} else if (dash.cnt == 3) {
dash.pattern[0] = end;
dash.pattern[1] = (begin - end);
dash.pattern[2] = length - begin;
} else {
dash.pattern[0] = 0; //zero dash to start with a space.
dash.pattern[1] = begin;
dash.pattern[2] = end - begin;
dash.pattern[3] = length - end;
}
trimmed = true;
//just a dash style.
if (dash.cnt == 0) {
if (trimmed) dash.pattern = (float*)malloc(sizeof(float) * 4);
else return nullptr;
} else {
if (dash.cnt == 0) return nullptr;
//TODO: handle dash + trim - for now trimming ignoring is forced
trimmed = false;
}
//offset?
//offset
auto patternLength = 0.0f;
uint32_t offIdx = 0;
if (!mathZero(offset)) {
if (!tvg::zero(offset)) {
for (size_t i = 0; i < dash.cnt; ++i) patternLength += dash.pattern[i];
bool isOdd = dash.cnt % 2;
if (isOdd) patternLength *= 2;
@ -322,6 +376,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
//must begin with moveTo
if (cmds[0] == PathCommand::MoveTo) {
if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous), trimBegin, trimEnd);
_dashMoveTo(dash, offIdx, offset, pts);
cmds++;
pts++;
@ -334,8 +389,12 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
break;
}
case PathCommand::MoveTo: {
if (rshape->stroke->trim.individual) _dashMoveTo(dash, pts);
else _dashMoveTo(dash, offIdx, offset, pts);
if (trimmed) {
if (simultaneous) {
_trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true), trimBegin, trimEnd);
_dashMoveTo(dash, offIdx, offset, pts);
} else _dashMoveTo(dash, pts);
} else _dashMoveTo(dash, offIdx, offset, pts);
++pts;
break;
}
@ -361,60 +420,11 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
}
static float _outlineLength(const RenderShape* rshape)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.pts.count;
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return 0.0f;
const Point* close = nullptr;
auto length = 0.0f;
auto slength = -1.0f;
auto simultaneous = !rshape->stroke->trim.individual;
//Compute the whole length
while (cmdCnt-- > 0) {
switch (*cmds) {
case PathCommand::Close: {
length += mathLength(pts - 1, close);
//retrieve the max length of the shape if the simultaneous mode.
if (simultaneous) {
if (slength < length) slength = length;
length = 0.0f;
}
break;
}
case PathCommand::MoveTo: {
close = pts;
++pts;
break;
}
case PathCommand::LineTo: {
length += mathLength(pts - 1, pts);
++pts;
break;
}
case PathCommand::CubicTo: {
length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)});
pts += 3;
break;
}
}
++cmds;
}
if (simultaneous && slength > length) return slength;
else return length;
}
static bool _axisAlignedRect(const SwOutline* outline)
{
//Fast Track: axis-aligned rectangle?
if (outline->pts.count != 5) return false;
if (outline->types[2] == SW_CURVE_TYPE_CUBIC) return false;
auto pt1 = outline->pts.data + 0;
auto pt2 = outline->pts.data + 1;
@ -430,7 +440,7 @@ static bool _axisAlignedRect(const SwOutline* outline)
}
static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite)
static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix& transform, SwMpool* mpool, unsigned tid, bool hasComposite)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
@ -486,12 +496,11 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix*
/* External Class Implementation */
/************************************************************************/
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
{
if (!_genOutline(shape, rshape, 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
@ -569,7 +578,7 @@ void shapeDelStroke(SwShape* shape)
}
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform)
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& transform)
{
if (!shape->stroke) shape->stroke = static_cast<SwStroke*>(calloc(1, sizeof(SwStroke)));
auto stroke = shape->stroke;
@ -580,18 +589,17 @@ void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* t
}
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
{
SwOutline* shapeOutline = nullptr;
SwOutline* strokeOutline = nullptr;
auto dashStroking = false;
auto ret = true;
auto length = rshape->strokeTrim() ? _outlineLength(rshape) : 0.0f;
//Dash style (+trimming)
if (rshape->stroke->dashCnt > 0 || length > 0) {
shapeOutline = _genDashOutline(rshape, transform, length, mpool, tid);
auto trimmed = rshape->strokeTrim();
if (rshape->stroke->dashCnt > 0 || trimmed) {
shapeOutline = _genDashOutline(rshape, transform, trimmed, mpool, tid);
if (!shapeOutline) return false;
dashStroking = true;
//Normal style
@ -624,13 +632,13 @@ clear:
}
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_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, uint8_t opacity, bool ctable)
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable);
}

View File

@ -241,7 +241,7 @@ static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
} else {
//this is a mitered (pointed) or beveled (truncated) corner
auto rotate = SIDE_TO_ROTATE(side);
auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false;
auto bevel = stroke.join == StrokeJoin::Bevel;
SwFixed phi = 0;
SwFixed thcos = 0;
@ -444,13 +444,23 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
//initialize with current direction
angleIn = angleOut = angleMid = stroke.angleIn;
if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
auto valid = mathCubicAngle(arc, angleIn, angleMid, angleOut);
//valid size
if (valid > 0 && arc < limit) {
if (stroke.firstPt) stroke.angleIn = angleIn;
mathSplitCubic(arc);
arc += 3;
continue;
}
//ignoreable size
if (valid < 0 && arc == bezStack) {
stroke.center = to;
return;
}
//small size
if (firstArc) {
firstArc = false;
//process corner if necessary
@ -808,18 +818,13 @@ void strokeFree(SwStroke* stroke)
}
void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* transform)
void strokeReset(SwStroke* stroke, const RenderShape* rshape, 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->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));
stroke->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap();
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit()) << 16;
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit() * 65536.0f);
//Save line join: it can be temporarily changed when stroking curves...
stroke->joinSaved = stroke->join = rshape->strokeJoin();
@ -853,31 +858,25 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
//A contour cannot start with a cubic control point
if (type == SW_CURVE_TYPE_CUBIC) return false;
++types;
auto closed = outline.closed.data ? outline.closed.data[i]: false;
_beginSubPath(*stroke, start, closed);
while (pt < limit) {
++pt;
++types;
//emit a single line_to
if (types[0] == SW_CURVE_TYPE_POINT) {
++pt;
++types;
_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;
pt += 3;
types += 3;
if (pt <= limit) _cubicTo(*stroke, pt[-2], pt[-1], pt[0]);
else if (pt - 1 == limit) _cubicTo(*stroke, pt[-2], pt[-1], start);
else goto close;
}
}
close:

View File

@ -112,6 +112,7 @@ struct TaskScheduler
} //namespace
#endif //_TVG_TASK_SCHEDULER_H_
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -38,9 +38,8 @@
/************************************************************************/
Text::Text() : pImpl(new Impl)
Text::Text() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_TEXT;
}
@ -74,6 +73,21 @@ Result Text::load(const std::string& path) noexcept
}
Result Text::load(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy) noexcept
{
if (!name || (size == 0 && data)) return Result::InvalidArguments;
//unload font
if (!data) {
if (LoaderMgr::retrieve(name)) return Result::Success;
return Result::InsufficientCondition;
}
if (!LoaderMgr::loader(name, data, size, mimeType, copy)) return Result::NonSupport;
return Result::Success;
}
Result Text::unload(const std::string& path) noexcept
{
if (LoaderMgr::retrieve(path)) return Result::Success;
@ -83,20 +97,13 @@ Result Text::unload(const std::string& path) noexcept
Result Text::fill(uint8_t r, uint8_t g, uint8_t b) noexcept
{
if (!pImpl->paint) return Result::InsufficientCondition;
return pImpl->fill(r, g, b);
return pImpl->shape->fill(r, g, b);
}
Result Text::fill(unique_ptr<Fill> f) noexcept
{
if (!pImpl->paint) return Result::InsufficientCondition;
auto p = f.release();
if (!p) return Result::MemoryCorruption;
return pImpl->fill(p);
return pImpl->shape->fill(std::move(f));
}
@ -106,9 +113,9 @@ unique_ptr<Text> Text::gen() noexcept
}
uint32_t Text::identifier() noexcept
Type Text::type() const noexcept
{
return TVG_CLASS_ID_TEXT;
return Type::Text;
}
#endif /* LV_USE_THORVG_INTERNAL */

View File

@ -29,37 +29,27 @@
#include <cstring>
#include "tvgShape.h"
#include "tvgFill.h"
#ifdef THORVG_TTF_LOADER_SUPPORT
#include "tvgTtfLoader.h"
#else
#include "tvgLoader.h"
#endif
#include "tvgLoader.h"
struct Text::Impl
{
FontLoader* loader = nullptr;
Shape* paint = nullptr;
Text* paint;
Shape* shape;
char* utf8 = nullptr;
float fontSize;
bool italic = false;
bool changed = false;
Impl(Text* p) : paint(p), shape(Shape::gen().release())
{
}
~Impl()
{
free(utf8);
LoaderMgr::retrieve(loader);
delete(paint);
}
Result fill(uint8_t r, uint8_t g, uint8_t b)
{
return paint->fill(r, g, b);
}
Result fill(Fill* f)
{
return paint->fill(cast<Fill>(f));
delete(shape);
}
Result text(const char* utf8)
@ -77,6 +67,11 @@ struct Text::Impl
auto loader = LoaderMgr::loader(name);
if (!loader) return Result::InsufficientCondition;
if (style && strstr(style, "italic")) italic = true;
else italic = false;
fontSize = size;
//Same resource has been loaded.
if (this->loader == loader) {
this->loader->sharing--; //make it sure the reference counting.
@ -86,52 +81,44 @@ struct Text::Impl
}
this->loader = static_cast<FontLoader*>(loader);
if (!paint) paint = Shape::gen().release();
fontSize = size;
if (style && strstr(style, "italic")) italic = true;
changed = true;
return Result::Success;
}
RenderRegion bounds(RenderMethod* renderer)
{
if (paint) return P(paint)->bounds(renderer);
else return {0, 0, 0, 0};
return P(shape)->bounds(renderer);
}
bool render(RenderMethod* renderer)
{
if (paint) return PP(paint)->render(renderer);
return false;
if (!loader) return true;
renderer->blend(PP(paint)->blendMethod);
return PP(shape)->render(renderer);
}
bool load()
{
if (!loader) return false;
loader->request(shape, utf8);
//reload
if (changed) {
loader->request(paint, utf8, italic);
loader->read();
changed = false;
}
if (paint) {
loader->resize(paint, fontSize, fontSize);
return true;
}
return false;
return loader->transform(shape, fontSize, italic);
}
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{
if (!load()) return nullptr;
//transform the gradient coordinates based on the final scaled font.
if (P(paint)->flag & RenderUpdateFlag::Gradient) {
auto fill = P(paint)->rs.fill;
auto fill = P(shape)->rs.fill;
if (fill && P(shape)->flag & RenderUpdateFlag::Gradient) {
auto scale = 1.0f / loader->scale;
if (fill->identifier() == TVG_CLASS_ID_LINEAR) {
if (fill->type() == Type::LinearGradient) {
P(static_cast<LinearGradient*>(fill))->x1 *= scale;
P(static_cast<LinearGradient*>(fill))->y1 *= scale;
P(static_cast<LinearGradient*>(fill))->x2 *= scale;
@ -145,23 +132,25 @@ struct Text::Impl
P(static_cast<RadialGradient*>(fill))->fr *= scale;
}
}
return PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper);
return PP(shape)->update(renderer, transform, clips, opacity, pFlag, false);
}
bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking)
{
if (!load() || !paint) return false;
paint->bounds(x, y, w, h, true);
if (!load()) return false;
PP(shape)->bounds(x, y, w, h, true, true, false);
return true;
}
Paint* duplicate()
Paint* duplicate(Paint* ret)
{
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
load();
auto ret = Text::gen().release();
auto dup = ret->pImpl;
if (paint) dup->paint = static_cast<Shape*>(paint->duplicate());
auto text = Text::gen().release();
auto dup = text->pImpl;
P(shape)->duplicate(dup->shape);
if (loader) {
dup->loader = loader;
@ -172,7 +161,7 @@ struct Text::Impl
dup->italic = italic;
dup->fontSize = fontSize;
return ret;
return text;
}
Iterator* iterator()

View File

@ -43,40 +43,49 @@ struct WgCanvas::Impl
/************************************************************************/
#ifdef THORVG_WG_RASTER_SUPPORT
WgCanvas::WgCanvas() : Canvas(WgRenderer::gen()), pImpl(new Impl)
WgCanvas::WgCanvas() : Canvas(WgRenderer::gen()), pImpl(nullptr)
#else
WgCanvas::WgCanvas() : Canvas(nullptr), pImpl(nullptr)
#endif
{
}
WgCanvas::~WgCanvas()
{
delete pImpl;
#ifdef THORVG_WG_RASTER_SUPPORT
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
renderer->target(nullptr, 0, 0);
#endif
}
Result WgCanvas::target(void* window, uint32_t w, uint32_t h) noexcept
Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h, void* device) noexcept
{
#ifdef THORVG_WG_RASTER_SUPPORT
if (!window) return Result::InvalidArguments;
if ((w == 0) || (h == 0)) return Result::InvalidArguments;
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
return Result::InsufficientCondition;
}
if (!instance || !surface || (w == 0) || (h == 0)) return Result::InvalidArguments;
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target(window, w, h)) return Result::Unknown;
if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h, (WGPUDevice)device)) return Result::Unknown;
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
renderer->viewport(Canvas::pImpl->vport);
//Paints must be updated again with this new target.
Canvas::pImpl->needRefresh();
Canvas::pImpl->status = Status::Damaged;
return Result::Success;
#endif
return Result::NonSupport;
}
unique_ptr<WgCanvas> WgCanvas::gen() noexcept
{
#ifdef THORVG_WG_RASTER_SUPPORT

View File

@ -495,13 +495,13 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt
key[0] = '\0';
val[0] = '\0';
if (next == nullptr && sep != nullptr) {
if (sep != nullptr && next == 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) {
} else if (sep != nullptr && sep < next) {
memcpy(key, buf, sep - buf);
key[sep - buf] = '\0';
@ -525,8 +525,9 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt
}
}
if (!next) break;
buf = next + 1;
} while (next != nullptr);
} while (true);
return true;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

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