Compare commits

...

10 commits

Author SHA1 Message Date
Hermet Park
7fc8e616da sw_engine: support hue, color, saturation, luminosity, hardmix blends
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
- Hue: Creates a result color with the luminance and saturation
  of the base color and the hue of the blend color.

- Color: Creates a result color with the luminance of the base color
  and the hue and saturation of the blend color. This preserves the gray
  levels in the image and is useful for coloring monochrome images and
  for tinting color images.

- Luminosity: reates a result color with the hue and saturation of the base color
  and the luminance of the blend color. This mode creates the inverse effect of
  Color mode.

- Saturation: Creates a result color with the luminance and hue of
  the base color and the saturation of the blend color. Painting with this mode
  in an area with no (0) saturation (gray) causes no change.

- HardMix: Adds the bottom & top. If the resulting sum for a channel is 255 or
  greater, it receives a value of 255; if less than 255, a value of 0.

issue: https://github.com/thorvg/thorvg/issues/2701
2025-07-23 18:47:32 +09:00
Sergii Liebodkin
bada5691bc wg_engine: tessellator optimization
1. new tesseletor and stroker are used: less vertexes generated
In general, the previous implementation was based on the path-outline-mesh approach.
It has now been changed to a path-mesh approach, so we skip the path-outline transformation.
For shape fills, a BW-tesselator now used, and all submeshes (moveTo) are stored in a single buffer.
For strokes, all intermediate operations such as trimming and dash use path-path logic instead of outline-outline logic.
In addition, the new stroker generates fewer polygons for joints, especially for Rounds
2. render all sub-shapes by single draw call

https://github.com/thorvg/thorvg/issues/3557
https://github.com/thorvg/thorvg/issues/3288
https://github.com/thorvg/thorvg/issues/3273
2025-07-23 15:38:36 +09:00
Hermet Park
16604a873a lottie: code cleanup++
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
just renamed internal variable.
2025-07-21 18:23:03 +09:00
Hermet Park
c3d1e6e519 common: consolidate color-related functions
- Introduced RGB, RGBA, and HSL structures
- Migrated hsl2Rgb() from SVG loader to common module

This is a refactoring for upcoming hsl color functionlaities.
2025-07-21 18:23:03 +09:00
Hermet Park
930de44359 api: Add Composition blend mode
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
The composition mode enables intermediate blending. When a scene
 uses Composition mode, it generates an intermediate buffer to
render the scene image first. This image can then be blended
with the canvas afterward.

Please avoid to use this, unless you really need to composite
the precomposite scene. This feature is relatively expensive
at performance.

C++ API:
+BlendMethod::Composition

C API:
+Tvg_Blend_Method::TVG_BLEND_METHOD_COMPOSITION
2025-07-21 11:23:07 +09:00
Sungun No
3ca2c0edfd gl_engine: Fix GlRenderTarget reset function
- Avoid deleting the framebuffer when mFbo == GL_INVALID_VALUE, as this is not a valid framebuffer object.
2025-07-21 11:22:07 +09:00
Hermet Park
a79f9788c1 common: code cleanup++
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
- Use RenderPath common interfaces instead of
  direct array manipulations.

- Replace multiple scalar operations with Point utility
  operations where applicable.
2025-07-18 23:06:26 +09:00
Sergii Liebodkin
d85952b252 renderer: refactored to share gl stroke dasher among engines
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
Move stroke dasher to the common code space to create an abillity
to use it on the cross API renderers (wg and gl). Stroke dasher is
a path-to-path operation, same as path trim, so can be placed to
the common space.

Now RenderShape can generate dashed path by itself. Dashing mechanics
fully hiden from the user but can be used in gl and wg renderers.

issue: https://github.com/thorvg/thorvg/issues/3557

Co-Authored-By: Hermet Park <hermet@lottiefiles.com>
2025-07-18 15:21:33 +09:00
Mira Grudzinska
d8bbb0df31 svg: fix nested use nodes
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
Previously, only single-level <use> references were supported.
If a <use> node pointed to another element that itself contained
a <use> node, the reference wasn’t resolved.
This has been fixed by replacing the array of postponed elements
with a list. The list is traversed, and nodes aren’t cloned while
they or any of their children remain unresolved. In such cases,
the target element also gets added to the list, enabling recursive
resolution of nested href references.

issue: https://github.com/thorvg/thorvg/issues/3615
2025-07-18 11:10:51 +09:00
Sergii Liebodkin
d44098180c gl_engine: remove level parameter from blur
level parameter removed from blur and dropshadows shaders.
data structures alligment keeped
2025-07-18 10:51:02 +09:00
50 changed files with 1726 additions and 1840 deletions

View file

@ -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);

View file

@ -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
};

View file

@ -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;

View file

@ -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
View 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
View 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_

View file

@ -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};

View file

@ -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: {

View file

@ -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)};
}

View file

@ -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);
}

View file

@ -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; }

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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();

View file

@ -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);

View file

@ -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_

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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_

View file

@ -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;
}

View file

@ -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;

View file

@ -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];

View file

@ -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();
}

View file

@ -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));

View file

@ -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;

View file

@ -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)
{
}

View file

@ -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:

View file

@ -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);

View file

@ -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
}
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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
}

View file

@ -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

View file

@ -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;
}

View file

@ -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();

View file

@ -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 = []

View file

@ -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) {

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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_

View file

@ -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);
}

View file

@ -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){};

View file

@ -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;
}

View file

@ -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_ */

View 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);
}

View 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_ */