diff --git a/inc/thorvg.h b/inc/thorvg.h index 04009df8..0c575296 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -230,6 +230,7 @@ public: //Stroke Result stroke(float width) noexcept; Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + Result stroke(std::unique_ptr f) noexcept; Result stroke(const float* dashPattern, uint32_t cnt) noexcept; Result stroke(StrokeCap cap) noexcept; Result stroke(StrokeJoin join) noexcept; @@ -248,6 +249,7 @@ public: float strokeWidth() const noexcept; Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; + const Fill* strokeFill() const noexcept; uint32_t strokeDash(const float** dashPattern) const noexcept; StrokeCap strokeCap() const noexcept; StrokeJoin strokeJoin() const noexcept; diff --git a/inc/thorvg_capi.h b/inc/thorvg_capi.h index 4c98d946..25ccbdb2 100644 --- a/inc/thorvg_capi.h +++ b/inc/thorvg_capi.h @@ -829,6 +829,10 @@ TVG_EXPORT Tvg_Result tvg_shape_set_stroke_color(Tvg_Paint* paint, uint8_t r, ui */ TVG_EXPORT Tvg_Result tvg_shape_get_stroke_color(const Tvg_Paint* paint, uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_linear_gradient(Tvg_Paint* paint, Tvg_Gradient* grad); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_radial_gradient(Tvg_Paint* paint, Tvg_Gradient* grad); +TVG_EXPORT Tvg_Result tvg_shape_get_stroke_gradient(const Tvg_Paint* paint, Tvg_Gradient** grad); + /*! * \fn TVG_EXPORT Tvg_Result tvg_shape_set_stroke_dash(Tvg_Paint* paint, const float* dashPattern, uint32_t cnt) diff --git a/src/bindings/capi/tvgCapi.cpp b/src/bindings/capi/tvgCapi.cpp index ff80aedb..410a08f2 100644 --- a/src/bindings/capi/tvgCapi.cpp +++ b/src/bindings/capi/tvgCapi.cpp @@ -313,6 +313,28 @@ TVG_EXPORT Tvg_Result tvg_shape_get_stroke_color(const Tvg_Paint* paint, uint8_t } +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_linear_gradient(Tvg_Paint* paint, Tvg_Gradient* gradient) +{ + if (!paint) return TVG_RESULT_INVALID_ARGUMENT; + return (Tvg_Result) reinterpret_cast(paint)->stroke(unique_ptr((LinearGradient*)(gradient))); +} + + +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_radial_gradient(Tvg_Paint* paint, Tvg_Gradient* gradient) +{ + if (!paint) return TVG_RESULT_INVALID_ARGUMENT; + return (Tvg_Result) reinterpret_cast(paint)->stroke(unique_ptr((RadialGradient*)(gradient))); +} + + +TVG_EXPORT Tvg_Result tvg_shape_stroke_get_gradient(const Tvg_Paint* paint, Tvg_Gradient** gradient) +{ + if (!paint || !gradient) return TVG_RESULT_INVALID_ARGUMENT; + *gradient = (Tvg_Gradient*)(reinterpret_cast(CCP(paint))->strokeFill()); + return TVG_RESULT_SUCCESS; +} + + TVG_EXPORT Tvg_Result tvg_shape_set_stroke_dash(Tvg_Paint* paint, const float* dashPattern, uint32_t cnt) { if (!paint) return TVG_RESULT_INVALID_ARGUMENT; diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index 24450db8..7ecb4021 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -127,6 +127,32 @@ struct SwBBox SwPoint min, max; }; +struct SwFill +{ + struct SwLinear { + float dx, dy; + float len; + float offset; + }; + + struct SwRadial { + float cx, cy; + float a; + float inv2a; + }; + + union { + SwLinear linear; + SwRadial radial; + }; + + uint32_t* ctable; + FillSpread spread; + float sx, sy; + + bool translucent; +}; + struct SwStrokeBorder { uint32_t ptsCnt; @@ -152,6 +178,7 @@ struct SwStroke StrokeCap cap; StrokeJoin join; StrokeJoin joinSaved; + SwFill* fill = nullptr; SwStrokeBorder borders[2]; @@ -174,32 +201,6 @@ struct SwDashStroke bool curOpGap; }; -struct SwFill -{ - struct SwLinear { - float dx, dy; - float len; - float offset; - }; - - struct SwRadial { - float cx, cy; - float a; - float inv2a; - }; - - union { - SwLinear linear; - SwRadial radial; - }; - - uint32_t* ctable; - FillSpread spread; - float sx, sy; - - bool translucent; -}; - struct SwShape { SwOutline* outline = nullptr; @@ -297,8 +298,11 @@ bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, unsigned tid, const M void shapeFree(SwShape* shape); void shapeDelStroke(SwShape* shape); bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable); +bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable); void shapeResetFill(SwShape* shape); +void shapeResetStrokeFill(SwShape* shape); void shapeDelFill(SwShape* shape); +void shapeDelStrokeFill(SwShape* shape); void strokeReset(SwStroke* stroke, const Shape* shape, const Matrix* transform); bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline); @@ -339,6 +343,7 @@ bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id); bool rasterSolidShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity); bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id); bool rasterClear(SwSurface* surface); static inline void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) diff --git a/src/lib/sw_engine/tvgSwRaster.cpp b/src/lib/sw_engine/tvgSwRaster.cpp index e9ed5812..2262a787 100644 --- a/src/lib/sw_engine/tvgSwRaster.cpp +++ b/src/lib/sw_engine/tvgSwRaster.cpp @@ -817,6 +817,15 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint } +bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id) +{ + if (id == FILL_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill); + return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill); + + return false; +} + + bool rasterClear(SwSurface* surface) { if (!surface || !surface->buffer || surface->stride <= 0 || surface->w <= 0 || surface->h <= 0) return false; diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 470f1a4d..94e8bc3e 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -76,6 +76,7 @@ struct SwShapeTask : SwTask if (HALF_STROKE(strokeWidth) > 0) { sdata->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha); } + bool validStroke = (strokeAlpha > 0) || sdata->strokeFill(); SwSize clip = {static_cast(surface->w), static_cast(surface->h)}; @@ -89,7 +90,7 @@ struct SwShapeTask : SwTask sdata->fillColor(nullptr, nullptr, nullptr, &alpha); alpha = static_cast(static_cast(alpha) * opacity / 255); bool renderShape = (alpha > 0 || sdata->fill()); - if (renderShape || strokeAlpha) { + if (renderShape || validStroke) { shapeReset(&shape); if (!shapePrepare(&shape, sdata, tid, clip, transform, bbox)) goto err; if (renderShape) { @@ -118,10 +119,18 @@ struct SwShapeTask : SwTask //Stroke if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { - if (strokeAlpha > 0) { + if (validStroke) { shapeResetStroke(&shape, sdata, transform); if (!shapeGenStrokeRle(&shape, sdata, tid, transform, clip, bbox)) goto err; ++addStroking; + + if (auto fill = sdata->strokeFill()) { + auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; + if (ctable) shapeResetStrokeFill(&shape); + if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err; + } else { + shapeDelStrokeFill(&shape); + } } else { shapeDelStroke(&shape); } @@ -324,6 +333,9 @@ bool SwRenderer::renderShape(RenderData data) if (a > 0) rasterSolidShape(surface, &task->shape, r, g, b, a); } + if (auto strokeFill = task->sdata->strokeFill()) { + rasterGradientStroke(surface, &task->shape, strokeFill->id()); + } else { if (task->sdata->strokeColor(&r, &g, &b, &a) == Result::Success) { a = static_cast((opacity * (uint32_t) a) / 255); if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a); diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index 6a2b4f55..02284ff1 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -650,6 +650,12 @@ bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transfor } +bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable) +{ + return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable); +} + + void shapeResetFill(SwShape* shape) { if (!shape->fill) { @@ -660,9 +666,28 @@ void shapeResetFill(SwShape* shape) } +void shapeResetStrokeFill(SwShape* shape) +{ + if (!shape->stroke->fill) { + shape->stroke->fill = static_cast(calloc(1, sizeof(SwFill))); + if (!shape->stroke->fill) return; + } + fillReset(shape->stroke->fill); +} + + void shapeDelFill(SwShape* shape) { if (!shape->fill) return; fillFree(shape->fill); shape->fill = nullptr; } + + +void shapeDelStrokeFill(SwShape* shape) +{ + if (!shape->stroke->fill) return; + fillFree(shape->stroke->fill); + shape->stroke->fill = nullptr; +} + diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index 4f461854..a93ebb64 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -23,7 +23,6 @@ #include #include "tvgSwCommon.h" - /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ @@ -821,6 +820,9 @@ void strokeFree(SwStroke* stroke) if (stroke->borders[1].pts) free(stroke->borders[1].pts); if (stroke->borders[1].tags) free(stroke->borders[1].tags); + fillFree(stroke->fill); + stroke->fill = nullptr; + free(stroke); } diff --git a/src/lib/tvgRender.h b/src/lib/tvgRender.h index 009e56a0..7815e216 100644 --- a/src/lib/tvgRender.h +++ b/src/lib/tvgRender.h @@ -44,7 +44,7 @@ struct Compositor { uint32_t opacity; }; -enum RenderUpdateFlag {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, All = 63}; +enum RenderUpdateFlag {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, All = 127}; struct RenderTransform { diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index 2ee50ea0..d5d6c835 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -335,6 +335,20 @@ Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const } +Result Shape::stroke(unique_ptr f) noexcept +{ + return pImpl->strokeFill(move(f)); +} + + +const Fill* Shape::strokeFill() const noexcept +{ + if (!pImpl->stroke) return nullptr; + + return pImpl->stroke->fill; +} + + Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept { if (cnt < 2 || !dashPattern) return Result::InvalidArguments; diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index 67aba9c5..2281ea0b 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -33,6 +33,7 @@ struct ShapeStroke { float width = 0; uint8_t color[4] = {0, 0, 0, 0}; + Fill *fill = nullptr; float* dashPattern = nullptr; uint32_t dashCnt = 0; StrokeCap cap = StrokeCap::Square; @@ -49,11 +50,14 @@ struct ShapeStroke memcpy(color, src->color, sizeof(color)); dashPattern = static_cast(malloc(sizeof(float) * dashCnt)); memcpy(dashPattern, src->dashPattern, sizeof(float) * dashCnt); + if (src->fill) + fill = src->fill->duplicate(); } ~ShapeStroke() { if (dashPattern) free(dashPattern); + if (fill) delete(fill); } }; @@ -291,6 +295,12 @@ struct Shape::Impl if (!stroke) stroke = new ShapeStroke(); if (!stroke) return false; + if (stroke->fill) { + delete(stroke->fill); + stroke->fill = nullptr; + flag |= RenderUpdateFlag::GradientStroke; + } + stroke->color[0] = r; stroke->color[1] = g; stroke->color[2] = b; @@ -301,6 +311,23 @@ struct Shape::Impl return true; } + Result strokeFill(unique_ptr f) + { + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + if (!stroke) stroke = new ShapeStroke(); + if (!stroke) return Result::FailedAllocation; + + if (stroke->fill && stroke->fill != p) delete(stroke->fill); + stroke->fill = p; + + flag |= RenderUpdateFlag::Stroke; + flag |= RenderUpdateFlag::GradientStroke; + + return Result::Success; + } + bool strokeDash(const float* pattern, uint32_t cnt) { if (!stroke) stroke = new ShapeStroke(); @@ -363,6 +390,9 @@ struct Shape::Impl if (stroke) { dup->stroke = new ShapeStroke(stroke); dup->flag |= RenderUpdateFlag::Stroke; + + if (stroke->fill) + dup->flag |= RenderUpdateFlag::GradientStroke; } //Fill