feat(thorvg): update thorvg to 0.15.3 (#7103)
Signed-off-by: lhdjply <lhdjply@126.com>
@ -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"
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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 */
|
||||
/************************************************************************/
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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_
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -58,7 +58,6 @@ struct Fill::Impl
|
||||
uint32_t cnt = 0;
|
||||
FillSpread spread;
|
||||
DuplicateMethod<Fill>* dup = nullptr;
|
||||
uint8_t id;
|
||||
|
||||
~Impl()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -103,7 +103,7 @@ struct Inlist
|
||||
if (element == tail) tail = element->prev;
|
||||
}
|
||||
|
||||
bool empty()
|
||||
bool empty() const
|
||||
{
|
||||
return head ? false : true;
|
||||
}
|
||||
|
@ -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 */
|
@ -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_
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
104
src/libs/thorvg/tvgLottieCommon.h
Normal 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 */
|
||||
|
@ -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);
|
||||
|
@ -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) {}
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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 ∠
|
||||
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;
|
||||
|
401
src/libs/thorvg/tvgLottieModifier.cpp
Normal 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 */
|
||||
|
77
src/libs/thorvg/tvgLottieModifier.h
Normal 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 */
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -89,7 +89,7 @@ bool LookaheadParserHandler::nextArrayValue()
|
||||
|
||||
int LookaheadParserHandler::getInt()
|
||||
{
|
||||
if (state != kHasNumber || !val.IsInt()) {
|
||||
if (state != kHasNumber) {
|
||||
Error();
|
||||
return 0;
|
||||
}
|
||||
|
@ -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_
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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_
|
||||
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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_
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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_
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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, '/');
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -72,5 +72,6 @@ size_t svgUtilURLDecode(const char *src, char** dst)
|
||||
return idx + 1;
|
||||
}
|
||||
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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_ */
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
214
src/libs/thorvg/tvgSwPostEffect.cpp
Normal 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 blur’s 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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -112,6 +112,7 @@ struct TaskScheduler
|
||||
} //namespace
|
||||
|
||||
#endif //_TVG_TASK_SCHEDULER_H_
|
||||
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |