feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

View file

@ -0,0 +1,576 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SW_COMMON_H_
#define _TVG_SW_COMMON_H_
#include <algorithm>
#include "tvgCommon.h"
#include "tvgRender.h"
#define SW_CURVE_TYPE_POINT 0
#define SW_CURVE_TYPE_CUBIC 1
#define SW_ANGLE_PI (180L << 16)
#define SW_ANGLE_2PI (SW_ANGLE_PI << 1)
#define SW_ANGLE_PI2 (SW_ANGLE_PI >> 1)
using SwCoord = signed long;
using SwFixed = signed long long;
static inline float TO_FLOAT(SwCoord val)
{
return static_cast<float>(val) / 64.0f;
}
struct SwPoint
{
SwCoord x, y;
SwPoint& operator+=(const SwPoint& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
SwPoint operator+(const SwPoint& rhs) const
{
return {x + rhs.x, y + rhs.y};
}
SwPoint operator-(const SwPoint& rhs) const
{
return {x - rhs.x, y - rhs.y};
}
bool operator==(const SwPoint& rhs) const
{
return (x == rhs.x && y == rhs.y);
}
bool operator!=(const SwPoint& rhs) const
{
return (x != rhs.x || y != rhs.y);
}
bool zero() const
{
if (x == 0 && y == 0) return true;
else return false;
}
bool small() const
{
//2 is epsilon...
if (abs(x) < 2 && abs(y) < 2) return true;
else return false;
}
Point toPoint() const
{
return {TO_FLOAT(x), TO_FLOAT(y)};
}
};
struct SwSize
{
SwCoord w, h;
};
struct SwOutline
{
Array<SwPoint> pts; //the outline's points
Array<uint32_t> cntrs; //the contour end points
Array<uint8_t> types; //curve type
Array<bool> closed; //opened or closed path?
FillRule fillRule;
};
struct SwSpan
{
uint16_t x, y;
uint16_t len;
uint8_t coverage;
};
struct SwRleData
{
SwSpan *spans;
uint32_t alloc;
uint32_t size;
};
struct SwBBox
{
SwPoint min, max;
void reset()
{
min.x = min.y = max.x = max.y = 0;
}
};
struct SwFill
{
struct SwLinear {
float dx, dy;
float len;
float offset;
};
struct SwRadial {
float a11, a12, a13;
float a21, a22, a23;
float fx, fy, fr;
float dx, dy, dr;
float invA, a;
};
union {
SwLinear linear;
SwRadial radial;
};
uint32_t* ctable;
FillSpread spread;
bool translucent;
};
struct SwStrokeBorder
{
uint32_t ptsCnt;
uint32_t maxPts;
SwPoint* pts;
uint8_t* tags;
int32_t start; //index of current sub-path start point
bool movable; //true: for ends of lineto borders
};
struct SwStroke
{
SwFixed angleIn;
SwFixed angleOut;
SwPoint center;
SwFixed lineLength;
SwFixed subPathAngle;
SwPoint ptStartSubPath;
SwFixed subPathLineLength;
SwFixed width;
SwFixed miterlimit;
StrokeCap cap;
StrokeJoin join;
StrokeJoin joinSaved;
SwFill* fill = nullptr;
SwStrokeBorder borders[2];
float sx, sy;
bool firstPt;
bool closedSubPath;
bool handleWideStrokes;
};
struct SwDashStroke
{
SwOutline* outline = nullptr;
float curLen = 0;
int32_t curIdx = 0;
Point ptStart = {0, 0};
Point ptCur = {0, 0};
float* pattern = nullptr;
uint32_t cnt = 0;
bool curOpGap = false;
bool move = true;
};
struct SwShape
{
SwOutline* outline = nullptr;
SwStroke* stroke = nullptr;
SwFill* fill = nullptr;
SwRleData* rle = nullptr;
SwRleData* strokeRle = nullptr;
SwBBox bbox; //Keep it boundary without stroke region. Using for optimal filling.
bool fastTrack = false; //Fast Track: axis-aligned rectangle without any clips?
};
struct SwImage
{
SwOutline* outline = nullptr;
SwRleData* rle = nullptr;
union {
pixel_t* data; //system based data pointer
uint32_t* buf32; //for explicit 32bits channels
uint8_t* buf8; //for explicit 8bits grayscale
};
uint32_t w, h, stride;
int32_t ox = 0; //offset x
int32_t oy = 0; //offset y
float scale;
uint8_t channelSize;
bool direct = false; //draw image directly (with offset)
bool scaled = false; //draw scaled image
};
typedef uint8_t(*SwMask)(uint8_t s, uint8_t d, uint8_t a); //src, dst, alpha
typedef uint32_t(*SwBlender)(uint32_t s, uint32_t d, uint8_t a); //src, dst, alpha
typedef uint32_t(*SwJoin)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); //color channel join
typedef uint8_t(*SwAlpha)(uint8_t*); //blending alpha
struct SwCompositor;
struct SwSurface : Surface
{
SwJoin join;
SwAlpha alphas[4]; //Alpha:2, InvAlpha:3, Luma:4, InvLuma:5
SwBlender blender = nullptr; //blender (optional)
SwCompositor* compositor = nullptr; //compositor (optional)
BlendMethod blendMethod; //blending method (uint8_t)
SwAlpha alpha(CompositeMethod method)
{
auto idx = (int)(method) - 2; //0: None, 1: ClipPath
return alphas[idx > 3 ? 0 : idx]; //CompositeMethod has only four Matting methods.
}
SwSurface()
{
}
SwSurface(const SwSurface* rhs) : Surface(rhs)
{
join = rhs->join;
memcpy(alphas, rhs->alphas, sizeof(alphas));
blender = rhs->blender;
compositor = rhs->compositor;
blendMethod = rhs->blendMethod;
}
};
struct SwCompositor : Compositor
{
SwSurface* recoverSfc; //Recover surface when composition is started
SwCompositor* recoverCmp; //Recover compositor when composition is done
SwImage image;
SwBBox bbox;
bool valid;
};
struct SwMpool
{
SwOutline* outline;
SwOutline* strokeOutline;
SwOutline* dashOutline;
unsigned allocSize;
};
static inline SwCoord TO_SWCOORD(float val)
{
return SwCoord(val * 64.0f);
}
static inline uint32_t JOIN(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)
{
return (c0 << 24 | c1 << 16 | c2 << 8 | c3);
}
static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a)
{
return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) +
((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff));
}
static inline uint32_t INTERPOLATE(uint32_t s, uint32_t d, uint8_t a)
{
return (((((((s >> 8) & 0xff00ff) - ((d >> 8) & 0xff00ff)) * a) + (d & 0xff00ff00)) & 0xff00ff00) + ((((((s & 0xff00ff) - (d & 0xff00ff)) * a) >> 8) + (d & 0xff00ff)) & 0xff00ff));
}
static inline uint8_t INTERPOLATE8(uint8_t s, uint8_t d, uint8_t a)
{
return (((s) * (a) + 0xff) >> 8) + (((d) * ~(a) + 0xff) >> 8);
}
static inline SwCoord HALF_STROKE(float width)
{
return TO_SWCOORD(width * 0.5f);
}
static inline uint8_t A(uint32_t c)
{
return ((c) >> 24);
}
static inline uint8_t IA(uint32_t c)
{
return (~(c) >> 24);
}
static inline uint8_t C1(uint32_t c)
{
return ((c) >> 16);
}
static inline uint8_t C2(uint32_t c)
{
return ((c) >> 8);
}
static inline uint8_t C3(uint32_t c)
{
return (c);
}
static inline uint32_t opBlendInterp(uint32_t s, uint32_t d, uint8_t a)
{
return INTERPOLATE(s, d, a);
}
static inline uint32_t opBlendNormal(uint32_t s, uint32_t d, uint8_t a)
{
auto t = ALPHA_BLEND(s, a);
return t + ALPHA_BLEND(d, IA(t));
}
static inline uint32_t opBlendPreNormal(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
return s + ALPHA_BLEND(d, IA(s));
}
static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNUSED uint8_t a)
{
return s;
}
//TODO: BlendMethod could remove the alpha parameter.
static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
//if (s > d) => s - d
//else => d - s
auto c1 = (C1(s) > C1(d)) ? (C1(s) - C1(d)) : (C1(d) - C1(s));
auto c2 = (C2(s) > C2(d)) ? (C2(s) - C2(d)) : (C2(d) - C2(s));
auto c3 = (C3(s) > C3(d)) ? (C3(s) - C3(d)) : (C3(d) - C3(s));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendExclusion(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
//A + B - 2AB
auto c1 = std::min(255, C1(s) + C1(d) - std::min(255, (C1(s) * C1(d)) << 1));
auto c2 = std::min(255, C2(s) + C2(d) - std::min(255, (C2(s) * C2(d)) << 1));
auto c3 = std::min(255, C3(s) + C3(d) - std::min(255, (C3(s) * C3(d)) << 1));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendAdd(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s + d
auto c1 = std::min(C1(s) + C1(d), 255);
auto c2 = std::min(C2(s) + C2(d), 255);
auto c3 = std::min(C3(s) + C3(d), 255);
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s + d - s * d
auto c1 = C1(s) + C1(d) - MULTIPLY(C1(s), C1(d));
auto c2 = C2(s) + C2(d) - MULTIPLY(C2(s), C2(d));
auto c3 = C3(s) + C3(d) - MULTIPLY(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s * d
auto c1 = MULTIPLY(C1(s), C1(d));
auto c2 = MULTIPLY(C2(s), C2(d));
auto c3 = MULTIPLY(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// if (2 * d < da) => 2 * s * d,
// else => 1 - 2 * (1 - s) * (1 - d)
auto c1 = (C1(d) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d))));
auto c2 = (C2(d) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d))));
auto c3 = (C3(d) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d))));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendDarken(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// min(s, d)
auto c1 = std::min(C1(s), C1(d));
auto c2 = std::min(C2(s), C2(d));
auto c3 = std::min(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendLighten(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// max(s, d)
auto c1 = std::max(C1(s), C1(d));
auto c2 = std::max(C2(s), C2(d));
auto c3 = std::max(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendColorDodge(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// d / (1 - s)
auto is = 0xffffffff - s;
auto c1 = (C1(is) > 0) ? (C1(d) / C1(is)) : C1(d);
auto c2 = (C2(is) > 0) ? (C2(d) / C2(is)) : C2(d);
auto c3 = (C3(is) > 0) ? (C3(d) / C3(is)) : C3(d);
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendColorBurn(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// 1 - (1 - d) / s
auto id = 0xffffffff - d;
auto c1 = 255 - ((C1(s) > 0) ? (C1(id) / C1(s)) : C1(id));
auto c2 = 255 - ((C2(s) > 0) ? (C2(id) / C2(s)) : C2(id));
auto c3 = 255 - ((C3(s) > 0) ? (C3(id) / C3(s)) : C3(id));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
auto c1 = (C1(s) < 128) ? std::min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d))));
auto c2 = (C2(s) < 128) ? std::min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d))));
auto c3 = (C3(s) < 128) ? std::min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - std::min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d))));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
//(255 - 2 * s) * (d * d) + (2 * s * b)
auto c1 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + 2 * MULTIPLY(C1(s), C1(d)));
auto c2 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + 2 * MULTIPLY(C2(s), C2(d)));
auto c3 = std::min(255, MULTIPLY(255 - std::min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + 2 * MULTIPLY(C3(s), C3(d)));
return JOIN(255, c1, c2, c3);
}
int64_t mathMultiply(int64_t a, int64_t b);
int64_t mathDivide(int64_t a, int64_t b);
int64_t mathMulDiv(int64_t a, int64_t b, int64_t c);
void mathRotate(SwPoint& pt, SwFixed angle);
SwFixed mathTan(SwFixed angle);
SwFixed mathAtan(const SwPoint& pt);
SwFixed mathCos(SwFixed angle);
SwFixed mathSin(SwFixed angle);
void mathSplitCubic(SwPoint* base);
SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
SwFixed mathLength(const SwPoint& pt);
bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
SwFixed mathMean(SwFixed angle1, SwFixed angle2);
SwPoint mathTransform(const Point* to, const Matrix* transform);
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee);
void shapeReset(SwShape* shape);
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
bool shapePrepared(const SwShape* shape);
bool shapeGenRle(SwShape* shape, const RenderShape* rshape, bool antiAlias);
void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid);
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform);
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
void shapeFree(SwShape* shape);
void shapeDelStroke(SwShape* shape);
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
void shapeResetFill(SwShape* shape);
void shapeResetStrokeFill(SwShape* shape);
void shapeDelFill(SwShape* shape);
void shapeDelStrokeFill(SwShape* shape);
void strokeReset(SwStroke* stroke, const RenderShape* shape, const Matrix* transform);
bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline);
SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid);
void strokeFree(SwStroke* stroke);
bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias);
void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid);
void imageReset(SwImage* image);
void imageFree(SwImage* image);
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
void fillReset(SwFill* fill);
void fillFree(SwFill* fill);
//OPTIMIZE_ME: Skip the function pointer access
void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t opacity); //composite masking ver.
void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t opacity); //direct masking ver.
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver.
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver.
void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask op, uint8_t a); //composite masking ver.
void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask op, uint8_t a) ; //direct masking ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver.
SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRleData* rleRender(const SwBBox* bbox);
void rleFree(SwRleData* rle);
void rleReset(SwRleData* rle);
void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2);
void rleClipPath(SwRleData* rle, const SwRleData* clip);
void rleClipRect(SwRleData* rle, const SwBBox* clip);
SwMpool* mpoolInit(uint32_t threads);
bool mpoolTerm(SwMpool* mpool);
bool mpoolClear(SwMpool* mpool);
SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx);
void mpoolRetOutline(SwMpool* mpool, unsigned idx);
SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx);
void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx);
SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx);
void mpoolRetDashOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface);
bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id);
bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity);
bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
void rasterUnpremultiply(Surface* surface);
void rasterPremultiply(Surface* surface);
bool rasterConvertCS(Surface* surface, ColorSpace to);
#endif /* _TVG_SW_COMMON_H_ */

View file

@ -0,0 +1,858 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgMath.h"
#include "tvgSwCommon.h"
#include "tvgFill.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
#define RADIAL_A_THRESHOLD 0.0005f
#define GRADIENT_STOP_SIZE 1024
#define FIXPT_BITS 8
#define FIXPT_SIZE (1<<FIXPT_BITS)
/*
* quadratic equation with the following coefficients (rx and ry defined in the _calculateCoefficients()):
* A = a // fill->radial.a
* B = 2 * (dr * fr + rx * dx + ry * dy)
* C = fr^2 - rx^2 - ry^2
* Derivatives are computed with respect to dx.
* This procedure aims to optimize and eliminate the need to calculate all values from the beginning
* for consecutive x values with a constant y. The Taylor series expansions are computed as long as
* its terms are non-zero.
*/
static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, float& b, float& deltaB, float& det, float& deltaDet, float& deltaDeltaDet)
{
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
b = (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy) * radial->invA;
deltaB = (radial->a11 * radial->dx + radial->a21 * radial->dy) * radial->invA;
auto rr = rx * rx + ry * ry;
auto deltaRr = 2.0f * (rx * radial->a11 + ry * radial->a21) * radial->invA;
auto deltaDeltaRr = 2.0f * (radial->a11 * radial->a11 + radial->a21 * radial->a21) * radial->invA;
det = b * b + (rr - radial->fr * radial->fr) * radial->invA;
deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr;
deltaDeltaDet = 2.0f * deltaB * deltaB + deltaDeltaRr;
}
static uint32_t _estimateAAMargin(const Fill* fdata)
{
constexpr float marginScalingFactor = 800.0f;
if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
auto radius = P(static_cast<const RadialGradient*>(fdata))->r;
return mathZero(radius) ? 0 : static_cast<uint32_t>(marginScalingFactor / radius);
}
auto grad = P(static_cast<const LinearGradient*>(fdata));
Point p1 {grad->x1, grad->y1};
Point p2 {grad->x2, grad->y2};
auto length = mathLength(&p1, &p2);
return mathZero(length) ? 0 : static_cast<uint32_t>(marginScalingFactor / length);
}
static void _adjustAAMargin(uint32_t& iMargin, uint32_t index)
{
constexpr float threshold = 0.1f;
constexpr uint32_t iMarginMax = 40;
auto iThreshold = static_cast<uint32_t>(index * threshold);
if (iMargin > iThreshold) iMargin = iThreshold;
if (iMargin > iMarginMax) iMargin = iMarginMax;
}
static inline uint32_t _alphaUnblend(uint32_t c)
{
auto a = (c >> 24);
if (a == 255 || a == 0) return c;
auto invA = 255.0f / static_cast<float>(a);
auto c0 = static_cast<uint8_t>(static_cast<float>((c >> 16) & 0xFF) * invA);
auto c1 = static_cast<uint8_t>(static_cast<float>((c >> 8) & 0xFF) * invA);
auto c2 = static_cast<uint8_t>(static_cast<float>(c & 0xFF) * invA);
return (a << 24) | (c0 << 16) | (c1 << 8) | c2;
}
static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end)
{
if (begin == 0 || end == 0) return;
auto i = GRADIENT_STOP_SIZE - end;
auto rgbaEnd = _alphaUnblend(fill->ctable[i]);
auto rgbaBegin = _alphaUnblend(fill->ctable[begin]);
auto dt = 1.0f / (begin + end + 1.0f);
float t = dt;
while (i != begin) {
auto dist = 255 - static_cast<int32_t>(255 * t);
auto color = INTERPOLATE(rgbaEnd, rgbaBegin, dist);
fill->ctable[i++] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
if (i == GRADIENT_STOP_SIZE) i = 0;
t += dt;
}
}
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
{
if (!fill->ctable) {
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
if (!fill->ctable) return false;
}
const Fill::ColorStop* colors;
auto cnt = fdata->colorStops(&colors);
if (cnt == 0 || !colors) return false;
auto pColors = colors;
auto a = MULTIPLY(pColors->a, opacity);
if (a < 255) fill->translucent = true;
auto r = pColors->r;
auto g = pColors->g;
auto b = pColors->b;
auto rgba = surface->join(r, g, b, a);
auto inc = 1.0f / static_cast<float>(GRADIENT_STOP_SIZE);
auto pos = 1.5f * inc;
uint32_t i = 0;
//If repeat is true, anti-aliasing must be applied between the last and the first colors.
auto repeat = fill->spread == FillSpread::Repeat;
uint32_t iAABegin = repeat ? _estimateAAMargin(fdata) : 0;
uint32_t iAAEnd = 0;
fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a);
while (pos <= pColors->offset) {
fill->ctable[i] = fill->ctable[i - 1];
++i;
pos += inc;
}
for (uint32_t j = 0; j < cnt - 1; ++j) {
if (repeat && j == cnt - 2 && iAAEnd == 0) {
iAAEnd = iAABegin;
_adjustAAMargin(iAAEnd, GRADIENT_STOP_SIZE - i);
}
auto curr = colors + j;
auto next = curr + 1;
auto delta = 1.0f / (next->offset - curr->offset);
auto a2 = MULTIPLY(next->a, opacity);
if (!fill->translucent && a2 < 255) fill->translucent = true;
auto rgba2 = surface->join(next->r, next->g, next->b, a2);
while (pos < next->offset && i < GRADIENT_STOP_SIZE) {
auto t = (pos - curr->offset) * delta;
auto dist = static_cast<int32_t>(255 * t);
auto dist2 = 255 - dist;
auto color = INTERPOLATE(rgba, rgba2, dist2);
fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
++i;
pos += inc;
}
rgba = rgba2;
a = a2;
if (repeat && j == 0) _adjustAAMargin(iAABegin, i - 1);
}
rgba = ALPHA_BLEND((rgba | 0xff000000), a);
for (; i < GRADIENT_STOP_SIZE; ++i)
fill->ctable[i] = rgba;
//For repeat fill spread apply anti-aliasing between the last and first colors,
//othewise make sure the last color stop is represented at the end of the table.
if (repeat) _applyAA(fill, iAABegin, iAAEnd);
else fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
return true;
}
bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform)
{
float x1, x2, y1, y2;
if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false;
fill->linear.dx = x2 - x1;
fill->linear.dy = y2 - y1;
fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
if (fill->linear.len < FLOAT_EPSILON) return true;
fill->linear.dx /= fill->linear.len;
fill->linear.dy /= fill->linear.len;
fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;
auto gradTransform = linear->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
if (isTransformation) {
if (transform) gradTransform = *transform * gradTransform;
} else if (transform) {
gradTransform = *transform;
isTransformation = true;
}
if (isTransformation) {
Matrix invTransform;
if (!mathInverse(&gradTransform, &invTransform)) return false;
fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23;
auto dx = fill->linear.dx;
fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21;
fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22;
fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
}
return true;
}
bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform)
{
auto cx = P(radial)->cx;
auto cy = P(radial)->cy;
auto r = P(radial)->r;
auto fx = P(radial)->fx;
auto fy = P(radial)->fy;
auto fr = P(radial)->fr;
if (r < FLOAT_EPSILON) return true;
fill->radial.dr = r - fr;
fill->radial.dx = cx - fx;
fill->radial.dy = cy - fy;
fill->radial.fr = fr;
fill->radial.fx = fx;
fill->radial.fy = fy;
fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy;
//This condition fulfills the SVG 1.1 std:
//the focal point, if outside the end circle, is moved to be on the end circle
//See: the SVG 2 std requirements: https://www.w3.org/TR/SVG2/pservers.html#RadialGradientNotes
if (fill->radial.a < 0) {
auto dist = sqrtf(fill->radial.dx * fill->radial.dx + fill->radial.dy * fill->radial.dy);
fill->radial.fx = cx + r * (fx - cx) / dist;
fill->radial.fy = cy + r * (fy - cy) / dist;
fill->radial.dx = cx - fill->radial.fx;
fill->radial.dy = cy - fill->radial.fy;
// Prevent loss of precision on Apple Silicon when dr=dy and dx=0 due to FMA
// https://github.com/thorvg/thorvg/issues/2014
auto dr2 = fill->radial.dr * fill->radial.dr;
auto dx2 = fill->radial.dx * fill->radial.dx;
auto dy2 = fill->radial.dy * fill->radial.dy;
fill->radial.a = dr2 - dx2 - dy2;
}
if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a;
auto gradTransform = radial->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
if (transform) {
if (isTransformation) gradTransform = *transform * gradTransform;
else {
gradTransform = *transform;
isTransformation = true;
}
}
if (isTransformation) {
Matrix invTransform;
if (!mathInverse(&gradTransform, &invTransform)) return false;
fill->radial.a11 = invTransform.e11;
fill->radial.a12 = invTransform.e12;
fill->radial.a13 = invTransform.e13;
fill->radial.a21 = invTransform.e21;
fill->radial.a22 = invTransform.e22;
fill->radial.a23 = invTransform.e23;
} else {
fill->radial.a11 = fill->radial.a22 = 1.0f;
fill->radial.a12 = fill->radial.a13 = 0.0f;
fill->radial.a21 = fill->radial.a23 = 0.0f;
}
return true;
}
static inline uint32_t _clamp(const SwFill* fill, int32_t pos)
{
switch (fill->spread) {
case FillSpread::Pad: {
if (pos >= GRADIENT_STOP_SIZE) pos = GRADIENT_STOP_SIZE - 1;
else if (pos < 0) pos = 0;
break;
}
case FillSpread::Repeat: {
pos = pos % GRADIENT_STOP_SIZE;
if (pos < 0) pos = GRADIENT_STOP_SIZE + pos;
break;
}
case FillSpread::Reflect: {
auto limit = GRADIENT_STOP_SIZE * 2;
pos = pos % limit;
if (pos < 0) pos = limit + pos;
if (pos >= GRADIENT_STOP_SIZE) pos = (limit - pos - 1);
break;
}
}
return pos;
}
static inline uint32_t _fixedPixel(const SwFill* fill, int32_t pos)
{
int32_t i = (pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS;
return fill->ctable[_clamp(fill, i)];
}
static inline uint32_t _pixel(const SwFill* fill, float pos)
{
auto i = static_cast<int32_t>(pos * (GRADIENT_STOP_SIZE - 1) + 0.5f);
return fill->ctable[_clamp(fill, i)];
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
{
//edge case
if (fill->radial.a < RADIAL_A_THRESHOLD) {
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
if (opacity == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
*dst = opBlendNormal(_pixel(fill, x0), *dst, alpha(cmp));
rx += radial->a11;
ry += radial->a21;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
*dst = opBlendNormal(_pixel(fill, x0), *dst, MULTIPLY(opacity, alpha(cmp)));
rx += radial->a11;
ry += radial->a21;
}
}
} else {
float b, deltaB, det, deltaDet, deltaDeltaDet;
_calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet);
if (opacity == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, alpha(cmp));
det += deltaDet;
deltaDet += deltaDeltaDet;
b += deltaB;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det) - b), *dst, MULTIPLY(opacity, alpha(cmp)));
det += deltaDet;
deltaDet += deltaDeltaDet;
b += deltaB;
}
}
}
}
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
{
if (fill->radial.a < RADIAL_A_THRESHOLD) {
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
*dst = op(_pixel(fill, x0), *dst, a);
rx += radial->a11;
ry += radial->a21;
}
} else {
float b, deltaB, det, deltaDet, deltaDeltaDet;
_calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet);
for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = op(_pixel(fill, sqrtf(det) - b), *dst, a);
det += deltaDet;
deltaDet += deltaDeltaDet;
b += deltaB;
}
}
}
void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t a)
{
if (fill->radial.a < RADIAL_A_THRESHOLD) {
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
auto src = MULTIPLY(a, A(_pixel(fill, x0)));
*dst = maskOp(src, *dst, ~src);
rx += radial->a11;
ry += radial->a21;
}
} else {
float b, deltaB, det, deltaDet, deltaDeltaDet;
_calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet);
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto src = MULTIPLY(a, A(_pixel(fill, sqrtf(det) - b)));
*dst = maskOp(src, *dst, ~src);
det += deltaDet;
deltaDet += deltaDeltaDet;
b += deltaB;
}
}
}
void fillRadial(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t a)
{
if (fill->radial.a < RADIAL_A_THRESHOLD) {
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
for (uint32_t i = 0 ; i < len ; ++i, ++dst, ++cmp) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
auto src = MULTIPLY(A(A(_pixel(fill, x0))), a);
auto tmp = maskOp(src, *cmp, 0);
*dst = tmp + MULTIPLY(*dst, ~tmp);
rx += radial->a11;
ry += radial->a21;
}
} else {
float b, deltaB, det, deltaDet, deltaDeltaDet;
_calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet);
for (uint32_t i = 0 ; i < len ; ++i, ++dst, ++cmp) {
auto src = MULTIPLY(A(_pixel(fill, sqrtf(det))), a);
auto tmp = maskOp(src, *cmp, 0);
*dst = tmp + MULTIPLY(*dst, ~tmp);
deltaDet += deltaDeltaDet;
b += deltaB;
}
}
}
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
{
if (fill->radial.a < RADIAL_A_THRESHOLD) {
auto radial = &fill->radial;
auto rx = (x + 0.5f) * radial->a11 + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
auto ry = (x + 0.5f) * radial->a21 + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;
if (a == 255) {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
auto tmp = op(_pixel(fill, x0), *dst, 255);
*dst = op2(tmp, *dst, 255);
rx += radial->a11;
ry += radial->a21;
}
} else {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr) / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);
auto tmp = op(_pixel(fill, x0), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
rx += radial->a11;
ry += radial->a21;
}
}
} else {
float b, deltaB, det, deltaDet, deltaDeltaDet;
_calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet);
if (a == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255);
*dst = op2(tmp, *dst, 255);
det += deltaDet;
deltaDet += deltaDeltaDet;
b += deltaB;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto tmp = op(_pixel(fill, sqrtf(det) - b), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
det += deltaDet;
deltaDet += deltaDeltaDet;
b += deltaB;
}
}
}
}
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (opacity == 255) {
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(color, *dst, alpha(cmp));
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) {
*dst = opBlendNormal(_fixedPixel(fill, t2), *dst, alpha(cmp));
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
*dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, alpha(cmp));
++dst;
t += inc;
cmp += csize;
}
}
} else {
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(color, *dst, MULTIPLY(alpha(cmp), opacity));
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) {
*dst = opBlendNormal(_fixedPixel(fill, t2), *dst, MULTIPLY(alpha(cmp), opacity));
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
*dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, MULTIPLY(opacity, alpha(cmp)));
++dst;
t += inc;
cmp += csize;
}
}
}
}
void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, SwMask maskOp, uint8_t a)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (mathZero(inc)) {
auto src = MULTIPLY(a, A(_fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE))));
for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = maskOp(src, *dst, ~src);
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
auto src = MULTIPLY(_fixedPixel(fill, t2), a);
*dst = maskOp(src, *dst, ~src);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto src = MULTIPLY(_pixel(fill, t / GRADIENT_STOP_SIZE), a);
*dst = maskOp(src, *dst, ~src);
++dst;
t += inc;
}
}
}
void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwMask maskOp, uint8_t a)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (mathZero(inc)) {
auto src = A(_fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE)));
src = MULTIPLY(src, a);
for (uint32_t i = 0; i < len; ++i, ++dst, ++cmp) {
auto tmp = maskOp(src, *cmp, 0);
*dst = tmp + MULTIPLY(*dst, ~tmp);
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst, ++cmp) {
auto src = MULTIPLY(a, A(_fixedPixel(fill, t2)));
auto tmp = maskOp(src, *cmp, 0);
*dst = tmp + MULTIPLY(*dst, ~tmp);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto src = MULTIPLY(A(_pixel(fill, t / GRADIENT_STOP_SIZE)), a);
auto tmp = maskOp(src, *cmp, 0);
*dst = tmp + MULTIPLY(*dst, ~tmp);
++dst;
++cmp;
t += inc;
}
}
}
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = op(color, *dst, a);
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
*dst = op(_fixedPixel(fill, t2), *dst, a);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
*dst = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, a);
++dst;
t += inc;
}
}
}
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
if (a == 255) {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto tmp = op(color, *dst, a);
*dst = op2(tmp, *dst, 255);
}
} else {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto tmp = op(color, *dst, a);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
}
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
if (a == 255) {
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
auto tmp = op(_fixedPixel(fill, t2), *dst, 255);
*dst = op2(tmp, *dst, 255);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255);
*dst = op2(tmp, *dst, 255);
++dst;
t += inc;
}
}
} else {
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
auto tmp = op(_fixedPixel(fill, t2), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
++dst;
t += inc;
}
}
}
}
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
if (!fill) return false;
fill->spread = fdata->spread();
if (ctable) {
if (!_updateColorTable(fill, fdata, surface, opacity)) return false;
}
if (fdata->identifier() == TVG_CLASS_ID_LINEAR) {
return _prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform);
} else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
return _prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform);
}
//LOG: What type of gradient?!
return false;
}
void fillReset(SwFill* fill)
{
if (fill->ctable) {
free(fill->ctable);
fill->ctable = nullptr;
}
fill->translucent = false;
}
void fillFree(SwFill* fill)
{
if (!fill) return;
if (fill->ctable) free(fill->ctable);
free(fill);
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgMath.h"
#include "tvgSwCommon.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static inline bool _onlyShifted(const Matrix* m)
{
if (mathEqual(m->e11, 1.0f) && mathEqual(m->e22, 1.0f) && mathZero(m->e12) && mathZero(m->e21)) return true;
return false;
}
static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* transform, SwMpool* mpool, unsigned tid)
{
image->outline = mpoolReqOutline(mpool, tid);
auto outline = image->outline;
outline->pts.reserve(5);
outline->types.reserve(5);
outline->cntrs.reserve(1);
outline->closed.reserve(1);
Point to[4];
if (mesh->triangleCnt > 0) {
// TODO: Optimise me. We appear to calculate this exact min/max bounding area in multiple
// places. We should be able to re-use one we have already done? Also see:
// tvgPicture.h --> bounds
// tvgSwRasterTexmap.h --> _rasterTexmapPolygonMesh
//
// TODO: Should we calculate the exact path(s) of the triangle mesh instead?
// i.e. copy tvgSwShape.capp -> _genOutline?
//
// TODO: Cntrs?
auto triangles = mesh->triangles;
auto min = triangles[0].vertex[0].pt;
auto max = triangles[0].vertex[0].pt;
for (uint32_t i = 0; i < mesh->triangleCnt; ++i) {
if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x;
else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x;
if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y;
else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y;
if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x;
else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x;
if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y;
else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y;
if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x;
else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x;
if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y;
else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y;
}
to[0] = {min.x, min.y};
to[1] = {max.x, min.y};
to[2] = {max.x, max.y};
to[3] = {min.x, max.y};
} else {
auto w = static_cast<float>(image->w);
auto h = static_cast<float>(image->h);
to[0] = {0, 0};
to[1] = {w, 0};
to[2] = {w, h};
to[3] = {0, h};
}
for (int i = 0; i < 4; i++) {
outline->pts.push(mathTransform(&to[i], transform));
outline->types.push(SW_CURVE_TYPE_POINT);
}
outline->pts.push(outline->pts[0]);
outline->types.push(SW_CURVE_TYPE_POINT);
outline->cntrs.push(outline->pts.count - 1);
outline->closed.push(true);
image->outline = outline;
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
{
image->direct = _onlyShifted(transform);
//Fast track: Non-transformed image but just shifted.
if (image->direct) {
image->ox = -static_cast<int32_t>(nearbyint(transform->e13));
image->oy = -static_cast<int32_t>(nearbyint(transform->e23));
//Figure out the scale factor by transform matrix
} else {
auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21));
auto scaleY = sqrtf((transform->e22 * transform->e22) + (transform->e12 * transform->e12));
image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX;
if (mathZero(transform->e12) && mathZero(transform->e21)) image->scaled = true;
else image->scaled = false;
}
if (!_genOutline(image, mesh, transform, mpool, tid)) return false;
return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, image->direct);
}
bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias)
{
if ((image->rle = rleRender(image->rle, image->outline, renderRegion, antiAlias))) return true;
return false;
}
void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid)
{
mpoolRetOutline(mpool, tid);
image->outline = nullptr;
}
void imageReset(SwImage* image)
{
rleReset(image->rle);
}
void imageFree(SwImage* image)
{
rleFree(image->rle);
}

View file

@ -0,0 +1,323 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgMath.h"
#include "tvgSwCommon.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static float TO_RADIAN(SwFixed angle)
{
return (float(angle) / 65536.0f) * (MATH_PI / 180.0f);
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
SwFixed mathMean(SwFixed angle1, SwFixed angle2)
{
return angle1 + mathDiff(angle1, angle2) / 2;
}
bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
{
auto d1 = base[2] - base[3];
auto d2 = base[1] - base[2];
auto d3 = base[0] - base[1];
if (d1.small()) {
if (d2.small()) {
if (d3.small()) {
angleIn = angleMid = angleOut = 0;
return true;
} else {
angleIn = angleMid = angleOut = mathAtan(d3);
}
} else {
if (d3.small()) {
angleIn = angleMid = angleOut = mathAtan(d2);
} else {
angleIn = angleMid = mathAtan(d2);
angleOut = mathAtan(d3);
}
}
} else {
if (d2.small()) {
if (d3.small()) {
angleIn = angleMid = angleOut = mathAtan(d1);
} else {
angleIn = mathAtan(d1);
angleOut = mathAtan(d3);
angleMid = mathMean(angleIn, angleOut);
}
} else {
if (d3.small()) {
angleIn = mathAtan(d1);
angleMid = angleOut = mathAtan(d2);
} else {
angleIn = mathAtan(d1);
angleMid = mathAtan(d2);
angleOut = mathAtan(d3);
}
}
}
auto theta1 = abs(mathDiff(angleIn, angleMid));
auto theta2 = abs(mathDiff(angleMid, angleOut));
if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true;
return false;
}
int64_t mathMultiply(int64_t a, int64_t b)
{
int32_t s = 1;
//move sign
if (a < 0) {
a = -a;
s = -s;
}
if (b < 0) {
b = -b;
s = -s;
}
int64_t c = (a * b + 0x8000L) >> 16;
return (s > 0) ? c : -c;
}
int64_t mathDivide(int64_t a, int64_t b)
{
int32_t s = 1;
//move sign
if (a < 0) {
a = -a;
s = -s;
}
if (b < 0) {
b = -b;
s = -s;
}
int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL;
return (s < 0 ? -q : q);
}
int64_t mathMulDiv(int64_t a, int64_t b, int64_t c)
{
int32_t s = 1;
//move sign
if (a < 0) {
a = -a;
s = -s;
}
if (b < 0) {
b = -b;
s = -s;
}
if (c < 0) {
c = -c;
s = -s;
}
int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL;
return (s > 0 ? d : -d);
}
void mathRotate(SwPoint& pt, SwFixed angle)
{
if (angle == 0 || pt.zero()) return;
Point v = pt.toPoint();
auto radian = TO_RADIAN(angle);
auto cosv = cosf(radian);
auto sinv = sinf(radian);
pt.x = SwCoord(nearbyint((v.x * cosv - v.y * sinv) * 64.0f));
pt.y = SwCoord(nearbyint((v.x * sinv + v.y * cosv) * 64.0f));
}
SwFixed mathTan(SwFixed angle)
{
if (angle == 0) return 0;
return SwFixed(tanf(TO_RADIAN(angle)) * 65536.0f);
}
SwFixed mathAtan(const SwPoint& pt)
{
if (pt.zero()) return 0;
return SwFixed(mathAtan2(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f);
}
SwFixed mathSin(SwFixed angle)
{
if (angle == 0) return 0;
return mathCos(SW_ANGLE_PI2 - angle);
}
SwFixed mathCos(SwFixed angle)
{
return SwFixed(cosf(TO_RADIAN(angle)) * 65536.0f);
}
SwFixed mathLength(const SwPoint& pt)
{
if (pt.zero()) return 0;
//trivial case
if (pt.x == 0) return abs(pt.y);
if (pt.y == 0) return abs(pt.x);
auto v = pt.toPoint();
//return static_cast<SwFixed>(sqrtf(v.x * v.x + v.y * v.y) * 65536.0f);
/* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm.
With alpha = 1, beta = 3/8, giving results with the largest error less
than 7% compared to the exact value. */
if (v.x < 0) v.x = -v.x;
if (v.y < 0) v.y = -v.y;
return static_cast<SwFixed>((v.x > v.y) ? (v.x + v.y * 0.375f) : (v.y + v.x * 0.375f));
}
void mathSplitCubic(SwPoint* base)
{
SwCoord a, b, c, d;
base[6].x = base[3].x;
c = base[1].x;
d = base[2].x;
base[1].x = a = (base[0].x + c) >> 1;
base[5].x = b = (base[3].x + d) >> 1;
c = (c + d) >> 1;
base[2].x = a = (a + c) >> 1;
base[4].x = b = (b + c) >> 1;
base[3].x = (a + b) >> 1;
base[6].y = base[3].y;
c = base[1].y;
d = base[2].y;
base[1].y = a = (base[0].y + c) >> 1;
base[5].y = b = (base[3].y + d) >> 1;
c = (c + d) >> 1;
base[2].y = a = (a + c) >> 1;
base[4].y = b = (b + c) >> 1;
base[3].y = (a + b) >> 1;
}
SwFixed mathDiff(SwFixed angle1, SwFixed angle2)
{
auto delta = angle2 - angle1;
delta %= SW_ANGLE_2PI;
if (delta < 0) delta += SW_ANGLE_2PI;
if (delta > SW_ANGLE_PI) delta -= SW_ANGLE_2PI;
return delta;
}
SwPoint mathTransform(const Point* to, const Matrix* transform)
{
if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)};
auto tx = to->x * transform->e11 + to->y * transform->e12 + transform->e13;
auto ty = to->x * transform->e21 + to->y * transform->e22 + transform->e23;
return {TO_SWCOORD(tx), TO_SWCOORD(ty)};
}
bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee)
{
clipee.max.x = (clipee.max.x < clipper.max.x) ? clipee.max.x : clipper.max.x;
clipee.max.y = (clipee.max.y < clipper.max.y) ? clipee.max.y : clipper.max.y;
clipee.min.x = (clipee.min.x > clipper.min.x) ? clipee.min.x : clipper.min.x;
clipee.min.y = (clipee.min.y > clipper.min.y) ? clipee.min.y : clipper.min.y;
//Check valid region
if (clipee.max.x - clipee.min.x < 1 && clipee.max.y - clipee.min.y < 1) return false;
//Check boundary
if (clipee.min.x >= clipper.max.x || clipee.min.y >= clipper.max.y ||
clipee.max.x <= clipper.min.x || clipee.max.y <= clipper.min.y) return false;
return true;
}
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack)
{
if (!outline) return false;
if (outline->pts.empty() || outline->cntrs.empty()) {
renderRegion.reset();
return false;
}
auto pt = outline->pts.begin();
auto xMin = pt->x;
auto xMax = pt->x;
auto yMin = pt->y;
auto yMax = pt->y;
for (++pt; pt < outline->pts.end(); ++pt) {
if (xMin > pt->x) xMin = pt->x;
if (xMax < pt->x) xMax = pt->x;
if (yMin > pt->y) yMin = pt->y;
if (yMax < pt->y) yMax = pt->y;
}
//Since no antialiasing is applied in the Fast Track case,
//the rasterization region has to be rearranged.
//https://github.com/Samsung/thorvg/issues/916
if (fastTrack) {
renderRegion.min.x = static_cast<SwCoord>(nearbyint(xMin / 64.0f));
renderRegion.max.x = static_cast<SwCoord>(nearbyint(xMax / 64.0f));
renderRegion.min.y = static_cast<SwCoord>(nearbyint(yMin / 64.0f));
renderRegion.max.y = static_cast<SwCoord>(nearbyint(yMax / 64.0f));
} else {
renderRegion.min.x = xMin >> 6;
renderRegion.max.x = (xMax + 63) >> 6;
renderRegion.min.y = yMin >> 6;
renderRegion.max.y = (yMax + 63) >> 6;
}
return mathClipBBox(clipRegion, renderRegion);
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgSwCommon.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx)
{
return &mpool->outline[idx];
}
void mpoolRetOutline(SwMpool* mpool, unsigned idx)
{
mpool->outline[idx].pts.clear();
mpool->outline[idx].cntrs.clear();
mpool->outline[idx].types.clear();
mpool->outline[idx].closed.clear();
}
SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx)
{
return &mpool->strokeOutline[idx];
}
void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx)
{
mpool->strokeOutline[idx].pts.clear();
mpool->strokeOutline[idx].cntrs.clear();
mpool->strokeOutline[idx].types.clear();
mpool->strokeOutline[idx].closed.clear();
}
SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx)
{
return &mpool->dashOutline[idx];
}
void mpoolRetDashOutline(SwMpool* mpool, unsigned idx)
{
mpool->dashOutline[idx].pts.clear();
mpool->dashOutline[idx].cntrs.clear();
mpool->dashOutline[idx].types.clear();
mpool->dashOutline[idx].closed.clear();
}
SwMpool* mpoolInit(uint32_t threads)
{
auto allocSize = threads + 1;
auto mpool = static_cast<SwMpool*>(calloc(1, sizeof(SwMpool)));
mpool->outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
mpool->strokeOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
mpool->dashOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
mpool->allocSize = allocSize;
return mpool;
}
bool mpoolClear(SwMpool* mpool)
{
for (unsigned i = 0; i < mpool->allocSize; ++i) {
mpool->outline[i].pts.reset();
mpool->outline[i].cntrs.reset();
mpool->outline[i].types.reset();
mpool->outline[i].closed.reset();
mpool->strokeOutline[i].pts.reset();
mpool->strokeOutline[i].cntrs.reset();
mpool->strokeOutline[i].types.reset();
mpool->strokeOutline[i].closed.reset();
mpool->dashOutline[i].pts.reset();
mpool->dashOutline[i].cntrs.reset();
mpool->dashOutline[i].types.reset();
mpool->dashOutline[i].closed.reset();
}
return true;
}
bool mpoolTerm(SwMpool* mpool)
{
if (!mpool) return false;
mpoolClear(mpool);
free(mpool->outline);
free(mpool->strokeOutline);
free(mpool->dashOutline);
free(mpool);
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,230 @@
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifdef THORVG_AVX_VECTOR_SUPPORT
#include <immintrin.h>
#define N_32BITS_IN_128REG 4
#define N_32BITS_IN_256REG 8
static inline __m128i ALPHA_BLEND(__m128i c, __m128i a)
{
//1. set the masks for the A/G and R/B channels
auto AG = _mm_set1_epi32(0xff00ff00);
auto RB = _mm_set1_epi32(0x00ff00ff);
//2. mask the alpha vector - originally quartet [a, a, a, a]
auto aAG = _mm_and_si128(a, AG);
auto aRB = _mm_and_si128(a, RB);
//3. calculate the alpha blending of the 2nd and 4th channel
//- mask the color vector
//- multiply it by the masked alpha vector
//- add the correction to compensate bit shifting used instead of dividing by 255
//- shift bits - corresponding to division by 256
auto even = _mm_and_si128(c, RB);
even = _mm_mullo_epi16(even, aRB);
even =_mm_add_epi16(even, RB);
even = _mm_srli_epi16(even, 8);
//4. calculate the alpha blending of the 1st and 3rd channel:
//- mask the color vector
//- multiply it by the corresponding masked alpha vector and store the high bits of the result
//- add the correction to compensate division by 256 instead of by 255 (next step)
//- remove the low 8 bits to mimic the division by 256
auto odd = _mm_and_si128(c, AG);
odd = _mm_mulhi_epu16(odd, aAG);
odd = _mm_add_epi16(odd, RB);
odd = _mm_and_si128(odd, AG);
//5. the final result
return _mm_or_si128(odd, even);
}
static void avxRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
{
dst += offset;
__m256i vecVal = _mm256_set1_epi8(val);
int32_t i = 0;
for (; i <= len - 32; i += 32) {
_mm256_storeu_si256((__m256i*)(dst + i), vecVal);
}
for (; i < len; ++i) {
dst[i] = val;
}
}
static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
{
//1. calculate how many iterations we need to cover the length
uint32_t iterations = len / N_32BITS_IN_256REG;
uint32_t avxFilled = iterations * N_32BITS_IN_256REG;
//2. set the beginning of the array
dst += offset;
//3. fill the octets
for (uint32_t i = 0; i < iterations; ++i, dst += N_32BITS_IN_256REG) {
_mm256_storeu_si256((__m256i*)dst, _mm256_set1_epi32(val));
}
//4. fill leftovers (in the first step we have to set the pointer to the place where the avx job is done)
int32_t leftovers = len - avxFilled;
while (leftovers--) *dst++ = val;
}
static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
uint32_t ialpha = 255 - a;
auto avxColor = _mm_set1_epi32(color);
auto avxIalpha = _mm_set1_epi8(ialpha);
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
//1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
auto notAligned = ((uintptr_t)dst & 0xf) / 4;
if (notAligned) {
notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned);
for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
*dst = color + ALPHA_BLEND(*dst, ialpha);
}
}
//2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once
uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG;
uint32_t avxFilled = iterations * N_32BITS_IN_128REG;
auto avxDst = (__m128i*)dst;
for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
*avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha));
}
//3. fill the remaining pixels
int32_t leftovers = w - notAligned - avxFilled;
dst += avxFilled;
while (leftovers--) {
*dst = color + ALPHA_BLEND(*dst, ialpha);
dst++;
}
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = a + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto span = rle->spans;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
auto ialpha = IA(src);
//1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
auto notAligned = ((uintptr_t)dst & 0xf) / 4;
if (notAligned) {
notAligned = (N_32BITS_IN_128REG - notAligned > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
*dst = src + ALPHA_BLEND(*dst, ialpha);
}
}
//2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
//In order to avoid unneccessary avx variables declarations a check is made whether there are any iterations at all
uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
uint32_t avxFilled = 0;
if (iterations > 0) {
auto avxSrc = _mm_set1_epi32(src);
auto avxIalpha = _mm_set1_epi8(ialpha);
avxFilled = iterations * N_32BITS_IN_128REG;
auto avxDst = (__m128i*)dst;
for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
*avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
}
}
//3. fill the remaining pixels
int32_t leftovers = span->len - notAligned - avxFilled;
dst += avxFilled;
while (leftovers--) {
*dst = src + ALPHA_BLEND(*dst, ialpha);
dst++;
}
++span;
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
#endif

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
template<typename PIXEL_T>
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len)
{
dst += offset;
//fix the misaligned memory
auto alignOffset = (long long) dst % 8;
if (alignOffset > 0) {
if (sizeof(PIXEL_T) == 4) alignOffset /= 4;
else if (sizeof(PIXEL_T) == 1) alignOffset = 8 - alignOffset;
while (alignOffset > 0 && len > 0) {
*dst++ = val;
--len;
--alignOffset;
}
}
//64bits faster clear
if ((sizeof(PIXEL_T) == 4)) {
auto val64 = (uint64_t(val) << 32) | uint64_t(val);
while (len > 1) {
*reinterpret_cast<uint64_t*>(dst) = val64;
len -= 2;
dst += 2;
}
} else if (sizeof(PIXEL_T) == 1) {
auto val32 = (uint32_t(val) << 24) | (uint32_t(val) << 16) | (uint32_t(val) << 8) | uint32_t(val);
auto val64 = (uint64_t(val32) << 32) | val32;
while (len > 7) {
*reinterpret_cast<uint64_t*>(dst) = val64;
len -= 8;
dst += 8;
}
}
//leftovers
while (len--) *dst++ = val;
}
static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto span = rle->spans;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
auto ialpha = IA(src);
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + ALPHA_BLEND(*dst, ialpha);
}
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = 255 - a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = color + ALPHA_BLEND(*dst, ialpha);
}
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = a + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
static bool inline cRasterABGRtoARGB(Surface* surface)
{
TVGLOG("SW_ENGINE", "Convert ColorSpace ABGR - ARGB [Size: %d x %d]", surface->w, surface->h);
//64bits faster converting
if (surface->w % 2 == 0) {
auto buffer = reinterpret_cast<uint64_t*>(surface->buf32);
for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride / 2) {
auto dst = buffer;
for (uint32_t x = 0; x < surface->w / 2; ++x, ++dst) {
auto c = *dst;
//flip Blue, Red channels
*dst = (c & 0xff000000ff000000) + ((c & 0x00ff000000ff0000) >> 16) + (c & 0x0000ff000000ff00) + ((c & 0x000000ff000000ff) << 16);
}
}
//default converting
} else {
auto buffer = surface->buf32;
for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride) {
auto dst = buffer;
for (uint32_t x = 0; x < surface->w; ++x, ++dst) {
auto c = *dst;
//flip Blue, Red channels
*dst = (c & 0xff000000) + ((c & 0x00ff0000) >> 16) + (c & 0x0000ff00) + ((c & 0x000000ff) << 16);
}
}
}
return true;
}
static bool inline cRasterARGBtoABGR(Surface* surface)
{
//exactly same with ABGRtoARGB
return cRasterABGRtoARGB(surface);
}

View file

@ -0,0 +1,200 @@
/*
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifdef THORVG_NEON_VECTOR_SUPPORT
#include <arm_neon.h>
//TODO : need to support windows ARM
#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64)
#define TVG_AARCH64 1
#else
#define TVG_AARCH64 0
#endif
static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a)
{
uint16x8_t t = vmull_u8(c, a);
return vshrn_n_u16(t, 8);
}
static void neonRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
{
dst += offset;
int32_t i = 0;
const uint8x16_t valVec = vdupq_n_u8(val);
#if TVG_AARCH64
uint8x16x4_t valQuad = {valVec, valVec, valVec, valVec};
for (; i <= len - 16 * 4; i += 16 * 4) {
vst1q_u8_x4(dst + i, valQuad);
}
#else
for (; i <= len - 16; i += 16) {
vst1q_u8(dst + i, valVec);
}
#endif
for (; i < len; i++) {
dst[i] = val;
}
}
static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
{
dst += offset;
uint32x4_t vectorVal = vdupq_n_u32(val);
#if TVG_AARCH64
uint32_t iterations = len / 16;
uint32_t neonFilled = iterations * 16;
uint32x4x4_t valQuad = {vectorVal, vectorVal, vectorVal, vectorVal};
for (uint32_t i = 0; i < iterations; ++i) {
vst4q_u32(dst, valQuad);
dst += 16;
}
#else
uint32_t iterations = len / 4;
uint32_t neonFilled = iterations * 4;
for (uint32_t i = 0; i < iterations; ++i) {
vst1q_u32(dst, vectorVal);
dst += 4;
}
#endif
int32_t leftovers = len - neonFilled;
while (leftovers--) *dst++ = val;
}
static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto span = rle->spans;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
uint32_t src;
uint8x8_t *vDst = nullptr;
uint16_t align;
for (uint32_t i = 0; i < rle->size; ++i) {
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
auto dst = &surface->buf32[span->y * surface->stride + span->x];
auto ialpha = IA(src);
if ((((uintptr_t) dst) & 0x7) != 0) {
//fill not aligned byte
*dst = src + ALPHA_BLEND(*dst, ialpha);
vDst = (uint8x8_t*)(dst + 1);
align = 1;
} else {
vDst = (uint8x8_t*) dst;
align = 0;
}
uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src);
uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha);
for (uint32_t x = 0; x < (span->len - align) / 2; ++x)
vDst[x] = vadd_u8(vSrc, ALPHA_BLEND(vDst[x], vIalpha));
auto leftovers = (span->len - align) % 2;
if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha);
++span;
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = 255 - a;
auto vColor = vdup_n_u32(color);
auto vIalpha = vdup_n_u8((uint8_t) ialpha);
uint8x8_t* vDst = nullptr;
uint32_t align;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
if ((((uintptr_t) dst) & 0x7) != 0) {
//fill not aligned byte
*dst = color + ALPHA_BLEND(*dst, ialpha);
vDst = (uint8x8_t*) (dst + 1);
align = 1;
} else {
vDst = (uint8x8_t*) dst;
align = 0;
}
for (uint32_t x = 0; x < (w - align) / 2; ++x)
vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha));
auto leftovers = (w - align) % 2;
if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = a + MULTIPLY(*dst, ialpha);
}
}
}
return true;
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,856 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <algorithm>
#include "tvgMath.h"
#include "tvgSwCommon.h"
#include "tvgTaskScheduler.h"
#include "tvgSwRenderer.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static int32_t initEngineCnt = false;
static int32_t rendererCnt = 0;
static SwMpool* globalMpool = nullptr;
static uint32_t threadsCnt = 0;
struct SwTask : Task
{
SwSurface* surface = nullptr;
SwMpool* mpool = nullptr;
SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region
Matrix* transform = nullptr;
Array<RenderData> clips;
RenderUpdateFlag flags = RenderUpdateFlag::None;
uint8_t opacity;
bool pushed = false; //Pushed into task list?
bool disposed = false; //Disposed task?
RenderRegion bounds()
{
//Can we skip the synchronization?
done();
RenderRegion region;
//Range over?
region.x = bbox.min.x > 0 ? bbox.min.x : 0;
region.y = bbox.min.y > 0 ? bbox.min.y : 0;
region.w = bbox.max.x - region.x;
region.h = bbox.max.y - region.y;
if (region.w < 0) region.w = 0;
if (region.h < 0) region.h = 0;
return region;
}
virtual void dispose() = 0;
virtual bool clip(SwRleData* target) = 0;
virtual SwRleData* rle() = 0;
virtual ~SwTask()
{
free(transform);
}
};
struct SwShapeTask : SwTask
{
SwShape shape;
const RenderShape* rshape = nullptr;
bool clipper = false;
/* We assume that if the stroke width is greater than 2,
the shape's outline beneath the stroke could be adequately covered by the stroke drawing.
Therefore, antialiasing is disabled under this condition.
Additionally, the stroke style should not be dashed. */
bool antialiasing(float strokeWidth)
{
return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;;
}
float validStrokeWidth()
{
if (!rshape->stroke) return 0.0f;
auto width = rshape->stroke->width;
if (mathZero(width)) return 0.0f;
if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f;
if (mathZero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f;
if (transform) return (width * sqrt(transform->e11 * transform->e11 + transform->e12 * transform->e12));
else return width;
}
bool clip(SwRleData* target) override
{
if (shape.fastTrack) rleClipRect(target, &bbox);
else if (shape.rle) rleClipPath(target, shape.rle);
else return false;
return true;
}
SwRleData* rle() override
{
if (!shape.rle && shape.fastTrack) {
shape.rle = rleRender(&shape.bbox);
}
return shape.rle;
}
void run(unsigned tid) override
{
if (opacity == 0 && !clipper) return; //Invisible
auto strokeWidth = validStrokeWidth();
bool visibleFill = false;
auto clipRegion = bbox;
//This checks also for the case, if the invisible shape turned to visible by alpha.
auto prepareShape = false;
if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true;
//Shape
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
uint8_t alpha = 0;
rshape->fillColor(nullptr, nullptr, nullptr, &alpha);
alpha = MULTIPLY(alpha, opacity);
visibleFill = (alpha > 0 || rshape->fill);
if (visibleFill || clipper) {
shapeReset(&shape);
if (!shapePrepare(&shape, rshape, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) {
visibleFill = false;
}
}
}
//Fill
if (flags & (RenderUpdateFlag::Path |RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
if (visibleFill || clipper) {
if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
}
if (auto fill = rshape->fill) {
auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false;
if (ctable) shapeResetFill(&shape);
if (!shapeGenFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err;
} else {
shapeDelFill(&shape);
}
}
//Stroke
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
if (strokeWidth > 0.0f) {
shapeResetStroke(&shape, rshape, transform);
if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err;
if (auto fill = rshape->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);
}
}
//Clear current task memorypool here if the clippers would use the same memory pool
shapeDelOutline(&shape, mpool, tid);
//Clip Path
for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
auto clipper = static_cast<SwTask*>(*clip);
//Clip shape rle
if (shape.rle && !clipper->clip(shape.rle)) goto err;
//Clip stroke rle
if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err;
}
return;
err:
shapeReset(&shape);
shapeDelOutline(&shape, mpool, tid);
}
void dispose() override
{
shapeFree(&shape);
}
};
struct SwSceneTask : SwTask
{
Array<RenderData> scene; //list of paints render data (SwTask)
SwRleData* sceneRle = nullptr;
bool clip(SwRleData* target) override
{
//Only one shape
if (scene.count == 1) {
return static_cast<SwTask*>(*scene.data)->clip(target);
}
//More than one shapes
if (sceneRle) rleClipPath(target, sceneRle);
else TVGLOG("SW_ENGINE", "No clippers in a scene?");
return true;
}
SwRleData* rle() override
{
return sceneRle;
}
void run(unsigned tid) override
{
//TODO: Skip the run if the scene hans't changed.
if (!sceneRle) sceneRle = static_cast<SwRleData*>(calloc(1, sizeof(SwRleData)));
else rleReset(sceneRle);
//Merge shapes if it has more than one shapes
if (scene.count > 1) {
//Merge first two clippers
auto clipper1 = static_cast<SwTask*>(*scene.data);
auto clipper2 = static_cast<SwTask*>(*(scene.data + 1));
rleMerge(sceneRle, clipper1->rle(), clipper2->rle());
//Unify the remained clippers
for (auto rd = scene.begin() + 2; rd < scene.end(); ++rd) {
auto clipper = static_cast<SwTask*>(*rd);
rleMerge(sceneRle, sceneRle, clipper->rle());
}
}
}
void dispose() override
{
rleFree(sceneRle);
}
};
struct SwImageTask : SwTask
{
SwImage image;
Surface* source; //Image source
const RenderMesh* mesh = nullptr; //Should be valid ptr in action
bool clip(SwRleData* target) override
{
TVGERR("SW_ENGINE", "Image is used as ClipPath?");
return true;
}
SwRleData* rle() override
{
TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?");
return nullptr;
}
void run(unsigned tid) override
{
auto clipRegion = bbox;
//Convert colorspace if it's not aligned.
rasterConvertCS(source, surface->cs);
rasterPremultiply(source);
image.data = source->data;
image.w = source->w;
image.h = source->h;
image.stride = source->stride;
image.channelSize = source->channelSize;
//Invisible shape turned to visible by alpha.
if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) {
imageReset(&image);
if (!image.data || image.w == 0 || image.h == 0) goto end;
if (!imagePrepare(&image, mesh, transform, clipRegion, bbox, mpool, tid)) goto end;
// TODO: How do we clip the triangle mesh? Only clip non-meshed images for now
if (mesh->triangleCnt == 0 && clips.count > 0) {
if (!imageGenRle(&image, bbox, false)) goto end;
if (image.rle) {
//Clear current task memorypool here if the clippers would use the same memory pool
imageDelOutline(&image, mpool, tid);
for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
auto clipper = static_cast<SwTask*>(*clip);
if (!clipper->clip(image.rle)) goto err;
}
return;
}
}
}
goto end;
err:
rleReset(image.rle);
end:
imageDelOutline(&image, mpool, tid);
}
void dispose() override
{
imageFree(&image);
}
};
static void _termEngine()
{
if (rendererCnt > 0) return;
mpoolTerm(globalMpool);
globalMpool = nullptr;
}
static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{
uint8_t r, g, b, a;
if (auto fill = task->rshape->fill) {
rasterGradientShape(surface, &task->shape, fill->identifier());
} else {
task->rshape->fillColor(&r, &g, &b, &a);
a = MULTIPLY(opacity, a);
if (a > 0) rasterShape(surface, &task->shape, r, g, b, a);
}
}
static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{
uint8_t r, g, b, a;
if (auto strokeFill = task->rshape->strokeFill()) {
rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
} else {
if (task->rshape->strokeColor(&r, &g, &b, &a)) {
a = MULTIPLY(opacity, a);
if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
}
}
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
SwRenderer::~SwRenderer()
{
clearCompositors();
delete(surface);
if (!sharedMpool) mpoolTerm(mpool);
--rendererCnt;
if (rendererCnt == 0 && initEngineCnt == 0) _termEngine();
}
bool SwRenderer::clear()
{
for (auto task = tasks.begin(); task < tasks.end(); ++task) {
if ((*task)->disposed) {
delete(*task);
} else {
(*task)->done();
(*task)->pushed = false;
}
}
tasks.clear();
if (!sharedMpool) mpoolClear(mpool);
if (surface) {
vport.x = vport.y = 0;
vport.w = surface->w;
vport.h = surface->h;
}
return true;
}
bool SwRenderer::sync()
{
return true;
}
RenderRegion SwRenderer::viewport()
{
return vport;
}
bool SwRenderer::viewport(const RenderRegion& vp)
{
vport = vp;
return true;
}
bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs)
{
if (!data || stride == 0 || w == 0 || h == 0 || w > stride) return false;
clearCompositors();
if (!surface) surface = new SwSurface;
surface->data = data;
surface->stride = stride;
surface->w = w;
surface->h = h;
surface->cs = cs;
surface->channelSize = CHANNEL_SIZE(cs);
surface->premultiplied = true;
return rasterCompositor(surface);
}
bool SwRenderer::preRender()
{
return rasterClear(surface, 0, 0, surface->w, surface->h);
}
void SwRenderer::clearCompositors()
{
//Free Composite Caches
for (auto comp = compositors.begin(); comp < compositors.end(); ++comp) {
free((*comp)->compositor->image.data);
delete((*comp)->compositor);
delete(*comp);
}
compositors.reset();
}
bool SwRenderer::postRender()
{
//Unmultiply alpha if needed
if (surface->cs == ColorSpace::ABGR8888S || surface->cs == ColorSpace::ARGB8888S) {
rasterUnpremultiply(surface);
}
for (auto task = tasks.begin(); task < tasks.end(); ++task) {
if ((*task)->disposed) delete(*task);
else (*task)->pushed = false;
}
tasks.clear();
return true;
}
bool SwRenderer::renderImage(RenderData data)
{
auto task = static_cast<SwImageTask*>(data);
task->done();
if (task->opacity == 0) return true;
return rasterImage(surface, &task->image, task->mesh, task->transform, task->bbox, task->opacity);
}
bool SwRenderer::renderShape(RenderData data)
{
auto task = static_cast<SwShapeTask*>(data);
if (!task) return false;
task->done();
if (task->opacity == 0) return true;
//Main raster stage
if (task->rshape->stroke && task->rshape->stroke->strokeFirst) {
_renderStroke(task, surface, task->opacity);
_renderFill(task, surface, task->opacity);
} else {
_renderFill(task, surface, task->opacity);
_renderStroke(task, surface, task->opacity);
}
return true;
}
bool SwRenderer::blend(BlendMethod method)
{
if (surface->blendMethod == method) return true;
surface->blendMethod = method;
switch (method) {
case BlendMethod::Add:
surface->blender = opBlendAdd;
break;
case BlendMethod::Screen:
surface->blender = opBlendScreen;
break;
case BlendMethod::Multiply:
surface->blender = opBlendMultiply;
break;
case BlendMethod::Overlay:
surface->blender = opBlendOverlay;
break;
case BlendMethod::Difference:
surface->blender = opBlendDifference;
break;
case BlendMethod::Exclusion:
surface->blender = opBlendExclusion;
break;
case BlendMethod::SrcOver:
surface->blender = opBlendSrcOver;
break;
case BlendMethod::Darken:
surface->blender = opBlendDarken;
break;
case BlendMethod::Lighten:
surface->blender = opBlendLighten;
break;
case BlendMethod::ColorDodge:
surface->blender = opBlendColorDodge;
break;
case BlendMethod::ColorBurn:
surface->blender = opBlendColorBurn;
break;
case BlendMethod::HardLight:
surface->blender = opBlendHardLight;
break;
case BlendMethod::SoftLight:
surface->blender = opBlendSoftLight;
break;
default:
surface->blender = nullptr;
break;
}
return false;
}
RenderRegion SwRenderer::region(RenderData data)
{
return static_cast<SwTask*>(data)->bounds();
}
bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity)
{
if (!cmp) return false;
auto p = static_cast<SwCompositor*>(cmp);
p->method = method;
p->opacity = opacity;
//Current Context?
if (p->method != CompositeMethod::None) {
surface = p->recoverSfc;
surface->compositor = p;
}
return true;
}
bool SwRenderer::mempool(bool shared)
{
if (shared == sharedMpool) return true;
if (shared) {
if (!sharedMpool) {
if (!mpoolTerm(mpool)) return false;
mpool = globalMpool;
}
} else {
if (sharedMpool) mpool = mpoolInit(threadsCnt);
}
sharedMpool = shared;
if (mpool) return true;
return false;
}
const Surface* SwRenderer::mainSurface()
{
return surface;
}
Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
{
auto x = region.x;
auto y = region.y;
auto w = region.w;
auto h = region.h;
auto sw = static_cast<int32_t>(surface->w);
auto sh = static_cast<int32_t>(surface->h);
//Out of boundary
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
SwSurface* cmp = nullptr;
auto reqChannelSize = CHANNEL_SIZE(cs);
//Use cached data
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) {
cmp = *p;
break;
}
}
//New Composition
if (!cmp) {
//Inherits attributes from main surface
cmp = new SwSurface(surface);
cmp->compositor = new SwCompositor;
//TODO: We can optimize compositor surface size from (surface->stride x surface->h) to Parameter(w x h)
cmp->compositor->image.data = (pixel_t*)malloc(reqChannelSize * surface->stride * surface->h);
cmp->channelSize = cmp->compositor->image.channelSize = reqChannelSize;
compositors.push(cmp);
}
//Boundary Check
if (x + w > sw) w = (sw - x);
if (y + h > sh) h = (sh - y);
cmp->compositor->recoverSfc = surface;
cmp->compositor->recoverCmp = surface->compositor;
cmp->compositor->valid = false;
cmp->compositor->bbox.min.x = x;
cmp->compositor->bbox.min.y = y;
cmp->compositor->bbox.max.x = x + w;
cmp->compositor->bbox.max.y = y + h;
cmp->compositor->image.stride = surface->stride;
cmp->compositor->image.w = surface->w;
cmp->compositor->image.h = surface->h;
cmp->compositor->image.direct = true;
cmp->data = cmp->compositor->image.data;
cmp->w = cmp->compositor->image.w;
cmp->h = cmp->compositor->image.h;
rasterClear(cmp, x, y, w, h);
//Switch render target
surface = cmp;
return cmp->compositor;
}
bool SwRenderer::endComposite(Compositor* cmp)
{
if (!cmp) return false;
auto p = static_cast<SwCompositor*>(cmp);
p->valid = true;
//Recover Context
surface = p->recoverSfc;
surface->compositor = p->recoverCmp;
//Default is alpha blending
if (p->method == CompositeMethod::None) {
return rasterImage(surface, &p->image, nullptr, nullptr, p->bbox, p->opacity);
}
return true;
}
ColorSpace SwRenderer::colorSpace()
{
if (surface) return surface->cs;
else return ColorSpace::Unsupported;
}
void SwRenderer::dispose(RenderData data)
{
auto task = static_cast<SwTask*>(data);
if (!task) return;
task->done();
task->dispose();
if (task->pushed) task->disposed = true;
else delete(task);
}
void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
if (!surface) return task;
if (flags == RenderUpdateFlag::None) return task;
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
static_cast<SwTask*>(*clip)->done();
}
task->clips = clips;
if (transform) {
if (!task->transform) task->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
*task->transform = transform->m;
} else {
if (task->transform) free(task->transform);
task->transform = nullptr;
}
//zero size?
if (task->transform) {
if (task->transform->e11 == 0.0f && task->transform->e12 == 0.0f) return task; //zero width
if (task->transform->e21 == 0.0f && task->transform->e22 == 0.0f) return task; //zero height
}
task->opacity = opacity;
task->surface = surface;
task->mpool = mpool;
task->flags = flags;
task->bbox.min.x = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
task->bbox.min.y = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
task->bbox.max.x = mathMin(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
task->bbox.max.y = mathMin(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
if (!task->pushed) {
task->pushed = true;
tasks.push(task);
}
TaskScheduler::request(task);
return task;
}
RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwImageTask*>(data);
if (!task) task = new SwImageTask;
else task->done();
task->source = surface;
task->mesh = mesh;
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwSceneTask*>(data);
if (!task) task = new SwSceneTask;
else task->done();
task->scene = scene;
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto task = scene.begin(); task < scene.end(); ++task) {
static_cast<SwTask*>(*task)->done();
}
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
//prepare task
auto task = static_cast<SwShapeTask*>(data);
if (!task) task = new SwShapeTask;
else task->done();
task->rshape = &rshape;
task->clipper = clipper;
return prepareCommon(task, transform, clips, opacity, flags);
}
SwRenderer::SwRenderer():mpool(globalMpool)
{
}
bool SwRenderer::init(uint32_t threads)
{
if ((initEngineCnt++) > 0) return true;
threadsCnt = threads;
//Share the memory pool among the renderer
globalMpool = mpoolInit(threads);
if (!globalMpool) {
--initEngineCnt;
return false;
}
return true;
}
int32_t SwRenderer::init()
{
return initEngineCnt;
}
bool SwRenderer::term()
{
if ((--initEngineCnt) > 0) return true;
initEngineCnt = 0;
_termEngine();
return true;
}
SwRenderer* SwRenderer::gen()
{
++rendererCnt;
return new SwRenderer();
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_SW_RENDERER_H_
#define _TVG_SW_RENDERER_H_
#include "tvgRender.h"
struct SwSurface;
struct SwTask;
struct SwCompositor;
struct SwMpool;
namespace tvg
{
class SwRenderer : public RenderMethod
{
public:
RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;
bool renderImage(RenderData data) override;
bool postRender() override;
void dispose(RenderData data) override;
RenderRegion region(RenderData data) override;
RenderRegion viewport() override;
bool viewport(const RenderRegion& vp) override;
bool blend(BlendMethod method) override;
ColorSpace colorSpace() override;
const Surface* mainSurface() override;
bool clear() override;
bool sync() override;
bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs);
bool mempool(bool shared);
Compositor* target(const RenderRegion& region, ColorSpace cs) override;
bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) override;
bool endComposite(Compositor* cmp) override;
void clearCompositors();
static SwRenderer* gen();
static bool init(uint32_t threads);
static int32_t init();
static bool term();
private:
SwSurface* surface = nullptr; //active surface
Array<SwTask*> tasks; //async task list
Array<SwSurface*> compositors; //render targets cache list
SwMpool* mpool; //private memory pool
RenderRegion vport; //viewport
bool sharedMpool = true; //memory-pool behavior policy
SwRenderer();
~SwRenderer();
RenderData prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};
}
#endif /* _TVG_SW_RENDERER_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,677 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgSwCommon.h"
#include "tvgMath.h"
#include "tvgLines.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static bool _outlineBegin(SwOutline& outline)
{
//Make a contour if lineTo/curveTo without calling close or moveTo beforehand.
if (outline.pts.empty()) return false;
outline.cntrs.push(outline.pts.count - 1);
outline.closed.push(false);
outline.pts.push(outline.pts[outline.cntrs.last()]);
outline.types.push(SW_CURVE_TYPE_POINT);
return false;
}
static bool _outlineEnd(SwOutline& outline)
{
if (outline.pts.empty()) return false;
outline.cntrs.push(outline.pts.count - 1);
outline.closed.push(false);
return false;
}
static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform, bool closed = false)
{
//make it a contour, if the last contour is not closed yet.
if (!closed) _outlineEnd(outline);
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
return false;
}
static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform)
{
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
{
outline.pts.push(mathTransform(ctrl1, transform));
outline.types.push(SW_CURVE_TYPE_CUBIC);
outline.pts.push(mathTransform(ctrl2, transform));
outline.types.push(SW_CURVE_TYPE_CUBIC);
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
static bool _outlineClose(SwOutline& outline)
{
uint32_t i;
if (outline.cntrs.count > 0) i = outline.cntrs.last() + 1;
else i = 0;
//Make sure there is at least one point in the current path
if (outline.pts.count == i) return false;
//Close the path
outline.pts.push(outline.pts[i]);
outline.cntrs.push(outline.pts.count - 1);
outline.types.push(SW_CURVE_TYPE_POINT);
outline.closed.push(true);
return true;
}
static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform)
{
Line cur = {dash.ptCur, *to};
auto len = lineLength(cur.pt1, cur.pt2);
if (mathZero(len)) {
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
//draw the current line fully
} else if (len <= dash.curLen) {
dash.curLen -= len;
if (!dash.curOpGap) {
if (dash.move) {
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
dash.move = false;
}
_outlineLineTo(*dash.outline, to, transform);
}
//draw the current line partially
} else {
while (len - dash.curLen > 0.0001f) {
Line left, right;
if (dash.curLen > 0) {
len -= dash.curLen;
lineSplitAt(cur, dash.curLen, left, right);
if (!dash.curOpGap) {
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
_outlineMoveTo(*dash.outline, &left.pt1, transform);
dash.move = false;
}
_outlineLineTo(*dash.outline, &left.pt2, transform);
}
} else {
right = cur;
}
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
dash.curOpGap = !dash.curOpGap;
cur = right;
dash.ptCur = cur.pt1;
dash.move = true;
}
//leftovers
dash.curLen -= len;
if (!dash.curOpGap) {
if (dash.move) {
_outlineMoveTo(*dash.outline, &cur.pt1, transform);
dash.move = false;
}
_outlineLineTo(*dash.outline, &cur.pt2, transform);
}
if (dash.curLen < 1 && TO_SWCOORD(len) > 1) {
//move to next dash
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
dash.curOpGap = !dash.curOpGap;
}
}
dash.ptCur = *to;
}
static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
{
Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
auto len = bezLength(cur);
//draw the current line fully
if (mathZero(len)) {
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
} else if (len <= dash.curLen) {
dash.curLen -= len;
if (!dash.curOpGap) {
if (dash.move) {
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
dash.move = false;
}
_outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform);
}
//draw the current line partially
} else {
while ((len - dash.curLen) > 0.0001f) {
Bezier left, right;
if (dash.curLen > 0) {
len -= dash.curLen;
bezSplitAt(cur, dash.curLen, left, right);
if (!dash.curOpGap) {
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
_outlineMoveTo(*dash.outline, &left.start, transform);
dash.move = false;
}
_outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform);
}
} else {
right = cur;
}
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
dash.curOpGap = !dash.curOpGap;
cur = right;
dash.ptCur = right.start;
dash.move = true;
}
//leftovers
dash.curLen -= len;
if (!dash.curOpGap) {
if (dash.move) {
_outlineMoveTo(*dash.outline, &cur.start, transform);
dash.move = false;
}
_outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform);
}
if (dash.curLen < 1 && TO_SWCOORD(len) > 1) {
//move to next dash
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
dash.curOpGap = !dash.curOpGap;
}
}
dash.ptCur = *to;
}
static void _dashClose(SwDashStroke& dash, const Matrix* transform)
{
_dashLineTo(dash, &dash.ptStart, transform);
}
static void _dashMoveTo(SwDashStroke& dash, const Point* pts)
{
dash.ptCur = *pts;
dash.ptStart = *pts;
dash.move = true;
}
static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const Point* pts)
{
dash.curIdx = offIdx % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx] - offset;
dash.curOpGap = offIdx % 2;
dash.ptStart = dash.ptCur = *pts;
dash.move = true;
}
static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length)
{
auto begin = length * rshape->stroke->trim.begin;
auto end = length * rshape->stroke->trim.end;
//default
if (end > begin) {
if (begin > 0.0f) dash->cnt = 4;
else dash->cnt = 2;
//looping
} else dash->cnt = 3;
if (dash->cnt == 2) {
dash->pattern[0] = end - begin;
dash->pattern[1] = length - (end - begin);
} else if (dash->cnt == 3) {
dash->pattern[0] = end;
dash->pattern[1] = (begin - end);
dash->pattern[2] = length - begin;
} else {
dash->pattern[0] = 0; //zero dash to start with a space.
dash->pattern[1] = begin;
dash->pattern[2] = end - begin;
dash->pattern[3] = length - end;
}
}
static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32_t shiftCmds, bool subpath)
{
const PathCommand* cmds = rshape->path.cmds.data + shiftCmds;
auto cmdCnt = rshape->path.cmds.count - shiftCmds;
const Point* pts = rshape->path.pts.data + shiftPts;
auto ptsCnt = rshape->path.pts.count - shiftPts;
//No actual shape data
if (cmdCnt <= 0 || ptsCnt <= 0) return 0.0f;
const Point* close = nullptr;
auto length = 0.0f;
//must begin with moveTo
if (cmds[0] == PathCommand::MoveTo) {
close = pts;
cmds++;
pts++;
cmdCnt--;
}
while (cmdCnt-- > 0) {
switch (*cmds) {
case PathCommand::Close: {
length += mathLength(pts - 1, close);
if (subpath) return length;
break;
}
case PathCommand::MoveTo: {
if (subpath) return length;
close = pts;
++pts;
break;
}
case PathCommand::LineTo: {
length += mathLength(pts - 1, pts);
++pts;
break;
}
case PathCommand::CubicTo: {
length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)});
pts += 3;
break;
}
}
++cmds;
}
return length;
}
static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, bool trimmed, SwMpool* mpool, unsigned tid)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.pts.count;
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return nullptr;
auto startPts = pts;
auto startCmds = cmds;
SwDashStroke dash;
auto offset = 0.0f;
dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset);
auto simultaneous = rshape->stroke->trim.simultaneous;
if (dash.cnt == 0) {
if (trimmed) dash.pattern = (float*)malloc(sizeof(float) * 4);
else return nullptr;
} else {
//TODO: handle dash + trim - for now trimming ignoring is forced
trimmed = false;
}
//offset
auto patternLength = 0.0f;
uint32_t offIdx = 0;
if (!mathZero(offset)) {
for (size_t i = 0; i < dash.cnt; ++i) patternLength += dash.pattern[i];
bool isOdd = dash.cnt % 2;
if (isOdd) patternLength *= 2;
offset = fmodf(offset, patternLength);
if (offset < 0) offset += patternLength;
for (size_t i = 0; i < dash.cnt * (1 + (size_t)isOdd); ++i, ++offIdx) {
auto curPattern = dash.pattern[i % dash.cnt];
if (offset < curPattern) break;
offset -= curPattern;
}
}
dash.outline = mpoolReqDashOutline(mpool, tid);
//must begin with moveTo
if (cmds[0] == PathCommand::MoveTo) {
if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous));
_dashMoveTo(dash, offIdx, offset, pts);
cmds++;
pts++;
}
while (--cmdCnt > 0) {
switch (*cmds) {
case PathCommand::Close: {
_dashClose(dash, transform);
break;
}
case PathCommand::MoveTo: {
if (trimmed) {
if (simultaneous) {
_trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true));
_dashMoveTo(dash, offIdx, offset, pts);
} else _dashMoveTo(dash, pts);
} else _dashMoveTo(dash, offIdx, offset, pts);
++pts;
break;
}
case PathCommand::LineTo: {
_dashLineTo(dash, pts, transform);
++pts;
break;
}
case PathCommand::CubicTo: {
_dashCubicTo(dash, pts, pts + 1, pts + 2, transform);
pts += 3;
break;
}
}
++cmds;
}
_outlineEnd(*dash.outline);
if (trimmed) free(dash.pattern);
return dash.outline;
}
static bool _axisAlignedRect(const SwOutline* outline)
{
//Fast Track: axis-aligned rectangle?
if (outline->pts.count != 5) return false;
if (outline->types[2] == SW_CURVE_TYPE_CUBIC) return false;
auto pt1 = outline->pts.data + 0;
auto pt2 = outline->pts.data + 1;
auto pt3 = outline->pts.data + 2;
auto pt4 = outline->pts.data + 3;
auto a = SwPoint{pt1->x, pt3->y};
auto b = SwPoint{pt3->x, pt1->y};
if ((*pt2 == a && *pt4 == b) || (*pt2 == b && *pt4 == a)) return true;
return false;
}
static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.pts.count;
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return false;
shape->outline = mpoolReqOutline(mpool, tid);
auto outline = shape->outline;
auto closed = false;
//Generate Outlines
while (cmdCnt-- > 0) {
switch (*cmds) {
case PathCommand::Close: {
if (!closed) closed = _outlineClose(*outline);
break;
}
case PathCommand::MoveTo: {
closed = _outlineMoveTo(*outline, pts, transform, closed);
++pts;
break;
}
case PathCommand::LineTo: {
if (closed) closed = _outlineBegin(*outline);
_outlineLineTo(*outline, pts, transform);
++pts;
break;
}
case PathCommand::CubicTo: {
if (closed) closed = _outlineBegin(*outline);
_outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform);
pts += 3;
break;
}
}
++cmds;
}
if (!closed) _outlineEnd(*outline);
outline->fillRule = rshape->rule;
shape->outline = outline;
shape->fastTrack = (!hasComposite && _axisAlignedRect(shape->outline));
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
{
if (!_genOutline(shape, rshape, transform, mpool, tid, hasComposite)) return false;
if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false;
//Keep it for Rasterization Region
shape->bbox = renderRegion;
//Check valid region
if (renderRegion.max.x - renderRegion.min.x < 1 && renderRegion.max.y - renderRegion.min.y < 1) return false;
//Check boundary
if (renderRegion.min.x >= clipRegion.max.x || renderRegion.min.y >= clipRegion.max.y ||
renderRegion.max.x <= clipRegion.min.x || renderRegion.max.y <= clipRegion.min.y) return false;
return true;
}
bool shapePrepared(const SwShape* shape)
{
return shape->rle ? true : false;
}
bool shapeGenRle(SwShape* shape, TVG_UNUSED const RenderShape* rshape, bool antiAlias)
{
//FIXME: Should we draw it?
//Case: Stroke Line
//if (shape.outline->opened) return true;
//Case A: Fast Track Rectangle Drawing
if (shape->fastTrack) return true;
//Case B: Normal Shape RLE Drawing
if ((shape->rle = rleRender(shape->rle, shape->outline, shape->bbox, antiAlias))) return true;
return false;
}
void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid)
{
mpoolRetOutline(mpool, tid);
shape->outline = nullptr;
}
void shapeReset(SwShape* shape)
{
rleReset(shape->rle);
rleReset(shape->strokeRle);
shape->fastTrack = false;
shape->bbox.reset();
}
void shapeFree(SwShape* shape)
{
rleFree(shape->rle);
shape->rle = nullptr;
shapeDelFill(shape);
if (shape->stroke) {
rleFree(shape->strokeRle);
shape->strokeRle = nullptr;
strokeFree(shape->stroke);
shape->stroke = nullptr;
}
}
void shapeDelStroke(SwShape* shape)
{
if (!shape->stroke) return;
rleFree(shape->strokeRle);
shape->strokeRle = nullptr;
strokeFree(shape->stroke);
shape->stroke = nullptr;
}
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform)
{
if (!shape->stroke) shape->stroke = static_cast<SwStroke*>(calloc(1, sizeof(SwStroke)));
auto stroke = shape->stroke;
if (!stroke) return;
strokeReset(stroke, rshape, transform);
rleReset(shape->strokeRle);
}
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
{
SwOutline* shapeOutline = nullptr;
SwOutline* strokeOutline = nullptr;
auto dashStroking = false;
auto ret = true;
//Dash style (+trimming)
auto trimmed = rshape->strokeTrim();
if (rshape->stroke->dashCnt > 0 || trimmed) {
shapeOutline = _genDashOutline(rshape, transform, trimmed, mpool, tid);
if (!shapeOutline) return false;
dashStroking = true;
//Normal style
} else {
if (!shape->outline) {
if (!_genOutline(shape, rshape, transform, mpool, tid, false)) return false;
}
shapeOutline = shape->outline;
}
if (!strokeParseOutline(shape->stroke, *shapeOutline)) {
ret = false;
goto clear;
}
strokeOutline = strokeExportOutline(shape->stroke, mpool, tid);
if (!mathUpdateOutlineBBox(strokeOutline, clipRegion, renderRegion, false)) {
ret = false;
goto clear;
}
shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderRegion, true);
clear:
if (dashStroking) mpoolRetDashOutline(mpool, tid);
mpoolRetStrokeOutline(mpool, tid);
return ret;
}
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable);
}
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable);
}
void shapeResetFill(SwShape* shape)
{
if (!shape->fill) {
shape->fill = static_cast<SwFill*>(calloc(1, sizeof(SwFill)));
if (!shape->fill) return;
}
fillReset(shape->fill);
}
void shapeResetStrokeFill(SwShape* shape)
{
if (!shape->stroke->fill) {
shape->stroke->fill = static_cast<SwFill*>(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;
}

View file

@ -0,0 +1,907 @@
/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <string.h>
#include <math.h>
#include "tvgSwCommon.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static constexpr auto SW_STROKE_TAG_POINT = 1;
static constexpr auto SW_STROKE_TAG_CUBIC = 2;
static constexpr auto SW_STROKE_TAG_BEGIN = 4;
static constexpr auto SW_STROKE_TAG_END = 8;
static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
{
return (SW_ANGLE_PI2 - static_cast<SwFixed>(s) * SW_ANGLE_PI);
}
static inline void SCALE(const SwStroke& stroke, SwPoint& pt)
{
pt.x = static_cast<SwCoord>(pt.x * stroke.sx);
pt.y = static_cast<SwCoord>(pt.y * stroke.sy);
}
static void _growBorder(SwStrokeBorder* border, uint32_t newPts)
{
auto maxOld = border->maxPts;
auto maxNew = border->ptsCnt + newPts;
if (maxNew <= maxOld) return;
auto maxCur = maxOld;
while (maxCur < maxNew)
maxCur += (maxCur >> 1) + 16;
//OPTIMIZE: use mempool!
border->pts = static_cast<SwPoint*>(realloc(border->pts, maxCur * sizeof(SwPoint)));
border->tags = static_cast<uint8_t*>(realloc(border->tags, maxCur * sizeof(uint8_t)));
border->maxPts = maxCur;
}
static void _borderClose(SwStrokeBorder* border, bool reverse)
{
auto start = border->start;
auto count = border->ptsCnt;
//Don't record empty paths!
if (count <= start + 1U) {
border->ptsCnt = start;
} else {
/* Copy the last point to the start of this sub-path,
since it contains the adjusted starting coordinates */
border->ptsCnt = --count;
border->pts[start] = border->pts[count];
if (reverse) {
//reverse the points
auto pt1 = border->pts + start + 1;
auto pt2 = border->pts + count - 1;
while (pt1 < pt2) {
auto tmp = *pt1;
*pt1 = *pt2;
*pt2 = tmp;
++pt1;
--pt2;
}
//reverse the tags
auto tag1 = border->tags + start + 1;
auto tag2 = border->tags + count - 1;
while (tag1 < tag2) {
auto tmp = *tag1;
*tag1 = *tag2;
*tag2 = tmp;
++tag1;
--tag2;
}
}
border->tags[start] |= SW_STROKE_TAG_BEGIN;
border->tags[count - 1] |= SW_STROKE_TAG_END;
}
border->start = -1;
border->movable = false;
}
static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
{
_growBorder(border, 3);
auto pt = border->pts + border->ptsCnt;
auto tag = border->tags + border->ptsCnt;
pt[0] = ctrl1;
pt[1] = ctrl2;
pt[2] = to;
tag[0] = SW_STROKE_TAG_CUBIC;
tag[1] = SW_STROKE_TAG_CUBIC;
tag[2] = SW_STROKE_TAG_POINT;
border->ptsCnt += 3;
border->movable = false;
}
static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke)
{
constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
SwPoint a = {static_cast<SwCoord>(radius), 0};
mathRotate(a, angleStart);
SCALE(stroke, a);
a += center;
auto total = angleDiff;
auto angle = angleStart;
auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2;
while (total != 0) {
auto step = total;
if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE;
else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE;
auto next = angle + step;
auto theta = step;
if (theta < 0) theta = -theta;
theta >>= 1;
//compute end point
SwPoint b = {static_cast<SwCoord>(radius), 0};
mathRotate(b, next);
SCALE(stroke, b);
b += center;
//compute first and second control points
auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
SwPoint a2 = {static_cast<SwCoord>(length), 0};
mathRotate(a2, angle + rotate);
SCALE(stroke, a2);
a2 += a;
SwPoint b2 = {static_cast<SwCoord>(length), 0};
mathRotate(b2, next - rotate);
SCALE(stroke, b2);
b2 += b;
//add cubic arc
_borderCubicTo(border, a2, b2, b);
//process the rest of the arc?
a = b;
total -= step;
angle = next;
}
}
static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable)
{
if (border->movable) {
//move last point
border->pts[border->ptsCnt - 1] = to;
} else {
//don't add zero-length line_to
if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
_growBorder(border, 1);
border->pts[border->ptsCnt] = to;
border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT;
border->ptsCnt += 1;
}
border->movable = movable;
}
static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
{
//close current open path if any?
if (border->start >= 0) _borderClose(border, false);
border->start = border->ptsCnt;
border->movable = false;
_borderLineTo(border, to, false);
}
static void _arcTo(SwStroke& stroke, int32_t side)
{
auto border = stroke.borders + side;
auto rotate = SIDE_TO_ROTATE(side);
auto total = mathDiff(stroke.angleIn, stroke.angleOut);
if (total == SW_ANGLE_PI) total = -rotate * 2;
_borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke);
border->movable = false;
}
static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
{
auto border = stroke.borders + side;
if (stroke.join == StrokeJoin::Round) {
_arcTo(stroke, side);
} else {
//this is a mitered (pointed) or beveled (truncated) corner
auto rotate = SIDE_TO_ROTATE(side);
auto bevel = stroke.join == StrokeJoin::Bevel;
SwFixed phi = 0;
SwFixed thcos = 0;
if (!bevel) {
auto theta = mathDiff(stroke.angleIn, stroke.angleOut);
if (theta == SW_ANGLE_PI) {
theta = rotate;
phi = stroke.angleIn;
} else {
theta /= 2;
phi = stroke.angleIn + theta + rotate;
}
thcos = mathCos(theta);
auto sigma = mathMultiply(stroke.miterlimit, thcos);
//is miter limit exceeded?
if (sigma < 0x10000L) bevel = true;
}
//this is a bevel (broken angle)
if (bevel) {
SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, stroke.angleOut + rotate);
SCALE(stroke, delta);
delta += stroke.center;
border->movable = false;
_borderLineTo(border, delta, false);
//this is a miter (intersection)
} else {
auto length = mathDivide(stroke.width, thcos);
SwPoint delta = {static_cast<SwCoord>(length), 0};
mathRotate(delta, phi);
SCALE(stroke, delta);
delta += stroke.center;
_borderLineTo(border, delta, false);
/* Now add and end point
Only needed if not lineto (lineLength is zero for curves) */
if (lineLength == 0) {
delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, stroke.angleOut + rotate);
SCALE(stroke, delta);
delta += stroke.center;
_borderLineTo(border, delta, false);
}
}
}
}
static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
{
auto border = stroke.borders + side;
auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
SwPoint delta;
bool intersect = false;
/* Only intersect borders if between two line_to's and both
lines are long enough (line length is zero for curves). */
if (border->movable && lineLength > 0) {
//compute minimum required length of lines
SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
}
auto rotate = SIDE_TO_ROTATE(side);
if (!intersect) {
delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, stroke.angleOut + rotate);
SCALE(stroke, delta);
delta += stroke.center;
border->movable = false;
} else {
//compute median angle
auto phi = stroke.angleIn + theta;
auto thcos = mathCos(theta);
delta = {static_cast<SwCoord>(mathDivide(stroke.width, thcos)), 0};
mathRotate(delta, phi + rotate);
SCALE(stroke, delta);
delta += stroke.center;
}
_borderLineTo(border, delta, false);
}
void _processCorner(SwStroke& stroke, SwFixed lineLength)
{
auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
//no specific corner processing is required if the turn is 0
if (turn == 0) return;
//when we turn to the right, the inside side is 0
int32_t inside = 0;
//otherwise, the inside is 1
if (turn < 0) inside = 1;
//process the inside
_inside(stroke, inside, lineLength);
//process the outside
_outside(stroke, 1 - inside, lineLength);
}
void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
{
SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, startAngle + SW_ANGLE_PI2);
SCALE(stroke, delta);
auto pt = stroke.center + delta;
auto border = stroke.borders;
_borderMoveTo(border, pt);
pt = stroke.center - delta;
++border;
_borderMoveTo(border, pt);
/* Save angle, position and line length for last join
lineLength is zero for curves */
stroke.subPathAngle = startAngle;
stroke.firstPt = false;
stroke.subPathLineLength = lineLength;
}
static void _lineTo(SwStroke& stroke, const SwPoint& to)
{
auto delta = to - stroke.center;
//a zero-length lineto is a no-op; avoid creating a spurious corner
if (delta.zero()) return;
/* The lineLength is used to determine the intersection of strokes outlines.
The scale needs to be reverted since the stroke width has not been scaled.
An alternative option is to scale the width of the stroke properly by
calculating the mixture of the sx/sy rating on the stroke direction. */
delta.x = static_cast<SwCoord>(delta.x / stroke.sx);
delta.y = static_cast<SwCoord>(delta.y / stroke.sy);
auto lineLength = mathLength(delta);
auto angle = mathAtan(delta);
delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, angle + SW_ANGLE_PI2);
SCALE(stroke, delta);
//process corner if necessary
if (stroke.firstPt) {
/* This is the first segment of a subpath. We need to add a point to each border
at their respective starting point locations. */
_firstSubPath(stroke, angle, lineLength);
} else {
//process the current corner
stroke.angleOut = angle;
_processCorner(stroke, lineLength);
}
//now add a line segment to both the inside and outside paths
auto border = stroke.borders;
auto side = 1;
while (side >= 0) {
auto pt = to + delta;
//the ends of lineto borders are movable
_borderLineTo(border, pt, true);
delta.x = -delta.x;
delta.y = -delta.y;
--side;
++border;
}
stroke.angleIn = angle;
stroke.center = to;
stroke.lineLength = lineLength;
}
static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
{
SwPoint bezStack[37]; //TODO: static?
auto limit = bezStack + 32;
auto arc = bezStack;
auto firstArc = true;
arc[0] = to;
arc[1] = ctrl2;
arc[2] = ctrl1;
arc[3] = stroke.center;
while (arc >= bezStack) {
SwFixed angleIn, angleOut, angleMid;
//initialize with current direction
angleIn = angleOut = angleMid = stroke.angleIn;
if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
if (stroke.firstPt) stroke.angleIn = angleIn;
mathSplitCubic(arc);
arc += 3;
continue;
}
if (firstArc) {
firstArc = false;
//process corner if necessary
if (stroke.firstPt) {
_firstSubPath(stroke, angleIn, 0);
} else {
stroke.angleOut = angleIn;
_processCorner(stroke, 0);
}
} else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) {
//if the deviation from one arc to the next is too great add a round corner
stroke.center = arc[3];
stroke.angleOut = angleIn;
stroke.join = StrokeJoin::Round;
_processCorner(stroke, 0);
//reinstate line join style
stroke.join = stroke.joinSaved;
}
//the arc's angle is small enough; we can add it directly to each border
auto theta1 = mathDiff(angleIn, angleMid) / 2;
auto theta2 = mathDiff(angleMid, angleOut) / 2;
auto phi1 = mathMean(angleIn, angleMid);
auto phi2 = mathMean(angleMid, angleOut);
auto length1 = mathDivide(stroke.width, mathCos(theta1));
auto length2 = mathDivide(stroke.width, mathCos(theta2));
SwFixed alpha0 = 0;
//compute direction of original arc
if (stroke.handleWideStrokes) {
alpha0 = mathAtan(arc[0] - arc[3]);
}
auto border = stroke.borders;
int32_t side = 0;
while (side < 2) {
auto rotate = SIDE_TO_ROTATE(side);
//compute control points
SwPoint _ctrl1 = {static_cast<SwCoord>(length1), 0};
mathRotate(_ctrl1, phi1 + rotate);
SCALE(stroke, _ctrl1);
_ctrl1 += arc[2];
SwPoint _ctrl2 = {static_cast<SwCoord>(length2), 0};
mathRotate(_ctrl2, phi2 + rotate);
SCALE(stroke, _ctrl2);
_ctrl2 += arc[1];
//compute end point
SwPoint _end = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(_end, angleOut + rotate);
SCALE(stroke, _end);
_end += arc[0];
if (stroke.handleWideStrokes) {
/* determine whether the border radius is greater than the radius of
curvature of the original arc */
auto _start = border->pts[border->ptsCnt - 1];
auto alpha1 = mathAtan(_end - _start);
//is the direction of the border arc opposite to that of the original arc?
if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) {
//use the sine rule to find the intersection point
auto beta = mathAtan(arc[3] - _start);
auto gamma = mathAtan(arc[0] - _end);
auto bvec = _end - _start;
auto blen = mathLength(bvec);
auto sinA = abs(mathSin(alpha1 - gamma));
auto sinB = abs(mathSin(beta - gamma));
auto alen = mathMulDiv(blen, sinA, sinB);
SwPoint delta = {static_cast<SwCoord>(alen), 0};
mathRotate(delta, beta);
delta += _start;
//circumnavigate the negative sector backwards
border->movable = false;
_borderLineTo(border, delta, false);
_borderLineTo(border, _end, false);
_borderCubicTo(border, _ctrl2, _ctrl1, _start);
//and then move to the endpoint
_borderLineTo(border, _end, false);
++side;
++border;
continue;
}
}
_borderCubicTo(border, _ctrl1, _ctrl2, _end);
++side;
++border;
}
arc -= 3;
stroke.angleIn = angleOut;
}
stroke.center = to;
}
static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side)
{
if (stroke.cap == StrokeCap::Square) {
auto rotate = SIDE_TO_ROTATE(side);
auto border = stroke.borders + side;
SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, angle);
SCALE(stroke, delta);
SwPoint delta2 = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta2, angle + rotate);
SCALE(stroke, delta2);
delta += stroke.center + delta2;
_borderLineTo(border, delta, false);
delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, angle);
SCALE(stroke, delta);
delta2 = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta2, angle - rotate);
SCALE(stroke, delta2);
delta += delta2 + stroke.center;
_borderLineTo(border, delta, false);
} else if (stroke.cap == StrokeCap::Round) {
stroke.angleIn = angle;
stroke.angleOut = angle + SW_ANGLE_PI;
_arcTo(stroke, side);
return;
} else { //Butt
auto rotate = SIDE_TO_ROTATE(side);
auto border = stroke.borders + side;
SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, angle + rotate);
SCALE(stroke, delta);
delta += stroke.center;
_borderLineTo(border, delta, false);
delta = {static_cast<SwCoord>(stroke.width), 0};
mathRotate(delta, angle - rotate);
SCALE(stroke, delta);
delta += stroke.center;
_borderLineTo(border, delta, false);
}
}
static void _addReverseLeft(SwStroke& stroke, bool opened)
{
auto right = stroke.borders + 0;
auto left = stroke.borders + 1;
auto newPts = left->ptsCnt - left->start;
if (newPts <= 0) return;
_growBorder(right, newPts);
auto dstPt = right->pts + right->ptsCnt;
auto dstTag = right->tags + right->ptsCnt;
auto srcPt = left->pts + left->ptsCnt - 1;
auto srcTag = left->tags + left->ptsCnt - 1;
while (srcPt >= left->pts + left->start) {
*dstPt = *srcPt;
*dstTag = *srcTag;
if (opened) {
dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
} else {
//switch begin/end tags if necessary
auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
}
--srcPt;
--srcTag;
++dstPt;
++dstTag;
}
left->ptsCnt = left->start;
right->ptsCnt += newPts;
right->movable = false;
left->movable = false;
}
static void _beginSubPath(SwStroke& stroke, const SwPoint& to, bool closed)
{
/* We cannot process the first point because there is not enough
information regarding its corner/cap. Later, it will be processed
in the _endSubPath() */
stroke.firstPt = true;
stroke.center = to;
stroke.closedSubPath = closed;
/* Determine if we need to check whether the border radius is greater
than the radius of curvature of a curve, to handle this case specially.
This is only required if bevel joins or butt caps may be created because
round & miter joins and round & square caps cover the nagative sector
created with wide strokes. */
if ((stroke.join != StrokeJoin::Round) || (!stroke.closedSubPath && stroke.cap == StrokeCap::Butt))
stroke.handleWideStrokes = true;
else
stroke.handleWideStrokes = false;
stroke.ptStartSubPath = to;
stroke.angleIn = 0;
}
static void _endSubPath(SwStroke& stroke)
{
if (stroke.closedSubPath) {
//close the path if needed
if (stroke.center != stroke.ptStartSubPath)
_lineTo(stroke, stroke.ptStartSubPath);
//process the corner
stroke.angleOut = stroke.subPathAngle;
auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
//No specific corner processing is required if the turn is 0
if (turn != 0) {
//when we turn to the right, the inside is 0
int32_t inside = 0;
//otherwise, the inside is 1
if (turn < 0) inside = 1;
_inside(stroke, inside, stroke.subPathLineLength); //inside
_outside(stroke, 1 - inside, stroke.subPathLineLength); //outside
}
_borderClose(stroke.borders + 0, false);
_borderClose(stroke.borders + 1, true);
} else {
auto right = stroke.borders;
/* all right, this is an opened path, we need to add a cap between
right & left, add the reverse of left, then add a final cap
between left & right */
_addCap(stroke, stroke.angleIn, 0);
//add reversed points from 'left' to 'right'
_addReverseLeft(stroke, true);
//now add the final cap
stroke.center = stroke.ptStartSubPath;
_addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0);
/* now end the right subpath accordingly. The left one is rewind
and deosn't need further processing */
_borderClose(right, false);
}
}
static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt)
{
auto count = border->ptsCnt;
auto tags = border->tags;
uint32_t _ptsCnt = 0;
uint32_t _cntrsCnt = 0;
bool inCntr = false;
while (count > 0) {
if (tags[0] & SW_STROKE_TAG_BEGIN) {
if (inCntr) goto fail;
inCntr = true;
} else if (!inCntr) goto fail;
if (tags[0] & SW_STROKE_TAG_END) {
inCntr = false;
++_cntrsCnt;
}
--count;
++_ptsCnt;
++tags;
}
if (inCntr) goto fail;
ptsCnt = _ptsCnt;
cntrsCnt = _cntrsCnt;
return;
fail:
ptsCnt = 0;
cntrsCnt = 0;
}
static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
{
auto border = stroke.borders + side;
if (border->ptsCnt == 0) return;
memcpy(outline->pts.data + outline->pts.count, border->pts, border->ptsCnt * sizeof(SwPoint));
auto cnt = border->ptsCnt;
auto src = border->tags;
auto tags = outline->types.data + outline->types.count;
auto idx = outline->pts.count;
while (cnt > 0) {
if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
else TVGERR("SW_ENGINE", "Invalid stroke tag was given! = %d", *src);
if (*src & SW_STROKE_TAG_END) outline->cntrs.push(idx);
++src;
++tags;
++idx;
--cnt;
}
outline->pts.count += border->ptsCnt;
outline->types.count += border->ptsCnt;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
void strokeFree(SwStroke* stroke)
{
if (!stroke) return;
//free borders
if (stroke->borders[0].pts) free(stroke->borders[0].pts);
if (stroke->borders[0].tags) free(stroke->borders[0].tags);
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);
}
void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* transform)
{
if (transform) {
stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f));
stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f));
} else {
stroke->sx = stroke->sy = 1.0f;
}
stroke->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap();
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit() * 65536.0f);
//Save line join: it can be temporarily changed when stroking curves...
stroke->joinSaved = stroke->join = rshape->strokeJoin();
stroke->borders[0].ptsCnt = 0;
stroke->borders[0].start = -1;
stroke->borders[1].ptsCnt = 0;
stroke->borders[1].start = -1;
}
bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
{
uint32_t first = 0;
uint32_t i = 0;
for (auto cntr = outline.cntrs.begin(); cntr < outline.cntrs.end(); ++cntr, ++i) {
auto last = *cntr; //index of last point in contour
auto limit = outline.pts.data + last;
//Skip empty points
if (last <= first) {
first = last + 1;
continue;
}
auto start = outline.pts[first];
auto pt = outline.pts.data + first;
auto types = outline.types.data + first;
auto type = types[0];
//A contour cannot start with a cubic control point
if (type == SW_CURVE_TYPE_CUBIC) return false;
auto closed = outline.closed.data ? outline.closed.data[i]: false;
_beginSubPath(*stroke, start, closed);
while (pt < limit) {
++pt;
++types;
//emit a signel line_to
if (types[0] == SW_CURVE_TYPE_POINT) {
_lineTo(*stroke, *pt);
//types cubic
} else {
if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false;
pt += 2;
types += 2;
if (pt <= limit) {
_cubicTo(*stroke, pt[-2], pt[-1], pt[0]);
continue;
}
_cubicTo(*stroke, pt[-2], pt[-1], start);
goto close;
}
}
close:
if (!stroke->firstPt) _endSubPath(*stroke);
first = last + 1;
}
return true;
}
SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
{
uint32_t count1, count2, count3, count4;
_getCounts(stroke->borders + 0, count1, count2);
_getCounts(stroke->borders + 1, count3, count4);
auto ptsCnt = count1 + count3;
auto cntrsCnt = count2 + count4;
auto outline = mpoolReqStrokeOutline(mpool, tid);
outline->pts.reserve(ptsCnt);
outline->types.reserve(ptsCnt);
outline->cntrs.reserve(cntrsCnt);
_exportBorderOutline(*stroke, outline, 0); //left
_exportBorderOutline(*stroke, outline, 1); //right
return outline;
}