mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-23 22:58:44 +00:00
Compare commits
10 commits
v1.0-pre24
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7fc8e616da | ||
![]() |
bada5691bc | ||
![]() |
16604a873a | ||
![]() |
c3d1e6e519 | ||
![]() |
930de44359 | ||
![]() |
3ca2c0edfd | ||
![]() |
a79f9788c1 | ||
![]() |
d85952b252 | ||
![]() |
d8bbb0df31 | ||
![]() |
d44098180c |
50 changed files with 1726 additions and 1840 deletions
|
@ -162,12 +162,12 @@ struct UserExample : tvgexam::Example
|
|||
blender(canvas, "SoftLight", tvg::BlendMethod::SoftLight, 900.0f, 0.0f, data);
|
||||
blender(canvas, "Difference", tvg::BlendMethod::Difference, 900.0f, 150.0f, data);
|
||||
blender(canvas, "Exclusion", tvg::BlendMethod::Exclusion, 900.0f, 300.0f, data);
|
||||
blender(canvas, "Hue (Not Supported)", tvg::BlendMethod::Hue, 900.0f, 450.0f, data);
|
||||
blender(canvas, "Saturation (Not Supported)", tvg::BlendMethod::Saturation, 900.0f, 600.0f, data);
|
||||
blender(canvas, "Color (Not Supported)", tvg::BlendMethod::Color, 900.0f, 750.0f, data);
|
||||
blender(canvas, "Luminosity (Not Supported)", tvg::BlendMethod::Luminosity, 900.0f, 900.0f, data);
|
||||
blender(canvas, "Hue", tvg::BlendMethod::Hue, 900.0f, 450.0f, data);
|
||||
blender(canvas, "Saturation", tvg::BlendMethod::Saturation, 900.0f, 600.0f, data);
|
||||
blender(canvas, "Color", tvg::BlendMethod::Color, 900.0f, 750.0f, data);
|
||||
blender(canvas, "Luminosity", tvg::BlendMethod::Luminosity, 900.0f, 900.0f, data);
|
||||
blender(canvas, "Add", tvg::BlendMethod::Add, 900.0f, 1050.0f, data);
|
||||
blender(canvas, "HardMix (Not Supported)", tvg::BlendMethod::HardMix, 900.0f, 1200.0f, data);
|
||||
blender(canvas, "HardMix", tvg::BlendMethod::HardMix, 900.0f, 1200.0f, data);
|
||||
|
||||
free(data);
|
||||
|
||||
|
|
11
inc/thorvg.h
11
inc/thorvg.h
|
@ -209,12 +209,13 @@ enum class BlendMethod : uint8_t
|
|||
SoftLight, ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (255 - 2 * S) * (D * D) + (2 * S * D)
|
||||
Difference, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S)
|
||||
Exclusion, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. S + D - (2 * S * D)
|
||||
Hue, ///< Reserved. Not supported.
|
||||
Saturation, ///< Reserved. Not supported.
|
||||
Color, ///< Reserved. Not supported.
|
||||
Luminosity, ///< Reserved. Not supported.
|
||||
Hue, ///< Combine with HSL(Sh + Ds + Dl) then convert it to RGB.
|
||||
Saturation, ///< Combine with HSL(Dh + Ss + Dl) then convert it to RGB.
|
||||
Color, ///< Combine with HSL(Sh + Ss + Dl) then convert it to RGB.
|
||||
Luminosity, ///< Combine with HSL(Dh + Ds + Sl) then convert it to RGB.
|
||||
Add, ///< Simply adds pixel values of one layer with the other. (S + D)
|
||||
HardMix ///< Reserved. Not supported.
|
||||
HardMix, ///< Adds S and D; result is 255 if the sum is greater than or equal to 255, otherwise 0.
|
||||
Composition = 255 ///< Used for intermediate composition. @since 1.0
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -159,12 +159,13 @@ typedef enum {
|
|||
TVG_BLEND_METHOD_SOFTLIGHT, ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (1 - 2 * S) * (D ^ 2) + (2 * S * D)
|
||||
TVG_BLEND_METHOD_DIFFERENCE, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S)
|
||||
TVG_BLEND_METHOD_EXCLUSION, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. s + d - (2 * s * d)
|
||||
TVG_BLEND_METHOD_HUE, ///< Reserved. Not supported.
|
||||
TVG_BLEND_METHOD_SATURATION, ///< Reserved. Not supported.
|
||||
TVG_BLEND_METHOD_COLOR, ///< Reserved. Not supported.
|
||||
TVG_BLEND_METHOD_LUMINOSITY, ///< Reserved. Not supported.
|
||||
TVG_BLEND_METHOD_HUE, ///< Combine with HSL(Sh + Ds + Dl) then convert it to RGB.
|
||||
TVG_BLEND_METHOD_SATURATION, ///< Combine with HSL(Dh + Ss + Dl) then convert it to RGB.
|
||||
TVG_BLEND_METHOD_COLOR, ///< Combine with HSL(Sh + Ss + Dl) then convert it to RGB.
|
||||
TVG_BLEND_METHOD_LUMINOSITY, ///< Combine with HSL(Dh + Ds + Sl) then convert it to RGB.
|
||||
TVG_BLEND_METHOD_ADD, ///< Simply adds pixel values of one layer with the other. (S + D)
|
||||
TVG_BLEND_METHOD_HARDMIX ///< Reserved. Not supported.
|
||||
TVG_BLEND_METHOD_HARDMIX, ///< Adds S and D; result is 255 if the sum is greater than or equal to 255, otherwise 0.
|
||||
TVG_BLEND_METHOD_COMPOSITION = 255 ///< Used for intermediate composition. @since 1.0
|
||||
} Tvg_Blend_Method;
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
source_file = [
|
||||
'tvgArray.h',
|
||||
'tvgColor.h',
|
||||
'tvgCompressor.h',
|
||||
'tvgInlist.h',
|
||||
'tvgLock.h',
|
||||
'tvgMath.h',
|
||||
'tvgStr.h',
|
||||
'tvgColor.cpp',
|
||||
'tvgCompressor.cpp',
|
||||
'tvgMath.cpp',
|
||||
'tvgStr.cpp'
|
||||
|
|
67
src/common/tvgColor.cpp
Normal file
67
src/common/tvgColor.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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 "tvgMath.h"
|
||||
#include "tvgColor.h"
|
||||
namespace tvg
|
||||
{
|
||||
|
||||
void hsl2rgb(float h, float s, float l, uint8_t& r, uint8_t& g, uint8_t& b)
|
||||
{
|
||||
if (tvg::zero(s)) {
|
||||
r = g = b = (uint8_t)nearbyint(l * 255.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tvg::equal(h, 360.0f)) {
|
||||
h = 0.0f;
|
||||
} else {
|
||||
h = fmod(h, 360.0f);
|
||||
if (h < 0.0f) h += 360.0f;
|
||||
h /= 60.0f;
|
||||
}
|
||||
|
||||
auto v = (l <= 0.5f) ? (l * (1.0f + s)) : (l + s - (l * s));
|
||||
auto p = l + l - v;
|
||||
auto sv = tvg::zero(v) ? 0.0f : (v - p) / v;
|
||||
auto i = static_cast<uint8_t>(h);
|
||||
auto f = h - i;
|
||||
auto vsf = v * sv * f;
|
||||
auto t = p + vsf;
|
||||
auto q = v - vsf;
|
||||
float tr, tg, tb;
|
||||
|
||||
switch (i) {
|
||||
case 0: tr = v; tg = t; tb = p; break;
|
||||
case 1: tr = q; tg = v; tb = p; break;
|
||||
case 2: tr = p; tg = v; tb = t; break;
|
||||
case 3: tr = p; tg = q; tb = v; break;
|
||||
case 4: tr = t; tg = p; tb = v; break;
|
||||
case 5: tr = v; tg = p; tb = q; break;
|
||||
default: tr = tg = tb = 0.0f; break;
|
||||
}
|
||||
r = (uint8_t)nearbyint(tr * 255.0f);
|
||||
g = (uint8_t)nearbyint(tg * 255.0f);
|
||||
b = (uint8_t)nearbyint(tb * 255.0f);
|
||||
}
|
||||
|
||||
}
|
49
src/common/tvgColor.h
Normal file
49
src/common/tvgColor.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2025 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _TVG_COLOR_H_
|
||||
#define _TVG_COLOR_H_
|
||||
|
||||
|
||||
#include "tvgCommon.h"
|
||||
|
||||
namespace tvg
|
||||
{
|
||||
struct RGB
|
||||
{
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
struct RGBA
|
||||
{
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
|
||||
struct HSL
|
||||
{
|
||||
float h, s, l;
|
||||
};
|
||||
|
||||
void hsl2rgb(float h, float s, float l, uint8_t& r, uint8_t& g, uint8_t& b);
|
||||
}
|
||||
|
||||
#endif //_TVG_COLOR_H_
|
|
@ -189,7 +189,6 @@ Point operator*(const Point& pt, const Matrix& m);
|
|||
Point normal(const Point& p1, const Point& p2);
|
||||
void normalize(Point& pt);
|
||||
|
||||
|
||||
static inline constexpr const Point operator*=(Point& pt, const Matrix* m)
|
||||
{
|
||||
if (m) pt *= *m;
|
||||
|
@ -313,6 +312,13 @@ static inline Point operator*(const Point& lhs, const float rhs)
|
|||
}
|
||||
|
||||
|
||||
static inline void operator*=(Point& lhs, const float rhs)
|
||||
{
|
||||
lhs.x *= rhs;
|
||||
lhs.y *= rhs;
|
||||
}
|
||||
|
||||
|
||||
static inline Point operator*(const float& lhs, const Point& rhs)
|
||||
{
|
||||
return {lhs * rhs.x, lhs * rhs.y};
|
||||
|
|
|
@ -260,7 +260,7 @@ bool LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child,
|
|||
|
||||
ctx->merging = nullptr;
|
||||
auto color = stroke->color(frameNo, tween, exps);
|
||||
ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], opacity);
|
||||
ctx->propagator->strokeFill(color.r, color.g, color.b, opacity);
|
||||
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, tween, exps);
|
||||
|
||||
return false;
|
||||
|
@ -296,7 +296,7 @@ bool LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, f
|
|||
|
||||
ctx->merging = nullptr;
|
||||
auto color = fill->color(frameNo, tween, exps);
|
||||
ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], opacity);
|
||||
ctx->propagator->fill(color.r, color.g, color.b, opacity);
|
||||
ctx->propagator->fillRule(fill->rule);
|
||||
|
||||
if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true);
|
||||
|
@ -916,7 +916,7 @@ static void _fontText(TextDocument& doc, Scene* scene)
|
|||
}
|
||||
|
||||
txt->text(token);
|
||||
txt->fill(doc.color.rgb[0], doc.color.rgb[1], doc.color.rgb[2]);
|
||||
txt->fill(doc.color.r, doc.color.g, doc.color.b);
|
||||
|
||||
float width;
|
||||
txt->bounds(nullptr, nullptr, &width, nullptr);
|
||||
|
@ -1044,14 +1044,14 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
|
|||
}
|
||||
}
|
||||
}
|
||||
shape->fill(doc.color.rgb[0], doc.color.rgb[1], doc.color.rgb[2]);
|
||||
shape->fill(doc.color.r, doc.color.g, doc.color.b);
|
||||
shape->translate(cursor.x - textGroupMatrix.e13, cursor.y - textGroupMatrix.e23);
|
||||
shape->opacity(255);
|
||||
|
||||
if (doc.stroke.width > 0.0f) {
|
||||
shape->strokeJoin(StrokeJoin::Round);
|
||||
shape->strokeWidth(doc.stroke.width / scale);
|
||||
shape->strokeFill(doc.stroke.color.rgb[0], doc.stroke.color.rgb[1], doc.stroke.color.rgb[2]);
|
||||
shape->strokeFill(doc.stroke.color.r, doc.stroke.color.g, doc.stroke.color.b);
|
||||
shape->order(doc.stroke.below);
|
||||
}
|
||||
|
||||
|
@ -1088,12 +1088,12 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
|
|||
range->color(frameNo, color, strokeColor, f, tween, exps);
|
||||
|
||||
fillOpacity = (uint8_t)(fillOpacity - f * (fillOpacity - range->style.fillOpacity(frameNo, tween, exps)));
|
||||
shape->fill(color.rgb[0], color.rgb[1], color.rgb[2], fillOpacity);
|
||||
shape->fill(color.r, color.g, color.b, fillOpacity);
|
||||
|
||||
if (range->style.flags.strokeWidth) shape->strokeWidth(f * range->style.strokeWidth(frameNo, tween, exps) / scale);
|
||||
if (shape->strokeWidth() > 0.0f) {
|
||||
strokeOpacity = (uint8_t)(strokeOpacity - f * (strokeOpacity - range->style.strokeOpacity(frameNo, tween, exps)));
|
||||
shape->strokeFill(strokeColor.rgb[0], strokeColor.rgb[1], strokeColor.rgb[2], strokeOpacity);
|
||||
shape->strokeFill(strokeColor.r, strokeColor.g, strokeColor.b, strokeOpacity);
|
||||
shape->order(doc.stroke.below);
|
||||
}
|
||||
cursor.x += f * range->style.letterSpacing(frameNo, tween, exps);
|
||||
|
@ -1287,7 +1287,7 @@ void LottieBuilder::updateStrokeEffect(LottieLayer* layer, LottieFxStroke* effec
|
|||
|
||||
//fill the color to the layer shapes if any
|
||||
auto color = effect->color(frameNo);
|
||||
if (color.rgb[0] != 255 || color.rgb[1] != 255 || color.rgb[2] != 255) {
|
||||
if (color.r != 255 || color.g != 255 || color.b != 255) {
|
||||
auto accessor = tvg::Accessor::gen();
|
||||
auto stroke = (layer->type == LottieLayer::Type::Shape) ? true : false;
|
||||
auto f = [color, size, stroke](const tvg::Paint* paint, void* data) -> bool {
|
||||
|
@ -1296,9 +1296,9 @@ void LottieBuilder::updateStrokeEffect(LottieLayer* layer, LottieFxStroke* effec
|
|||
//expand shape to fill the stroke region
|
||||
if (stroke) {
|
||||
shape->strokeWidth(size);
|
||||
shape->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], 255);
|
||||
shape->strokeFill(color.r, color.g, color.b, 255);
|
||||
}
|
||||
shape->fill(color.rgb[0], color.rgb[1], color.rgb[2], 255);
|
||||
shape->fill(color.r, color.g, color.b, 255);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
@ -1324,13 +1324,13 @@ void LottieBuilder::updateEffect(LottieLayer* layer, float frameNo)
|
|||
auto effect = static_cast<LottieFxTint*>(*p);
|
||||
auto black = effect->black(frameNo);
|
||||
auto white = effect->white(frameNo);
|
||||
layer->scene->push(SceneEffect::Tint, black.rgb[0], black.rgb[1], black.rgb[2], white.rgb[0], white.rgb[1], white.rgb[2], (double)effect->intensity(frameNo));
|
||||
layer->scene->push(SceneEffect::Tint, black.r, black.g, black.b, white.r, white.g, white.b, (double)effect->intensity(frameNo));
|
||||
break;
|
||||
}
|
||||
case LottieEffect::Fill: {
|
||||
auto effect = static_cast<LottieFxFill*>(*p);
|
||||
auto color = effect->color(frameNo);
|
||||
layer->scene->push(SceneEffect::Fill, color.rgb[0], color.rgb[1], color.rgb[2], (int)(255.0f * effect->opacity(frameNo)));
|
||||
layer->scene->push(SceneEffect::Fill, color.r, color.g, color.b, (int)(255.0f * effect->opacity(frameNo)));
|
||||
break;
|
||||
}
|
||||
case LottieEffect::Stroke: {
|
||||
|
@ -1343,14 +1343,14 @@ void LottieBuilder::updateEffect(LottieLayer* layer, float frameNo)
|
|||
auto dark = effect->dark(frameNo);
|
||||
auto midtone = effect->midtone(frameNo);
|
||||
auto bright = effect->bright(frameNo);
|
||||
layer->scene->push(SceneEffect::Tritone, dark.rgb[0], dark.rgb[1], dark.rgb[2], midtone.rgb[0], midtone.rgb[1], midtone.rgb[2], bright.rgb[0], bright.rgb[1], bright.rgb[2], (int)effect->blend(frameNo));
|
||||
layer->scene->push(SceneEffect::Tritone, dark.r, dark.g, dark.b, midtone.r, midtone.g, midtone.b, bright.r, bright.g, bright.b, (int)effect->blend(frameNo));
|
||||
break;
|
||||
}
|
||||
case LottieEffect::DropShadow: {
|
||||
auto effect = static_cast<LottieFxDropShadow*>(*p);
|
||||
auto color = effect->color(frameNo);
|
||||
//seems the opacity range in dropshadow is 0 ~ 256
|
||||
layer->scene->push(SceneEffect::DropShadow, color.rgb[0], color.rgb[1], color.rgb[2], std::min(255, (int)effect->opacity(frameNo)), (double)effect->angle(frameNo), double(effect->distance(frameNo) * 0.5f), (double)(effect->blurness(frameNo) * BLUR_TO_SIGMA), QUALITY);
|
||||
layer->scene->push(SceneEffect::DropShadow, color.r, color.g, color.b, std::min(255, (int)effect->opacity(frameNo)), (double)effect->angle(frameNo), double(effect->distance(frameNo) * 0.5f), (double)(effect->blurness(frameNo) * BLUR_TO_SIGMA), QUALITY);
|
||||
break;
|
||||
}
|
||||
case LottieEffect::GaussianBlur: {
|
||||
|
|
|
@ -36,9 +36,9 @@ struct PathSet
|
|||
};
|
||||
|
||||
|
||||
struct RGB24
|
||||
struct RGB32
|
||||
{
|
||||
int32_t rgb[3];
|
||||
int32_t r, g, b;
|
||||
};
|
||||
|
||||
|
||||
|
@ -54,13 +54,13 @@ struct TextDocument
|
|||
char* text = nullptr;
|
||||
float height;
|
||||
float shift;
|
||||
RGB24 color;
|
||||
RGB32 color;
|
||||
struct {
|
||||
Point pos;
|
||||
Point size;
|
||||
} bbox;
|
||||
struct {
|
||||
RGB24 color;
|
||||
RGB32 color;
|
||||
float width;
|
||||
bool below = false;
|
||||
} stroke;
|
||||
|
@ -86,21 +86,21 @@ static inline int32_t REMAP255(float val)
|
|||
}
|
||||
|
||||
|
||||
static inline RGB24 operator-(const RGB24& lhs, const RGB24& rhs)
|
||||
static inline RGB32 operator-(const RGB32& lhs, const RGB32& rhs)
|
||||
{
|
||||
return {lhs.rgb[0] - rhs.rgb[0], lhs.rgb[1] - rhs.rgb[1], lhs.rgb[2] - rhs.rgb[2]};
|
||||
return {lhs.r - rhs.r, lhs.g - rhs.g, lhs.b - rhs.b};
|
||||
}
|
||||
|
||||
|
||||
static inline RGB24 operator+(const RGB24& lhs, const RGB24& rhs)
|
||||
static inline RGB32 operator+(const RGB32& lhs, const RGB32& rhs)
|
||||
{
|
||||
return {lhs.rgb[0] + rhs.rgb[0], lhs.rgb[1] + rhs.rgb[1], lhs.rgb[2] + rhs.rgb[2]};
|
||||
return {lhs.r + rhs.r, lhs.g + rhs.g, lhs.b + rhs.b};
|
||||
}
|
||||
|
||||
|
||||
static inline RGB24 operator*(const RGB24& lhs, float rhs)
|
||||
static inline RGB32 operator*(const RGB32& 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)};
|
||||
return {(int32_t)nearbyintf(lhs.r * rhs), (int32_t)nearbyintf(lhs.g * rhs), (int32_t)nearbyintf(lhs.b * rhs)};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -99,12 +99,12 @@ static jerry_value_t _point2d(const Point& pt)
|
|||
}
|
||||
|
||||
|
||||
static jerry_value_t _color(RGB24 rgb)
|
||||
static jerry_value_t _color(RGB32 rgb)
|
||||
{
|
||||
auto value = jerry_object();
|
||||
auto r = jerry_number((float)rgb.rgb[0]);
|
||||
auto g = jerry_number((float)rgb.rgb[1]);
|
||||
auto b = jerry_number((float)rgb.rgb[2]);
|
||||
auto r = jerry_number((float)rgb.r);
|
||||
auto g = jerry_number((float)rgb.g);
|
||||
auto b = jerry_number((float)rgb.b);
|
||||
jerry_object_set_index(value, 0, r);
|
||||
jerry_object_set_index(value, 1, g);
|
||||
jerry_object_set_index(value, 2, b);
|
||||
|
@ -125,15 +125,13 @@ static Point _point2d(jerry_value_t obj)
|
|||
return pt;
|
||||
}
|
||||
|
||||
static RGB24 _color(jerry_value_t obj)
|
||||
static RGB32 _color(jerry_value_t obj)
|
||||
{
|
||||
RGB24 out;
|
||||
RGB32 out;
|
||||
auto r = jerry_object_get_index(obj, 0);
|
||||
auto g = jerry_object_get_index(obj, 1);
|
||||
auto b = jerry_object_get_index(obj, 2);
|
||||
out.rgb[0] = jerry_value_as_int32(r);
|
||||
out.rgb[1] = jerry_value_as_int32(g);
|
||||
out.rgb[2] = jerry_value_as_int32(b);
|
||||
out = {jerry_value_as_int32(r), jerry_value_as_int32(g), jerry_value_as_int32(b)};
|
||||
jerry_value_free(r);
|
||||
jerry_value_free(g);
|
||||
jerry_value_free(b);
|
||||
|
@ -1486,7 +1484,7 @@ Point LottieExpressions::toPoint2d(jerry_value_t obj)
|
|||
}
|
||||
|
||||
|
||||
RGB24 LottieExpressions::toColor(jerry_value_t obj)
|
||||
RGB32 LottieExpressions::toColor(jerry_value_t obj)
|
||||
{
|
||||
return _color(obj);
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
}
|
||||
|
||||
template<typename Property>
|
||||
bool result(float frameNo, RGB24& out, LottieExpression* exp)
|
||||
bool result(float frameNo, RGB32& out, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
if (jerry_value_is_undefined(bm_rt)) return false;
|
||||
|
@ -144,7 +144,7 @@ private:
|
|||
void buildWritables(LottieExpression* exp);
|
||||
|
||||
Point toPoint2d(jerry_value_t obj);
|
||||
RGB24 toColor(jerry_value_t obj);
|
||||
RGB32 toColor(jerry_value_t obj);
|
||||
|
||||
//global object, attributes, methods
|
||||
jerry_value_t global;
|
||||
|
@ -160,7 +160,7 @@ struct LottieExpressions
|
|||
{
|
||||
template<typename Property, typename NumType> bool result(TVG_UNUSED float, TVG_UNUSED NumType&, TVG_UNUSED LottieExpression*) { return false; }
|
||||
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 RGB32&, 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 RenderPath&, TVG_UNUSED Matrix*, TVG_UNUSED LottieModifier*, TVG_UNUSED LottieExpression*) { return false; }
|
||||
bool result(TVG_UNUSED float, TVG_UNUSED TextDocument& doc, TVG_UNUSED LottieExpression*) { return false; }
|
||||
|
|
|
@ -660,7 +660,7 @@ LottieProperty* LottieLayer::property(uint16_t ix)
|
|||
}
|
||||
|
||||
|
||||
void LottieLayer::prepare(RGB24* color)
|
||||
void LottieLayer::prepare(RGB32* color)
|
||||
{
|
||||
/* if layer is hidden, only useful data is its transform matrix.
|
||||
so force it to be a Null Layer and release all resource. */
|
||||
|
@ -681,7 +681,7 @@ void LottieLayer::prepare(RGB24* color)
|
|||
} else if (color && type == LottieLayer::Solid) {
|
||||
auto solidFill = Shape::gen();
|
||||
solidFill->appendRect(0, 0, static_cast<float>(w), static_cast<float>(h));
|
||||
solidFill->fill(color->rgb[0], color->rgb[1], color->rgb[2]);
|
||||
solidFill->fill(color->r, color->g, color->b);
|
||||
solidFill->ref();
|
||||
statical.pooler.push(solidFill);
|
||||
}
|
||||
|
|
|
@ -333,8 +333,8 @@ struct LottieTextRange
|
|||
}
|
||||
|
||||
struct {
|
||||
LottieColor fillColor = RGB24{255, 255, 255};
|
||||
LottieColor strokeColor = RGB24{255, 255, 255};
|
||||
LottieColor fillColor = RGB32{255, 255, 255};
|
||||
LottieColor strokeColor = RGB32{255, 255, 255};
|
||||
LottieVector position = Point{0, 0};
|
||||
LottieScalar scale = Point{100, 100};
|
||||
LottieFloat letterSpacing = 0.0f;
|
||||
|
@ -367,19 +367,19 @@ struct LottieTextRange
|
|||
|
||||
float factor(float frameNo, float totalLen, float idx);
|
||||
|
||||
void color(float frameNo, RGB24& fillColor, RGB24& strokeColor, float factor, Tween& tween, LottieExpressions* exps)
|
||||
void color(float frameNo, RGB32& fillColor, RGB32& strokeColor, float factor, Tween& tween, LottieExpressions* exps)
|
||||
{
|
||||
if (style.flags.fillColor) {
|
||||
auto color = style.fillColor(frameNo, tween, exps);
|
||||
fillColor.rgb[0] = tvg::lerp<uint8_t>(fillColor.rgb[0], color.rgb[0], factor);
|
||||
fillColor.rgb[1] = tvg::lerp<uint8_t>(fillColor.rgb[1], color.rgb[1], factor);
|
||||
fillColor.rgb[2] = tvg::lerp<uint8_t>(fillColor.rgb[2], color.rgb[2], factor);
|
||||
fillColor.r = tvg::lerp<uint8_t>(fillColor.r, color.r, factor);
|
||||
fillColor.g = tvg::lerp<uint8_t>(fillColor.g, color.g, factor);
|
||||
fillColor.b = tvg::lerp<uint8_t>(fillColor.b, color.b, factor);
|
||||
}
|
||||
if (style.flags.strokeColor) {
|
||||
auto color = style.strokeColor(frameNo, tween, exps);
|
||||
strokeColor.rgb[0] = tvg::lerp<uint8_t>(strokeColor.rgb[0], color.rgb[0], factor);
|
||||
strokeColor.rgb[1] = tvg::lerp<uint8_t>(strokeColor.rgb[1], color.rgb[1], factor);
|
||||
strokeColor.rgb[2] = tvg::lerp<uint8_t>(strokeColor.rgb[2], color.rgb[2], factor);
|
||||
strokeColor.r = tvg::lerp<uint8_t>(strokeColor.r, color.r, factor);
|
||||
strokeColor.g = tvg::lerp<uint8_t>(strokeColor.g, color.g, factor);
|
||||
strokeColor.b = tvg::lerp<uint8_t>(strokeColor.b, color.b, factor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -726,7 +726,7 @@ struct LottieTransform : LottieObject
|
|||
|
||||
struct LottieSolid : LottieObject
|
||||
{
|
||||
LottieColor color = RGB24{255, 255, 255};
|
||||
LottieColor color = RGB32{255, 255, 255};
|
||||
LottieOpacity opacity = 255;
|
||||
|
||||
LottieProperty* property(uint16_t ix) override
|
||||
|
@ -975,7 +975,7 @@ struct LottieLayer : LottieGroup
|
|||
~LottieLayer();
|
||||
|
||||
bool mergeable() override { return false; }
|
||||
void prepare(RGB24* color = nullptr);
|
||||
void prepare(RGB32* color = nullptr);
|
||||
float remap(LottieComposition* comp, float frameNo, LottieExpressions* exp);
|
||||
LottieProperty* property(uint16_t ix) override;
|
||||
bool assign(const char* layer, uint32_t ix, const char* var, float val);
|
||||
|
|
|
@ -33,22 +33,17 @@ static bool _colinear(const Point* p)
|
|||
}
|
||||
|
||||
|
||||
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, Point& prev, Point& curr, Point& next, float r)
|
||||
static void _roundCorner(RenderPath& out, Point& prev, Point& curr, 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);
|
||||
out.lineTo(curr - 2.0f * dPrev);
|
||||
out.cubicTo(curr - dPrev, curr - dNext, curr - 2.0f * dNext);
|
||||
}
|
||||
|
||||
|
||||
|
@ -106,24 +101,14 @@ void LottieOffsetModifier::corner(RenderPath& out, Line& line, Line& nextLine, u
|
|||
} else {
|
||||
out.pts.push(line.pt2);
|
||||
if (join == StrokeJoin::Round) {
|
||||
out.cmds.push(PathCommand::CubicTo);
|
||||
out.pts.push((line.pt2 + intersect) * 0.5f);
|
||||
out.pts.push((nextLine.pt1 + intersect) * 0.5f);
|
||||
out.pts.push(nextLine.pt1);
|
||||
out.cubicTo((line.pt2 + intersect) * 0.5f, (nextLine.pt1 + intersect) * 0.5f, 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);
|
||||
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) {
|
||||
out.cmds.push(PathCommand::LineTo);
|
||||
out.pts.push(intersect);
|
||||
}
|
||||
out.cmds.push(PathCommand::LineTo);
|
||||
out.pts.push(nextLine.pt1);
|
||||
} else {
|
||||
out.cmds.push(PathCommand::LineTo);
|
||||
out.pts.push(nextLine.pt1);
|
||||
}
|
||||
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) out.lineTo(intersect);
|
||||
out.lineTo(nextLine.pt1);
|
||||
} else out.lineTo(nextLine.pt1);
|
||||
}
|
||||
} else out.pts.push(line.pt2);
|
||||
}
|
||||
|
@ -139,9 +124,8 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i
|
|||
if (inCmds[curCmd - 1] != PathCommand::LineTo) state.line = _offset(inPts[curPt - 1], inPts[curPt], offset);
|
||||
|
||||
if (state.moveto) {
|
||||
out.cmds.push(PathCommand::MoveTo);
|
||||
out.moveTo(state.line.pt1);
|
||||
state.movetoOutIndex = out.pts.count;
|
||||
out.pts.push(state.line.pt1);
|
||||
state.firstLine = state.line;
|
||||
state.moveto = false;
|
||||
}
|
||||
|
@ -179,7 +163,6 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
|
|||
buffer->clear();
|
||||
|
||||
auto& path = (next) ? *buffer : out;
|
||||
|
||||
path.cmds.reserve(inCmdsCnt * 2);
|
||||
path.pts.reserve((uint32_t)(inPtsCnt * 1.5));
|
||||
auto pivot = path.pts.count;
|
||||
|
@ -189,8 +172,7 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
|
|||
switch (inCmds[iCmds]) {
|
||||
case PathCommand::MoveTo: {
|
||||
startIndex = path.pts.count;
|
||||
path.cmds.push(PathCommand::MoveTo);
|
||||
path.pts.push(inPts[iPts++]);
|
||||
path.moveTo(inPts[iPts++]);
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
|
@ -198,24 +180,22 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
|
|||
auto& prev = inPts[iPts - 1];
|
||||
auto& curr = inPts[iPts + 2];
|
||||
if (inCmds[iCmds + 1] == PathCommand::CubicTo && _colinear(inPts + iPts + 2)) {
|
||||
_roundCorner(path.cmds, path.pts, prev, curr, inPts[iPts + 5], r);
|
||||
_roundCorner(path, prev, curr, inPts[iPts + 5], r);
|
||||
iPts += 3;
|
||||
break;
|
||||
} else if (inCmds[iCmds + 1] == PathCommand::Close) {
|
||||
_roundCorner(path.cmds, path.pts, prev, curr, inPts[2], r);
|
||||
_roundCorner(path, prev, curr, inPts[2], r);
|
||||
path.pts[startIndex] = path.pts.last();
|
||||
iPts += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
path.cmds.push(PathCommand::CubicTo);
|
||||
path.pts.push(inPts[iPts++]);
|
||||
path.pts.push(inPts[iPts++]);
|
||||
path.pts.push(inPts[iPts++]);
|
||||
path.cubicTo(inPts[iPts], inPts[iPts + 1], inPts[iPts + 2]);
|
||||
iPts += 3;
|
||||
break;
|
||||
}
|
||||
case PathCommand::Close: {
|
||||
path.cmds.push(PathCommand::Close);
|
||||
path.close();
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
|
@ -249,8 +229,7 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
|
|||
path.pts.grow((uint32_t)(4.5 * in.cmds.count));
|
||||
|
||||
int start = 3 * tvg::zero(outerRoundness);
|
||||
path.cmds.push(PathCommand::MoveTo);
|
||||
path.pts.push(in.pts[start]);
|
||||
path.moveTo(in.pts[start]);
|
||||
|
||||
for (uint32_t i = 1 + start; i < in.pts.count; i += 6) {
|
||||
auto& prev = in.pts[i];
|
||||
|
@ -265,12 +244,9 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
|
|||
auto p2 = curr - dNext;
|
||||
auto p3 = curr - 2.0f * dNext;
|
||||
|
||||
path.cmds.push(PathCommand::CubicTo);
|
||||
path.pts.push(prev); path.pts.push(p0); path.pts.push(p0);
|
||||
path.cmds.push(PathCommand::CubicTo);
|
||||
path.pts.push(p1); path.pts.push(p2); path.pts.push(p3);
|
||||
path.cmds.push(PathCommand::CubicTo);
|
||||
path.pts.push(p3); path.pts.push(next); path.pts.push(nextCtrl);
|
||||
path.cubicTo(prev, p0, p0);
|
||||
path.cubicTo(p1, p2, p3);
|
||||
path.cubicTo(p3, next, nextCtrl);
|
||||
}
|
||||
} else {
|
||||
path.cmds.grow(2 * in.cmds.count);
|
||||
|
@ -278,8 +254,7 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
|
|||
|
||||
auto dPrev = r * (in.pts[1] - in.pts[0]);
|
||||
auto p = in.pts[0] + 2.0f * dPrev;
|
||||
path.cmds.push(PathCommand::MoveTo);
|
||||
path.pts.push(p);
|
||||
path.moveTo(p);
|
||||
|
||||
for (uint32_t i = 1; i < in.pts.count; ++i) {
|
||||
auto& curr = in.pts[i];
|
||||
|
@ -291,10 +266,8 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
|
|||
auto p2 = curr - dNext;
|
||||
auto p3 = curr - 2.0f * dNext;
|
||||
|
||||
path.cmds.push(PathCommand::LineTo);
|
||||
path.pts.push(p0);
|
||||
path.cmds.push(PathCommand::CubicTo);
|
||||
path.pts.push(p1); path.pts.push(p2); path.pts.push(p3);
|
||||
path.lineTo(p0);
|
||||
path.cubicTo(p1, p2, p3);
|
||||
|
||||
dPrev = -1.0f * dNext;
|
||||
}
|
||||
|
@ -359,9 +332,8 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P
|
|||
auto line3 = _offset(bezier.ctrl2, bezier.end, offset);
|
||||
|
||||
if (state.moveto) {
|
||||
out.cmds.push(PathCommand::MoveTo);
|
||||
out.moveTo(line1.pt1);
|
||||
state.movetoOutIndex = out.pts.count;
|
||||
out.pts.push(line1.pt1);
|
||||
state.firstLine = line1;
|
||||
state.moveto = false;
|
||||
}
|
||||
|
|
|
@ -92,9 +92,9 @@ MaskMethod LottieParser::getMaskMethod(bool inversed)
|
|||
}
|
||||
|
||||
|
||||
RGB24 LottieParser::getColor(const char *str)
|
||||
RGB32 LottieParser::getColor(const char *str)
|
||||
{
|
||||
RGB24 color = {0, 0, 0};
|
||||
RGB32 color = {0, 0, 0};
|
||||
|
||||
if (!str) return color;
|
||||
|
||||
|
@ -106,15 +106,15 @@ RGB24 LottieParser::getColor(const char *str)
|
|||
char tmp[3] = {'\0', '\0', '\0'};
|
||||
tmp[0] = str[1];
|
||||
tmp[1] = str[2];
|
||||
color.rgb[0] = uint8_t(strtol(tmp, nullptr, 16));
|
||||
color.r = uint8_t(strtol(tmp, nullptr, 16));
|
||||
|
||||
tmp[0] = str[3];
|
||||
tmp[1] = str[4];
|
||||
color.rgb[1] = uint8_t(strtol(tmp, nullptr, 16));
|
||||
color.g = uint8_t(strtol(tmp, nullptr, 16));
|
||||
|
||||
tmp[0] = str[5];
|
||||
tmp[1] = str[6];
|
||||
color.rgb[2] = uint8_t(strtol(tmp, nullptr, 16));
|
||||
color.b = uint8_t(strtol(tmp, nullptr, 16));
|
||||
|
||||
return color;
|
||||
}
|
||||
|
@ -181,44 +181,36 @@ bool LottieParser::getValue(PathSet& path)
|
|||
auto pt = pts.begin();
|
||||
|
||||
//Store manipulated results
|
||||
Array<Point> outPts;
|
||||
Array<PathCommand> outCmds;
|
||||
RenderPath temp;
|
||||
|
||||
//Reuse the buffers
|
||||
outPts.data = path.pts;
|
||||
outPts.reserved = path.ptsCnt;
|
||||
outCmds.data = path.cmds;
|
||||
outCmds.reserved = path.cmdsCnt;
|
||||
temp.pts.data = path.pts;
|
||||
temp.pts.reserved = path.ptsCnt;
|
||||
temp.cmds.data = path.cmds;
|
||||
temp.cmds.reserved = path.cmdsCnt;
|
||||
|
||||
size_t extra = closed ? 3 : 0;
|
||||
outPts.reserve(pts.count * 3 + 1 + extra);
|
||||
outCmds.reserve(pts.count + 2);
|
||||
temp.pts.reserve(pts.count * 3 + 1 + extra);
|
||||
temp.cmds.reserve(pts.count + 2);
|
||||
|
||||
outCmds.push(PathCommand::MoveTo);
|
||||
outPts.push(*pt);
|
||||
temp.moveTo(*pt);
|
||||
|
||||
for (++pt, ++out, ++in; pt < pts.end(); ++pt, ++out, ++in) {
|
||||
outCmds.push(PathCommand::CubicTo);
|
||||
outPts.push(*(pt - 1) + *(out - 1));
|
||||
outPts.push(*pt + *in);
|
||||
outPts.push(*pt);
|
||||
temp.cubicTo(*(pt - 1) + *(out - 1), *pt + *in, *pt);
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
outPts.push(pts.last() + outs.last());
|
||||
outPts.push(pts.first() + ins.first());
|
||||
outPts.push(pts.first());
|
||||
outCmds.push(PathCommand::CubicTo);
|
||||
outCmds.push(PathCommand::Close);
|
||||
temp.cubicTo(pts.last() + outs.last(), pts.first() + ins.first(), pts.first());
|
||||
temp.close();
|
||||
}
|
||||
|
||||
path.pts = outPts.data;
|
||||
path.cmds = outCmds.data;
|
||||
path.ptsCnt = outPts.count;
|
||||
path.cmdsCnt = outCmds.count;
|
||||
path.pts = temp.pts.data;
|
||||
path.cmds = temp.cmds.data;
|
||||
path.ptsCnt = temp.pts.count;
|
||||
path.cmdsCnt = temp.cmds.count;
|
||||
|
||||
outPts.data = nullptr;
|
||||
outCmds.data = nullptr;
|
||||
temp.pts.data = nullptr;
|
||||
temp.cmds.data = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -313,16 +305,16 @@ bool LottieParser::getValue(Point& pt)
|
|||
}
|
||||
|
||||
|
||||
bool LottieParser::getValue(RGB24& color)
|
||||
bool LottieParser::getValue(RGB32& color)
|
||||
{
|
||||
if (peekType() == kArrayType) {
|
||||
enterArray();
|
||||
if (!nextArrayValue()) return false;
|
||||
}
|
||||
|
||||
color.rgb[0] = REMAP255(getFloat());
|
||||
color.rgb[1] = REMAP255(getFloat());
|
||||
color.rgb[2] = REMAP255(getFloat());
|
||||
color.r = REMAP255(getFloat());
|
||||
color.g = REMAP255(getFloat());
|
||||
color.b = REMAP255(getFloat());
|
||||
|
||||
while (nextArrayValue()) getFloat(); //drop
|
||||
|
||||
|
@ -1450,7 +1442,7 @@ LottieLayer* LottieParser::parseLayer(LottieLayer* precomp)
|
|||
context.layer = layer;
|
||||
|
||||
auto ddd = false;
|
||||
RGB24 color;
|
||||
RGB32 color;
|
||||
|
||||
enterObject();
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public:
|
|||
bool expressions = false; //support expressions?
|
||||
|
||||
private:
|
||||
RGB24 getColor(const char *str);
|
||||
RGB32 getColor(const char *str);
|
||||
FillRule getFillRule();
|
||||
MaskMethod getMaskMethod(bool inversed);
|
||||
LottieInterpolator* getInterpolator(const char* key, Point& in, Point& out);
|
||||
|
@ -65,7 +65,7 @@ private:
|
|||
bool getValue(float& val);
|
||||
bool getValue(uint8_t& val);
|
||||
bool getValue(int8_t& val);
|
||||
bool getValue(RGB24& color);
|
||||
bool getValue(RGB32& color);
|
||||
bool getValue(Point& pt);
|
||||
|
||||
template<typename T> bool parseTangent(const char *key, LottieVectorFrame<T>& value);
|
||||
|
|
|
@ -1006,7 +1006,7 @@ using LottieFloat = LottieGenericProperty<LottieScalarFrame<float>, float, Lotti
|
|||
using LottieInteger = LottieGenericProperty<LottieScalarFrame<int8_t>, int8_t, LottieProperty::Type::Integer>;
|
||||
using LottieScalar = LottieGenericProperty<LottieScalarFrame<Point>, Point, LottieProperty::Type::Scalar>;
|
||||
using LottieVector = LottieGenericProperty<LottieVectorFrame<Point>, Point, LottieProperty::Type::Vector, 0>;
|
||||
using LottieColor = LottieGenericProperty<LottieScalarFrame<RGB24>, RGB24, LottieProperty::Type::Color>;
|
||||
using LottieColor = LottieGenericProperty<LottieScalarFrame<RGB32>, RGB32, LottieProperty::Type::Color>;
|
||||
using LottieOpacity = LottieGenericProperty<LottieScalarFrame<uint8_t>, uint8_t, LottieProperty::Type::Opacity>;
|
||||
|
||||
#endif //_TVG_LOTTIE_PROPERTY_H_
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <fstream>
|
||||
#include "tvgStr.h"
|
||||
#include "tvgMath.h"
|
||||
#include "tvgColor.h"
|
||||
#include "tvgLoader.h"
|
||||
#include "tvgXmlParser.h"
|
||||
#include "tvgSvgLoader.h"
|
||||
|
@ -573,85 +574,7 @@ static constexpr struct
|
|||
};
|
||||
|
||||
|
||||
static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* red, uint8_t* green, uint8_t* blue)
|
||||
{
|
||||
auto r = 0.0f, g = 0.0f, b = 0.0f;
|
||||
auto i = 0;
|
||||
|
||||
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)) r = g = b = brightness;
|
||||
else {
|
||||
if (tvg::equal(hue, 360.0)) hue = 0.0f;
|
||||
hue /= 60.0f;
|
||||
|
||||
auto v = (brightness <= 0.5f) ? (brightness * (1.0f + saturation)) : (brightness + saturation - (brightness * saturation));
|
||||
auto p = brightness + brightness - v;
|
||||
|
||||
float sv;
|
||||
if (!tvg::zero(v)) sv = (v - p) / v;
|
||||
else sv = 0.0f;
|
||||
|
||||
i = static_cast<uint8_t>(hue);
|
||||
auto f = hue - i;
|
||||
|
||||
auto vsf = v * sv * f;
|
||||
|
||||
auto t = p + vsf;
|
||||
auto q = v - vsf;
|
||||
|
||||
switch (i) {
|
||||
case 0: {
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*red = (uint8_t)nearbyint(r * 255.0f);
|
||||
*green = (uint8_t)nearbyint(g * 255.0f);
|
||||
*blue = (uint8_t)nearbyint(b * 255.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref)
|
||||
static bool _toColor(const char* str, uint8_t& r, uint8_t&g, uint8_t& b, char** ref)
|
||||
{
|
||||
auto len = strlen(str);
|
||||
|
||||
|
@ -661,13 +584,13 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
|||
char tmp[3] = { '\0', '\0', '\0' };
|
||||
tmp[0] = str[1];
|
||||
tmp[1] = str[1];
|
||||
*r = strtol(tmp, nullptr, 16);
|
||||
r = strtol(tmp, nullptr, 16);
|
||||
tmp[0] = str[2];
|
||||
tmp[1] = str[2];
|
||||
*g = strtol(tmp, nullptr, 16);
|
||||
g = strtol(tmp, nullptr, 16);
|
||||
tmp[0] = str[3];
|
||||
tmp[1] = str[3];
|
||||
*b = strtol(tmp, nullptr, 16);
|
||||
b = strtol(tmp, nullptr, 16);
|
||||
}
|
||||
return true;
|
||||
} else if (len == 7 && str[0] == '#') {
|
||||
|
@ -675,13 +598,13 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
|||
char tmp[3] = { '\0', '\0', '\0' };
|
||||
tmp[0] = str[1];
|
||||
tmp[1] = str[2];
|
||||
*r = strtol(tmp, nullptr, 16);
|
||||
r = strtol(tmp, nullptr, 16);
|
||||
tmp[0] = str[3];
|
||||
tmp[1] = str[4];
|
||||
*g = strtol(tmp, nullptr, 16);
|
||||
g = strtol(tmp, nullptr, 16);
|
||||
tmp[0] = str[5];
|
||||
tmp[1] = str[6];
|
||||
*b = strtol(tmp, nullptr, 16);
|
||||
b = strtol(tmp, nullptr, 16);
|
||||
}
|
||||
return true;
|
||||
} else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') {
|
||||
|
@ -692,9 +615,9 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
|||
if (green && *green == ',') {
|
||||
auto tb = _parseColor(green + 1, &blue);
|
||||
if (blue && blue[0] == ')' && blue[1] == '\0') {
|
||||
*r = tr;
|
||||
*g = tg;
|
||||
*b = tb;
|
||||
r = tr;
|
||||
g = tg;
|
||||
b = tb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -704,25 +627,26 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
|||
*ref = _idFromUrl((const char*)(str + 3));
|
||||
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;
|
||||
tvg::HSL hsl;
|
||||
const char* content = _skipSpace(str + 4, nullptr);
|
||||
const char* hue = nullptr;
|
||||
if (_parseNumber(&content, &hue, &th) && hue) {
|
||||
if (_parseNumber(&content, &hue, &hsl.h) && hue) {
|
||||
const char* saturation = nullptr;
|
||||
hue = _skipSpace(hue, nullptr);
|
||||
hue = (char*)_skipComma(hue);
|
||||
hue = _skipSpace(hue, nullptr);
|
||||
if (_parseNumber(&hue, &saturation, &ts) && saturation && *saturation == '%') {
|
||||
if (_parseNumber(&hue, &saturation, &hsl.s) && saturation && *saturation == '%') {
|
||||
const char* brightness = nullptr;
|
||||
ts /= 100.0f;
|
||||
hsl.s /= 100.0f;
|
||||
saturation = _skipSpace(saturation + 1, nullptr);
|
||||
saturation = (char*)_skipComma(saturation);
|
||||
saturation = _skipSpace(saturation, nullptr);
|
||||
if (_parseNumber(&saturation, &brightness, &tb) && brightness && *brightness == '%') {
|
||||
tb /= 100.0f;
|
||||
if (_parseNumber(&saturation, &brightness, &hsl.l) && brightness && *brightness == '%') {
|
||||
hsl.l /= 100.0f;
|
||||
brightness = _skipSpace(brightness + 1, nullptr);
|
||||
if (brightness && brightness[0] == ')' && brightness[1] == '\0') {
|
||||
return _hslToRgb(th, ts, tb, r, g, b);
|
||||
hsl2rgb(hsl.h, tvg::clamp(hsl.s, 0.0f, 1.0f), tvg::clamp(hsl.l, 0.0f, 1.0f), r, g, b);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -731,9 +655,9 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
|||
//Handle named color
|
||||
for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) {
|
||||
if (!strcasecmp(colors[i].name, str)) {
|
||||
*r = (((uint8_t*)(&(colors[i].value)))[2]);
|
||||
*g = (((uint8_t*)(&(colors[i].value)))[1]);
|
||||
*b = (((uint8_t*)(&(colors[i].value)))[0]);
|
||||
r = ((uint8_t*)(&(colors[i].value)))[2];
|
||||
g = ((uint8_t*)(&(colors[i].value)))[1];
|
||||
b = ((uint8_t*)(&(colors[i].value)))[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -894,12 +818,6 @@ error:
|
|||
}
|
||||
|
||||
|
||||
static void _postpone(Array<SvgNodeIdPair>& nodes, SvgNode *node, char* id)
|
||||
{
|
||||
nodes.push({node, id});
|
||||
}
|
||||
|
||||
|
||||
static bool _attrParseSvgNode(void* data, const char* key, const char* value)
|
||||
{
|
||||
SvgLoaderData* loader = (SvgLoaderData*)data;
|
||||
|
@ -970,14 +888,14 @@ static void _handlePaintAttr(SvgPaint* paint, const char* value)
|
|||
paint->none = false;
|
||||
return;
|
||||
}
|
||||
if (_toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url)) paint->none = false;
|
||||
if (_toColor(value, paint->color.r, paint->color.g, paint->color.b, &paint->url)) paint->none = false;
|
||||
}
|
||||
|
||||
|
||||
static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
|
||||
{
|
||||
SvgStyleProperty* style = node->style;
|
||||
if (_toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr)) {
|
||||
if (_toColor(value, style->color.r, style->color.g, style->color.b, nullptr)) {
|
||||
style->curColorSet = true;
|
||||
}
|
||||
}
|
||||
|
@ -1157,7 +1075,7 @@ static void _handleCssClassAttr(SvgLoaderData* loader, SvgNode* node, const char
|
|||
cssCopyStyleAttr(node, cssNode);
|
||||
}
|
||||
|
||||
if (!cssClassFound) _postpone(loader->nodesToStyle, node, *cssClass);
|
||||
if (!cssClassFound) loader->nodesToStyle.push({node, *cssClass});
|
||||
}
|
||||
|
||||
|
||||
|
@ -2155,6 +2073,23 @@ static SvgNode* _findParentById(SvgNode* node, char* id, SvgNode* doc)
|
|||
}
|
||||
|
||||
|
||||
static bool _checkPostponed(SvgNode* node, SvgNode* cloneNode, int depth)
|
||||
{
|
||||
if (node == cloneNode) return true;
|
||||
|
||||
if (depth == 512) {
|
||||
TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth);
|
||||
return false;
|
||||
}
|
||||
|
||||
ARRAY_FOREACH(p, node->child) {
|
||||
if (_checkPostponed(*p, cloneNode, depth + 1)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static constexpr struct
|
||||
{
|
||||
const char* tag;
|
||||
|
@ -2196,17 +2131,30 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value)
|
|||
nodeFrom = _findNodeById(defs, id);
|
||||
if (nodeFrom) {
|
||||
if (!_findParentById(node, id, loader->doc)) {
|
||||
_cloneNode(nodeFrom, node, 0);
|
||||
if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom;
|
||||
//Check if none of nodeFrom's children are in the cloneNodes list
|
||||
auto postpone = false;
|
||||
INLIST_FOREACH(loader->cloneNodes, pair) {
|
||||
if (_checkPostponed(nodeFrom, pair->node, 1)) {
|
||||
postpone = true;
|
||||
loader->cloneNodes.back(new(tvg::malloc<SvgNodeIdPair*>(sizeof(SvgNodeIdPair))) SvgNodeIdPair(node, id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
//None of the children of nodeFrom are on the cloneNodes list, so it can be cloned immediately
|
||||
if (!postpone) {
|
||||
_cloneNode(nodeFrom, node, 0);
|
||||
if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom;
|
||||
tvg::free(id);
|
||||
}
|
||||
} else {
|
||||
TVGLOG("SVG", "%s is ancestor element. This reference is invalid.", id);
|
||||
tvg::free(id);
|
||||
}
|
||||
tvg::free(id);
|
||||
} else {
|
||||
//some svg export software include <defs> element at the end of the file
|
||||
//if so the 'from' element won't be found now and we have to repeat finding
|
||||
//after the whole file is parsed
|
||||
_postpone(loader->cloneNodes, node, id);
|
||||
loader->cloneNodes.back(new(tvg::malloc<SvgNodeIdPair*>(sizeof(SvgNodeIdPair))) SvgNodeIdPair(node, id));
|
||||
}
|
||||
} else {
|
||||
return _attrParseGNode(data, key, value);
|
||||
|
@ -2686,7 +2634,7 @@ static bool _attrParseStopsStyle(void* data, const char* key, const char* value)
|
|||
stop->g = latestColor->g;
|
||||
stop->b = latestColor->b;
|
||||
}
|
||||
} else if (_toColor(value, &stop->r, &stop->g, &stop->b, nullptr)) {
|
||||
} else if (_toColor(value, stop->r, stop->g, stop->b, nullptr)) {
|
||||
loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopColor);
|
||||
}
|
||||
} else {
|
||||
|
@ -2716,7 +2664,7 @@ static bool _attrParseStops(void* data, const char* key, const char* value)
|
|||
stop->b = latestColor->b;
|
||||
}
|
||||
} else if (!(loader->svgParse->flags & SvgStopStyleFlags::StopColor)) {
|
||||
_toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
|
||||
_toColor(value, stop->r, stop->g, stop->b, nullptr);
|
||||
}
|
||||
} else if (STR_AS(key, "style")) {
|
||||
xmlParseW3CAttribute(value, strlen(value), _attrParseStopsStyle, data);
|
||||
|
@ -3356,22 +3304,39 @@ static void _cloneNode(SvgNode* from, SvgNode* parent, int depth)
|
|||
}
|
||||
|
||||
|
||||
static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc)
|
||||
static void _clonePostponedNodes(Inlist<SvgNodeIdPair>* cloneNodes, SvgNode* doc)
|
||||
{
|
||||
ARRAY_FOREACH(p, *cloneNodes) {
|
||||
auto nodeIdPair = *p;
|
||||
auto defs = _getDefsNode(nodeIdPair.node);
|
||||
auto nodeFrom = _findNodeById(defs, nodeIdPair.id);
|
||||
if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair.id);
|
||||
if (!_findParentById(nodeIdPair.node, nodeIdPair.id, doc)) {
|
||||
_cloneNode(nodeFrom, nodeIdPair.node, 0);
|
||||
if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) {
|
||||
nodeIdPair.node->node.use.symbol = nodeFrom;
|
||||
auto nodeIdPair = cloneNodes->front();
|
||||
while (nodeIdPair) {
|
||||
if (!_findParentById(nodeIdPair->node, nodeIdPair->id, doc)) {
|
||||
//Check if none of nodeFrom's children are in the cloneNodes list
|
||||
auto postpone = false;
|
||||
auto nodeFrom = _findNodeById(_getDefsNode(nodeIdPair->node), nodeIdPair->id);
|
||||
if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair->id);
|
||||
if (nodeFrom) {
|
||||
INLIST_FOREACH((*cloneNodes), pair) {
|
||||
if (_checkPostponed(nodeFrom, pair->node, 1)) {
|
||||
postpone = true;
|
||||
cloneNodes->back(nodeIdPair);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Since none of the child nodes of nodeFrom are present in the cloneNodes list, it can be cloned immediately
|
||||
if (!postpone) {
|
||||
_cloneNode(nodeFrom, nodeIdPair->node, 0);
|
||||
if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair->node->type == SvgNodeType::Use) {
|
||||
nodeIdPair->node->node.use.symbol = nodeFrom;
|
||||
}
|
||||
tvg::free(nodeIdPair->id);
|
||||
tvg::free(nodeIdPair);
|
||||
}
|
||||
} else {
|
||||
TVGLOG("SVG", "%s is ancestor element. This reference is invalid.", nodeIdPair.id);
|
||||
TVGLOG("SVG", "%s is ancestor element. This reference is invalid.", nodeIdPair->id);
|
||||
tvg::free(nodeIdPair->id);
|
||||
tvg::free(nodeIdPair);
|
||||
}
|
||||
tvg::free(nodeIdPair.id);
|
||||
nodeIdPair = cloneNodes->front();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3901,7 +3866,7 @@ void SvgLoader::run(unsigned tid)
|
|||
if (loaderData.nodesToStyle.count > 0) cssApplyStyleToPostponeds(loaderData.nodesToStyle, loaderData.cssStyle);
|
||||
if (loaderData.cssStyle) cssUpdateStyle(loaderData.doc, loaderData.cssStyle);
|
||||
|
||||
if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes, loaderData.doc);
|
||||
if (!loaderData.cloneNodes.empty()) _clonePostponedNodes(&loaderData.cloneNodes, loaderData.doc);
|
||||
|
||||
_updateComposite(loaderData.doc, loaderData.doc);
|
||||
if (defs) _updateComposite(loaderData.doc, defs);
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgArray.h"
|
||||
#include "tvgInlist.h"
|
||||
#include "tvgColor.h"
|
||||
|
||||
using SvgColor = tvg::RGB;
|
||||
|
||||
struct Box
|
||||
{
|
||||
|
@ -427,11 +431,6 @@ struct SvgComposite
|
|||
bool applying; //flag for checking circular dependency.
|
||||
};
|
||||
|
||||
struct SvgColor
|
||||
{
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
struct SvgPaint
|
||||
{
|
||||
SvgStyleGradient* gradient;
|
||||
|
@ -564,6 +563,8 @@ struct SvgParser
|
|||
|
||||
struct SvgNodeIdPair
|
||||
{
|
||||
INLIST_ITEM(SvgNodeIdPair);
|
||||
SvgNodeIdPair(SvgNode* n, char* i) : node{n}, id{i} {}
|
||||
SvgNode* node;
|
||||
char *id;
|
||||
};
|
||||
|
@ -592,7 +593,7 @@ struct SvgLoaderData
|
|||
Array<SvgStyleGradient*> gradients;
|
||||
Array<SvgStyleGradient*> gradientStack; //For stops
|
||||
SvgParser* svgParse = nullptr;
|
||||
Array<SvgNodeIdPair> cloneNodes;
|
||||
Inlist<SvgNodeIdPair> cloneNodes;
|
||||
Array<SvgNodeIdPair> nodesToStyle;
|
||||
Array<char*> images; //embedded images
|
||||
Array<FontFace> fonts;
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgShape.h"
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
#include "tvgSvgPath.h"
|
||||
#include "tvgStr.h"
|
||||
|
@ -69,108 +68,64 @@ static bool _parseFlag(char** content, int* number)
|
|||
}
|
||||
|
||||
|
||||
void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
|
||||
//Some helpful stuff is available here:
|
||||
//http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
||||
void _pathAppendArcTo(RenderPath& out, Point& cur, Point& curCtl, const Point& next, Point radius, float angle, bool largeArc, bool sweep)
|
||||
{
|
||||
float cxp, cyp, cx, cy;
|
||||
float sx, sy;
|
||||
float cosPhi, sinPhi;
|
||||
float dx2, dy2;
|
||||
float x1p, y1p;
|
||||
float x1p2, y1p2;
|
||||
float rx2, ry2;
|
||||
float lambda;
|
||||
float c;
|
||||
float at;
|
||||
float theta1, deltaTheta;
|
||||
float nat;
|
||||
float delta, bcp;
|
||||
float cosPhiRx, cosPhiRy;
|
||||
float sinPhiRx, sinPhiRy;
|
||||
float cosTheta1, sinTheta1;
|
||||
int segments;
|
||||
|
||||
//Some helpful stuff is available here:
|
||||
//http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
||||
sx = cur->x;
|
||||
sy = cur->y;
|
||||
|
||||
//Correction of out-of-range radii, see F6.6.1 (step 2)
|
||||
rx = fabsf(rx);
|
||||
ry = fabsf(ry);
|
||||
|
||||
angle = deg2rad(angle);
|
||||
cosPhi = cosf(angle);
|
||||
sinPhi = sinf(angle);
|
||||
dx2 = (sx - x) / 2.0f;
|
||||
dy2 = (sy - y) / 2.0f;
|
||||
x1p = cosPhi * dx2 + sinPhi * dy2;
|
||||
y1p = cosPhi * dy2 - sinPhi * dx2;
|
||||
x1p2 = x1p * x1p;
|
||||
y1p2 = y1p * y1p;
|
||||
rx2 = rx * rx;
|
||||
ry2 = ry * ry;
|
||||
lambda = (x1p2 / rx2) + (y1p2 / ry2);
|
||||
auto start = cur;
|
||||
auto cosPhi = cosf(angle);
|
||||
auto sinPhi = sinf(angle);
|
||||
auto d2 = (start - next) * 0.5f;
|
||||
auto x1p = cosPhi * d2.x + sinPhi * d2.y;
|
||||
auto y1p = cosPhi * d2.y - sinPhi * d2.x;
|
||||
auto x1p2 = x1p * x1p;
|
||||
auto y1p2 = y1p * y1p;
|
||||
auto radius2 = Point{radius.x * radius.x, radius.y * radius.y};
|
||||
auto lambda = (x1p2 / radius2.x) + (y1p2 / radius2.y);
|
||||
|
||||
//Correction of out-of-range radii, see F6.6.2 (step 4)
|
||||
if (lambda > 1.0f) {
|
||||
//See F6.6.3
|
||||
float lambdaRoot = sqrtf(lambda);
|
||||
|
||||
rx *= lambdaRoot;
|
||||
ry *= lambdaRoot;
|
||||
//Update rx2 and ry2
|
||||
rx2 = rx * rx;
|
||||
ry2 = ry * ry;
|
||||
radius *= sqrtf(lambda);
|
||||
radius2 = {radius.x * radius.x, radius.y * radius.y};
|
||||
}
|
||||
|
||||
c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
|
||||
Point cp, center;
|
||||
auto c = (radius2.x * radius2.y) - (radius2.x * y1p2) - (radius2.y * x1p2);
|
||||
|
||||
//Check if there is no possible solution
|
||||
//(i.e. we can't do a square root of a negative value)
|
||||
if (c < 0.0f) {
|
||||
//Scale uniformly until we have a single solution
|
||||
//(see F6.2) i.e. when c == 0.0
|
||||
float scale = sqrtf(1.0f - c / (rx2 * ry2));
|
||||
rx *= scale;
|
||||
ry *= scale;
|
||||
//Update rx2 and ry2
|
||||
rx2 = rx * rx;
|
||||
ry2 = ry * ry;
|
||||
|
||||
radius *= sqrtf(1.0f - c / (radius2.x * radius2.y));
|
||||
radius2 = {radius.x * radius.x, radius.y * radius.y};
|
||||
//Step 2 (F6.5.2) - simplified since c == 0.0
|
||||
cxp = 0.0f;
|
||||
cyp = 0.0f;
|
||||
cp = {0.0f, 0.0f};
|
||||
//Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
|
||||
cx = 0.0f;
|
||||
cy = 0.0f;
|
||||
center = {0.0f, 0.0f};
|
||||
} else {
|
||||
//Complete c calculation
|
||||
c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
|
||||
c = sqrtf(c / ((radius2.x * y1p2) + (radius2.y * x1p2)));
|
||||
//Inverse sign if Fa == Fs
|
||||
if (largeArc == sweep) c = -c;
|
||||
|
||||
//Step 2 (F6.5.2)
|
||||
cxp = c * (rx * y1p / ry);
|
||||
cyp = c * (-ry * x1p / rx);
|
||||
|
||||
cp = c * Point{(radius.x * y1p / radius.y), (-radius.y * x1p / radius.x)};
|
||||
//Step 3 (F6.5.3 first part)
|
||||
cx = cosPhi * cxp - sinPhi * cyp;
|
||||
cy = sinPhi * cxp + cosPhi * cyp;
|
||||
center = {cosPhi * cp.x - sinPhi * cp.y, sinPhi * cp.x + cosPhi * cp.y};
|
||||
}
|
||||
|
||||
//Step 3 (F6.5.3 second part) we now have the center point of the ellipse
|
||||
cx += (sx + x) / 2.0f;
|
||||
cy += (sy + y) / 2.0f;
|
||||
center += (start + next) * 0.5f;
|
||||
|
||||
//Step 4 (F6.5.4)
|
||||
//We dont' use arccos (as per w3c doc), see
|
||||
//http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
|
||||
//Note: atan2 (0.0, 1.0) == 0.0
|
||||
at = tvg::atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
|
||||
theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
|
||||
|
||||
nat = tvg::atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
|
||||
deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
|
||||
auto at = tvg::atan2(((y1p - cp.y) / radius.y), ((x1p - cp.x) / radius.x));
|
||||
auto theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
|
||||
auto nat = tvg::atan2(((-y1p - cp.y) / radius.y), ((-x1p - cp.x) / radius.x));
|
||||
auto deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
|
||||
|
||||
if (sweep) {
|
||||
//Ensure delta theta < 0 or else add 360 degrees
|
||||
|
@ -184,52 +139,35 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
|
|||
//(smaller than 90 degrees)
|
||||
//We add one extra segment because we want something
|
||||
//Smaller than 90deg (i.e. not 90 itself)
|
||||
segments = static_cast<int>(fabsf(deltaTheta / MATH_PI2) + 1.0f);
|
||||
delta = deltaTheta / segments;
|
||||
auto segments = int(fabsf(deltaTheta / MATH_PI2) + 1.0f);
|
||||
auto delta = deltaTheta / segments;
|
||||
|
||||
//http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
|
||||
bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
|
||||
|
||||
cosPhiRx = cosPhi * rx;
|
||||
cosPhiRy = cosPhi * ry;
|
||||
sinPhiRx = sinPhi * rx;
|
||||
sinPhiRy = sinPhi * ry;
|
||||
|
||||
cosTheta1 = cosf(theta1);
|
||||
sinTheta1 = sinf(theta1);
|
||||
auto bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
|
||||
auto cosPhiR = Point{cosPhi * radius.x, cosPhi * radius.y};
|
||||
auto sinPhiR = Point{sinPhi * radius.x, sinPhi * radius.y};
|
||||
auto cosTheta1 = cosf(theta1);
|
||||
auto sinTheta1 = sinf(theta1);
|
||||
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
//End angle (for this segment) = current + delta
|
||||
float c1x, c1y, ex, ey, c2x, c2y;
|
||||
float theta2 = theta1 + delta;
|
||||
float cosTheta2 = cosf(theta2);
|
||||
float sinTheta2 = sinf(theta2);
|
||||
Point p[3];
|
||||
auto theta2 = theta1 + delta;
|
||||
auto cosTheta2 = cosf(theta2);
|
||||
auto sinTheta2 = sinf(theta2);
|
||||
|
||||
//First control point (based on start point sx,sy)
|
||||
c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
|
||||
c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
|
||||
auto c1 = start + Point{-bcp * (cosPhiR.x * sinTheta1 + sinPhiR.y * cosTheta1), bcp * (cosPhiR.y * cosTheta1 - sinPhiR.x * sinTheta1)};
|
||||
|
||||
//End point (for this segment)
|
||||
ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
|
||||
ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
|
||||
auto e = center + Point{cosPhiR.x * cosTheta2 - sinPhiR.y * sinTheta2, sinPhiR.x * cosTheta2 + cosPhiR.y * sinTheta2};
|
||||
|
||||
//Second control point (based on end point ex,ey)
|
||||
c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
|
||||
c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {c1x, c1y};
|
||||
p[1] = {c2x, c2y};
|
||||
p[2] = {ex, ey};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = p[1];
|
||||
*cur = p[2];
|
||||
curCtl = e + Point{bcp * (cosPhiR.x * sinTheta2 + sinPhiR.y * cosTheta2), bcp * (sinPhiR.x * sinTheta2 - cosPhiR.y * cosTheta2)};
|
||||
cur = e;
|
||||
out.cubicTo(c1, curCtl, cur);
|
||||
|
||||
//Next start point is the current end point (same for angle)
|
||||
sx = ex;
|
||||
sy = ey;
|
||||
start = e;
|
||||
theta1 = theta2;
|
||||
//Avoid recomputations
|
||||
cosTheta1 = cosTheta2;
|
||||
|
@ -237,6 +175,7 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static int _numberCount(char cmd)
|
||||
{
|
||||
int count = 0;
|
||||
|
@ -276,14 +215,13 @@ static int _numberCount(char cmd)
|
|||
count = 7;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic, bool* closed)
|
||||
static bool _processCommand(RenderPath& out, char cmd, float* arr, int count, Point& cur, Point& curCtl, Point& start, bool& quadratic, bool& closed)
|
||||
{
|
||||
switch (cmd) {
|
||||
case 'm':
|
||||
|
@ -293,169 +231,120 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
|
|||
case 'q':
|
||||
case 't': {
|
||||
for (int i = 0; i < count - 1; i += 2) {
|
||||
arr[i] = arr[i] + cur->x;
|
||||
arr[i + 1] = arr[i + 1] + cur->y;
|
||||
arr[i] = arr[i] + cur.x;
|
||||
arr[i + 1] = arr[i + 1] + cur.y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
arr[0] = arr[0] + cur->x;
|
||||
arr[0] = arr[0] + cur.x;
|
||||
break;
|
||||
}
|
||||
case 'v': {
|
||||
arr[0] = arr[0] + cur->y;
|
||||
arr[0] = arr[0] + cur.y;
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
arr[5] = arr[5] + cur->x;
|
||||
arr[6] = arr[6] + cur->y;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
arr[5] = arr[5] + cur.x;
|
||||
arr[6] = arr[6] + cur.y;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 'm':
|
||||
case 'M': {
|
||||
Point p = {arr[0], arr[1]};
|
||||
cmds->push(PathCommand::MoveTo);
|
||||
pts->push(p);
|
||||
*cur = {arr[0], arr[1]};
|
||||
*startPoint = {arr[0], arr[1]};
|
||||
start = cur = {arr[0], arr[1]};
|
||||
out.moveTo(cur);
|
||||
break;
|
||||
}
|
||||
case 'l':
|
||||
case 'L': {
|
||||
Point p = {arr[0], arr[1]};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
*cur = {arr[0], arr[1]};
|
||||
cur = {arr[0], arr[1]};
|
||||
out.lineTo(cur);
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
case 'C': {
|
||||
Point p[3];
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {arr[0], arr[1]};
|
||||
p[1] = {arr[2], arr[3]};
|
||||
p[2] = {arr[4], arr[5]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = p[1];
|
||||
*cur = p[2];
|
||||
*isQuadratic = false;
|
||||
curCtl = {arr[2], arr[3]};
|
||||
cur = {arr[4], arr[5]};
|
||||
out.cubicTo({arr[0], arr[1]}, curCtl, cur);
|
||||
quadratic = false;
|
||||
break;
|
||||
}
|
||||
case 's':
|
||||
case 'S': {
|
||||
Point p[3], ctrl;
|
||||
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
|
||||
!(*isQuadratic)) {
|
||||
ctrl.x = 2 * cur->x - curCtl->x;
|
||||
ctrl.y = 2 * cur->y - curCtl->y;
|
||||
Point ctrl;
|
||||
if ((out.cmds.count > 1) && (out.cmds.last() == PathCommand::CubicTo) && !quadratic) {
|
||||
ctrl = 2 * cur - curCtl;
|
||||
} else {
|
||||
ctrl = *cur;
|
||||
ctrl = cur;
|
||||
}
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = ctrl;
|
||||
p[1] = {arr[0], arr[1]};
|
||||
p[2] = {arr[2], arr[3]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = p[1];
|
||||
*cur = p[2];
|
||||
*isQuadratic = false;
|
||||
curCtl = {arr[0], arr[1]};
|
||||
cur = {arr[2], arr[3]};
|
||||
out.cubicTo(ctrl, curCtl, cur);
|
||||
quadratic = false;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {ctrl_x0, ctrl_y0};
|
||||
p[1] = {ctrl_x1, ctrl_y1};
|
||||
p[2] = {arr[2], arr[3]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = {arr[0], arr[1]};
|
||||
*cur = p[2];
|
||||
*isQuadratic = true;
|
||||
auto ctrl1 = (cur + 2 * Point{arr[0], arr[1]}) * (1.0f / 3.0f);
|
||||
auto ctrl2 = (Point{arr[2], arr[3]} + 2 * Point{arr[0], arr[1]}) * (1.0f / 3.0f);
|
||||
curCtl = {arr[0], arr[1]};
|
||||
cur = {arr[2], arr[3]};
|
||||
out.cubicTo(ctrl1, ctrl2, cur);
|
||||
quadratic = true;
|
||||
break;
|
||||
}
|
||||
case 't':
|
||||
case 'T': {
|
||||
Point p[3], ctrl;
|
||||
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
|
||||
*isQuadratic) {
|
||||
ctrl.x = 2 * cur->x - curCtl->x;
|
||||
ctrl.y = 2 * cur->y - curCtl->y;
|
||||
Point ctrl;
|
||||
if ((out.cmds.count > 1) && (out.cmds.last() == PathCommand::CubicTo) && quadratic) {
|
||||
ctrl = 2 * cur - curCtl;
|
||||
} else {
|
||||
ctrl = *cur;
|
||||
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);
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {ctrl_x0, ctrl_y0};
|
||||
p[1] = {ctrl_x1, ctrl_y1};
|
||||
p[2] = {arr[0], arr[1]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = {ctrl.x, ctrl.y};
|
||||
*cur = p[2];
|
||||
*isQuadratic = true;
|
||||
auto ctrl1 = (cur + 2 * ctrl) * (1.0f / 3.0f);
|
||||
auto ctrl2 = (Point{arr[0], arr[1]} + 2 * ctrl) * (1.0f / 3.0f);
|
||||
curCtl = {ctrl.x, ctrl.y};
|
||||
cur = {arr[0], arr[1]};
|
||||
out.cubicTo(ctrl1, ctrl2, cur);
|
||||
quadratic = true;
|
||||
break;
|
||||
}
|
||||
case 'h':
|
||||
case 'H': {
|
||||
Point p = {arr[0], cur->y};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
cur->x = arr[0];
|
||||
out.lineTo({arr[0], cur.y});
|
||||
cur.x = arr[0];
|
||||
break;
|
||||
}
|
||||
case 'v':
|
||||
case 'V': {
|
||||
Point p = {cur->x, arr[0]};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
cur->y = arr[0];
|
||||
out.lineTo({cur.x, arr[0]});
|
||||
cur.y = arr[0];
|
||||
break;
|
||||
}
|
||||
case 'z':
|
||||
case 'Z': {
|
||||
cmds->push(PathCommand::Close);
|
||||
*cur = *startPoint;
|
||||
*closed = true;
|
||||
out.close();
|
||||
cur = start;
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
case 'a':
|
||||
case 'A': {
|
||||
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 (!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;
|
||||
cur = {arr[5], arr[6]};
|
||||
out.lineTo(cur);
|
||||
} else if (!tvg::equal(cur.x, arr[5]) || !tvg::equal(cur.y, arr[6])) {
|
||||
_pathAppendArcTo(out, cur, curCtl, {arr[5], arr[6]}, {fabsf(arr[0]), fabsf(arr[1])}, deg2rad(arr[2]), arr[3], arr[4]);
|
||||
cur = curCtl = {arr[5], arr[6]};
|
||||
quadratic = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -497,12 +386,12 @@ static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* c
|
|||
}
|
||||
}
|
||||
*count = 0;
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
for (int i = 0; i < *count; i++) {
|
||||
if (!_parseNumber(&path, &arr[i])) {
|
||||
*count = 0;
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
path = _skipComma(path);
|
||||
}
|
||||
|
@ -515,29 +404,26 @@ static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* c
|
|||
/************************************************************************/
|
||||
|
||||
|
||||
bool svgPathToShape(const char* svgPath, Shape* shape)
|
||||
bool svgPathToShape(const char* svgPath, RenderPath& out)
|
||||
{
|
||||
float numberArray[7];
|
||||
int numberCount = 0;
|
||||
Point cur = { 0, 0 };
|
||||
Point curCtl = { 0, 0 };
|
||||
Point startPoint = { 0, 0 };
|
||||
Point cur = {0, 0};
|
||||
Point curCtl = {0, 0};
|
||||
Point start = {0, 0};
|
||||
char cmd = 0;
|
||||
bool isQuadratic = false;
|
||||
bool closed = false;
|
||||
char* path = (char*)svgPath;
|
||||
|
||||
auto& pts = SHAPE(shape)->rs.path.pts;
|
||||
auto& cmds = SHAPE(shape)->rs.path.cmds;
|
||||
auto lastCmds = cmds.count;
|
||||
auto path = (char*)svgPath;
|
||||
auto lastCmds = out.cmds.count;
|
||||
auto isQuadratic = false;
|
||||
auto closed = false;
|
||||
|
||||
while ((path[0] != '\0')) {
|
||||
path = _nextCommand(path, &cmd, numberArray, &numberCount, &closed);
|
||||
if (!path) break;
|
||||
closed = false;
|
||||
if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic, &closed)) break;
|
||||
if (!_processCommand(out, cmd, numberArray, numberCount, cur, curCtl, start, isQuadratic, closed)) break;
|
||||
}
|
||||
|
||||
if (cmds.count > lastCmds && cmds[lastCmds] != PathCommand::MoveTo) return false;
|
||||
if (out.cmds.count > lastCmds && out.cmds[lastCmds] != PathCommand::MoveTo) return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
#ifndef _TVG_SVG_PATH_H_
|
||||
#define _TVG_SVG_PATH_H_
|
||||
|
||||
#include <tvgCommon.h>
|
||||
#include "tvgRender.h"
|
||||
|
||||
bool svgPathToShape(const char* svgPath, Shape* shape);
|
||||
bool svgPathToShape(const char* svgPath, RenderPath& out);
|
||||
|
||||
#endif //_TVG_SVG_PATH_H_
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "tvgCompressor.h"
|
||||
#include "tvgFill.h"
|
||||
#include "tvgStr.h"
|
||||
#include "tvgShape.h"
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
#include "tvgSvgSceneBuilder.h"
|
||||
#include "tvgSvgPath.h"
|
||||
|
@ -454,7 +455,7 @@ static bool _recognizeShape(SvgNode* node, Shape* shape)
|
|||
switch (node->type) {
|
||||
case SvgNodeType::Path: {
|
||||
if (node->node.path.path) {
|
||||
if (!svgPathToShape(node->node.path.path, shape)) {
|
||||
if (!svgPathToShape(node->node.path.path, SHAPE(shape)->rs.path)) {
|
||||
TVGERR("SVG", "Invalid path information.");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -474,10 +474,9 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
|
|||
if (!this->points(outline, flags, pts, ptsCnt, offset + kerning)) return false;
|
||||
|
||||
//generate tvg paths.
|
||||
auto& pathCmds = SHAPE(shape)->rs.path.cmds;
|
||||
auto& pathPts = SHAPE(shape)->rs.path.pts;
|
||||
pathCmds.reserve(ptsCnt);
|
||||
pathPts.reserve(ptsCnt);
|
||||
auto& path = SHAPE(shape)->rs.path;
|
||||
path.cmds.reserve(ptsCnt);
|
||||
path.pts.reserve(ptsCnt);
|
||||
|
||||
uint32_t begin = 0;
|
||||
|
||||
|
@ -485,42 +484,31 @@ bool TtfReader::convert(Shape* shape, TtfGlyphMetrics& gmetrics, const Point& of
|
|||
//contour must start with move to
|
||||
bool offCurve = !(flags[begin] & ON_CURVE);
|
||||
Point ptsBegin = offCurve ? (pts[begin] + pts[endPts[i]]) * 0.5f : pts[begin];
|
||||
pathCmds.push(PathCommand::MoveTo);
|
||||
pathPts.push(ptsBegin);
|
||||
path.moveTo(ptsBegin);
|
||||
|
||||
auto cnt = endPts[i] - begin + 1;
|
||||
for (uint32_t x = 1; x < cnt; ++x) {
|
||||
if (flags[begin + x] & ON_CURVE) {
|
||||
if (offCurve) {
|
||||
pathCmds.push(PathCommand::CubicTo);
|
||||
pathPts.push(pathPts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - pathPts.last()));
|
||||
pathPts.push(pts[begin + x] + (2.0f/3.0f) * (pts[begin + x - 1] - pts[begin + x]));
|
||||
pathPts.push(pts[begin + x]);
|
||||
path.cubicTo(path.pts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - path.pts.last()), pts[begin + x] + (2.0f/3.0f) * (pts[begin + x - 1] - pts[begin + x]), pts[begin + x]);
|
||||
offCurve = false;
|
||||
} else {
|
||||
pathCmds.push(PathCommand::LineTo);
|
||||
pathPts.push(pts[begin + x]);
|
||||
path.lineTo(pts[begin + x]);
|
||||
}
|
||||
} else {
|
||||
if (offCurve) {
|
||||
pathCmds.push(PathCommand::CubicTo);
|
||||
auto end = (pts[begin + x] + pts[begin + x - 1]) * 0.5f;
|
||||
pathPts.push(pathPts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - pathPts.last()));
|
||||
pathPts.push(end + (2.0f/3.0f) * (pts[begin + x - 1] - end));
|
||||
pathPts.push(end);
|
||||
path.cubicTo(path.pts.last() + (2.0f/3.0f) * (pts[begin + x - 1] - path.pts.last()), end + (2.0f/3.0f) * (pts[begin + x - 1] - end), end);
|
||||
} else {
|
||||
offCurve = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (offCurve) {
|
||||
pathCmds.push(PathCommand::CubicTo);
|
||||
pathPts.push(pathPts.last() + (2.0f/3.0f) * (pts[begin + cnt - 1] - pathPts.last()));
|
||||
pathPts.push(ptsBegin + (2.0f/3.0f) * (pts[begin + cnt - 1] - ptsBegin));
|
||||
pathPts.push(ptsBegin);
|
||||
path.cubicTo(path.pts.last() + (2.0f/3.0f) * (pts[begin + cnt - 1] - path.pts.last()), ptsBegin + (2.0f/3.0f) * (pts[begin + cnt - 1] - ptsBegin), ptsBegin);
|
||||
}
|
||||
//contour must end with close
|
||||
pathCmds.push(PathCommand::Close);
|
||||
path.close();
|
||||
begin = endPts[i] + 1;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -32,13 +32,11 @@
|
|||
/* Gaussian Blur */
|
||||
/************************************************************************/
|
||||
|
||||
#define GL_GAUSSIAN_MAX_LEVEL 3
|
||||
|
||||
struct GlGaussianBlur {
|
||||
int level{};
|
||||
float sigma{};
|
||||
float scale{};
|
||||
float extend{};
|
||||
float dummy0{};
|
||||
};
|
||||
|
||||
|
||||
|
@ -64,7 +62,6 @@ void GlEffect::update(RenderEffectGaussianBlur* effect, const Matrix& transform)
|
|||
blur->sigma = effect->sigma;
|
||||
blur->scale = std::sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12);
|
||||
blur->extend = 2 * blur->sigma * blur->scale;
|
||||
blur->level = int(GL_GAUSSIAN_MAX_LEVEL * ((effect->quality - 1) * 0.01f)) + 1;
|
||||
effect->rd = blur;
|
||||
effect->valid = (blur->extend > 0);
|
||||
}
|
||||
|
@ -135,7 +132,6 @@ void GlEffect::update(RenderEffectDropShadow* effect, const Matrix& transform)
|
|||
};
|
||||
dropShadow->sigma = sigma;
|
||||
dropShadow->scale = scale;
|
||||
dropShadow->level = int(GL_GAUSSIAN_MAX_LEVEL * ((effect->quality - 1) * 0.01f)) + 1;
|
||||
dropShadow->color[3] = effect->color[3] / 255.0f;
|
||||
//Drop shadow effect applies blending in the shader (GL_BLEND disabled), so the color should be premultiplied:
|
||||
dropShadow->color[0] = effect->color[0] / 255.0f * dropShadow->color[3];
|
||||
|
|
|
@ -28,18 +28,16 @@
|
|||
|
||||
bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag)
|
||||
{
|
||||
const RenderPath* path = nullptr;
|
||||
RenderPath trimmedPath;
|
||||
if (rshape.trimpath()) {
|
||||
if (!rshape.stroke->trim.trim(rshape.path, trimmedPath)) return true;
|
||||
path = &trimmedPath;
|
||||
} else path = &rshape.path;
|
||||
|
||||
if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Path)) {
|
||||
fill.clear();
|
||||
|
||||
BWTessellator bwTess{&fill};
|
||||
bwTess.tessellate(*path, matrix);
|
||||
if (rshape.trimpath()) {
|
||||
RenderPath trimmedPath;
|
||||
if (rshape.stroke->trim.trim(rshape.path, trimmedPath)) bwTess.tessellate(trimmedPath, matrix);
|
||||
else return true;
|
||||
} else bwTess.tessellate(rshape.path, matrix);
|
||||
|
||||
fillRule = rshape.rule;
|
||||
bounds = bwTess.bounds();
|
||||
}
|
||||
|
@ -48,7 +46,7 @@ bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag)
|
|||
stroke.clear();
|
||||
|
||||
Stroker stroker{&stroke, matrix};
|
||||
stroker.stroke(&rshape, *path);
|
||||
stroker.stroke(&rshape);
|
||||
bounds = stroker.bounds();
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ void GlRenderTarget::init(uint32_t width, uint32_t height, GLint resolveId)
|
|||
|
||||
void GlRenderTarget::reset()
|
||||
{
|
||||
if (mFbo == 0) return;
|
||||
if (mFbo == 0 || mFbo == GL_INVALID_VALUE) return;
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
GL_CHECK(glDeleteFramebuffers(1, &mFbo));
|
||||
GL_CHECK(glDeleteRenderbuffers(1, &mColorBuffer));
|
||||
|
|
|
@ -740,10 +740,10 @@ void main()
|
|||
const char* GAUSSIAN_VERTICAL = R"(
|
||||
uniform sampler2D uSrcTexture;
|
||||
layout(std140) uniform Gaussian {
|
||||
int level;
|
||||
float sigma;
|
||||
float scale;
|
||||
float extend;
|
||||
float dummy0;
|
||||
} uGaussian;
|
||||
|
||||
in vec2 vUV;
|
||||
|
@ -776,10 +776,10 @@ void main()
|
|||
const char* GAUSSIAN_HORIZONTAL = R"(
|
||||
uniform sampler2D uSrcTexture;
|
||||
layout(std140) uniform Gaussian {
|
||||
int level;
|
||||
float sigma;
|
||||
float scale;
|
||||
float extend;
|
||||
float dummy0;
|
||||
} uGaussian;
|
||||
|
||||
in vec2 vUV;
|
||||
|
@ -813,10 +813,10 @@ const char* EFFECT_DROPSHADOW = R"(
|
|||
uniform sampler2D uSrcTexture;
|
||||
uniform sampler2D uBlrTexture;
|
||||
layout(std140) uniform DropShadow {
|
||||
int level;
|
||||
float sigma;
|
||||
float scale;
|
||||
float extend;
|
||||
float dummy0;
|
||||
vec4 color;
|
||||
vec2 offset;
|
||||
} uDropShadow;
|
||||
|
|
|
@ -39,7 +39,7 @@ Stroker::Stroker(GlGeometryBuffer* buffer, const Matrix& matrix) : mBuffer(buffe
|
|||
}
|
||||
|
||||
|
||||
void Stroker::stroke(const RenderShape *rshape, const RenderPath& path)
|
||||
void Stroker::stroke(const RenderShape *rshape)
|
||||
{
|
||||
mMiterLimit = rshape->strokeMiterlimit();
|
||||
mStrokeCap = rshape->strokeCap();
|
||||
|
@ -52,9 +52,12 @@ void Stroker::stroke(const RenderShape *rshape, const RenderPath& path)
|
|||
mStrokeWidth = strokeWidth / mMatrix.e11;
|
||||
}
|
||||
|
||||
auto& dash = rshape->stroke->dash;
|
||||
if (dash.length < DASH_PATTERN_THRESHOLD) doStroke(path);
|
||||
else doDashStroke(path, dash.pattern, dash.count, dash.offset, dash.length);
|
||||
RenderPath dashed;
|
||||
if (rshape->strokeDash(dashed)) doStroke(dashed);
|
||||
else if (rshape->trimpath()) {
|
||||
RenderPath trimmedPath;
|
||||
if (rshape->stroke->trim.trim(rshape->path, trimmedPath)) doStroke(trimmedPath);
|
||||
} else doStroke(rshape->path);
|
||||
}
|
||||
|
||||
|
||||
|
@ -109,19 +112,6 @@ void Stroker::doStroke(const RenderPath& path)
|
|||
}
|
||||
|
||||
|
||||
void Stroker::doDashStroke(const RenderPath& path, const float *patterns, uint32_t patternCnt, float offset, float length)
|
||||
{
|
||||
RenderPath dpath;
|
||||
|
||||
dpath.cmds.reserve(20 * path.cmds.count);
|
||||
dpath.pts.reserve(20 * path.pts.count);
|
||||
|
||||
DashStroke dash(&dpath.cmds, &dpath.pts, patterns, patternCnt, offset, length);
|
||||
dash.doStroke(path, mStrokeCap != StrokeCap::Butt);
|
||||
doStroke(dpath);
|
||||
}
|
||||
|
||||
|
||||
void Stroker::strokeCap()
|
||||
{
|
||||
if (mStrokeCap == StrokeCap::Butt) return;
|
||||
|
@ -442,227 +432,6 @@ void Stroker::strokeRound(const Point& p, const Point& outDir)
|
|||
}
|
||||
|
||||
|
||||
DashStroke::DashStroke(Array<PathCommand> *cmds, Array<Point> *pts, const float *patterns, uint32_t patternCnt, float offset, float length)
|
||||
: mCmds(cmds),
|
||||
mPts(pts),
|
||||
mDashPattern(patterns),
|
||||
mDashCount(patternCnt),
|
||||
mDashOffset(offset),
|
||||
mDashLength(length)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::doStroke(const RenderPath& path, bool validPoint)
|
||||
{
|
||||
//validPoint: zero length segment with non-butt cap still should be rendered as a point - only the caps are visible
|
||||
int32_t idx = 0;
|
||||
auto offset = mDashOffset;
|
||||
bool gap = false;
|
||||
if (!tvg::zero(mDashOffset)) {
|
||||
auto length = (mDashCount % 2) ? mDashLength * 2 : mDashLength;
|
||||
offset = fmodf(offset, length);
|
||||
if (offset < 0) offset += length;
|
||||
|
||||
for (uint32_t i = 0; i < mDashCount * (mDashCount % 2 + 1); ++i, ++idx) {
|
||||
auto curPattern = mDashPattern[i % mDashCount];
|
||||
if (offset < curPattern) break;
|
||||
offset -= curPattern;
|
||||
gap = !gap;
|
||||
}
|
||||
idx = idx % mDashCount;
|
||||
}
|
||||
|
||||
auto pts = path.pts.data;
|
||||
ARRAY_FOREACH(cmd, path.cmds) {
|
||||
switch (*cmd) {
|
||||
case PathCommand::Close: {
|
||||
this->dashLineTo(mPtStart, validPoint);
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
// reset the dash state
|
||||
mCurrIdx = idx;
|
||||
mCurrLen = mDashPattern[idx] - offset;
|
||||
mCurOpGap = gap;
|
||||
mMove = true;
|
||||
mPtStart = mPtCur = *pts;
|
||||
pts++;
|
||||
break;
|
||||
}
|
||||
case PathCommand::LineTo: {
|
||||
this->dashLineTo(*pts, validPoint);
|
||||
pts++;
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
this->dashCubicTo(pts[0], pts[1], pts[2], validPoint);
|
||||
pts += 3;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::drawPoint(const Point& p)
|
||||
{
|
||||
if (mMove || mDashPattern[mCurrIdx] < FLOAT_EPSILON) {
|
||||
this->moveTo(p);
|
||||
mMove = false;
|
||||
}
|
||||
this->lineTo(p);
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::dashLineTo(const Point& to, bool validPoint)
|
||||
{
|
||||
auto len = length(mPtCur - to);
|
||||
|
||||
if (tvg::zero(len)) {
|
||||
this->moveTo(mPtCur);
|
||||
} else if (len <= mCurrLen) {
|
||||
mCurrLen -= len;
|
||||
if (!mCurOpGap) {
|
||||
if (mMove) {
|
||||
this->moveTo(mPtCur);
|
||||
mMove = false;
|
||||
}
|
||||
this->lineTo(to);
|
||||
}
|
||||
} else {
|
||||
Line curr = {mPtCur, to};
|
||||
|
||||
while (len - mCurrLen > DASH_PATTERN_THRESHOLD) {
|
||||
Line right;
|
||||
if (mCurrLen > 0.0f) {
|
||||
Line left;
|
||||
curr.split(mCurrLen, left, right);
|
||||
len -= mCurrLen;
|
||||
if (!mCurOpGap) {
|
||||
if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) {
|
||||
this->moveTo(left.pt1);
|
||||
mMove = false;
|
||||
}
|
||||
this->lineTo(left.pt2);
|
||||
}
|
||||
} else {
|
||||
if (validPoint && !mCurOpGap) drawPoint(curr.pt1);
|
||||
right = curr;
|
||||
}
|
||||
mCurrIdx = (mCurrIdx + 1) % mDashCount;
|
||||
mCurrLen = mDashPattern[mCurrIdx];
|
||||
mCurOpGap = !mCurOpGap;
|
||||
curr = right;
|
||||
mPtCur = curr.pt1;
|
||||
mMove = true;
|
||||
}
|
||||
mCurrLen -= len;
|
||||
if (!mCurOpGap) {
|
||||
if (mMove) {
|
||||
this->moveTo(curr.pt1);
|
||||
mMove = false;
|
||||
}
|
||||
this->lineTo(curr.pt2);
|
||||
}
|
||||
|
||||
if (mCurrLen < 0.1f) {
|
||||
mCurrIdx = (mCurrIdx + 1) % mDashCount;
|
||||
mCurrLen = mDashPattern[mCurrIdx];
|
||||
mCurOpGap = !mCurOpGap;
|
||||
}
|
||||
}
|
||||
|
||||
mPtCur = to;
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& end, bool validPoint)
|
||||
{
|
||||
Bezier cur{ mPtCur, cnt1, cnt2, end };
|
||||
|
||||
auto len = cur.length();
|
||||
|
||||
if (tvg::zero(len)) {
|
||||
this->moveTo(mPtCur);
|
||||
} else if (len <= mCurrLen) {
|
||||
mCurrLen -= len;
|
||||
if (!mCurOpGap) {
|
||||
if (mMove) {
|
||||
this->moveTo(mPtCur);
|
||||
mMove = false;
|
||||
}
|
||||
this->cubicTo(cnt1, cnt2, end);
|
||||
}
|
||||
} else {
|
||||
while (len - mCurrLen > DASH_PATTERN_THRESHOLD) {
|
||||
Bezier right;
|
||||
if (mCurrLen > 0.0f) {
|
||||
Bezier left;
|
||||
cur.split(mCurrLen, left, right);
|
||||
len -= mCurrLen;
|
||||
if (!mCurOpGap) {
|
||||
if (mMove || mDashPattern[mCurrIdx] - mCurrLen < FLOAT_EPSILON) {
|
||||
this->moveTo(left.start);
|
||||
mMove = false;
|
||||
}
|
||||
this->cubicTo(left.ctrl1, left.ctrl2, left.end);
|
||||
}
|
||||
} else {
|
||||
if (validPoint && !mCurOpGap) drawPoint(cur.start);
|
||||
right = cur;
|
||||
}
|
||||
mCurrIdx = (mCurrIdx + 1) % mDashCount;
|
||||
mCurrLen = mDashPattern[mCurrIdx];
|
||||
mCurOpGap = !mCurOpGap;
|
||||
cur = right;
|
||||
mPtCur = cur.start;
|
||||
mMove = true;
|
||||
}
|
||||
|
||||
mCurrLen -= len;
|
||||
if (!mCurOpGap) {
|
||||
if (mMove) {
|
||||
this->moveTo(cur.start);
|
||||
mMove = false;
|
||||
}
|
||||
this->cubicTo(cur.ctrl1, cur.ctrl2, cur.end);
|
||||
}
|
||||
|
||||
if (mCurrLen < 0.1f) {
|
||||
mCurrIdx = (mCurrIdx + 1) % mDashCount;
|
||||
mCurrLen = mDashPattern[mCurrIdx];
|
||||
mCurOpGap = !mCurOpGap;
|
||||
}
|
||||
}
|
||||
mPtCur = end;
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::moveTo(const Point& pt)
|
||||
{
|
||||
mPts->push(pt);
|
||||
mCmds->push(PathCommand::MoveTo);
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::lineTo(const Point& pt)
|
||||
{
|
||||
mPts->push(pt);
|
||||
mCmds->push(PathCommand::LineTo);
|
||||
}
|
||||
|
||||
|
||||
void DashStroke::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end)
|
||||
{
|
||||
mPts->push(cnt1);
|
||||
mPts->push(cnt2);
|
||||
mPts->push(end);
|
||||
mCmds->push(PathCommand::CubicTo);
|
||||
}
|
||||
|
||||
|
||||
BWTessellator::BWTessellator(GlGeometryBuffer* buffer): mBuffer(buffer)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -39,12 +39,11 @@ class Stroker
|
|||
};
|
||||
public:
|
||||
Stroker(GlGeometryBuffer* buffer, const Matrix& matrix);
|
||||
void stroke(const RenderShape *rshape, const RenderPath& path);
|
||||
void stroke(const RenderShape *rshape);
|
||||
RenderRegion bounds() const;
|
||||
|
||||
private:
|
||||
void doStroke(const RenderPath& path);
|
||||
void doDashStroke(const RenderPath& path, const float* patterns, uint32_t patternCnt, float offset, float length);
|
||||
|
||||
float strokeRadius() const
|
||||
{
|
||||
|
@ -75,34 +74,6 @@ private:
|
|||
Point mRightBottom = {0.0f, 0.0f};
|
||||
};
|
||||
|
||||
class DashStroke
|
||||
{
|
||||
public:
|
||||
DashStroke(Array<PathCommand>* cmds, Array<Point>* pts, const float* patterns, uint32_t patternCnt, float offset, float length);
|
||||
void doStroke(const RenderPath& path, bool drawPoint);
|
||||
|
||||
private:
|
||||
void drawPoint(const Point& p);
|
||||
void dashLineTo(const Point& pt, bool drawPoint);
|
||||
void dashCubicTo(const Point& pt1, const Point& pt2, const Point& pt3, bool drawPoint);
|
||||
void moveTo(const Point& pt);
|
||||
void lineTo(const Point& pt);
|
||||
void cubicTo(const Point& pt1, const Point& pt2, const Point& pt3);
|
||||
|
||||
Array<PathCommand>* mCmds;
|
||||
Array<Point>* mPts;
|
||||
const float* mDashPattern;
|
||||
uint32_t mDashCount;
|
||||
float mDashOffset;
|
||||
float mDashLength;
|
||||
float mCurrLen = 0.0f;
|
||||
int32_t mCurrIdx = 0;
|
||||
bool mCurOpGap = false;
|
||||
bool mMove = true;
|
||||
Point mPtStart = {};
|
||||
Point mPtCur = {};
|
||||
};
|
||||
|
||||
class BWTessellator
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <algorithm>
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgMath.h"
|
||||
#include "tvgColor.h"
|
||||
#include "tvgRender.h"
|
||||
|
||||
#define SW_CURVE_TYPE_POINT 0
|
||||
|
@ -564,6 +565,80 @@ static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d)
|
|||
return BLEND_PRE(JOIN(255, f(C1(s), o.r), f(C2(s), o.g), f(C3(s), o.b)), s, o.a);
|
||||
}
|
||||
|
||||
void rasterRGB2HSL(uint8_t r, uint8_t g, uint8_t b, float* h, float* s, float* l);
|
||||
|
||||
static inline uint32_t opBlendHue(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float sh, ds, dl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), &sh, 0, 0);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, 0, &ds, &dl);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(sh, ds, dl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendSaturation(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float dh, ss, dl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), 0, &ss, 0);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, &dh, 0, &dl);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(dh, ss, dl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendColor(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float sh, ss, dl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), &sh, &ss, 0);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, 0, 0, &dl);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(sh, ss, dl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendLuminosity(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
float dh, ds, sl;
|
||||
rasterRGB2HSL(C1(s), C2(s), C3(s), 0, 0, &sl);
|
||||
rasterRGB2HSL(o.r, o.g, o.b, &dh, &ds, 0);
|
||||
|
||||
uint8_t r, g, b;
|
||||
hsl2rgb(dh, ds, sl, r, g, b);
|
||||
|
||||
return BLEND_PRE(JOIN(255, r, g, b), s, o.a);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendHardMix(uint32_t s, uint32_t d)
|
||||
{
|
||||
RenderColor o;
|
||||
if (!BLEND_UPRE(d, o)) return s;
|
||||
|
||||
auto f = [](uint8_t s, uint8_t d) {
|
||||
return (s + d >= 255) ? 255 : 0;
|
||||
};
|
||||
|
||||
return BLEND_PRE(JOIN(255, f(C1(s), o.r), f(C2(s), o.g), f(C3(s), o.b)), s, o.a);
|
||||
}
|
||||
|
||||
|
||||
int64_t mathMultiply(int64_t a, int64_t b);
|
||||
int64_t mathDivide(int64_t a, int64_t b);
|
||||
|
|
|
@ -1769,4 +1769,40 @@ void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: can be moved in tvgColor
|
||||
void rasterRGB2HSL(uint8_t r, uint8_t g, uint8_t b, float* h, float* s, float* l)
|
||||
{
|
||||
auto rf = r / 255.0f;
|
||||
auto gf = g / 255.0f;
|
||||
auto bf = b / 255.0f;
|
||||
auto maxVal = std::max(std::max(rf, gf), bf);
|
||||
auto minVal = std::min(std::min(rf, gf), bf);
|
||||
auto delta = maxVal - minVal;
|
||||
|
||||
//lightness
|
||||
float t;
|
||||
if (l || s) {
|
||||
t = (maxVal + minVal) * 0.5f;
|
||||
if (l) *l = t;
|
||||
}
|
||||
|
||||
if (tvg::zero(delta)) {
|
||||
if (h) *h = 0.0f;
|
||||
if (s) *s = 0.0f;
|
||||
} else {
|
||||
//saturation
|
||||
if (s) {
|
||||
*s = (t < 0.5f) ? (delta / (maxVal + minVal)) : (delta / (2.0f - maxVal - minVal));
|
||||
}
|
||||
//hue
|
||||
if (h) {
|
||||
if (maxVal == rf) *h = (gf - bf) / delta + (gf < bf ? 6.0f : 0.0f);
|
||||
else if (maxVal == gf) *h = (bf - rf) / delta + 2.0f;
|
||||
else *h = (rf - gf) / delta + 4.0f;
|
||||
*h *= 60.0f; //directly convert to degrees
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -528,6 +528,7 @@ bool SwRenderer::blend(BlendMethod method)
|
|||
|
||||
switch (method) {
|
||||
case BlendMethod::Normal:
|
||||
case BlendMethod::Composition:
|
||||
surface->blender = nullptr;
|
||||
break;
|
||||
case BlendMethod::Multiply:
|
||||
|
@ -563,9 +564,24 @@ bool SwRenderer::blend(BlendMethod method)
|
|||
case BlendMethod::Exclusion:
|
||||
surface->blender = opBlendExclusion;
|
||||
break;
|
||||
case BlendMethod::Hue:
|
||||
surface->blender = opBlendHue;
|
||||
break;
|
||||
case BlendMethod::Saturation:
|
||||
surface->blender = opBlendSaturation;
|
||||
break;
|
||||
case BlendMethod::Color:
|
||||
surface->blender = opBlendColor;
|
||||
break;
|
||||
case BlendMethod::Luminosity:
|
||||
surface->blender = opBlendLuminosity;
|
||||
break;
|
||||
case BlendMethod::Add:
|
||||
surface->blender = opBlendAdd;
|
||||
break;
|
||||
case BlendMethod::HardMix:
|
||||
surface->blender = opBlendHardMix;
|
||||
break;
|
||||
default:
|
||||
TVGLOG("SW_ENGINE", "Non supported blending option = %d", (int) method);
|
||||
surface->blender = nullptr;
|
||||
|
|
|
@ -429,11 +429,11 @@ uint8_t Paint::opacity() const noexcept
|
|||
|
||||
Result Paint::blend(BlendMethod method) noexcept
|
||||
{
|
||||
if (method > BlendMethod::HardMix) return Result::InvalidArguments;
|
||||
//TODO: Remove later
|
||||
if (method == BlendMethod::Hue || method == BlendMethod::Saturation || method == BlendMethod::Color || method == BlendMethod::Luminosity || method == BlendMethod::HardMix) return Result::NonSupport;
|
||||
pImpl->blend(method);
|
||||
return Result::Success;
|
||||
if (method <= BlendMethod::HardMix || method == BlendMethod::Composition) {
|
||||
pImpl->blend(method);
|
||||
return Result::Success;
|
||||
}
|
||||
return Result::InvalidArguments;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -587,4 +587,210 @@ bool RenderTrimPath::trim(const RenderPath& in, RenderPath& out) const
|
|||
}
|
||||
|
||||
return out.pts.count >= 2;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* StrokeDashPath Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
//TODO: use this common function from all engines
|
||||
#ifdef THORVG_GL_RASTER_SUPPORT
|
||||
|
||||
struct StrokeDashPath
|
||||
{
|
||||
public:
|
||||
StrokeDashPath(RenderStroke::Dash dash) : dash(dash) {}
|
||||
bool gen(const RenderPath& in, RenderPath& out, bool drawPoint);
|
||||
|
||||
private:
|
||||
void lineTo(RenderPath& out, const Point& pt, bool drawPoint);
|
||||
void cubicTo(RenderPath& out, const Point& pt1, const Point& pt2, const Point& pt3, bool drawPoint);
|
||||
void point(RenderPath& out, const Point& p);
|
||||
|
||||
template<typename Segment, typename LengthFn, typename SplitFn, typename DrawFn, typename PointFn>
|
||||
void segment(Segment seg, float len, RenderPath& out, bool allowDot, LengthFn lengthFn, SplitFn splitFn, DrawFn drawFn, PointFn getStartPt, const Point& endPos);
|
||||
|
||||
RenderStroke::Dash dash;
|
||||
float curLen = 0.0f;
|
||||
int32_t curIdx = 0;
|
||||
Point curPos{};
|
||||
bool opGap = false;
|
||||
bool move = true;
|
||||
};
|
||||
|
||||
|
||||
template<typename Segment, typename LengthFn, typename SplitFn, typename DrawFn, typename PointFn>
|
||||
void StrokeDashPath::segment(Segment seg, float len, RenderPath& out, bool allowDot, LengthFn lengthFn, SplitFn splitFn, DrawFn drawFn, PointFn getStartPt, const Point& end)
|
||||
{
|
||||
#define MIN_CURR_LEN_THRESHOLD 0.1f
|
||||
|
||||
if (tvg::zero(len)) {
|
||||
out.moveTo(curPos);
|
||||
} else if (len <= curLen) {
|
||||
curLen -= len;
|
||||
if (!opGap) {
|
||||
if (move) {
|
||||
out.moveTo(curPos);
|
||||
move = false;
|
||||
}
|
||||
drawFn(seg);
|
||||
}
|
||||
} else {
|
||||
Segment left, right;
|
||||
while (len - curLen > DASH_PATTERN_THRESHOLD) {
|
||||
if (curLen > 0.0f) {
|
||||
splitFn(seg, curLen, left, right);
|
||||
len -= curLen;
|
||||
if (!opGap) {
|
||||
if (move || dash.pattern[curIdx] - curLen < FLOAT_EPSILON) {
|
||||
out.moveTo(getStartPt(left));
|
||||
move = false;
|
||||
}
|
||||
drawFn(left);
|
||||
}
|
||||
} else {
|
||||
if (allowDot && !opGap) point(out, getStartPt(seg));
|
||||
right = seg;
|
||||
}
|
||||
|
||||
curIdx = (curIdx + 1) % dash.count;
|
||||
curLen = dash.pattern[curIdx];
|
||||
opGap = !opGap;
|
||||
seg = right;
|
||||
curPos = getStartPt(seg);
|
||||
move = true;
|
||||
}
|
||||
curLen -= len;
|
||||
if (!opGap) {
|
||||
if (move) {
|
||||
out.moveTo(getStartPt(seg));
|
||||
move = false;
|
||||
}
|
||||
drawFn(seg);
|
||||
}
|
||||
if (curLen < MIN_CURR_LEN_THRESHOLD) {
|
||||
curIdx = (curIdx + 1) % dash.count;
|
||||
curLen = dash.pattern[curIdx];
|
||||
opGap = !opGap;
|
||||
}
|
||||
}
|
||||
curPos = end;
|
||||
}
|
||||
|
||||
|
||||
//allowDot: zero length segment with non-butt cap still should be rendered as a point - only the caps are visible
|
||||
bool StrokeDashPath::gen(const RenderPath& in, RenderPath& out, bool allowDot)
|
||||
{
|
||||
int32_t idx = 0;
|
||||
auto offset = dash.offset;
|
||||
auto gap = false;
|
||||
if (!tvg::zero(dash.offset)) {
|
||||
auto length = (dash.count % 2) ? dash.length * 2 : dash.length;
|
||||
offset = fmodf(offset, length);
|
||||
if (offset < 0) offset += length;
|
||||
|
||||
for (uint32_t i = 0; i < dash.count * (dash.count % 2 + 1); ++i, ++idx) {
|
||||
auto curPattern = dash.pattern[i % dash.count];
|
||||
if (offset < curPattern) break;
|
||||
offset -= curPattern;
|
||||
gap = !gap;
|
||||
}
|
||||
idx = idx % dash.count;
|
||||
}
|
||||
|
||||
auto pts = in.pts.data;
|
||||
Point start{};
|
||||
|
||||
ARRAY_FOREACH(cmd, in.cmds) {
|
||||
switch (*cmd) {
|
||||
case PathCommand::Close: {
|
||||
lineTo(out, start, allowDot);
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
// reset the dash state
|
||||
curIdx = idx;
|
||||
curLen = dash.pattern[idx] - offset;
|
||||
opGap = gap;
|
||||
move = true;
|
||||
start = curPos = *pts;
|
||||
pts++;
|
||||
break;
|
||||
}
|
||||
case PathCommand::LineTo: {
|
||||
lineTo(out, *pts, allowDot);
|
||||
pts++;
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
cubicTo(out, pts[0], pts[1], pts[2], allowDot);
|
||||
pts += 3;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void StrokeDashPath::point(RenderPath& out, const Point& p)
|
||||
{
|
||||
if (move || dash.pattern[curIdx] < FLOAT_EPSILON) {
|
||||
out.moveTo(p);
|
||||
move = false;
|
||||
}
|
||||
out.lineTo(p);
|
||||
}
|
||||
|
||||
|
||||
void StrokeDashPath::lineTo(RenderPath& out, const Point& to, bool allowDot)
|
||||
{
|
||||
Line line = {curPos, to};
|
||||
auto len = length(to - curPos);
|
||||
segment<Line>(line, len, out, allowDot,
|
||||
[](const Line& l) { return length(l.pt2 - l.pt1); },
|
||||
[](const Line& l, float len, Line& left, Line& right) { l.split(len, left, right); },
|
||||
[&](const Line& l) { out.lineTo(l.pt2); },
|
||||
[](const Line& l) { return l.pt1; },
|
||||
to
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void StrokeDashPath::cubicTo(RenderPath& out, const Point& cnt1, const Point& cnt2, const Point& end, bool allowDot)
|
||||
{
|
||||
Bezier curve = {curPos, cnt1, cnt2, end};
|
||||
auto len = curve.length();
|
||||
segment<Bezier>(curve, len, out, allowDot,
|
||||
[](const Bezier& b) { return b.length(); },
|
||||
[](const Bezier& b, float len, Bezier& left, Bezier& right) { b.split(len, left, right); },
|
||||
[&](const Bezier& b) { out.cubicTo(b.ctrl1, b.ctrl2, b.end); },
|
||||
[](const Bezier& b) { return b.start; },
|
||||
end
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool RenderShape::strokeDash(RenderPath& out) const
|
||||
{
|
||||
if (!stroke || stroke->dash.count == 0 || stroke->dash.length < DASH_PATTERN_THRESHOLD) return false;
|
||||
|
||||
//TODO: use this common function from all engines
|
||||
#ifdef THORVG_GL_RASTER_SUPPORT
|
||||
out.cmds.reserve(20 * path.cmds.count);
|
||||
out.pts.reserve(20 * path.pts.count);
|
||||
|
||||
StrokeDashPath dash(stroke->dash);
|
||||
auto allowDot = stroke->cap != StrokeCap::Butt;
|
||||
|
||||
if (trimpath()) {
|
||||
RenderPath tpath;
|
||||
if (stroke->trim.trim(path, tpath)) return dash.gen(tpath, out, allowDot);
|
||||
else return false;
|
||||
}
|
||||
return dash.gen(path, out, allowDot);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
|
@ -28,11 +28,13 @@
|
|||
#include "tvgCommon.h"
|
||||
#include "tvgArray.h"
|
||||
#include "tvgLock.h"
|
||||
#include "tvgColor.h"
|
||||
|
||||
namespace tvg
|
||||
{
|
||||
|
||||
using RenderData = void*;
|
||||
using RenderColor = tvg::RGBA;
|
||||
using pixel_t = uint32_t;
|
||||
|
||||
#define DASH_PATTERN_THRESHOLD 0.001f
|
||||
|
@ -81,11 +83,6 @@ struct RenderSurface
|
|||
}
|
||||
};
|
||||
|
||||
struct RenderColor
|
||||
{
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
|
||||
struct RenderCompositor
|
||||
{
|
||||
MaskMethod method;
|
||||
|
@ -233,6 +230,33 @@ struct RenderPath
|
|||
cmds.clear();
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
//Don't close multiple times.
|
||||
if (cmds.count > 0 && cmds.last() == PathCommand::Close) return;
|
||||
cmds.push(PathCommand::Close);
|
||||
}
|
||||
|
||||
void moveTo(const Point& pt)
|
||||
{
|
||||
pts.push(pt);
|
||||
cmds.push(PathCommand::MoveTo);
|
||||
}
|
||||
|
||||
void lineTo(const Point& pt)
|
||||
{
|
||||
pts.push(pt);
|
||||
cmds.push(PathCommand::LineTo);
|
||||
}
|
||||
|
||||
void cubicTo(const Point& cnt1, const Point& cnt2, const Point& end)
|
||||
{
|
||||
pts.push(cnt1);
|
||||
pts.push(cnt2);
|
||||
pts.push(end);
|
||||
cmds.push(PathCommand::CubicTo);
|
||||
}
|
||||
|
||||
bool bounds(Matrix* m, float* x, float* y, float* w, float* h);
|
||||
};
|
||||
|
||||
|
@ -256,7 +280,7 @@ struct RenderStroke
|
|||
float width = 0.0f;
|
||||
RenderColor color{};
|
||||
Fill *fill = nullptr;
|
||||
struct {
|
||||
struct Dash {
|
||||
float* pattern = nullptr;
|
||||
uint32_t count = 0;
|
||||
float offset = 0.0f;
|
||||
|
@ -383,6 +407,8 @@ struct RenderShape
|
|||
if (!stroke) return 4.0f;
|
||||
return stroke->miterlimit;;
|
||||
}
|
||||
|
||||
bool strokeDash(RenderPath& out) const;
|
||||
};
|
||||
|
||||
struct RenderEffect
|
||||
|
|
|
@ -66,28 +66,31 @@ Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point*
|
|||
|
||||
Result Shape::moveTo(float x, float y) noexcept
|
||||
{
|
||||
SHAPE(this)->moveTo(x, y);
|
||||
SHAPE(this)->rs.path.moveTo({x, y});
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::lineTo(float x, float y) noexcept
|
||||
{
|
||||
SHAPE(this)->lineTo(x, y);
|
||||
SHAPE(this)->rs.path.lineTo({x, y});
|
||||
SHAPE(this)->impl.mark(RenderUpdateFlag::Path);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept
|
||||
{
|
||||
SHAPE(this)->cubicTo(cx1, cy1, cx2, cy2, x, y);
|
||||
SHAPE(this)->rs.path.cubicTo({cx1, cy1}, {cx2, cy2}, {x, y});
|
||||
SHAPE(this)->impl.mark(RenderUpdateFlag::Path);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::close() noexcept
|
||||
{
|
||||
SHAPE(this)->close();
|
||||
SHAPE(this)->rs.path.close();
|
||||
SHAPE(this)->impl.mark(RenderUpdateFlag::Path);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
|
|
@ -170,37 +170,6 @@ struct ShapeImpl : Shape
|
|||
rs.path.pts.count += ptsCnt;
|
||||
}
|
||||
|
||||
void moveTo(float x, float y)
|
||||
{
|
||||
rs.path.cmds.push(PathCommand::MoveTo);
|
||||
rs.path.pts.push({x, y});
|
||||
}
|
||||
|
||||
void lineTo(float x, float y)
|
||||
{
|
||||
rs.path.cmds.push(PathCommand::LineTo);
|
||||
rs.path.pts.push({x, y});
|
||||
impl.mark(RenderUpdateFlag::Path);
|
||||
}
|
||||
|
||||
void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
|
||||
{
|
||||
rs.path.cmds.push(PathCommand::CubicTo);
|
||||
rs.path.pts.push({cx1, cy1});
|
||||
rs.path.pts.push({cx2, cy2});
|
||||
rs.path.pts.push({x, y});
|
||||
|
||||
impl.mark(RenderUpdateFlag::Path);
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
//Don't close multiple times.
|
||||
if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return;
|
||||
rs.path.cmds.push(PathCommand::Close);
|
||||
impl.mark(RenderUpdateFlag::Path);
|
||||
}
|
||||
|
||||
void strokeWidth(float width)
|
||||
{
|
||||
if (!rs.stroke) rs.stroke = new RenderStroke();
|
||||
|
|
|
@ -10,6 +10,7 @@ source_file = [
|
|||
'tvgWgRenderTask.h',
|
||||
'tvgWgShaderSrc.h',
|
||||
'tvgWgShaderTypes.h',
|
||||
'tvgWgTessellator.h',
|
||||
'tvgWgBindGroups.cpp',
|
||||
'tvgWgCommon.cpp',
|
||||
'tvgWgCompositor.cpp',
|
||||
|
@ -20,7 +21,8 @@ source_file = [
|
|||
'tvgWgRenderTarget.cpp',
|
||||
'tvgWgRenderTask.cpp',
|
||||
'tvgWgShaderSrc.cpp',
|
||||
'tvgWgShaderTypes.cpp'
|
||||
'tvgWgShaderTypes.cpp',
|
||||
'tvgWgTessellator.cpp'
|
||||
]
|
||||
|
||||
wgpu_dep = []
|
||||
|
|
|
@ -39,7 +39,6 @@ void WgContext::initialize(WGPUInstance instance, WGPUDevice device)
|
|||
assert(queue);
|
||||
|
||||
// create shared webgpu assets
|
||||
allocateBufferIndexFan(32768);
|
||||
samplerNearestRepeat = createSampler(WGPUFilterMode_Nearest, WGPUMipmapFilterMode_Nearest, WGPUAddressMode_Repeat);
|
||||
samplerLinearRepeat = createSampler(WGPUFilterMode_Linear, WGPUMipmapFilterMode_Linear, WGPUAddressMode_Repeat, 4);
|
||||
samplerLinearMirror = createSampler(WGPUFilterMode_Linear, WGPUMipmapFilterMode_Linear, WGPUAddressMode_MirrorRepeat, 4);
|
||||
|
@ -61,7 +60,6 @@ void WgContext::release()
|
|||
releaseSampler(samplerLinearMirror);
|
||||
releaseSampler(samplerLinearRepeat);
|
||||
releaseSampler(samplerNearestRepeat);
|
||||
releaseBuffer(bufferIndexFan);
|
||||
releaseQueue(queue);
|
||||
}
|
||||
|
||||
|
@ -222,26 +220,6 @@ bool WgContext::allocateBufferIndex(WGPUBuffer& buffer, const uint32_t* data, ui
|
|||
}
|
||||
|
||||
|
||||
bool WgContext::allocateBufferIndexFan(uint64_t vertexCount)
|
||||
{
|
||||
uint64_t indexCount = (vertexCount - 2) * 3;
|
||||
if ((!bufferIndexFan) || (wgpuBufferGetSize(bufferIndexFan) < indexCount * sizeof(uint32_t))) {
|
||||
tvg::Array<uint32_t> indexes(indexCount);
|
||||
for (size_t i = 0; i < vertexCount - 2; i++) {
|
||||
indexes.push(0);
|
||||
indexes.push(i + 1);
|
||||
indexes.push(i + 2);
|
||||
}
|
||||
releaseBuffer(bufferIndexFan);
|
||||
WGPUBufferDescriptor bufferDesc{ .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index, .size = indexCount * sizeof(uint32_t) };
|
||||
bufferIndexFan = wgpuDeviceCreateBuffer(device, &bufferDesc);
|
||||
wgpuQueueWriteBuffer(queue, bufferIndexFan, 0, &indexes[0], indexCount * sizeof(uint32_t));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void WgContext::releaseBuffer(WGPUBuffer& buffer)
|
||||
{
|
||||
if (buffer) {
|
||||
|
|
|
@ -34,7 +34,6 @@ struct WgContext {
|
|||
WGPUQueue queue{};
|
||||
WGPUTextureFormat preferredFormat{};
|
||||
// shared webgpu assets
|
||||
WGPUBuffer bufferIndexFan{};
|
||||
WGPUSampler samplerNearestRepeat{};
|
||||
WGPUSampler samplerLinearRepeat{};
|
||||
WGPUSampler samplerLinearMirror{};
|
||||
|
@ -63,7 +62,6 @@ struct WgContext {
|
|||
bool allocateBufferUniform(WGPUBuffer& buffer, const void* data, uint64_t size);
|
||||
bool allocateBufferVertex(WGPUBuffer& buffer, const float* data, uint64_t size);
|
||||
bool allocateBufferIndex(WGPUBuffer& buffer, const uint32_t* data, uint64_t size);
|
||||
bool allocateBufferIndexFan(uint64_t vertexCount);
|
||||
|
||||
// release buffer objects
|
||||
void releaseBuffer(WGPUBuffer& buffer);
|
||||
|
|
|
@ -347,19 +347,6 @@ void WgCompositor::drawMesh(WgContext& context, WgMeshData* meshData)
|
|||
};
|
||||
|
||||
|
||||
void WgCompositor::drawMeshFan(WgContext& context, WgMeshData* meshData)
|
||||
{
|
||||
assert(meshData);
|
||||
assert(renderPassEncoder);
|
||||
uint64_t icount = (meshData->vbuffer.count - 2) * 3;
|
||||
uint64_t vsize = meshData->vbuffer.count * sizeof(Point);
|
||||
uint64_t isize = icount * sizeof(uint32_t);
|
||||
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, stageBufferGeometry.vbuffer_gpu, meshData->voffset, vsize);
|
||||
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, context.bufferIndexFan, WGPUIndexFormat_Uint32, 0, isize);
|
||||
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, icount, 1, 0, 0, 0);
|
||||
};
|
||||
|
||||
|
||||
void WgCompositor::drawMeshImage(WgContext& context, WgMeshData* meshData)
|
||||
{
|
||||
assert(meshData);
|
||||
|
@ -378,8 +365,7 @@ void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData)
|
|||
{
|
||||
assert(renderData);
|
||||
assert(renderPassEncoder);
|
||||
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
||||
if (renderData->renderSettingsShape.skip || renderData->meshGroupShapes.meshes.count == 0 || renderData->viewport.invalid()) return;
|
||||
if (renderData->renderSettingsShape.skip || renderData->meshShape.vbuffer.count == 0 || renderData->viewport.invalid()) return;
|
||||
WgRenderSettings& settings = renderData->renderSettingsShape;
|
||||
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h());
|
||||
// setup stencil rules
|
||||
|
@ -389,8 +375,7 @@ void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData)
|
|||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
||||
// draw to stencil (first pass)
|
||||
ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes)
|
||||
drawMeshFan(context, (*p));
|
||||
drawMesh(context, &renderData->meshShape);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
|
@ -406,7 +391,7 @@ void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData)
|
|||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
}
|
||||
|
||||
|
||||
|
@ -414,8 +399,7 @@ void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData,
|
|||
{
|
||||
assert(renderData);
|
||||
assert(renderPassEncoder);
|
||||
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
||||
if (renderData->renderSettingsShape.skip || renderData->meshGroupShapes.meshes.count == 0 || renderData->viewport.invalid()) return;
|
||||
if (renderData->renderSettingsShape.skip || renderData->meshShape.vbuffer.count == 0 || renderData->viewport.invalid()) return;
|
||||
WgRenderSettings& settings = renderData->renderSettingsShape;
|
||||
// copy current render target data to dst target
|
||||
WgRenderTarget *target = currentTarget;
|
||||
|
@ -431,8 +415,7 @@ void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData,
|
|||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
||||
// draw to stencil (first pass)
|
||||
ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes)
|
||||
drawMeshFan(context, (*p));
|
||||
drawMesh(context, &renderData->meshShape);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
|
@ -450,7 +433,7 @@ void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData,
|
|||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial_blend[blendMethodInd]);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
}
|
||||
|
||||
|
||||
|
@ -458,8 +441,7 @@ void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData)
|
|||
{
|
||||
assert(renderData);
|
||||
assert(renderPassEncoder);
|
||||
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
||||
if (renderData->renderSettingsShape.skip || renderData->meshGroupShapes.meshes.count == 0 || renderData->viewport.invalid()) return;
|
||||
if (renderData->renderSettingsShape.skip || renderData->meshShape.vbuffer.count == 0 || renderData->viewport.invalid()) return;
|
||||
WgRenderSettings& settings = renderData->renderSettingsShape;
|
||||
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h());
|
||||
// setup stencil rules
|
||||
|
@ -469,13 +451,12 @@ void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData)
|
|||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
||||
// draw to stencil (first pass)
|
||||
ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes)
|
||||
drawMeshFan(context, (*p));
|
||||
drawMesh(context, &renderData->meshShape);
|
||||
// merge depth and stencil buffer
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.merge_depth_stencil);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
|
@ -491,7 +472,7 @@ void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData)
|
|||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
}
|
||||
|
||||
|
||||
|
@ -499,36 +480,33 @@ void WgCompositor::drawStrokes(WgContext& context, WgRenderDataShape* renderData
|
|||
{
|
||||
assert(renderData);
|
||||
assert(renderPassEncoder);
|
||||
assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count);
|
||||
if (renderData->renderSettingsStroke.skip || renderData->meshGroupStrokes.meshes.count == 0 || renderData->viewport.invalid()) return;
|
||||
if (renderData->renderSettingsStroke.skip || renderData->meshStrokes.vbuffer.count == 0 || renderData->viewport.invalid()) return;
|
||||
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
||||
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h());
|
||||
// draw strokes to stencil (first pass)
|
||||
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
||||
// setup stencil rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
// draw to stencil (first pass)
|
||||
drawMesh(context, renderData->meshGroupStrokes.meshes[i]);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
if (settings.fillType == WgRenderSettingsType::Solid) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMeshFan(context, renderData->meshGroupStrokesBBox.meshes[i]);
|
||||
// setup stencil rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
// draw to stencil (first pass)
|
||||
drawMesh(context, &renderData->meshStrokes);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
if (settings.fillType == WgRenderSettingsType::Solid) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMesh(context, &renderData->meshStrokesBBox);
|
||||
}
|
||||
|
||||
|
||||
|
@ -536,8 +514,7 @@ void WgCompositor::blendStrokes(WgContext& context, WgRenderDataShape* renderDat
|
|||
{
|
||||
assert(renderData);
|
||||
assert(renderPassEncoder);
|
||||
assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count);
|
||||
if (renderData->renderSettingsStroke.skip || renderData->meshGroupStrokes.meshes.count == 0 || renderData->viewport.invalid()) return;
|
||||
if (renderData->renderSettingsStroke.skip || renderData->meshStrokes.vbuffer.count == 0 || renderData->viewport.invalid()) return;
|
||||
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
||||
// copy current render target data to dst target
|
||||
WgRenderTarget *target = currentTarget;
|
||||
|
@ -546,33 +523,31 @@ void WgCompositor::blendStrokes(WgContext& context, WgRenderDataShape* renderDat
|
|||
beginRenderPass(commandEncoder, target, false);
|
||||
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h());
|
||||
// draw strokes to stencil (first pass)
|
||||
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
||||
// setup stencil rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
// draw to stencil (first pass)
|
||||
drawMesh(context, renderData->meshGroupStrokes.meshes[i]);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, targetTemp0.bindGroupTexure, 0, nullptr);
|
||||
uint32_t blendMethodInd = (uint32_t)blendMethod;
|
||||
if (settings.fillType == WgRenderSettingsType::Solid) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid_blend[blendMethodInd]);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear_blend[blendMethodInd]);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial_blend[blendMethodInd]);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMeshFan(context, renderData->meshGroupStrokesBBox.meshes[i]);
|
||||
// setup stencil rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
// draw to stencil (first pass)
|
||||
drawMesh(context, &renderData->meshStrokes);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, targetTemp0.bindGroupTexure, 0, nullptr);
|
||||
uint32_t blendMethodInd = (uint32_t)blendMethod;
|
||||
if (settings.fillType == WgRenderSettingsType::Solid) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid_blend[blendMethodInd]);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear_blend[blendMethodInd]);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial_blend[blendMethodInd]);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMesh(context, &renderData->meshStrokesBBox);
|
||||
};
|
||||
|
||||
|
||||
|
@ -580,43 +555,38 @@ void WgCompositor::clipStrokes(WgContext& context, WgRenderDataShape* renderData
|
|||
{
|
||||
assert(renderData);
|
||||
assert(renderPassEncoder);
|
||||
assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count);
|
||||
if (renderData->renderSettingsStroke.skip) return;
|
||||
if (renderData->meshGroupStrokes.meshes.count == 0) return;
|
||||
if (renderData->viewport.invalid()) return;
|
||||
if (renderData->renderSettingsStroke.skip || renderData->meshStrokes.vbuffer.count == 0 || renderData->viewport.invalid()) return;
|
||||
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
||||
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h());
|
||||
// draw strokes to stencil (first pass)
|
||||
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
||||
// setup stencil rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
// draw to stencil (first pass)
|
||||
drawMesh(context, renderData->meshGroupStrokes.meshes[i]);
|
||||
// merge depth and stencil buffer
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.merge_depth_stencil);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
if (settings.fillType == WgRenderSettingsType::Solid) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMeshFan(context, renderData->meshGroupStrokesBBox.meshes[i]);
|
||||
// setup stencil rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
// draw to stencil (first pass)
|
||||
drawMesh(context, &renderData->meshStrokes);
|
||||
// merge depth and stencil buffer
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.merge_depth_stencil);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
// setup fill rules
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
if (settings.fillType == WgRenderSettingsType::Solid) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear);
|
||||
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial);
|
||||
}
|
||||
// draw to color (second pass)
|
||||
drawMesh(context, &renderData->meshStrokesBBox);
|
||||
}
|
||||
|
||||
|
||||
|
@ -744,21 +714,19 @@ void WgCompositor::markupClipPath(WgContext& context, WgRenderDataShape* renderD
|
|||
{
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
||||
// markup stencil
|
||||
if (renderData->meshGroupStrokes.meshes.count > 0) {
|
||||
if (renderData->meshStrokes.vbuffer.count > 0) {
|
||||
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct);
|
||||
ARRAY_FOREACH(p, renderData->meshGroupStrokes.meshes)
|
||||
drawMesh(context, (*p));
|
||||
drawMesh(context, &renderData->meshStrokes);
|
||||
} else {
|
||||
WGPURenderPipeline stencilPipeline = (renderData->fillRule == FillRule::NonZero) ? pipelines.nonzero : pipelines.evenodd;
|
||||
WgRenderSettings& settings = renderData->renderSettingsShape;
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
||||
ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes)
|
||||
drawMeshFan(context, (*p));
|
||||
drawMesh(context, &renderData->meshShape);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -781,7 +749,7 @@ void WgCompositor::renderClipPath(WgContext& context, WgRenderDataPaint* paint)
|
|||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings0.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_stencil_to_depth);
|
||||
drawMeshFan(context, &renderData0->meshDataBBox);
|
||||
drawMesh(context, &renderData0->meshBBox);
|
||||
// merge clip pathes with AND logic
|
||||
for (auto p = paint->clips.begin() + 1; p < paint->clips.end(); ++p) {
|
||||
// get render data
|
||||
|
@ -794,31 +762,31 @@ void WgCompositor::renderClipPath(WgContext& context, WgRenderDataPaint* paint)
|
|||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[190], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_stencil_to_depth_interm);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
// copy depth to stencil
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 1);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[190], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_depth_to_stencil);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
// clear depth current (keep stencil)
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[255], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.clear_depth);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
// clear depth original (keep stencil)
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings0.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[255], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.clear_depth);
|
||||
drawMeshFan(context, &renderData0->meshDataBBox);
|
||||
drawMesh(context, &renderData0->meshBBox);
|
||||
// copy stencil to depth (clear stencil)
|
||||
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_stencil_to_depth);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -840,7 +808,7 @@ void WgCompositor::clearClipPath(WgContext& context, WgRenderDataPaint* paint)
|
|||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[255], 0, nullptr);
|
||||
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.clear_depth);
|
||||
drawMeshFan(context, &renderData->meshDataBBox);
|
||||
drawMesh(context, &renderData->meshBBox);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1019,4 +987,4 @@ bool WgCompositor::tritoneEffect(WgContext& context, WgRenderTarget* dst, const
|
|||
wgpuComputePassEncoderRelease(computePassEncoder);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ private:
|
|||
|
||||
// base meshes draw
|
||||
void drawMesh(WgContext& context, WgMeshData* meshData);
|
||||
void drawMeshFan(WgContext& context, WgMeshData* meshData);
|
||||
void drawMeshImage(WgContext& context, WgMeshData* meshData);
|
||||
|
||||
// shapes
|
||||
|
|
|
@ -22,62 +22,73 @@
|
|||
|
||||
#include "tvgWgGeometry.h"
|
||||
|
||||
//***********************************************************************
|
||||
// WgMeshData
|
||||
//***********************************************************************
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static WgGeometryBufferPool _pool;
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
WgVertexBuffer* WgGeometryBufferPool::reqVertexBuffer(float scale)
|
||||
void WgMeshData::bbox(const Point pmin, const Point pmax)
|
||||
{
|
||||
ARRAY_FOREACH(p, vbuffers) {
|
||||
if ((*p)->count == 0) {
|
||||
(*p)->scale = scale;
|
||||
return (*p);
|
||||
}
|
||||
}
|
||||
vbuffers.push(new WgVertexBuffer(scale));
|
||||
return vbuffers.last();
|
||||
}
|
||||
|
||||
void WgGeometryBufferPool::retVertexBuffer(WgVertexBuffer* buffer)
|
||||
{
|
||||
buffer->reset(1.0f);
|
||||
}
|
||||
|
||||
WgIndexedVertexBuffer* WgGeometryBufferPool::reqIndexedVertexBuffer(float scale)
|
||||
{
|
||||
ARRAY_FOREACH(p, ibuffers) {
|
||||
if ((*p)->vcount == 0) {
|
||||
(*p)->scale = scale;
|
||||
return (*p);
|
||||
}
|
||||
}
|
||||
ibuffers.push(new WgIndexedVertexBuffer(this, scale));
|
||||
return ibuffers.last();
|
||||
}
|
||||
|
||||
void WgGeometryBufferPool::retIndexedVertexBuffer(WgIndexedVertexBuffer* buffer)
|
||||
{
|
||||
buffer->reset(1.0f);
|
||||
const float vdata[] = {pmin.x, pmin.y, pmax.x, pmin.y, pmax.x, pmax.y, pmin.x, pmax.y};
|
||||
const uint32_t idata[] = {0, 1, 2, 0, 2, 3};
|
||||
// setup vertex data
|
||||
vbuffer.reserve(4);
|
||||
vbuffer.count = 4;
|
||||
memcpy(vbuffer.data, vdata, sizeof(vdata));
|
||||
// setup tex coords data
|
||||
tbuffer.clear();
|
||||
// setup indexes data
|
||||
ibuffer.reserve(6);
|
||||
ibuffer.count = 6;
|
||||
memcpy(ibuffer.data, idata, sizeof(idata));
|
||||
}
|
||||
|
||||
|
||||
WgGeometryBufferPool* WgGeometryBufferPool::instance()
|
||||
void WgMeshData::imageBox(float w, float h)
|
||||
{
|
||||
/* TODO: These could be easily addressed per threads. i.e _pool[thread_cnt]; */
|
||||
return &_pool;
|
||||
const float vdata[] = {0.0f, 0.0f, w, 0.0f, w, h, 0.0f, h};
|
||||
const float tdata[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||
const uint32_t idata[] = {0, 1, 2, 0, 2, 3};
|
||||
// setup vertex data
|
||||
vbuffer.reserve(4);
|
||||
vbuffer.count = 4;
|
||||
memcpy(vbuffer.data, vdata, sizeof(vdata));
|
||||
// setup tex coords data
|
||||
tbuffer.reserve(4);
|
||||
tbuffer.count = 4;
|
||||
memcpy(tbuffer.data, tdata, sizeof(tdata));
|
||||
// setup indexes data
|
||||
ibuffer.reserve(6);
|
||||
ibuffer.count = 6;
|
||||
memcpy(ibuffer.data, idata, sizeof(idata));
|
||||
}
|
||||
|
||||
|
||||
WgGeometryBufferPool::~WgGeometryBufferPool()
|
||||
void WgMeshData::blitBox()
|
||||
{
|
||||
//The indexed buffer may contain the vertex buffer, so free the memory in reverse order.
|
||||
ARRAY_FOREACH(p, ibuffers) delete(*p);
|
||||
ARRAY_FOREACH(p, vbuffers) delete(*p);
|
||||
const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f};
|
||||
const float tdata[] = {+0.0f, +0.0f, +1.0f, +0.0f, +1.0f, +1.0f, +0.0f, +1.0f};
|
||||
const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 };
|
||||
// setup vertex data
|
||||
vbuffer.reserve(4);
|
||||
vbuffer.count = 4;
|
||||
memcpy(vbuffer.data, vdata, sizeof(vdata));
|
||||
// setup tex coords data
|
||||
tbuffer.reserve(4);
|
||||
tbuffer.count = 4;
|
||||
memcpy(tbuffer.data, tdata, sizeof(tdata));
|
||||
// setup indexes data
|
||||
ibuffer.reserve(6);
|
||||
ibuffer.count = 6;
|
||||
memcpy(ibuffer.data, idata, sizeof(idata));
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::clear()
|
||||
{
|
||||
vbuffer.clear();
|
||||
tbuffer.clear();
|
||||
ibuffer.clear();
|
||||
voffset = 0;
|
||||
toffset = 0;
|
||||
ioffset = 0;
|
||||
}
|
|
@ -25,523 +25,20 @@
|
|||
|
||||
#include <cassert>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgRender.h"
|
||||
#include "tvgArray.h"
|
||||
|
||||
struct WgMeshData {
|
||||
Array<Point> vbuffer;
|
||||
Array<Point> tbuffer;
|
||||
Array<uint32_t> ibuffer;
|
||||
size_t voffset{};
|
||||
size_t toffset{};
|
||||
size_t ioffset{};
|
||||
|
||||
// default size of vertex and index buffers
|
||||
#define WG_DEFAULT_BUFFER_SIZE 2048
|
||||
|
||||
struct WgVertexBuffer;
|
||||
struct WgIndexedVertexBuffer;
|
||||
|
||||
struct WgGeometryBufferPool
|
||||
{
|
||||
private:
|
||||
Array<WgVertexBuffer*> vbuffers;
|
||||
Array<WgIndexedVertexBuffer*> ibuffers;
|
||||
|
||||
public:
|
||||
~WgGeometryBufferPool();
|
||||
|
||||
WgVertexBuffer* reqVertexBuffer(float scale = 1.0f);
|
||||
WgIndexedVertexBuffer* reqIndexedVertexBuffer(float scale = 1.0f);
|
||||
void retVertexBuffer(WgVertexBuffer* buffer);
|
||||
void retIndexedVertexBuffer(WgIndexedVertexBuffer* buffer);
|
||||
|
||||
static WgGeometryBufferPool* instance(); //return the shared buffer pool
|
||||
};
|
||||
|
||||
|
||||
// simple vertex buffer
|
||||
struct WgVertexBuffer
|
||||
{
|
||||
Point* data; // vertex buffer
|
||||
struct Distance {
|
||||
float interval; // distance to previous point
|
||||
float length; // distance to the first point through all previous points
|
||||
} *dist;
|
||||
uint32_t count = 0;
|
||||
uint32_t reserved = WG_DEFAULT_BUFFER_SIZE;
|
||||
float scale; // tesselation scale
|
||||
bool closed = false;
|
||||
|
||||
// callback for external process of polyline
|
||||
using onPolylineFn = std::function<void(const WgVertexBuffer& buff)>;
|
||||
|
||||
WgVertexBuffer(float scale = 1.0f) : scale(scale)
|
||||
{
|
||||
data = tvg::malloc<Point*>(sizeof(Point) * reserved);
|
||||
dist = tvg::malloc<Distance*>(sizeof(Distance) * reserved);
|
||||
}
|
||||
|
||||
~WgVertexBuffer()
|
||||
{
|
||||
tvg::free(data);
|
||||
tvg::free(dist);
|
||||
}
|
||||
|
||||
// reset buffer
|
||||
void reset(float scale)
|
||||
{
|
||||
count = 0;
|
||||
closed = false;
|
||||
this->scale = scale;
|
||||
}
|
||||
|
||||
// get the last point with optional index offset from the end
|
||||
Point last(size_t offset = 0) const
|
||||
{
|
||||
return data[count - offset - 1];
|
||||
}
|
||||
|
||||
// get the last distance with optional index offset from the end
|
||||
float lastDist(size_t offset = 0) const
|
||||
{
|
||||
return dist[count - offset - 1].interval;
|
||||
}
|
||||
|
||||
// get total length
|
||||
float total() const
|
||||
{
|
||||
return (count == 0) ? 0.0f : dist[count-1].length;
|
||||
}
|
||||
|
||||
// get next vertex index by length using binary search
|
||||
size_t getIndexByLength(float len) const
|
||||
{
|
||||
if (count <= 1) return 0;
|
||||
size_t left = 0;
|
||||
size_t right = count - 1;
|
||||
while (left <= right) {
|
||||
size_t mid = left + (right - left) / 2;
|
||||
if (dist[mid].length == len) return mid;
|
||||
else if (dist[mid].length < len) left = mid + 1;
|
||||
else right = mid - 1;
|
||||
}
|
||||
return right + 1;
|
||||
}
|
||||
|
||||
// get min and max values of the buffer
|
||||
void getMinMax(Point& pmin, Point& pmax) const
|
||||
{
|
||||
if (count == 0) return;
|
||||
pmax = pmin = data[0];
|
||||
for (size_t i = 1; i < count; i++) {
|
||||
pmin = min(pmin, data[i]);
|
||||
pmax = max(pmax, data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// update points distancess to the prev point and total length
|
||||
void updateDistances()
|
||||
{
|
||||
if (count == 0) return;
|
||||
dist[0].interval = 0.0f;
|
||||
dist[0].length = 0.0f;
|
||||
for (size_t i = 1; i < count; i++) {
|
||||
dist[i].interval = tvg::length(data[i-1] - data[i]);
|
||||
dist[i].length = dist[i-1].length + dist[i].interval;
|
||||
}
|
||||
}
|
||||
|
||||
// close vertex buffer
|
||||
void close()
|
||||
{
|
||||
// check if last point is not to close to the first point
|
||||
if (length(data[0] - last()) > 0.015625f) {
|
||||
append(data[0]);
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
|
||||
// append point
|
||||
void append(const Point& p)
|
||||
{
|
||||
if (count >= reserved) {
|
||||
reserved *= 2;
|
||||
data = tvg::realloc<Point*>(data, reserved * sizeof(Point));
|
||||
dist = tvg::realloc<Distance*>(dist, reserved * sizeof(Distance));
|
||||
}
|
||||
data[count++] = p;
|
||||
}
|
||||
|
||||
// append source vertex buffer in index range from start to end (end not included)
|
||||
void appendRange(const WgVertexBuffer& buff, size_t start_index, size_t end_index)
|
||||
{
|
||||
for (size_t i = start_index; i < end_index; i++) {
|
||||
append(buff.data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// append circle (list of triangles)
|
||||
void appendCircle(float radius)
|
||||
{
|
||||
// get approx circle length
|
||||
float clen = 2.0f * radius * MATH_PI;
|
||||
size_t nsegs = std::max((uint32_t)(clen * scale / 8), 16U);
|
||||
// append circle^
|
||||
Point prev { std::sin(0.0f) * radius, std::cos(0.0f) * radius };
|
||||
for (size_t i = 1; i <= nsegs; i++) {
|
||||
float t = (2.0f * MATH_PI * i) / nsegs;
|
||||
Point curr { std::sin(t) * radius, std::cos(t) * radius };
|
||||
append(Point{0.0f, 0.0f});
|
||||
append(prev);
|
||||
append(curr);
|
||||
prev = curr;
|
||||
}
|
||||
}
|
||||
|
||||
// append cubic spline
|
||||
void appendCubic(const Point& v0, const Point& v1, const Point& v2, const Point& v3)
|
||||
{
|
||||
// get approx cubic length
|
||||
float clen = (tvg::length(v0 - v1) + tvg::length(v1 - v2) + tvg::length(v2 - v3));
|
||||
size_t nsegs = std::max((uint32_t)(clen * scale / 16), 16U);
|
||||
// append cubic
|
||||
Bezier bezier{v0, v1, v2, v3};
|
||||
for (size_t i = 1; i <= nsegs; i++) {
|
||||
append(bezier.at((float)i / nsegs));
|
||||
}
|
||||
}
|
||||
|
||||
// decode path with callback for external prcesses
|
||||
void decodePath(const RenderShape& rshape, bool update_dist, onPolylineFn onPolyline, bool trim = false)
|
||||
{
|
||||
// decode path
|
||||
reset(scale);
|
||||
|
||||
PathCommand *cmds, *trimmedCmds = nullptr;
|
||||
Point *pts, *trimmedPts = nullptr;
|
||||
uint32_t cmdCnt{};
|
||||
|
||||
if (trim) {
|
||||
RenderPath trimmedPath;
|
||||
if (!rshape.stroke->trim.trim(rshape.path, trimmedPath)) return;
|
||||
|
||||
cmds = trimmedCmds = trimmedPath.cmds.data;
|
||||
cmdCnt = trimmedPath.cmds.count;
|
||||
pts = trimmedPts = trimmedPath.pts.data;
|
||||
|
||||
trimmedPath.cmds.data = nullptr;
|
||||
trimmedPath.pts.data = nullptr;
|
||||
} else {
|
||||
cmds = rshape.path.cmds.data;
|
||||
cmdCnt = rshape.path.cmds.count;
|
||||
pts = rshape.path.pts.data;
|
||||
}
|
||||
|
||||
size_t pntIndex = 0;
|
||||
for (uint32_t i = 0; i < cmdCnt; i++) {
|
||||
auto& cmd = cmds[i];
|
||||
if (cmd == PathCommand::MoveTo) {
|
||||
// after path decoding we need to update distances and total length
|
||||
if (update_dist) updateDistances();
|
||||
if ((onPolyline) && (count > 0)) onPolyline(*this);
|
||||
reset(scale);
|
||||
append(pts[pntIndex]);
|
||||
pntIndex++;
|
||||
} else if (cmd == PathCommand::LineTo) {
|
||||
append(pts[pntIndex]);
|
||||
pntIndex++;
|
||||
} else if (cmd == PathCommand::Close) {
|
||||
close();
|
||||
// proceed path if close command is not the last command and next command is LineTo or CubicTo
|
||||
if (i + 1 < cmdCnt && (cmds[i + 1] == PathCommand::LineTo || cmds[i + 1] == PathCommand::CubicTo)) {
|
||||
// proceed current path
|
||||
if (update_dist) updateDistances();
|
||||
if ((count > 0) && (onPolyline)) onPolyline(*this);
|
||||
// append closing point of current path as a first point of the new path
|
||||
Point last_pt = last();
|
||||
reset(scale);
|
||||
append(last_pt);
|
||||
}
|
||||
} else if (cmd == PathCommand::CubicTo) {
|
||||
// append tesselated cubic spline with scale param
|
||||
appendCubic(data[count - 1], pts[pntIndex + 0], pts[pntIndex + 1], pts[pntIndex + 2]);
|
||||
pntIndex += 3;
|
||||
}
|
||||
}
|
||||
|
||||
tvg::free(trimmedCmds);
|
||||
tvg::free(trimmedPts);
|
||||
|
||||
// after path decoding we need to update distances and total length
|
||||
if (update_dist) updateDistances();
|
||||
if ((count > 0) && (onPolyline)) onPolyline(*this);
|
||||
reset(scale);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct WgIndexedVertexBuffer
|
||||
{
|
||||
Point* vbuff;
|
||||
uint32_t* ibuff;
|
||||
uint32_t vcount = 0, icount = 0;
|
||||
size_t vreserved = WG_DEFAULT_BUFFER_SIZE;
|
||||
size_t ireserved = WG_DEFAULT_BUFFER_SIZE * 2;
|
||||
WgGeometryBufferPool* pool;
|
||||
WgVertexBuffer* dashed; // intermediate buffer for stroke dashing
|
||||
float scale;
|
||||
|
||||
WgIndexedVertexBuffer(WgGeometryBufferPool* pool, float scale = 1.0f) : pool(pool), scale(scale)
|
||||
{
|
||||
vbuff = tvg::malloc<Point*>(sizeof(Point) * vreserved);
|
||||
ibuff = tvg::malloc<uint32_t*>(sizeof(uint32_t) * ireserved);
|
||||
dashed = pool->reqVertexBuffer();
|
||||
}
|
||||
|
||||
~WgIndexedVertexBuffer()
|
||||
{
|
||||
pool->retVertexBuffer(dashed);
|
||||
tvg::free(vbuff);
|
||||
tvg::free(ibuff);
|
||||
}
|
||||
|
||||
// reset buffer
|
||||
void reset(float scale)
|
||||
{
|
||||
icount = vcount = 0;
|
||||
this->scale = scale;
|
||||
}
|
||||
|
||||
void growIndex(size_t grow)
|
||||
{
|
||||
if (icount + grow >= ireserved) {
|
||||
ireserved *= 2;
|
||||
ibuff = tvg::realloc<uint32_t*>(ibuff, ireserved * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
void growVertex(size_t grow)
|
||||
{
|
||||
if (vcount + grow >= vreserved) {
|
||||
vreserved *= 2;
|
||||
vbuff = tvg::realloc<Point*>(vbuff, vreserved * sizeof(Point));
|
||||
}
|
||||
}
|
||||
|
||||
// get min and max values of the buffer
|
||||
void getMinMax(Point& pmin, Point& pmax) const
|
||||
{
|
||||
if (vcount == 0) return;
|
||||
pmax = pmin = vbuff[0];
|
||||
for (size_t i = 1; i < vcount; i++) {
|
||||
pmin = min(pmin, vbuff[i]);
|
||||
pmax = max(pmax, vbuff[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// append quad - two triangles formed from four points
|
||||
void appendQuad(const Point& p0, const Point& p1, const Point& p2, const Point& p3)
|
||||
{
|
||||
growVertex(4);
|
||||
vbuff[vcount+0] = p0;
|
||||
vbuff[vcount+1] = p1;
|
||||
vbuff[vcount+2] = p2;
|
||||
vbuff[vcount+3] = p3;
|
||||
|
||||
growIndex(6);
|
||||
ibuff[icount+0] = vcount + 0;
|
||||
ibuff[icount+1] = vcount + 1;
|
||||
ibuff[icount+2] = vcount + 2;
|
||||
ibuff[icount+3] = vcount + 1;
|
||||
ibuff[icount+4] = vcount + 3;
|
||||
ibuff[icount+5] = vcount + 2;
|
||||
|
||||
vcount += 4;
|
||||
icount += 6;
|
||||
}
|
||||
|
||||
|
||||
void appendStrokesDashed(const WgVertexBuffer& buff, const RenderStroke* rstroke)
|
||||
{
|
||||
dashed->reset(scale);
|
||||
auto& dash = rstroke->dash;
|
||||
|
||||
if (buff.count < 2) return;
|
||||
|
||||
uint32_t index = 0;
|
||||
auto total = dash.pattern[index];
|
||||
auto length = (dash.count % 2) ? dash.length * 2 : dash.length;
|
||||
|
||||
// normalize dash offset
|
||||
auto dashOffset = dash.offset;
|
||||
while(dashOffset < 0) dashOffset += length;
|
||||
while(dashOffset > length) dashOffset -= length;
|
||||
auto gap = false;
|
||||
|
||||
// scip dashes by offset
|
||||
if (dashOffset > 0.0f) {
|
||||
while(total <= dashOffset) {
|
||||
index = (index + 1) % dash.count;
|
||||
total += dash.pattern[index];
|
||||
gap = !gap;
|
||||
}
|
||||
total -= dashOffset;
|
||||
}
|
||||
|
||||
// iterate by polyline points
|
||||
for (uint32_t i = 0; i < buff.count - 1; i++) {
|
||||
if (!gap) dashed->append(buff.data[i]);
|
||||
// move inside polyline segment
|
||||
while(total < buff.dist[i+1].interval) {
|
||||
// get current point
|
||||
dashed->append(tvg::lerp(buff.data[i], buff.data[i+1], total / buff.dist[i+1].interval));
|
||||
// update current state
|
||||
index = (index + 1) % dash.count;
|
||||
total += dash.pattern[index];
|
||||
// preceed stroke if dash
|
||||
if (!gap) {
|
||||
dashed->updateDistances();
|
||||
appendStrokes(*dashed, rstroke);
|
||||
dashed->reset(scale);
|
||||
}
|
||||
gap = !gap;
|
||||
}
|
||||
// update current subline length
|
||||
total -= buff.dist[i+1].interval;
|
||||
}
|
||||
// draw last subline
|
||||
if (!gap) {
|
||||
dashed->append(buff.last());
|
||||
dashed->updateDistances();
|
||||
appendStrokes(*dashed, rstroke);
|
||||
}
|
||||
}
|
||||
|
||||
// append buffer with optional offset
|
||||
void appendBuffer(const WgVertexBuffer& buff, Point offset = Point{0.0f, 0.0f})
|
||||
{
|
||||
growVertex(buff.count);
|
||||
growIndex(buff.count);
|
||||
|
||||
for (uint32_t i = 0; i < buff.count; i++) {
|
||||
vbuff[vcount + i] = buff.data[i] + offset;
|
||||
ibuff[icount + i] = vcount + i;
|
||||
}
|
||||
vcount += buff.count;
|
||||
icount += buff.count;
|
||||
};
|
||||
|
||||
void appendLine(const Point& v0, const Point& v1, float dist, float halfWidth)
|
||||
{
|
||||
if(tvg::zero(dist)) return;
|
||||
Point sub = v1 - v0;
|
||||
Point nrm = {sub.y / dist * halfWidth, -sub.x / dist * halfWidth};
|
||||
appendQuad(v0 - nrm, v0 + nrm, v1 - nrm, v1 + nrm);
|
||||
}
|
||||
|
||||
void appendBevel(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth)
|
||||
{
|
||||
if(tvg::zero(dist1) || tvg::zero(dist2)) return;
|
||||
Point sub1 = v1 - v0;
|
||||
Point sub2 = v2 - v1;
|
||||
Point nrm1 {sub1.y / dist1 * halfWidth, -sub1.x / dist1 * halfWidth};
|
||||
Point nrm2 {sub2.y / dist2 * halfWidth, -sub2.x / dist2 * halfWidth};
|
||||
appendQuad(v1 - nrm1, v1 + nrm1, v1 - nrm2, v1 + nrm2);
|
||||
}
|
||||
|
||||
void appendMiter(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth, float miterLimit)
|
||||
{
|
||||
if(tvg::zero(dist1) || tvg::zero(dist2)) return;
|
||||
auto sub1 = v1 - v0;
|
||||
auto sub2 = v2 - v1;
|
||||
auto nrm1 = Point{+sub1.y / dist1, -sub1.x / dist1};
|
||||
auto nrm2 = Point{+sub2.y / dist2, -sub2.x / dist2};
|
||||
auto offset1 = nrm1 * halfWidth;
|
||||
auto offset2 = nrm2 * halfWidth;
|
||||
auto nrm = nrm1 + nrm2;
|
||||
normalize(nrm);
|
||||
float cosine = dot(nrm, nrm1);
|
||||
if (tvg::zero(cosine)) return;
|
||||
float angle = std::acos(dot(nrm1, -nrm2));
|
||||
if (tvg::zero(angle) || tvg::equal(angle, MATH_PI)) return;
|
||||
float miterRatio = 1.0f / (std::sin(angle) * 0.5f);
|
||||
if (miterRatio <= miterLimit) {
|
||||
appendQuad(v1 + nrm * (halfWidth / cosine), v1 + offset2, v1 + offset1, v1);
|
||||
appendQuad(v1 - nrm * (halfWidth / cosine), v1 - offset2, v1 - offset1, v1);
|
||||
} else {
|
||||
appendQuad(v1 - offset1, v1 + offset2, v1 - offset2, v1 + offset1);
|
||||
}
|
||||
}
|
||||
|
||||
void appendSquare(Point v0, Point v1, float dist, float halfWidth)
|
||||
{
|
||||
// zero length segment with square cap still should be rendered as a point - only the caps are visible
|
||||
if(tvg::zero(dist)) {
|
||||
appendQuad(v1 + Point{-halfWidth, -halfWidth}, v1 + Point{-halfWidth, halfWidth}, v1 + Point{halfWidth, -halfWidth}, v1 + Point{halfWidth, halfWidth});
|
||||
return;
|
||||
}
|
||||
Point sub = v1 - v0;
|
||||
Point offset = sub / dist * halfWidth;
|
||||
Point nrm = {+offset.y, -offset.x};
|
||||
appendQuad(v1 - nrm, v1 + nrm, v1 + offset - nrm, v1 + offset + nrm);
|
||||
}
|
||||
|
||||
void appendStrokes(const WgVertexBuffer& buff, const RenderStroke* rstroke)
|
||||
{
|
||||
assert(rstroke);
|
||||
// empty buffer gueard
|
||||
if (buff.count < 2) return;
|
||||
|
||||
float halfWidth = rstroke->width * 0.5f;
|
||||
|
||||
// append core lines
|
||||
for (size_t i = 1; i < buff.count; i++) {
|
||||
appendLine(buff.data[i-1], buff.data[i], buff.dist[i].interval, halfWidth);
|
||||
}
|
||||
|
||||
// append caps (square)
|
||||
if ((rstroke->cap == StrokeCap::Square) && !buff.closed) {
|
||||
appendSquare(buff.data[1], buff.data[0], buff.dist[1].interval, halfWidth);
|
||||
appendSquare(buff.last(1), buff.last(0), buff.lastDist(0), halfWidth);
|
||||
}
|
||||
|
||||
// append round joints and caps
|
||||
if ((rstroke->join == StrokeJoin::Round) || (rstroke->cap == StrokeCap::Round)) {
|
||||
// create mesh for circle
|
||||
WgVertexBuffer circle;
|
||||
circle.reset(buff.scale);
|
||||
circle.appendCircle(halfWidth);
|
||||
// append caps (round)
|
||||
if (rstroke->cap == StrokeCap::Round) {
|
||||
appendBuffer(circle, buff.data[0]);
|
||||
// append ending cap if polyline is not closed
|
||||
if (!buff.closed) appendBuffer(circle, buff.last());
|
||||
}
|
||||
// append joints (round)
|
||||
if (rstroke->join == StrokeJoin::Round) {
|
||||
for (size_t i = 1; i < buff.count - 1; i++) {
|
||||
appendBuffer(circle, buff.data[i]);
|
||||
}
|
||||
if (buff.closed) appendBuffer(circle, buff.last());
|
||||
}
|
||||
}
|
||||
|
||||
// append closed endings
|
||||
if (buff.closed) {
|
||||
// close by bevel
|
||||
if (rstroke->join == StrokeJoin::Bevel) {
|
||||
appendBevel(buff.last(1), buff.data[0], buff.data[1], buff.lastDist(0), buff.dist[1].interval, halfWidth);
|
||||
// close by mitter
|
||||
} else if (rstroke->join == StrokeJoin::Miter) {
|
||||
appendMiter(buff.last(1), buff.data[0], buff.data[1], buff.lastDist(0), buff.dist[1].interval, halfWidth, rstroke->miterlimit);
|
||||
}
|
||||
}
|
||||
|
||||
// append joints (bevel)
|
||||
if (rstroke->join == StrokeJoin::Bevel) {
|
||||
for (size_t i = 1; i < buff.count - 1; i++) {
|
||||
appendBevel(buff.data[i-1], buff.data[i], buff.data[i+1], buff.dist[i].interval, buff.dist[i+1].interval, halfWidth);
|
||||
}
|
||||
// append joints (mitter)
|
||||
} else if (rstroke->join == StrokeJoin::Miter) {
|
||||
for (size_t i = 1; i < buff.count - 1; i++) {
|
||||
appendMiter(buff.data[i-1], buff.data[i], buff.data[i+1], buff.dist[i].interval, buff.dist[i+1].interval, halfWidth, rstroke->miterlimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
void bbox(const Point pmin, const Point pmax);
|
||||
void imageBox(float w, float h);
|
||||
void blitBox();
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif // _TVG_WG_GEOMETRY_H_
|
|
@ -22,127 +22,10 @@
|
|||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgWgTessellator.h"
|
||||
#include "tvgWgRenderData.h"
|
||||
#include "tvgWgShaderTypes.h"
|
||||
|
||||
//***********************************************************************
|
||||
// WgMeshData
|
||||
//***********************************************************************
|
||||
|
||||
void WgMeshData::update(const WgVertexBuffer& vertexBuffer)
|
||||
{
|
||||
assert(vertexBuffer.count > 2);
|
||||
// setup vertex data
|
||||
vbuffer.reserve(vertexBuffer.count);
|
||||
vbuffer.count = vertexBuffer.count;
|
||||
memcpy(vbuffer.data, vertexBuffer.data, sizeof(vertexBuffer.data[0])*vertexBuffer.count);
|
||||
// setup tex coords data
|
||||
tbuffer.clear();
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::update(const WgIndexedVertexBuffer& vertexBufferInd)
|
||||
{
|
||||
assert(vertexBufferInd.vcount > 2);
|
||||
// setup vertex data
|
||||
vbuffer.reserve(vertexBufferInd.vcount);
|
||||
vbuffer.count = vertexBufferInd.vcount;
|
||||
memcpy(vbuffer.data, vertexBufferInd.vbuff, sizeof(vertexBufferInd.vbuff[0])*vertexBufferInd.vcount);
|
||||
// setup tex coords data
|
||||
tbuffer.clear();
|
||||
// copy index data
|
||||
ibuffer.reserve(vertexBufferInd.icount);
|
||||
ibuffer.count = vertexBufferInd.icount;
|
||||
memcpy(ibuffer.data, vertexBufferInd.ibuff, sizeof(vertexBufferInd.ibuff[0])*vertexBufferInd.icount);
|
||||
};
|
||||
|
||||
|
||||
void WgMeshData::bbox(const Point pmin, const Point pmax)
|
||||
{
|
||||
const float data[] = {pmin.x, pmin.y, pmax.x, pmin.y, pmax.x, pmax.y, pmin.x, pmax.y};
|
||||
// setup vertex data
|
||||
vbuffer.reserve(4);
|
||||
vbuffer.count = 4;
|
||||
memcpy(vbuffer.data, data, sizeof(data));
|
||||
// setup tex coords data
|
||||
tbuffer.clear();
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::imageBox(float w, float h)
|
||||
{
|
||||
const float vdata[] = {0.0f, 0.0f, w, 0.0f, w, h, 0.0f, h};
|
||||
const float tdata[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||
const uint32_t idata[] = {0, 1, 2, 0, 2, 3};
|
||||
// setup vertex data
|
||||
vbuffer.reserve(4);
|
||||
vbuffer.count = 4;
|
||||
memcpy(vbuffer.data, vdata, sizeof(vdata));
|
||||
// setup tex coords data
|
||||
tbuffer.reserve(4);
|
||||
tbuffer.count = 4;
|
||||
memcpy(tbuffer.data, tdata, sizeof(tdata));
|
||||
// setup indexes data
|
||||
ibuffer.reserve(6);
|
||||
ibuffer.count = 6;
|
||||
memcpy(ibuffer.data, idata, sizeof(idata));
|
||||
}
|
||||
|
||||
|
||||
void WgMeshData::blitBox()
|
||||
{
|
||||
const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f};
|
||||
const float tdata[] = {+0.0f, +0.0f, +1.0f, +0.0f, +1.0f, +1.0f, +0.0f, +1.0f};
|
||||
const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 };
|
||||
// setup vertex data
|
||||
vbuffer.reserve(4);
|
||||
vbuffer.count = 4;
|
||||
memcpy(vbuffer.data, vdata, sizeof(vdata));
|
||||
// setup tex coords data
|
||||
tbuffer.reserve(4);
|
||||
tbuffer.count = 4;
|
||||
memcpy(tbuffer.data, tdata, sizeof(tdata));
|
||||
// setup indexes data
|
||||
ibuffer.reserve(6);
|
||||
ibuffer.count = 6;
|
||||
memcpy(ibuffer.data, idata, sizeof(idata));
|
||||
}
|
||||
|
||||
//***********************************************************************
|
||||
// WgMeshDataGroup
|
||||
//***********************************************************************
|
||||
|
||||
void WgMeshDataGroup::append(const WgVertexBuffer& vertexBuffer)
|
||||
{
|
||||
assert(vertexBuffer.count >= 3);
|
||||
meshes.push(new WgMeshData());
|
||||
meshes.last()->update(vertexBuffer);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshDataGroup::append(const WgIndexedVertexBuffer& vertexBufferInd)
|
||||
{
|
||||
assert(vertexBufferInd.vcount >= 3);
|
||||
meshes.push(new WgMeshData());
|
||||
meshes.last()->update(vertexBufferInd);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshDataGroup::append(const Point pmin, const Point pmax)
|
||||
{
|
||||
meshes.push(new WgMeshData());
|
||||
meshes.last()->bbox(pmin, pmax);
|
||||
}
|
||||
|
||||
|
||||
void WgMeshDataGroup::release()
|
||||
{
|
||||
ARRAY_FOREACH(p, meshes) delete *p;
|
||||
meshes.clear();
|
||||
};
|
||||
|
||||
|
||||
//***********************************************************************
|
||||
// WgImageData
|
||||
//***********************************************************************
|
||||
|
@ -257,95 +140,75 @@ void WgRenderDataPaint::updateClips(tvg::Array<tvg::RenderData> &clips) {
|
|||
// WgRenderDataShape
|
||||
//***********************************************************************
|
||||
|
||||
void WgRenderDataShape::appendShape(const WgVertexBuffer& vertexBuffer)
|
||||
void WgRenderDataShape::updateBBox(BBox bb)
|
||||
{
|
||||
if (vertexBuffer.count < 3) return;
|
||||
Point pmin{}, pmax{};
|
||||
vertexBuffer.getMinMax(pmin, pmax);
|
||||
meshGroupShapes.append(vertexBuffer);
|
||||
meshGroupShapesBBox.append(pmin, pmax);
|
||||
updateBBox(pmin, pmax);
|
||||
bbox.min = tvg::min(bbox.min, bb.min);
|
||||
bbox.max = tvg::max(bbox.max, bb.max);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::appendStroke(const WgIndexedVertexBuffer& vertexBufferInd)
|
||||
void WgRenderDataShape::updateAABB(const Matrix& matrix)
|
||||
{
|
||||
if (vertexBufferInd.vcount < 3) return;
|
||||
Point pmin{}, pmax{};
|
||||
vertexBufferInd.getMinMax(pmin, pmax);
|
||||
meshGroupStrokes.append(vertexBufferInd);
|
||||
meshGroupStrokesBBox.append(pmin, pmax);
|
||||
updateBBox(pmin, pmax);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::updateBBox(Point pmin, Point pmax)
|
||||
{
|
||||
pMin.x = std::min(pMin.x, pmin.x);
|
||||
pMin.y = std::min(pMin.y, pmin.y);
|
||||
pMax.x = std::max(pMax.x, pmax.x);
|
||||
pMax.y = std::max(pMax.y, pmax.y);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::updateAABB(const Matrix& tr) {
|
||||
auto p0 = Point{pMin.x, pMin.y} * tr;
|
||||
auto p1 = Point{pMax.x, pMin.y} * tr;
|
||||
auto p2 = Point{pMin.x, pMax.y} * tr;
|
||||
auto p3 = Point{pMax.x, pMax.y} * tr;
|
||||
auto p0 = Point{bbox.min.x, bbox.min.y} * matrix;
|
||||
auto p1 = Point{bbox.max.x, bbox.min.y} * matrix;
|
||||
auto p2 = Point{bbox.min.x, bbox.max.y} * matrix;
|
||||
auto p3 = Point{bbox.max.x, bbox.max.y} * matrix;
|
||||
aabb.min = {std::min(std::min(p0.x, p1.x), std::min(p2.x, p3.x)), std::min(std::min(p0.y, p1.y), std::min(p2.y, p3.y))};
|
||||
aabb.max = {std::max(std::max(p0.x, p1.x), std::max(p2.x, p3.x)), std::max(std::max(p0.y, p1.y), std::max(p2.y, p3.y))};
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::updateMeshes(const RenderShape &rshape, const Matrix& tr, WgGeometryBufferPool* pool)
|
||||
void WgRenderDataShape::updateMeshes(const RenderShape &rshape, RenderUpdateFlag flag, const Matrix& matrix)
|
||||
{
|
||||
releaseMeshes();
|
||||
strokeFirst = rshape.strokeFirst();
|
||||
|
||||
// get object scale
|
||||
float scale = std::max(std::min(length(Point{tr.e11 + tr.e12,tr.e21 + tr.e22}), 8.0f), 1.0f);
|
||||
// update fill shapes
|
||||
if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Path)) {
|
||||
meshShape.clear();
|
||||
|
||||
// path decoded vertex buffer
|
||||
auto pbuff = pool->reqVertexBuffer(scale);
|
||||
WgBWTessellator bwTess{&meshShape};
|
||||
if (rshape.trimpath()) {
|
||||
RenderPath trimmedPath;
|
||||
if (rshape.stroke->trim.trim(rshape.path, trimmedPath))
|
||||
bwTess.tessellate(trimmedPath, matrix);
|
||||
} else bwTess.tessellate(rshape.path, matrix);
|
||||
|
||||
pbuff->decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
|
||||
appendShape(path_buff);
|
||||
if ((rshape.stroke) && (rshape.stroke->width > 0)) proceedStrokes(rshape.stroke, path_buff, pool);
|
||||
}, rshape.trimpath());
|
||||
if (meshShape.ibuffer.count > 0) {;
|
||||
auto bbox = bwTess.getBBox();
|
||||
meshShapeBBox.bbox(bbox.min, bbox.max);
|
||||
updateBBox(bbox);
|
||||
} else meshShape.clear();
|
||||
}
|
||||
|
||||
// update strokes shapes
|
||||
if (rshape.stroke && (flag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke | RenderUpdateFlag::Transform))) {
|
||||
meshStrokes.clear();
|
||||
|
||||
WgStroker stroker{&meshStrokes, matrix};
|
||||
stroker.stroke(&rshape);
|
||||
|
||||
if (meshStrokes.ibuffer.count > 0) {
|
||||
auto bbox = stroker.getBBox();
|
||||
meshStrokesBBox.bbox(bbox.min, bbox.max);
|
||||
updateBBox(bbox);
|
||||
} else meshStrokes.clear();
|
||||
}
|
||||
|
||||
// update shapes bbox (with empty path handling)
|
||||
if ((this->meshGroupShapesBBox.meshes.count > 0 ) ||
|
||||
(this->meshGroupStrokesBBox.meshes.count > 0)) {
|
||||
updateAABB(tr);
|
||||
if ((meshShape.vbuffer.count > 0 ) || (meshStrokes.vbuffer.count > 0)) {
|
||||
updateAABB(matrix);
|
||||
} else aabb = {{0, 0}, {0, 0}};
|
||||
meshDataBBox.bbox(pMin, pMax);
|
||||
|
||||
pool->retVertexBuffer(pbuff);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::proceedStrokes(const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool)
|
||||
{
|
||||
assert(rstroke);
|
||||
auto strokesGenerator = pool->reqIndexedVertexBuffer(buff.scale);
|
||||
if (rstroke->dash.length < DASH_PATTERN_THRESHOLD) strokesGenerator->appendStrokes(buff, rstroke);
|
||||
else strokesGenerator->appendStrokesDashed(buff, rstroke);
|
||||
|
||||
appendStroke(*strokesGenerator);
|
||||
|
||||
pool->retIndexedVertexBuffer(strokesGenerator);
|
||||
meshBBox.bbox(bbox.min, bbox.max);
|
||||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::releaseMeshes()
|
||||
{
|
||||
meshGroupStrokesBBox.release();
|
||||
meshGroupStrokes.release();
|
||||
meshGroupShapesBBox.release();
|
||||
meshGroupShapes.release();
|
||||
pMin = {FLT_MAX, FLT_MAX};
|
||||
pMax = {0.0f, 0.0f};
|
||||
meshStrokes.clear();
|
||||
meshShape.clear();
|
||||
bbox.min = {FLT_MAX, FLT_MAX};
|
||||
bbox.max = {0.0f, 0.0f};
|
||||
aabb = {{0, 0}, {0, 0}};
|
||||
clips.clear();
|
||||
}
|
||||
|
@ -379,10 +242,7 @@ WgRenderDataShape* WgRenderDataShapePool::allocate(WgContext& context)
|
|||
|
||||
void WgRenderDataShapePool::free(WgContext& context, WgRenderDataShape* renderData)
|
||||
{
|
||||
renderData->meshGroupShapes.release();
|
||||
renderData->meshGroupShapesBBox.release();
|
||||
renderData->meshGroupStrokes.release();
|
||||
renderData->meshGroupStrokesBBox.release();
|
||||
renderData->releaseMeshes();
|
||||
renderData->clips.clear();
|
||||
mPool.push(renderData);
|
||||
}
|
||||
|
@ -620,7 +480,6 @@ void WgStageBufferGeometry::append(WgMeshData* meshData)
|
|||
uint32_t vsize = meshData->vbuffer.count * sizeof(meshData->vbuffer[0]);
|
||||
uint32_t tsize = meshData->tbuffer.count * sizeof(meshData->tbuffer[0]);
|
||||
uint32_t isize = meshData->ibuffer.count * sizeof(meshData->ibuffer[0]);
|
||||
vmaxcount = std::max(vmaxcount, meshData->vbuffer.count);
|
||||
// append vertex data
|
||||
if (vbuffer.reserved < vbuffer.count + vsize)
|
||||
vbuffer.grow(std::max(vsize, vbuffer.reserved));
|
||||
|
@ -648,19 +507,13 @@ void WgStageBufferGeometry::append(WgMeshData* meshData)
|
|||
}
|
||||
|
||||
|
||||
void WgStageBufferGeometry::append(WgMeshDataGroup* meshDataGroup)
|
||||
{
|
||||
ARRAY_FOREACH(p, meshDataGroup->meshes) append(*p);
|
||||
}
|
||||
|
||||
|
||||
void WgStageBufferGeometry::append(WgRenderDataShape* renderDataShape)
|
||||
{
|
||||
append(&renderDataShape->meshGroupShapes);
|
||||
append(&renderDataShape->meshGroupShapesBBox);
|
||||
append(&renderDataShape->meshGroupStrokes);
|
||||
append(&renderDataShape->meshGroupStrokesBBox);
|
||||
append(&renderDataShape->meshDataBBox);
|
||||
append(&renderDataShape->meshShape);
|
||||
append(&renderDataShape->meshShapeBBox);
|
||||
append(&renderDataShape->meshStrokes);
|
||||
append(&renderDataShape->meshStrokesBBox);
|
||||
append(&renderDataShape->meshBBox);
|
||||
}
|
||||
|
||||
|
||||
|
@ -681,7 +534,6 @@ void WgStageBufferGeometry::clear()
|
|||
{
|
||||
vbuffer.clear();
|
||||
ibuffer.clear();
|
||||
vmaxcount = 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -689,5 +541,4 @@ void WgStageBufferGeometry::flush(WgContext& context)
|
|||
{
|
||||
context.allocateBufferVertex(vbuffer_gpu, (float *)vbuffer.data, vbuffer.count);
|
||||
context.allocateBufferIndex(ibuffer_gpu, (uint32_t *)ibuffer.data, ibuffer.count);
|
||||
context.allocateBufferIndexFan(vmaxcount);
|
||||
}
|
||||
|
|
|
@ -27,30 +27,6 @@
|
|||
#include "tvgWgGeometry.h"
|
||||
#include "tvgWgShaderTypes.h"
|
||||
|
||||
struct WgMeshData {
|
||||
Array<Point> vbuffer;
|
||||
Array<Point> tbuffer;
|
||||
Array<uint32_t> ibuffer;
|
||||
size_t voffset{};
|
||||
size_t toffset{};
|
||||
size_t ioffset{};
|
||||
|
||||
void update(const WgVertexBuffer& vertexBuffer);
|
||||
void update(const WgIndexedVertexBuffer& vertexBufferInd);
|
||||
void bbox(const Point pmin, const Point pmax);
|
||||
void imageBox(float w, float h);
|
||||
void blitBox();
|
||||
};
|
||||
|
||||
struct WgMeshDataGroup {
|
||||
Array<WgMeshData*> meshes{};
|
||||
|
||||
void append(const WgVertexBuffer& vertexBuffer);
|
||||
void append(const WgIndexedVertexBuffer& vertexBufferInd);
|
||||
void append(const Point pmin, const Point pmax);
|
||||
void release();
|
||||
};
|
||||
|
||||
struct WgImageData {
|
||||
WGPUTexture texture{};
|
||||
WGPUTextureView textureView{};
|
||||
|
@ -96,22 +72,18 @@ struct WgRenderDataShape: public WgRenderDataPaint
|
|||
{
|
||||
WgRenderSettings renderSettingsShape{};
|
||||
WgRenderSettings renderSettingsStroke{};
|
||||
WgMeshDataGroup meshGroupShapes{};
|
||||
WgMeshDataGroup meshGroupShapesBBox{};
|
||||
WgMeshData meshDataBBox{};
|
||||
WgMeshDataGroup meshGroupStrokes{};
|
||||
WgMeshDataGroup meshGroupStrokesBBox{};
|
||||
Point pMin{};
|
||||
Point pMax{};
|
||||
WgMeshData meshBBox{};
|
||||
WgMeshData meshShape{};
|
||||
WgMeshData meshShapeBBox{};
|
||||
WgMeshData meshStrokes{};
|
||||
WgMeshData meshStrokesBBox{};
|
||||
bool strokeFirst{};
|
||||
FillRule fillRule{};
|
||||
BBox bbox;
|
||||
|
||||
void appendShape(const WgVertexBuffer& vertexBuffer);
|
||||
void appendStroke(const WgIndexedVertexBuffer& vertexBufferInd);
|
||||
void updateBBox(Point pmin, Point pmax);
|
||||
void updateAABB(const Matrix& tr);
|
||||
void updateMeshes(const RenderShape& rshape, const Matrix& tr, WgGeometryBufferPool* pool);
|
||||
void proceedStrokes(const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool);
|
||||
void updateBBox(BBox bb);
|
||||
void updateAABB(const Matrix& matrix);
|
||||
void updateMeshes(const RenderShape& rshape, RenderUpdateFlag flag, const Matrix& matrix);
|
||||
void releaseMeshes();
|
||||
void release(WgContext& context) override;
|
||||
Type type() override { return Type::Shape; };
|
||||
|
@ -207,13 +179,11 @@ class WgStageBufferGeometry {
|
|||
private:
|
||||
Array<uint8_t> vbuffer;
|
||||
Array<uint8_t> ibuffer;
|
||||
uint32_t vmaxcount{};
|
||||
public:
|
||||
WGPUBuffer vbuffer_gpu{};
|
||||
WGPUBuffer ibuffer_gpu{};
|
||||
|
||||
void append(WgMeshData* meshData);
|
||||
void append(WgMeshDataGroup* meshDataGroup);
|
||||
void append(WgRenderDataShape* renderDataShape);
|
||||
void append(WgRenderDataPicture* renderDataPicture);
|
||||
void initialize(WgContext& context){};
|
||||
|
|
|
@ -135,7 +135,7 @@ RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const
|
|||
|
||||
// update geometry
|
||||
if (!data || (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke))) {
|
||||
renderDataShape->updateMeshes(rshape, transform, mBufferPool.pool);
|
||||
renderDataShape->updateMeshes(rshape, flags, transform);
|
||||
}
|
||||
|
||||
// update paint settings
|
||||
|
@ -408,10 +408,6 @@ WgRenderer::WgRenderer()
|
|||
{
|
||||
if (TaskScheduler::onthread()) {
|
||||
TVGLOG("WG_RENDERER", "Running on a non-dominant thread!, Renderer(%p)", this);
|
||||
mBufferPool.pool = new WgGeometryBufferPool;
|
||||
mBufferPool.individual = true;
|
||||
} else {
|
||||
mBufferPool.pool = WgGeometryBufferPool::instance();
|
||||
}
|
||||
|
||||
++rendererCnt;
|
||||
|
@ -422,8 +418,6 @@ WgRenderer::~WgRenderer()
|
|||
{
|
||||
release();
|
||||
|
||||
if (mBufferPool.individual) delete(mBufferPool.pool);
|
||||
|
||||
--rendererCnt;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,11 +107,6 @@ private:
|
|||
WGPUTexture targetTexture{}; // external handle
|
||||
WGPUSurfaceTexture surfaceTexture{};
|
||||
WGPUSurface surface{}; // external handle
|
||||
|
||||
struct {
|
||||
WgGeometryBufferPool* pool; //private buffer pool
|
||||
bool individual = false; //buffer-pool sharing policy
|
||||
} mBufferPool;
|
||||
};
|
||||
|
||||
#endif /* _TVG_WG_RENDERER_H_ */
|
||||
|
|
537
src/renderer/wg_engine/tvgWgTessellator.cpp
Normal file
537
src/renderer/wg_engine/tvgWgTessellator.cpp
Normal file
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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 "tvgWgTessellator.h"
|
||||
#include "tvgMath.h"
|
||||
|
||||
|
||||
WgStroker::WgStroker(WgMeshData* buffer, const Matrix& matrix) : mBuffer(buffer), mMatrix(matrix)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::stroke(const RenderShape *rshape)
|
||||
{
|
||||
mMiterLimit = rshape->strokeMiterlimit();
|
||||
mStrokeCap = rshape->strokeCap();
|
||||
mStrokeJoin = rshape->strokeJoin();
|
||||
mStrokeWidth = rshape->strokeWidth();
|
||||
|
||||
if (isinf(mMatrix.e11)) {
|
||||
auto strokeWidth = rshape->strokeWidth() * scaling(mMatrix);
|
||||
if (strokeWidth <= MIN_WG_STROKE_WIDTH) strokeWidth = MIN_WG_STROKE_WIDTH;
|
||||
mStrokeWidth = strokeWidth / mMatrix.e11;
|
||||
}
|
||||
|
||||
RenderPath dashed;
|
||||
if (rshape->strokeDash(dashed)) doStroke(dashed);
|
||||
else if (rshape->trimpath()) {
|
||||
RenderPath trimmedPath;
|
||||
if (rshape->stroke->trim.trim(rshape->path, trimmedPath)) doStroke(trimmedPath);
|
||||
} else
|
||||
doStroke(rshape->path);
|
||||
}
|
||||
|
||||
|
||||
RenderRegion WgStroker::bounds() const
|
||||
{
|
||||
return {{int32_t(floor(mLeftTop.x)), int32_t(floor(mLeftTop.y))}, {int32_t(ceil(mRightBottom.x)), int32_t(ceil(mRightBottom.y))}};
|
||||
}
|
||||
|
||||
|
||||
BBox WgStroker::getBBox() const
|
||||
{
|
||||
return {mLeftTop, mRightBottom};
|
||||
}
|
||||
|
||||
void WgStroker::doStroke(const RenderPath& path)
|
||||
{
|
||||
mBuffer->vbuffer.reserve(path.pts.count * 4 + 16);
|
||||
mBuffer->ibuffer.reserve(path.pts.count * 3);
|
||||
|
||||
auto validStrokeCap = false;
|
||||
auto pts = path.pts.data;
|
||||
|
||||
ARRAY_FOREACH(cmd, path.cmds) {
|
||||
switch (*cmd) {
|
||||
case PathCommand::MoveTo: {
|
||||
if (validStrokeCap) { // check this, so we can skip if path only contains move instruction
|
||||
strokeCap();
|
||||
validStrokeCap = false;
|
||||
}
|
||||
mStrokeState.firstPt = *pts;
|
||||
mStrokeState.firstPtDir = {0.0f, 0.0f};
|
||||
mStrokeState.prevPt = *pts;
|
||||
mStrokeState.prevPtDir = {0.0f, 0.0f};
|
||||
pts++;
|
||||
validStrokeCap = false;
|
||||
} break;
|
||||
case PathCommand::LineTo: {
|
||||
validStrokeCap = true;
|
||||
this->strokeLineTo(*pts);
|
||||
pts++;
|
||||
} break;
|
||||
case PathCommand::CubicTo: {
|
||||
validStrokeCap = true;
|
||||
this->strokeCubicTo(pts[0], pts[1], pts[2]);
|
||||
pts += 3;
|
||||
} break;
|
||||
case PathCommand::Close: {
|
||||
this->strokeClose();
|
||||
|
||||
validStrokeCap = false;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (validStrokeCap) strokeCap();
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeCap()
|
||||
{
|
||||
if (mStrokeCap == StrokeCap::Butt) return;
|
||||
|
||||
if (mStrokeCap == StrokeCap::Square) {
|
||||
if (mStrokeState.firstPt == mStrokeState.prevPt) strokeSquarePoint(mStrokeState.firstPt);
|
||||
else {
|
||||
strokeSquare(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y});
|
||||
strokeSquare(mStrokeState.prevPt, mStrokeState.prevPtDir);
|
||||
}
|
||||
} else if (mStrokeCap == StrokeCap::Round) {
|
||||
if (mStrokeState.firstPt == mStrokeState.prevPt) strokeRoundPoint(mStrokeState.firstPt);
|
||||
else {
|
||||
strokeRound(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y});
|
||||
strokeRound(mStrokeState.prevPt, mStrokeState.prevPtDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeLineTo(const Point& curr)
|
||||
{
|
||||
auto dir = (curr - mStrokeState.prevPt);
|
||||
normalize(dir);
|
||||
|
||||
if (dir.x == 0.f && dir.y == 0.f) return; //same point
|
||||
|
||||
auto normal = Point{-dir.y, dir.x};
|
||||
auto a = mStrokeState.prevPt + normal * strokeRadius();
|
||||
auto b = mStrokeState.prevPt - normal * strokeRadius();
|
||||
auto c = curr + normal * strokeRadius();
|
||||
auto d = curr - normal * strokeRadius();
|
||||
|
||||
auto ia = mBuffer->vbuffer.count; mBuffer->vbuffer.push(a);
|
||||
auto ib = mBuffer->vbuffer.count; mBuffer->vbuffer.push(b);
|
||||
auto ic = mBuffer->vbuffer.count; mBuffer->vbuffer.push(c);
|
||||
auto id = mBuffer->vbuffer.count; mBuffer->vbuffer.push(d);
|
||||
|
||||
/**
|
||||
* a --------- c
|
||||
* | |
|
||||
* | |
|
||||
* b-----------d
|
||||
*/
|
||||
|
||||
mBuffer->ibuffer.push(ia);
|
||||
mBuffer->ibuffer.push(ib);
|
||||
mBuffer->ibuffer.push(ic);
|
||||
mBuffer->ibuffer.push(ib);
|
||||
mBuffer->ibuffer.push(id);
|
||||
mBuffer->ibuffer.push(ic);
|
||||
|
||||
if (mStrokeState.prevPt == mStrokeState.firstPt) {
|
||||
// first point after moveTo
|
||||
mStrokeState.prevPt = curr;
|
||||
mStrokeState.prevPtDir = dir;
|
||||
mStrokeState.firstPtDir = dir;
|
||||
} else {
|
||||
this->strokeJoin(dir);
|
||||
mStrokeState.prevPtDir = dir;
|
||||
mStrokeState.prevPt = curr;
|
||||
}
|
||||
|
||||
if (ia == 0) {
|
||||
mRightBottom.x = mLeftTop.x = curr.x;
|
||||
mRightBottom.y = mLeftTop.y = curr.y;
|
||||
}
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x)));
|
||||
mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y)));
|
||||
mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x)));
|
||||
mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y)));
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeCubicTo(const Point& cnt1, const Point& cnt2, const Point& end)
|
||||
{
|
||||
Bezier curve {mStrokeState.prevPt, cnt1, cnt2, end};
|
||||
|
||||
Bezier relCurve {curve.start, curve.ctrl1, curve.ctrl2, curve.end};
|
||||
relCurve.start *= mMatrix;
|
||||
relCurve.ctrl1 *= mMatrix;
|
||||
relCurve.ctrl2 *= mMatrix;
|
||||
relCurve.end *= mMatrix;
|
||||
|
||||
auto count = relCurve.segments();
|
||||
auto step = 1.f / count;
|
||||
|
||||
for (uint32_t i = 0; i <= count; i++) {
|
||||
strokeLineTo(curve.at(step * i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeClose()
|
||||
{
|
||||
if (length(mStrokeState.prevPt - mStrokeState.firstPt) > 0.015625f) {
|
||||
this->strokeLineTo(mStrokeState.firstPt);
|
||||
}
|
||||
|
||||
// join firstPt with prevPt
|
||||
this->strokeJoin(mStrokeState.firstPtDir);
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeJoin(const Point& dir)
|
||||
{
|
||||
auto orient = orientation(mStrokeState.prevPt - mStrokeState.prevPtDir, mStrokeState.prevPt, mStrokeState.prevPt + dir);
|
||||
|
||||
if (orient == Orientation::Linear) {
|
||||
if (mStrokeState.prevPtDir == dir) return; // check is same direction
|
||||
if (mStrokeJoin != StrokeJoin::Round) return; // opposite direction
|
||||
|
||||
auto normal = Point{-dir.y, dir.x};
|
||||
auto p1 = mStrokeState.prevPt + normal * strokeRadius();
|
||||
auto p2 = mStrokeState.prevPt - normal * strokeRadius();
|
||||
auto oc = mStrokeState.prevPt + dir * strokeRadius();
|
||||
|
||||
this->strokeRound(p1, oc, mStrokeState.prevPt);
|
||||
this->strokeRound(oc, p2, mStrokeState.prevPt);
|
||||
|
||||
} else {
|
||||
auto normal = Point{-dir.y, dir.x};
|
||||
auto prevNormal = Point{-mStrokeState.prevPtDir.y, mStrokeState.prevPtDir.x};
|
||||
Point prevJoin, currJoin;
|
||||
|
||||
if (orient == Orientation::CounterClockwise) {
|
||||
prevJoin = mStrokeState.prevPt + prevNormal * strokeRadius();
|
||||
currJoin = mStrokeState.prevPt + normal * strokeRadius();
|
||||
} else {
|
||||
prevJoin = mStrokeState.prevPt - prevNormal * strokeRadius();
|
||||
currJoin = mStrokeState.prevPt - normal * strokeRadius();
|
||||
}
|
||||
|
||||
if (mStrokeJoin == StrokeJoin::Miter) strokeMiter(prevJoin, currJoin, mStrokeState.prevPt);
|
||||
else if (mStrokeJoin == StrokeJoin::Bevel) strokeBevel(prevJoin, currJoin, mStrokeState.prevPt);
|
||||
else this->strokeRound(prevJoin, currJoin, mStrokeState.prevPt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeRound(const Point &prev, const Point& curr, const Point& center)
|
||||
{
|
||||
if (orientation(prev, center, curr) == Orientation::Linear) return;
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, std::min(center.x, std::min(prev.x, curr.x)));
|
||||
mLeftTop.y = std::min(mLeftTop.y, std::min(center.y, std::min(prev.y, curr.y)));
|
||||
mRightBottom.x = std::max(mRightBottom.x, std::max(center.x, std::max(prev.x, curr.x)));
|
||||
mRightBottom.y = std::max(mRightBottom.y, std::max(center.y, std::max(prev.y, curr.y)));
|
||||
|
||||
// Fixme: just use bezier curve to calculate step count
|
||||
auto count = Bezier(prev, curr, strokeRadius()).segments();
|
||||
auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(center);
|
||||
auto pi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(prev);
|
||||
auto step = 1.f / (count - 1);
|
||||
auto dir = curr - prev;
|
||||
|
||||
for (uint32_t i = 1; i < static_cast<uint32_t>(count); i++) {
|
||||
auto t = i * step;
|
||||
auto p = prev + dir * t;
|
||||
auto o_dir = p - center;
|
||||
normalize(o_dir);
|
||||
|
||||
auto out = center + o_dir * strokeRadius();
|
||||
auto oi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(out);
|
||||
|
||||
mBuffer->ibuffer.push(c);
|
||||
mBuffer->ibuffer.push(pi);
|
||||
mBuffer->ibuffer.push(oi);
|
||||
|
||||
pi = oi;
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, out.x);
|
||||
mLeftTop.y = std::min(mLeftTop.y, out.y);
|
||||
mRightBottom.x = std::max(mRightBottom.x, out.x);
|
||||
mRightBottom.y = std::max(mRightBottom.y, out.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeRoundPoint(const Point &p)
|
||||
{
|
||||
// Fixme: just use bezier curve to calculate step count
|
||||
auto count = Bezier(p, p, strokeRadius()).segments() * 2;
|
||||
auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(p);
|
||||
auto step = 2 * MATH_PI / (count - 1);
|
||||
|
||||
for (uint32_t i = 1; i <= static_cast<uint32_t>(count); i++) {
|
||||
float angle = i * step;
|
||||
Point dir = {cos(angle), sin(angle)};
|
||||
Point out = p + dir * strokeRadius();
|
||||
auto oi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(out);
|
||||
|
||||
if (oi > 1) {
|
||||
mBuffer->ibuffer.push(c);
|
||||
mBuffer->ibuffer.push(oi);
|
||||
mBuffer->ibuffer.push(oi - 1);
|
||||
}
|
||||
}
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, p.x - strokeRadius());
|
||||
mLeftTop.y = std::min(mLeftTop.y, p.y - strokeRadius());
|
||||
mRightBottom.x = std::max(mRightBottom.x, p.x + strokeRadius());
|
||||
mRightBottom.y = std::max(mRightBottom.y, p.y + strokeRadius());
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeMiter(const Point& prev, const Point& curr, const Point& center)
|
||||
{
|
||||
auto pp1 = prev - center;
|
||||
auto pp2 = curr - center;
|
||||
auto out = pp1 + pp2;
|
||||
auto k = 2.f * strokeRadius() * strokeRadius() / (out.x * out.x + out.y * out.y);
|
||||
auto pe = out * k;
|
||||
|
||||
if (length(pe) >= mMiterLimit * strokeRadius()) {
|
||||
this->strokeBevel(prev, curr, center);
|
||||
return;
|
||||
}
|
||||
|
||||
auto join = center + pe;
|
||||
auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(center);
|
||||
auto cp1 = mBuffer->vbuffer.count; mBuffer->vbuffer.push(prev);
|
||||
auto cp2 = mBuffer->vbuffer.count; mBuffer->vbuffer.push(curr);
|
||||
auto e = mBuffer->vbuffer.count; mBuffer->vbuffer.push(join);
|
||||
|
||||
mBuffer->ibuffer.push(c);
|
||||
mBuffer->ibuffer.push(cp1);
|
||||
mBuffer->ibuffer.push(e);
|
||||
|
||||
mBuffer->ibuffer.push(e);
|
||||
mBuffer->ibuffer.push(cp2);
|
||||
mBuffer->ibuffer.push(c);
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, join.x);
|
||||
mLeftTop.y = std::min(mLeftTop.y, join.y);
|
||||
|
||||
mRightBottom.x = std::max(mRightBottom.x, join.x);
|
||||
mRightBottom.y = std::max(mRightBottom.y, join.y);
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeBevel(const Point& prev, const Point& curr, const Point& center)
|
||||
{
|
||||
auto a = mBuffer->vbuffer.count; mBuffer->vbuffer.push(prev);
|
||||
auto b = mBuffer->vbuffer.count; mBuffer->vbuffer.push(curr);
|
||||
auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(center);
|
||||
|
||||
mBuffer->ibuffer.push(a);
|
||||
mBuffer->ibuffer.push(b);
|
||||
mBuffer->ibuffer.push(c);
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeSquare(const Point& p, const Point& outDir)
|
||||
{
|
||||
auto normal = Point{-outDir.y, outDir.x};
|
||||
|
||||
auto a = p + normal * strokeRadius();
|
||||
auto b = p - normal * strokeRadius();
|
||||
auto c = a + outDir * strokeRadius();
|
||||
auto d = b + outDir * strokeRadius();
|
||||
|
||||
auto ai = mBuffer->vbuffer.count; mBuffer->vbuffer.push(a);
|
||||
auto bi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(b);
|
||||
auto ci = mBuffer->vbuffer.count; mBuffer->vbuffer.push(c);
|
||||
auto di = mBuffer->vbuffer.count; mBuffer->vbuffer.push(d);
|
||||
|
||||
mBuffer->ibuffer.push(ai);
|
||||
mBuffer->ibuffer.push(bi);
|
||||
mBuffer->ibuffer.push(ci);
|
||||
|
||||
mBuffer->ibuffer.push(ci);
|
||||
mBuffer->ibuffer.push(bi);
|
||||
mBuffer->ibuffer.push(di);
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x)));
|
||||
mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y)));
|
||||
mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x)));
|
||||
mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y)));
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeSquarePoint(const Point& p)
|
||||
{
|
||||
auto offsetX = Point{strokeRadius(), 0.0f};
|
||||
auto offsetY = Point{0.0f, strokeRadius()};
|
||||
|
||||
auto a = p + offsetX + offsetY;
|
||||
auto b = p - offsetX + offsetY;
|
||||
auto c = p - offsetX - offsetY;
|
||||
auto d = p + offsetX - offsetY;
|
||||
|
||||
auto ai = mBuffer->vbuffer.count; mBuffer->vbuffer.push(a);
|
||||
auto bi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(b);
|
||||
auto ci = mBuffer->vbuffer.count; mBuffer->vbuffer.push(c);
|
||||
auto di = mBuffer->vbuffer.count; mBuffer->vbuffer.push(d);
|
||||
|
||||
mBuffer->ibuffer.push(ai);
|
||||
mBuffer->ibuffer.push(bi);
|
||||
mBuffer->ibuffer.push(ci);
|
||||
|
||||
mBuffer->ibuffer.push(ci);
|
||||
mBuffer->ibuffer.push(di);
|
||||
mBuffer->ibuffer.push(ai);
|
||||
|
||||
mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x)));
|
||||
mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y)));
|
||||
mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x)));
|
||||
mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y)));
|
||||
}
|
||||
|
||||
|
||||
void WgStroker::strokeRound(const Point& p, const Point& outDir)
|
||||
{
|
||||
auto normal = Point{-outDir.y, outDir.x};
|
||||
auto a = p + normal * strokeRadius();
|
||||
auto b = p - normal * strokeRadius();
|
||||
auto c = p + outDir * strokeRadius();
|
||||
|
||||
strokeRound(a, c, p);
|
||||
strokeRound(c, b, p);
|
||||
}
|
||||
|
||||
|
||||
WgBWTessellator::WgBWTessellator(WgMeshData* buffer): mBuffer(buffer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void WgBWTessellator::tessellate(const RenderPath& path, const Matrix& matrix)
|
||||
{
|
||||
if (path.pts.count <= 2) return;
|
||||
|
||||
auto cmds = path.cmds.data;
|
||||
auto cmdCnt = path.cmds.count;
|
||||
auto pts = path.pts.data;
|
||||
auto ptsCnt = path.pts.count;
|
||||
|
||||
uint32_t firstIndex = 0;
|
||||
uint32_t prevIndex = 0;
|
||||
|
||||
mBuffer->vbuffer.reserve(ptsCnt * 2);
|
||||
mBuffer->ibuffer.reserve((ptsCnt - 2) * 3);
|
||||
|
||||
for (uint32_t i = 0; i < cmdCnt; i++) {
|
||||
switch(cmds[i]) {
|
||||
case PathCommand::MoveTo: {
|
||||
firstIndex = pushVertex(pts->x, pts->y);
|
||||
prevIndex = 0;
|
||||
pts++;
|
||||
} break;
|
||||
case PathCommand::LineTo: {
|
||||
if (prevIndex == 0) {
|
||||
prevIndex = pushVertex(pts->x, pts->y);
|
||||
pts++;
|
||||
} else {
|
||||
auto currIndex = pushVertex(pts->x, pts->y);
|
||||
pushTriangle(firstIndex, prevIndex, currIndex);
|
||||
prevIndex = currIndex;
|
||||
pts++;
|
||||
}
|
||||
} break;
|
||||
case PathCommand::CubicTo: {
|
||||
Bezier curve{pts[-1], pts[0], pts[1], pts[2]};
|
||||
Bezier relCurve {pts[-1], pts[0], pts[1], pts[2]};
|
||||
relCurve.start *= matrix;
|
||||
relCurve.ctrl1 *= matrix;
|
||||
relCurve.ctrl2 *= matrix;
|
||||
relCurve.end *= matrix;
|
||||
|
||||
auto stepCount = relCurve.segments();
|
||||
if (stepCount <= 1) stepCount = 2;
|
||||
|
||||
float step = 1.f / stepCount;
|
||||
|
||||
for (uint32_t s = 1; s <= static_cast<uint32_t>(stepCount); s++) {
|
||||
auto pt = curve.at(step * s);
|
||||
auto currIndex = pushVertex(pt.x, pt.y);
|
||||
|
||||
if (prevIndex == 0) {
|
||||
prevIndex = currIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
pushTriangle(firstIndex, prevIndex, currIndex);
|
||||
prevIndex = currIndex;
|
||||
}
|
||||
|
||||
pts += 3;
|
||||
} break;
|
||||
case PathCommand::Close:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RenderRegion WgBWTessellator::bounds() const
|
||||
{
|
||||
return {{int32_t(floor(bbox.min.x)), int32_t(floor(bbox.min.y))}, {int32_t(ceil(bbox.max.x)), int32_t(ceil(bbox.max.y))}};
|
||||
}
|
||||
|
||||
|
||||
BBox WgBWTessellator::getBBox() const
|
||||
{
|
||||
return bbox;
|
||||
}
|
||||
|
||||
|
||||
uint32_t WgBWTessellator::pushVertex(float x, float y)
|
||||
{
|
||||
auto index = mBuffer->vbuffer.count;
|
||||
mBuffer->vbuffer.push({x, y});
|
||||
if (index == 0) bbox.max = bbox.min = {x, y};
|
||||
else bbox = {{std::min(bbox.min.x, x), std::min(bbox.min.y, y)}, {std::max(bbox.max.x, x), std::max(bbox.max.y, y)}};
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
void WgBWTessellator::pushTriangle(uint32_t a, uint32_t b, uint32_t c)
|
||||
{
|
||||
mBuffer->ibuffer.push(a);
|
||||
mBuffer->ibuffer.push(b);
|
||||
mBuffer->ibuffer.push(c);
|
||||
}
|
92
src/renderer/wg_engine/tvgWgTessellator.h
Normal file
92
src/renderer/wg_engine/tvgWgTessellator.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2025 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _TVG_WG_TESSELLATOR_H_
|
||||
#define _TVG_WG_TESSELLATOR_H_
|
||||
|
||||
#include "tvgRender.h"
|
||||
#include "tvgWgGeometry.h"
|
||||
|
||||
#define MIN_WG_STROKE_WIDTH 1.0f
|
||||
|
||||
class WgStroker
|
||||
{
|
||||
struct State
|
||||
{
|
||||
Point firstPt;
|
||||
Point firstPtDir;
|
||||
Point prevPt;
|
||||
Point prevPtDir;
|
||||
};
|
||||
public:
|
||||
WgStroker(WgMeshData* buffer, const Matrix& matrix);
|
||||
void stroke(const RenderShape *rshape);
|
||||
RenderRegion bounds() const;
|
||||
BBox getBBox() const;
|
||||
private:
|
||||
void doStroke(const RenderPath& path);
|
||||
|
||||
float strokeRadius() const
|
||||
{
|
||||
return mStrokeWidth * 0.5f;
|
||||
}
|
||||
|
||||
void strokeCap();
|
||||
void strokeLineTo(const Point& curr);
|
||||
void strokeCubicTo(const Point& cnt1, const Point& cnt2, const Point& end);
|
||||
void strokeClose();
|
||||
void strokeJoin(const Point& dir);
|
||||
void strokeRound(const Point& prev, const Point& curr, const Point& center);
|
||||
void strokeMiter(const Point& prev, const Point& curr, const Point& center);
|
||||
void strokeBevel(const Point& prev, const Point& curr, const Point& center);
|
||||
void strokeSquare(const Point& p, const Point& outDir);
|
||||
void strokeSquarePoint(const Point& p);
|
||||
void strokeRound(const Point& p, const Point& outDir);
|
||||
void strokeRoundPoint(const Point& p);
|
||||
|
||||
WgMeshData* mBuffer;
|
||||
Matrix mMatrix;
|
||||
float mStrokeWidth = MIN_WG_STROKE_WIDTH;
|
||||
float mMiterLimit = 4.f;
|
||||
StrokeCap mStrokeCap = StrokeCap::Square;
|
||||
StrokeJoin mStrokeJoin = StrokeJoin::Bevel;
|
||||
State mStrokeState = {};
|
||||
Point mLeftTop = {0.0f, 0.0f};
|
||||
Point mRightBottom = {0.0f, 0.0f};
|
||||
};
|
||||
|
||||
class WgBWTessellator
|
||||
{
|
||||
public:
|
||||
WgBWTessellator(WgMeshData* buffer);
|
||||
void tessellate(const RenderPath& path, const Matrix& matrix);
|
||||
RenderRegion bounds() const;
|
||||
BBox getBBox() const;
|
||||
private:
|
||||
uint32_t pushVertex(float x, float y);
|
||||
void pushTriangle(uint32_t a, uint32_t b, uint32_t c);
|
||||
|
||||
WgMeshData* mBuffer;
|
||||
BBox bbox = {};
|
||||
};
|
||||
|
||||
#endif /* _TVG_WG_TESSELLATOR_H_ */
|
Loading…
Add table
Reference in a new issue