Lottie: optimization

This commit is contained in:
Isaac 2024-05-09 19:21:23 +04:00
parent 3123519821
commit c5e30d509d
78 changed files with 20225 additions and 12 deletions

View File

@ -24,6 +24,7 @@ objc_library(
],
deps = [
"//submodules/TelegramUI/Components/LottieCpp",
"//Tests/LottieMetalTest/thorvg",
],
sdk_frameworks = [
"Foundation",

View File

@ -2,6 +2,7 @@
#import "Canvas.h"
#import "CoreGraphicsCanvasImpl.h"
#import "ThorVGCanvasImpl.h"
#include <LottieCpp/RenderTreeNode.h>
@ -506,8 +507,6 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
processRenderTree(renderNode, lottie::Vector2D((int)size.width, (int)size.height), lottie::CATransform3D::identity().scaled(lottie::Vector2D(size.width / (double)animation.size.width, size.height / (double)animation.size.height)), false, *_bezierPathsBoundingBoxContext.get());
//LottieRenderNode *lottieNode = [_animationContainer getCurrentRenderTreeForSize:size];
if (useReferenceRendering) {
auto context = std::make_shared<lottieRendering::CanvasImpl>((int)size.width, (int)size.height);
@ -520,6 +519,13 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
return [[UIImage alloc] initWithCGImage:std::static_pointer_cast<lottieRendering::ImageImpl>(image)->nativeImage()];
} else {
/*auto context = std::make_shared<lottieRendering::ThorVGCanvasImpl>((int)size.width, (int)size.height);
CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height);
context->concatenate(lottie::CATransform3D::makeScale(scale.x, scale.y, 1.0));
renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), 1.0);*/
return nil;
}
}

View File

@ -0,0 +1,65 @@
#ifndef ThorVGCanvasImpl_h
#define ThorVGCanvasImpl_h
#include "Canvas.h"
#include <thorvg/thorvg.h>
namespace lottieRendering {
class ThorVGCanvasImpl: public Canvas {
public:
ThorVGCanvasImpl(int width, int height);
virtual ~ThorVGCanvasImpl();
virtual int width() const override;
virtual int height() const override;
virtual std::shared_ptr<Canvas> makeLayer(int width, int height) override;
virtual void saveState() override;
virtual void restoreState() override;
virtual void fillPath(std::shared_ptr<lottie::CGPath> const &path, FillRule fillRule, Color const &color) override;
virtual void linearGradientFillPath(std::shared_ptr<lottie::CGPath> const &path, FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override;
virtual void radialGradientFillPath(std::shared_ptr<lottie::CGPath> const &path, FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) override;
virtual void strokePath(std::shared_ptr<lottie::CGPath> const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector<double> const &dashPattern, Color const &color) override;
virtual void linearGradientStrokePath(std::shared_ptr<lottie::CGPath> const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector<double> const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override;
virtual void radialGradientStrokePath(std::shared_ptr<lottie::CGPath> const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector<double> const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) override;
virtual void fill(lottie::CGRect const &rect, Color const &fillColor) override;
virtual void setBlendMode(BlendMode blendMode) override;
virtual void setAlpha(double alpha) override;
virtual void concatenate(lottie::CATransform3D const &transform) override;
virtual void draw(std::shared_ptr<Canvas> const &other, lottie::CGRect const &rect) override;
uint32_t *backingData() {
return _backingData;
}
int bytesPerRow() const {
return _bytesPerRow;
}
void flush();
private:
int _width = 0;
int _height = 0;
std::unique_ptr<tvg::SwCanvas> _canvas;
//SkBlendMode _blendMode = SkBlendMode::kSrcOver;
double _alpha = 1.0;
lottie::CATransform3D _transform;
std::vector<lottie::CATransform3D> _stateStack;
int _bytesPerRow = 0;
uint32_t *_backingData = nullptr;
int _statsNumStrokes = 0;
};
}
#endif

View File

@ -0,0 +1,290 @@
#include "ThorVGCanvasImpl.h"
namespace lottieRendering {
namespace {
void tvgPath(std::shared_ptr<lottie::CGPath> const &path, tvg::Shape *shape) {
path->enumerate([shape](lottie::CGPathItem const &item) {
switch (item.type) {
case lottie::CGPathItem::Type::MoveTo: {
shape->moveTo(item.points[0].x, item.points[0].y);
break;
}
case lottie::CGPathItem::Type::LineTo: {
shape->lineTo(item.points[0].x, item.points[0].y);
break;
}
case lottie::CGPathItem::Type::CurveTo: {
shape->cubicTo(item.points[0].x, item.points[0].y, item.points[1].x, item.points[1].y, item.points[2].x, item.points[2].y);
break;
}
case lottie::CGPathItem::Type::Close: {
shape->close();
break;
}
}
});
}
tvg::Matrix tvgTransform(lottie::CATransform3D const &transform) {
CGAffineTransform affineTransform = CATransform3DGetAffineTransform(lottie::nativeTransform(transform));
tvg::Matrix result;
result.e11 = affineTransform.a;
result.e21 = affineTransform.b;
result.e31 = 0.0f;
result.e12 = affineTransform.c;
result.e22 = affineTransform.d;
result.e32 = 0.0f;
result.e13 = affineTransform.tx;
result.e23 = affineTransform.ty;
result.e33 = 1.0f;
return result;
}
}
ThorVGCanvasImpl::ThorVGCanvasImpl(int width, int height) :
_width(width), _height(height), _transform(lottie::CATransform3D::identity()) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
tvg::Initializer::init(0);
});
_canvas = tvg::SwCanvas::gen();
_bytesPerRow = width * 4;
static uint32_t *sharedBackingData = (uint32_t *)malloc(_bytesPerRow * height);
_backingData = sharedBackingData;
_canvas->target(_backingData, _bytesPerRow / 4, width, height, tvg::SwCanvas::ARGB8888);
}
ThorVGCanvasImpl::~ThorVGCanvasImpl() {
}
int ThorVGCanvasImpl::width() const {
return _width;
}
int ThorVGCanvasImpl::height() const {
return _height;
}
std::shared_ptr<Canvas> ThorVGCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<ThorVGCanvasImpl>(width, height);
}
void ThorVGCanvasImpl::saveState() {
_stateStack.push_back(_transform);
}
void ThorVGCanvasImpl::restoreState() {
if (_stateStack.empty()) {
assert(false);
return;
}
_transform = _stateStack[_stateStack.size() - 1];
_stateStack.pop_back();
}
void ThorVGCanvasImpl::fillPath(std::shared_ptr<lottie::CGPath> const &path, FillRule fillRule, Color const &color) {
auto shape = tvg::Shape::gen();
tvgPath(path, shape.get());
shape->transform(tvgTransform(_transform));
shape->fill((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0));
shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding);
_canvas->push(std::move(shape));
}
void ThorVGCanvasImpl::linearGradientFillPath(std::shared_ptr<lottie::CGPath> const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) {
auto shape = tvg::Shape::gen();
tvgPath(path, shape.get());
shape->transform(tvgTransform(_transform));
auto fill = tvg::LinearGradient::gen();
fill->linear(start.x, start.y, end.x, end.y);
std::vector<tvg::Fill::ColorStop> colors;
for (size_t i = 0; i < gradient.colors().size(); i++) {
const auto &color = gradient.colors()[i];
tvg::Fill::ColorStop colorStop;
colorStop.offset = gradient.locations()[i];
colorStop.r = (int)(color.r * 255.0);
colorStop.g = (int)(color.g * 255.0);
colorStop.b = (int)(color.b * 255.0);
colorStop.a = (int)(color.a * _alpha * 255.0);
colors.push_back(colorStop);
}
fill->colorStops(colors.data(), (uint32_t)colors.size());
shape->fill(std::move(fill));
shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding);
_canvas->push(std::move(shape));
}
void ThorVGCanvasImpl::radialGradientFillPath(std::shared_ptr<lottie::CGPath> const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) {
auto shape = tvg::Shape::gen();
tvgPath(path, shape.get());
shape->transform(tvgTransform(_transform));
auto fill = tvg::RadialGradient::gen();
fill->radial(startCenter.x, startCenter.y, endRadius);
std::vector<tvg::Fill::ColorStop> colors;
for (size_t i = 0; i < gradient.colors().size(); i++) {
const auto &color = gradient.colors()[i];
tvg::Fill::ColorStop colorStop;
colorStop.offset = gradient.locations()[i];
colorStop.r = (int)(color.r * 255.0);
colorStop.g = (int)(color.g * 255.0);
colorStop.b = (int)(color.b * 255.0);
colorStop.a = (int)(color.a * _alpha * 255.0);
colors.push_back(colorStop);
}
fill->colorStops(colors.data(), (uint32_t)colors.size());
shape->fill(std::move(fill));
shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding);
_canvas->push(std::move(shape));
}
void ThorVGCanvasImpl::strokePath(std::shared_ptr<lottie::CGPath> const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector<double> const &dashPattern, Color const &color) {
auto shape = tvg::Shape::gen();
tvgPath(path, shape.get());
shape->transform(tvgTransform(_transform));
shape->strokeFill((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0));
shape->strokeWidth(lineWidth);
switch (lineJoin) {
case LineJoin::Miter: {
shape->strokeJoin(tvg::StrokeJoin::Miter);
break;
}
case LineJoin::Round: {
shape->strokeJoin(tvg::StrokeJoin::Round);
break;
}
case LineJoin::Bevel: {
shape->strokeJoin(tvg::StrokeJoin::Bevel);
break;
}
default: {
shape->strokeJoin(tvg::StrokeJoin::Bevel);
break;
}
}
switch (lineCap) {
case LineCap::Butt: {
shape->strokeCap(tvg::StrokeCap::Butt);
break;
}
case LineCap::Round: {
shape->strokeCap(tvg::StrokeCap::Round);
break;
}
case LineCap::Square: {
shape->strokeCap(tvg::StrokeCap::Square);
break;
}
default: {
shape->strokeCap(tvg::StrokeCap::Square);
break;
}
}
if (!dashPattern.empty()) {
std::vector<float> intervals;
intervals.reserve(dashPattern.size());
for (auto value : dashPattern) {
intervals.push_back(value);
}
shape->strokeDash(intervals.data(), (uint32_t)intervals.size());
//TODO:phase
}
_canvas->push(std::move(shape));
}
void ThorVGCanvasImpl::linearGradientStrokePath(std::shared_ptr<lottie::CGPath> const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector<double> const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) {
assert(false);
}
void ThorVGCanvasImpl::radialGradientStrokePath(std::shared_ptr<lottie::CGPath> const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector<double> const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) {
assert(false);
}
void ThorVGCanvasImpl::fill(lottie::CGRect const &rect, Color const &fillColor) {
auto shape = tvg::Shape::gen();
shape->appendRect(rect.x, rect.y, rect.width, rect.height, 0.0f, 0.0f);
shape->transform(tvgTransform(_transform));
shape->fill((int)(fillColor.r * 255.0), (int)(fillColor.g * 255.0), (int)(fillColor.b * 255.0), (int)(fillColor.a * _alpha * 255.0));
_canvas->push(std::move(shape));
}
void ThorVGCanvasImpl::setBlendMode(BlendMode blendMode) {
/*switch (blendMode) {
case CGBlendMode::Normal: {
_blendMode = SkBlendMode::kSrcOver;
break;
}
case CGBlendMode::DestinationIn: {
_blendMode = SkBlendMode::kDstIn;
break;
}
case CGBlendMode::DestinationOut: {
_blendMode = SkBlendMode::kDstOut;
break;
}
default: {
_blendMode = SkBlendMode::kSrcOver;
break;
}
}*/
}
void ThorVGCanvasImpl::setAlpha(double alpha) {
_alpha = alpha;
}
void ThorVGCanvasImpl::concatenate(lottie::CATransform3D const &transform) {
_transform = transform * _transform;
/*_canvas->concat(SkM44(
transform.m11, transform.m21, transform.m31, transform.m41,
transform.m12, transform.m22, transform.m32, transform.m42,
transform.m13, transform.m23, transform.m33, transform.m43,
transform.m14, transform.m24, transform.m34, transform.m44
));*/
}
void ThorVGCanvasImpl::draw(std::shared_ptr<Canvas> const &other, lottie::CGRect const &rect) {
/*ThorVGCanvasImpl *impl = (ThorVGCanvasImpl *)other.get();
auto image = impl->surface()->makeImageSnapshot();
SkPaint paint;
paint.setBlendMode(_blendMode);
paint.setAlphaf(_alpha);
_canvas->drawImageRect(image.get(), SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), SkSamplingOptions(SkFilterMode::kLinear), &paint);*/
}
void ThorVGCanvasImpl::flush() {
_canvas->draw();
_canvas->sync();
_statsNumStrokes = 0;
}
}

View File

@ -113,6 +113,8 @@ public final class ViewController: UIViewController {
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
let filePath = bundlePath + "/fireworks.json"
let performanceFrameSize = 8
self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
if "".isEmpty {
@ -153,12 +155,14 @@ public final class ViewController: UIViewController {
animationContainer.update(0)
print("Build time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
let animationRenderer = SoftwareLottieRenderer(animationContainer: animationContainer)
startTime = CFAbsoluteTimeGetCurrent()
var numUpdates: Int = 0
var frameIndex = 0
while true {
animationContainer.update(frameIndex)
let _ = animationContainer.getCurrentRenderTree(for: CGSize(width: 512.0, height: 512.0))
let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false)
frameIndex = (frameIndex + 1) % animationContainer.animation.frameCount
numUpdates += 1
let timestamp = CFAbsoluteTimeGetCurrent()
@ -177,8 +181,7 @@ public final class ViewController: UIViewController {
let animationInstance = LottieInstance(data: try! Data(contentsOf: URL(fileURLWithPath: filePath)), fitzModifier: .none, colorReplacements: nil, cacheKey: "")!
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
let frameSize = 8
let frameBuffer = malloc(frameSize * 4 * frameSize)!
let frameBuffer = malloc(performanceFrameSize * 4 * performanceFrameSize)!
defer {
free(frameBuffer)
}
@ -187,7 +190,7 @@ public final class ViewController: UIViewController {
var numUpdates: Int = 0
var frameIndex = 0
while true {
animationInstance.renderFrame(with: Int32(frameIndex), into: frameBuffer, width: Int32(frameSize), height: Int32(frameSize), bytesPerRow: Int32(frameSize * 4))
animationInstance.renderFrame(with: Int32(frameIndex), into: frameBuffer, width: Int32(performanceFrameSize), height: Int32(performanceFrameSize), bytesPerRow: Int32(performanceFrameSize * 4))
frameIndex = (frameIndex + 1) % Int(animationInstance.frameCount)
numUpdates += 1

View File

@ -0,0 +1,39 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
objc_library(
name = "thorvg",
enable_modules = True,
module_name = "thorvg",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.mm",
"Sources/**/*.h",
"Sources/**/*.c",
"Sources/**/*.cpp",
"Sources/**/*.hpp",
]),
copts = [
"-Werror",
"-I{}/Sources".format(package_name()),
"-I{}/PublicHeaders/thorvg".format(package_name()),
"-I{}/Sources/common".format(package_name()),
"-I{}/Sources/loaders/raw".format(package_name()),
"-I{}/Sources/loaders/tvg".format(package_name()),
"-I{}/Sources/renderer".format(package_name()),
"-I{}/Sources/renderer/sw_engine".format(package_name()),
],
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
deps = [
],
sdk_frameworks = [
"Foundation",
],
visibility = [
"//visibility:public",
],
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
/*
* 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_ARRAY_H_
#define _TVG_ARRAY_H_
#include <memory.h>
#include <cstdint>
#include <cstdlib>
namespace tvg
{
template<class T>
struct Array
{
T* data = nullptr;
uint32_t count = 0;
uint32_t reserved = 0;
Array(){}
Array(int32_t size)
{
reserve(size);
}
Array(const Array& rhs)
{
reset();
*this = rhs;
}
void push(T element)
{
if (count + 1 > reserved) {
reserved = count + (count + 2) / 2;
data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
}
data[count++] = element;
}
void push(Array<T>& rhs)
{
if (rhs.count == 0) return;
grow(rhs.count);
memcpy(data + count, rhs.data, rhs.count * sizeof(T));
count += rhs.count;
}
bool reserve(uint32_t size)
{
if (size > reserved) {
reserved = size;
data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
}
return true;
}
bool grow(uint32_t size)
{
return reserve(count + size);
}
const T& operator[](size_t idx) const
{
return data[idx];
}
T& operator[](size_t idx)
{
return data[idx];
}
const T* begin() const
{
return data;
}
T* begin()
{
return data;
}
T* end()
{
return data + count;
}
const T* end() const
{
return data + count;
}
const T& last() const
{
return data[count - 1];
}
const T& first() const
{
return data[0];
}
T& last()
{
return data[count - 1];
}
T& first()
{
return data[0];
}
void pop()
{
if (count > 0) --count;
}
void reset()
{
free(data);
data = nullptr;
count = reserved = 0;
}
void clear()
{
count = 0;
}
bool empty() const
{
return count == 0;
}
template<class COMPARE>
void sort()
{
qsort<COMPARE>(data, 0, static_cast<int32_t>(count) - 1);
}
void operator=(const Array& rhs)
{
reserve(rhs.count);
if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * rhs.count);
count = rhs.count;
}
~Array()
{
free(data);
}
private:
template<class COMPARE>
void qsort(T* arr, int32_t low, int32_t high)
{
if (low < high) {
int32_t i = low;
int32_t j = high;
T tmp = arr[low];
while (i < j) {
while (i < j && !COMPARE{}(arr[j], tmp)) --j;
if (i < j) {
arr[i] = arr[j];
++i;
}
while (i < j && COMPARE{}(arr[i], tmp)) ++i;
if (i < j) {
arr[j] = arr[i];
--j;
}
}
arr[i] = tmp;
qsort<COMPARE>(arr, low, i - 1);
qsort<COMPARE>(arr, i + 1, high);
}
}
};
}
#endif //_TVG_ARRAY_H_

View File

@ -0,0 +1,475 @@
/*
* 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.
*/
/*
* LempelZivWelch (LZW) encoder/decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com)
* This is the compression scheme used by the GIF image format and the Unix 'compress' tool.
* Main differences from this implementation is that End Of Input (EOI) and Clear Codes (CC)
* are not stored in the output and the max code length in bits is 12, vs 16 in compress.
*
* EOI is simply detected by the end of the data stream, while CC happens if the
* dictionary gets filled. Data is written/read from bit streams, which handle
* byte-alignment for us in a transparent way.
* The decoder relies on the hardcoded data layout produced by the encoder, since
* no additional reconstruction data is added to the output, so they must match.
* The nice thing about LZW is that we can reconstruct the dictionary directly from
* the stream of codes generated by the encoder, so this avoids storing additional
* headers in the bit stream.
* The output code length is variable. It starts with the minimum number of bits
* required to store the base byte-sized dictionary and automatically increases
* as the dictionary gets larger (it starts at 9-bits and grows to 10-bits when
* code 512 is added, then 11-bits when 1024 is added, and so on). If the dictionary
* is filled (4096 items for a 12-bits dictionary), the whole thing is cleared and
* the process starts over. This is the main reason why the encoder and the decoder
* must match perfectly, since the lengths of the codes will not be specified with
* the data itself.
* USEFUL LINKS:
* https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch
* http://rosettacode.org/wiki/LZW_compression
* http://www.cs.duke.edu/csed/curious/compression/lzw.html
* http://www.cs.cf.ac.uk/Dave/Multimedia/node214.html
* http://marknelson.us/1989/10/01/lzw-data-compression/
*/
#include "config.h"
#include <string>
#include <memory.h>
#include "tvgCompressor.h"
namespace tvg {
/************************************************************************/
/* LZW Implementation */
/************************************************************************/
//LZW Dictionary helper:
constexpr int Nil = -1;
constexpr int MaxDictBits = 12;
constexpr int StartBits = 9;
constexpr int FirstCode = (1 << (StartBits - 1)); // 256
constexpr int MaxDictEntries = (1 << MaxDictBits); // 4096
//Round up to the next power-of-two number, e.g. 37 => 64
static int nextPowerOfTwo(int num)
{
--num;
for (size_t i = 1; i < sizeof(num) * 8; i <<= 1) {
num = num | num >> i;
}
return ++num;
}
struct BitStreamWriter
{
uint8_t* stream; //Growable buffer to store our bits. Heap allocated & owned by the class instance.
int bytesAllocated; //Current size of heap-allocated stream buffer *in bytes*.
int granularity; //Amount bytesAllocated multiplies by when auto-resizing in appendBit().
int currBytePos; //Current byte being written to, from 0 to bytesAllocated-1.
int nextBitPos; //Bit position within the current byte to access next. 0 to 7.
int numBitsWritten; //Number of bits in use from the stream buffer, not including byte-rounding padding.
void internalInit()
{
stream = nullptr;
bytesAllocated = 0;
granularity = 2;
currBytePos = 0;
nextBitPos = 0;
numBitsWritten = 0;
}
uint8_t* allocBytes(const int bytesWanted, uint8_t * oldPtr, const int oldSize)
{
auto newMemory = static_cast<uint8_t *>(malloc(bytesWanted));
memset(newMemory, 0, bytesWanted);
if (oldPtr) {
memcpy(newMemory, oldPtr, oldSize);
free(oldPtr);
}
return newMemory;
}
BitStreamWriter()
{
/* 8192 bits for a start (1024 bytes). It will resize if needed.
Default granularity is 2. */
internalInit();
allocate(8192);
}
BitStreamWriter(const int initialSizeInBits, const int growthGranularity = 2)
{
internalInit();
setGranularity(growthGranularity);
allocate(initialSizeInBits);
}
~BitStreamWriter()
{
free(stream);
}
void allocate(int bitsWanted)
{
//Require at least a byte.
if (bitsWanted <= 0) bitsWanted = 8;
//Round upwards if needed:
if ((bitsWanted % 8) != 0) bitsWanted = nextPowerOfTwo(bitsWanted);
//We might already have the required count.
const int sizeInBytes = bitsWanted / 8;
if (sizeInBytes <= bytesAllocated) return;
stream = allocBytes(sizeInBytes, stream, bytesAllocated);
bytesAllocated = sizeInBytes;
}
void appendBit(const int bit)
{
const uint32_t mask = uint32_t(1) << nextBitPos;
stream[currBytePos] = (stream[currBytePos] & ~mask) | (-bit & mask);
++numBitsWritten;
if (++nextBitPos == 8) {
nextBitPos = 0;
if (++currBytePos == bytesAllocated) allocate(bytesAllocated * granularity * 8);
}
}
void appendBitsU64(const uint64_t num, const int bitCount)
{
for (int b = 0; b < bitCount; ++b) {
const uint64_t mask = uint64_t(1) << b;
const int bit = !!(num & mask);
appendBit(bit);
}
}
uint8_t* release()
{
auto oldPtr = stream;
internalInit();
return oldPtr;
}
void setGranularity(const int growthGranularity)
{
granularity = (growthGranularity >= 2) ? growthGranularity : 2;
}
int getByteCount() const
{
int usedBytes = numBitsWritten / 8;
int leftovers = numBitsWritten % 8;
if (leftovers != 0) ++usedBytes;
return usedBytes;
}
};
struct BitStreamReader
{
const uint8_t* stream; // Pointer to the external bit stream. Not owned by the reader.
const int sizeInBytes; // Size of the stream *in bytes*. Might include padding.
const int sizeInBits; // Size of the stream *in bits*, padding *not* include.
int currBytePos = 0; // Current byte being read in the stream.
int nextBitPos = 0; // Bit position within the current byte to access next. 0 to 7.
int numBitsRead = 0; // Total bits read from the stream so far. Never includes byte-rounding padding.
BitStreamReader(const uint8_t* bitStream, const int byteCount, const int bitCount) : stream(bitStream), sizeInBytes(byteCount), sizeInBits(bitCount)
{
}
bool readNextBit(int& bitOut)
{
if (numBitsRead >= sizeInBits) return false; //We are done.
const uint32_t mask = uint32_t(1) << nextBitPos;
bitOut = !!(stream[currBytePos] & mask);
++numBitsRead;
if (++nextBitPos == 8) {
nextBitPos = 0;
++currBytePos;
}
return true;
}
uint64_t readBitsU64(const int bitCount)
{
uint64_t num = 0;
for (int b = 0; b < bitCount; ++b) {
int bit;
if (!readNextBit(bit)) break;
/* Based on a "Stanford bit-hack":
http://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching */
const uint64_t mask = uint64_t(1) << b;
num = (num & ~mask) | (-bit & mask);
}
return num;
}
bool isEndOfStream() const
{
return numBitsRead >= sizeInBits;
}
};
struct Dictionary
{
struct Entry
{
int code;
int value;
};
//Dictionary entries 0-255 are always reserved to the byte/ASCII range.
int size;
Entry entries[MaxDictEntries];
Dictionary()
{
/* First 256 dictionary entries are reserved to the byte/ASCII range.
Additional entries follow for the character sequences found in the input.
Up to 4096 - 256 (MaxDictEntries - FirstCode). */
size = FirstCode;
for (int i = 0; i < size; ++i) {
entries[i].code = Nil;
entries[i].value = i;
}
}
int findIndex(const int code, const int value) const
{
if (code == Nil) return value;
//Linear search for now.
//TODO: Worth optimizing with a proper hash-table?
for (int i = 0; i < size; ++i) {
if (entries[i].code == code && entries[i].value == value) return i;
}
return Nil;
}
bool add(const int code, const int value)
{
if (size == MaxDictEntries) return false;
entries[size].code = code;
entries[size].value = value;
++size;
return true;
}
bool flush(int & codeBitsWidth)
{
if (size == (1 << codeBitsWidth)) {
++codeBitsWidth;
if (codeBitsWidth > MaxDictBits) {
//Clear the dictionary (except the first 256 byte entries).
codeBitsWidth = StartBits;
size = FirstCode;
return true;
}
}
return false;
}
};
static bool outputByte(int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar)
{
if (bytesDecodedSoFar >= outputSizeBytes) return false;
*output++ = static_cast<uint8_t>(code);
++bytesDecodedSoFar;
return true;
}
static bool outputSequence(const Dictionary& dict, int code, uint8_t*& output, int outputSizeBytes, int& bytesDecodedSoFar, int& firstByte)
{
/* A sequence is stored backwards, so we have to write
it to a temp then output the buffer in reverse. */
int i = 0;
uint8_t sequence[MaxDictEntries];
do {
sequence[i++] = dict.entries[code].value;
code = dict.entries[code].code;
} while (code >= 0);
firstByte = sequence[--i];
for (; i >= 0; --i) {
if (!outputByte(sequence[i], output, outputSizeBytes, bytesDecodedSoFar)) return false;
}
return true;
}
uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes)
{
int code = Nil;
int prevCode = Nil;
int firstByte = 0;
int bytesDecoded = 0;
int codeBitsWidth = StartBits;
auto uncompressed = (uint8_t*) malloc(sizeof(uint8_t) * uncompressedSizeBytes);
auto ptr = uncompressed;
/* We'll reconstruct the dictionary based on the bit stream codes.
Unlike Huffman encoding, we don't store the dictionary as a prefix to the data. */
Dictionary dictionary;
BitStreamReader bitStream(compressed, compressedSizeBytes, compressedSizeBits);
/* We check to avoid an overflow of the user buffer.
If the buffer is smaller than the decompressed size, we break the loop and return the current decompression count. */
while (!bitStream.isEndOfStream()) {
code = static_cast<int>(bitStream.readBitsU64(codeBitsWidth));
if (prevCode == Nil) {
if (!outputByte(code, ptr, uncompressedSizeBytes, bytesDecoded)) break;
firstByte = code;
prevCode = code;
continue;
}
if (code >= dictionary.size) {
if (!outputSequence(dictionary, prevCode, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break;
if (!outputByte(firstByte, ptr, uncompressedSizeBytes, bytesDecoded)) break;
} else if (!outputSequence(dictionary, code, ptr, uncompressedSizeBytes, bytesDecoded, firstByte)) break;
dictionary.add(prevCode, firstByte);
if (dictionary.flush(codeBitsWidth)) prevCode = Nil;
else prevCode = code;
}
return uncompressed;
}
uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits)
{
//LZW encoding context:
int code = Nil;
int codeBitsWidth = StartBits;
Dictionary dictionary;
//Output bit stream we write to. This will allocate memory as needed to accommodate the encoded data.
BitStreamWriter bitStream;
for (; uncompressedSizeBytes > 0; --uncompressedSizeBytes, ++uncompressed) {
const int value = *uncompressed;
const int index = dictionary.findIndex(code, value);
if (index != Nil) {
code = index;
continue;
}
//Write the dictionary code using the minimum bit-with:
bitStream.appendBitsU64(code, codeBitsWidth);
//Flush it when full so we can restart the sequences.
if (!dictionary.flush(codeBitsWidth)) {
//There's still space for this sequence.
dictionary.add(code, value);
}
code = value;
}
//Residual code at the end:
if (code != Nil) bitStream.appendBitsU64(code, codeBitsWidth);
//Pass ownership of the compressed data buffer to the user pointer:
*compressedSizeBytes = bitStream.getByteCount();
*compressedSizeBits = bitStream.numBitsWritten;
return bitStream.release();
}
/************************************************************************/
/* B64 Implementation */
/************************************************************************/
size_t b64Decode(const char* encoded, const size_t len, char** decoded)
{
static constexpr const char B64_INDEX[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
if (!decoded || !encoded || len == 0) return 0;
auto reserved = 3 * (1 + (len >> 2)) + 1;
auto output = static_cast<char*>(malloc(reserved * sizeof(char)));
if (!output) return 0;
output[reserved - 1] = '\0';
size_t idx = 0;
while (*encoded && *(encoded + 1)) {
if (*encoded <= 0x20) {
++encoded;
continue;
}
auto value1 = B64_INDEX[(size_t)encoded[0]];
auto value2 = B64_INDEX[(size_t)encoded[1]];
output[idx++] = (value1 << 2) + ((value2 & 0x30) >> 4);
if (!encoded[2] || encoded[3] < 0 || encoded[2] == '=' || encoded[2] == '.') break;
auto value3 = B64_INDEX[(size_t)encoded[2]];
output[idx++] = ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2);
if (!encoded[3] || encoded[3] < 0 || encoded[3] == '=' || encoded[3] == '.') break;
auto value4 = B64_INDEX[(size_t)encoded[3]];
output[idx++] = ((value3 & 0x03) << 6) + value4;
encoded += 4;
}
*decoded = output;
return reserved;
}
}

View File

@ -0,0 +1,35 @@
/*
* 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_COMPRESSOR_H_
#define _TVG_COMPRESSOR_H_
#include <cstdint>
namespace tvg
{
uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits);
uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes);
size_t b64Decode(const char* encoded, const size_t len, char** decoded);
}
#endif //_TVG_COMPRESSOR_H_

View File

@ -0,0 +1,95 @@
/*
* 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.
*/
#ifndef _TVG_FORMAT_H_
#define _TVG_FORMAT_H_
/* TODO: Need to consider whether uin8_t is enough size for extension...
Rather than optimal data, we can use enough size and data compress? */
using TvgBinByte = uint8_t;
using TvgBinCounter = uint32_t;
using TvgBinTag = TvgBinByte;
using TvgBinFlag = TvgBinByte;
//Header
#define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE
#define TVG_HEADER_SIGNATURE "ThorVG"
#define TVG_HEADER_SIGNATURE_LENGTH 6
#define TVG_HEADER_VERSION "010000" //Major 01, Minor 00, Micro 00
#define TVG_HEADER_VERSION_LENGTH 6
#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions
#define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS
//Compress Size
#define TVG_HEADER_UNCOMPRESSED_SIZE 4 //SIZE (TvgBinCounter)
#define TVG_HEADER_COMPRESSED_SIZE 4 //SIZE (TvgBinCounter)
#define TVG_HEADER_COMPRESSED_SIZE_BITS 4 //SIZE (TvgBinCounter)
//Reserved Flag
#define TVG_HEAD_FLAG_COMPRESSED 0x01
//Paint Type
#define TVG_TAG_CLASS_PICTURE (TvgBinTag)0xfc
#define TVG_TAG_CLASS_SHAPE (TvgBinTag)0xfd
#define TVG_TAG_CLASS_SCENE (TvgBinTag)0xfe
//Paint
#define TVG_TAG_PAINT_OPACITY (TvgBinTag)0x10
#define TVG_TAG_PAINT_TRANSFORM (TvgBinTag)0x11
#define TVG_TAG_PAINT_CMP_TARGET (TvgBinTag)0x01
#define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20
//Shape
#define TVG_TAG_SHAPE_PATH (TvgBinTag)0x40
#define TVG_TAG_SHAPE_STROKE (TvgBinTag)0x41
#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42
#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43
#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44
//Stroke
#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50
#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51
#define TVG_TAG_SHAPE_STROKE_WIDTH (TvgBinTag)0x52
#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53
#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54
#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55
#define TVG_TAG_SHAPE_STROKE_MITERLIMIT (TvgBinTag)0x56
#define TVG_TAG_SHAPE_STROKE_ORDER (TvgBinTag)0x57
#define TVG_TAG_SHAPE_STROKE_DASH_OFFSET (TvgBinTag)0x58
//Fill
#define TVG_TAG_FILL_LINEAR_GRADIENT (TvgBinTag)0x60
#define TVG_TAG_FILL_RADIAL_GRADIENT (TvgBinTag)0x61
#define TVG_TAG_FILL_COLORSTOPS (TvgBinTag)0x62
#define TVG_TAG_FILL_FILLSPREAD (TvgBinTag)0x63
#define TVG_TAG_FILL_TRANSFORM (TvgBinTag)0x64
#define TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL (TvgBinTag)0x65
//Picture
#define TVG_TAG_PICTURE_RAW_IMAGE (TvgBinTag)0x70
#define TVG_TAG_PICTURE_MESH (TvgBinTag)0x71
#endif //_TVG_FORMAT_H_

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2023 - 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_INLIST_H_
#define _TVG_INLIST_H_
namespace tvg {
//NOTE: declare this in your list item
#define INLIST_ITEM(T) \
T* prev; \
T* next
template<typename T>
struct Inlist
{
T* head = nullptr;
T* tail = nullptr;
void free()
{
while (head) {
auto t = head;
head = t->next;
delete(t);
}
head = tail = nullptr;
}
void back(T* element)
{
if (tail) {
tail->next = element;
element->prev = tail;
element->next = nullptr;
tail = element;
} else {
head = tail = element;
element->prev = nullptr;
element->next = nullptr;
}
}
void front(T* element)
{
if (head) {
head->prev = element;
element->prev = nullptr;
element->next = head;
head = element;
} else {
head = tail = element;
element->prev = nullptr;
element->next = nullptr;
}
}
T* back()
{
if (!tail) return nullptr;
auto t = tail;
tail = t->prev;
if (!tail) head = nullptr;
return t;
}
T* front()
{
if (!head) return nullptr;
auto t = head;
head = t->next;
if (!head) tail = nullptr;
return t;
}
void remove(T* element)
{
if (element->prev) element->prev->next = element->next;
if (element->next) element->next->prev = element->prev;
if (element == head) head = element->next;
if (element == tail) tail = element->prev;
}
bool empty()
{
return head ? false : true;
}
};
}
#endif // _TVG_INLIST_H_

View File

@ -0,0 +1,245 @@
/*
* 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 "tvgLines.h"
#define BEZIER_EPSILON 1e-2f
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static float _lineLengthApprox(const Point& pt1, const Point& pt2)
{
/* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm.
With alpha = 1, beta = 3/8, giving results with the largest error less
than 7% compared to the exact value. */
Point diff = {pt2.x - pt1.x, pt2.y - pt1.y};
if (diff.x < 0) diff.x = -diff.x;
if (diff.y < 0) diff.y = -diff.y;
return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f);
}
static float _lineLength(const Point& pt1, const Point& pt2)
{
Point diff = {pt2.x - pt1.x, pt2.y - pt1.y};
return sqrtf(diff.x * diff.x + diff.y * diff.y);
}
template<typename LengthFunc>
float _bezLength(const Bezier& cur, LengthFunc lineLengthFunc)
{
Bezier left, right;
auto len = lineLengthFunc(cur.start, cur.ctrl1) + lineLengthFunc(cur.ctrl1, cur.ctrl2) + lineLengthFunc(cur.ctrl2, cur.end);
auto chord = lineLengthFunc(cur.start, cur.end);
if (fabsf(len - chord) > BEZIER_EPSILON) {
tvg::bezSplit(cur, left, right);
return _bezLength(left, lineLengthFunc) + _bezLength(right, lineLengthFunc);
}
return len;
}
template<typename LengthFunc>
float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc)
{
auto biggest = 1.0f;
auto smallest = 0.0f;
auto t = 0.5f;
//just in case to prevent an infinite loop
if (at <= 0) return 0.0f;
if (at >= length) return 1.0f;
while (true) {
auto right = bz;
Bezier left;
bezSplitLeft(right, t, left);
length = _bezLength(left, lineLengthFunc);
if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
break;
}
if (length < at) {
smallest = t;
t = (t + biggest) * 0.5f;
} else {
biggest = t;
t = (smallest + t) * 0.5f;
}
}
return t;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
namespace tvg
{
float lineLength(const Point& pt1, const Point& pt2)
{
return _lineLength(pt1, pt2);
}
void lineSplitAt(const Line& cur, float at, Line& left, Line& right)
{
auto len = lineLength(cur.pt1, cur.pt2);
auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at;
auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at;
left.pt1 = cur.pt1;
left.pt2.x = left.pt1.x + dx;
left.pt2.y = left.pt1.y + dy;
right.pt1 = left.pt2;
right.pt2 = cur.pt2;
}
void bezSplit(const Bezier& cur, Bezier& left, Bezier& right)
{
auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f;
left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f;
right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f;
left.start.x = cur.start.x;
right.end.x = cur.end.x;
left.ctrl2.x = (left.ctrl1.x + c) * 0.5f;
right.ctrl1.x = (right.ctrl2.x + c) * 0.5f;
left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f;
c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f;
left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f;
right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f;
left.start.y = cur.start.y;
right.end.y = cur.end.y;
left.ctrl2.y = (left.ctrl1.y + c) * 0.5f;
right.ctrl1.y = (right.ctrl2.y + c) * 0.5f;
left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f;
}
float bezLength(const Bezier& cur)
{
return _bezLength(cur, _lineLength);
}
float bezLengthApprox(const Bezier& cur)
{
return _bezLength(cur, _lineLengthApprox);
}
void bezSplitLeft(Bezier& cur, float at, Bezier& left)
{
left.start = cur.start;
left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x);
left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y);
left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); //temporary holding spot
left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); //temporary holding spot
cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x);
cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y);
cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x);
cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y);
left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x);
left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y);
left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x);
left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y);
}
float bezAt(const Bezier& bz, float at, float length)
{
return _bezAt(bz, at, length, _lineLength);
}
float bezAtApprox(const Bezier& bz, float at, float length)
{
return _bezAt(bz, at, length, _lineLengthApprox);
}
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right)
{
right = cur;
auto t = bezAt(right, at, bezLength(right));
bezSplitLeft(right, t, left);
}
Point bezPointAt(const Bezier& bz, float t)
{
Point cur;
auto it = 1.0f - t;
auto ax = bz.start.x * it + bz.ctrl1.x * t;
auto bx = bz.ctrl1.x * it + bz.ctrl2.x * t;
auto cx = bz.ctrl2.x * it + bz.end.x * t;
ax = ax * it + bx * t;
bx = bx * it + cx * t;
cur.x = ax * it + bx * t;
float ay = bz.start.y * it + bz.ctrl1.y * t;
float by = bz.ctrl1.y * it + bz.ctrl2.y * t;
float cy = bz.ctrl2.y * it + bz.end.y * t;
ay = ay * it + by * t;
by = by * it + cy * t;
cur.y = ay * it + by * t;
return cur;
}
float bezAngleAt(const Bezier& bz, float t)
{
if (t < 0 || t > 1) return 0;
//derivate
// p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 *
// t^2) * p2 + t^2 * p3)
float mt = 1.0f - t;
float d = t * t;
float a = -mt * mt;
float b = 1 - 4 * t + 3 * d;
float c = 2 * t - 3 * d;
Point pt ={a * bz.start.x + b * bz.ctrl1.x + c * bz.ctrl2.x + d * bz.end.x, a * bz.start.y + b * bz.ctrl1.y + c * bz.ctrl2.y + d * bz.end.y};
pt.x *= 3;
pt.y *= 3;
return mathRad2Deg(atan2(pt.x, pt.y));
}
}

View File

@ -0,0 +1,61 @@
/*
* 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_LINES_H_
#define _TVG_LINES_H_
#include "tvgCommon.h"
namespace tvg
{
struct Line
{
Point pt1;
Point pt2;
};
float lineLength(const Point& pt1, const Point& pt2);
void lineSplitAt(const Line& cur, float at, Line& left, Line& right);
struct Bezier
{
Point start;
Point ctrl1;
Point ctrl2;
Point end;
};
void bezSplit(const Bezier&cur, Bezier& left, Bezier& right);
float bezLength(const Bezier& cur);
void bezSplitLeft(Bezier& cur, float at, Bezier& left);
float bezAt(const Bezier& bz, float at, float length);
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right);
Point bezPointAt(const Bezier& bz, float t);
float bezAngleAt(const Bezier& bz, float t);
float bezLengthApprox(const Bezier& cur);
float bezAtApprox(const Bezier& bz, float at, float length);
}
#endif //_TVG_LINES_H_

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_LOCK_H_
#define _TVG_LOCK_H_
#ifdef THORVG_THREAD_SUPPORT
#include <mutex>
namespace tvg {
struct Key
{
std::mutex mtx;
};
struct ScopedLock
{
Key* key = nullptr;
ScopedLock(Key& k)
{
k.mtx.lock();
key = &k;
}
~ScopedLock()
{
key->mtx.unlock();
}
};
}
#else //THORVG_THREAD_SUPPORT
namespace tvg {
struct Key {};
struct ScopedLock
{
ScopedLock(Key& key) {}
};
}
#endif //THORVG_THREAD_SUPPORT
#endif //_TVG_LOCK_H_

View File

@ -0,0 +1,102 @@
/*
* 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.
*/
#include "tvgMath.h"
bool mathInverse(const Matrix* m, Matrix* out)
{
auto det = m->e11 * (m->e22 * m->e33 - m->e32 * m->e23) -
m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) +
m->e13 * (m->e21 * m->e32 - m->e22 * m->e31);
if (mathZero(det)) return false;
auto invDet = 1 / det;
out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet;
out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet;
out->e13 = (m->e12 * m->e23 - m->e13 * m->e22) * invDet;
out->e21 = (m->e23 * m->e31 - m->e21 * m->e33) * invDet;
out->e22 = (m->e11 * m->e33 - m->e13 * m->e31) * invDet;
out->e23 = (m->e21 * m->e13 - m->e11 * m->e23) * invDet;
out->e31 = (m->e21 * m->e32 - m->e31 * m->e22) * invDet;
out->e32 = (m->e31 * m->e12 - m->e11 * m->e32) * invDet;
out->e33 = (m->e11 * m->e22 - m->e21 * m->e12) * invDet;
return true;
}
Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs)
{
Matrix m;
m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31;
m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32;
m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33;
m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31;
m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32;
m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33;
m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31;
m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32;
m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33;
return m;
}
void mathRotate(Matrix* m, float degree)
{
if (degree == 0.0f) return;
auto radian = degree / 180.0f * MATH_PI;
auto cosVal = cosf(radian);
auto sinVal = sinf(radian);
m->e12 = m->e11 * -sinVal;
m->e11 *= cosVal;
m->e21 = m->e22 * sinVal;
m->e22 *= cosVal;
}
bool mathIdentity(const Matrix* m)
{
if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f ||
m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f ||
m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) {
return false;
}
return true;
}
void mathMultiply(Point* pt, const Matrix* transform)
{
auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13;
auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23;
pt->x = tx;
pt->y = ty;
}

View File

@ -0,0 +1,209 @@
/*
* 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.
*/
#ifndef _TVG_MATH_H_
#define _TVG_MATH_H_
#define _USE_MATH_DEFINES
#include <float.h>
#include <math.h>
#include "tvgCommon.h"
#define MATH_PI 3.14159265358979323846f
#define MATH_PI2 1.57079632679489661923f
#define PATH_KAPPA 0.552284f
#define mathMin(x, y) (((x) < (y)) ? (x) : (y))
#define mathMax(x, y) (((x) > (y)) ? (x) : (y))
bool mathInverse(const Matrix* m, Matrix* out);
Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs);
void mathRotate(Matrix* m, float degree);
bool mathIdentity(const Matrix* m);
void mathMultiply(Point* pt, const Matrix* transform);
static inline float mathDeg2Rad(float degree)
{
return degree * (MATH_PI / 180.0f);
}
static inline float mathRad2Deg(float radian)
{
return radian * (180.0f / MATH_PI);
}
static inline bool mathZero(float a)
{
return (fabsf(a) < FLT_EPSILON) ? true : false;
}
static inline bool mathEqual(float a, float b)
{
return (fabsf(a - b) < FLT_EPSILON);
}
static inline bool mathEqual(const Matrix& a, const Matrix& b)
{
if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) ||
!mathEqual(a.e21, b.e21) || !mathEqual(a.e22, b.e22) || !mathEqual(a.e23, b.e23) ||
!mathEqual(a.e31, b.e31) || !mathEqual(a.e32, b.e32) || !mathEqual(a.e33, b.e33)) {
return false;
}
return true;
}
static inline bool mathRightAngle(const Matrix* m)
{
auto radian = fabsf(atan2f(m->e21, m->e11));
if (radian < FLT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true;
return false;
}
static inline bool mathSkewed(const Matrix* m)
{
return (fabsf(m->e21 + m->e12) > FLT_EPSILON);
}
static inline void mathIdentity(Matrix* m)
{
m->e11 = 1.0f;
m->e12 = 0.0f;
m->e13 = 0.0f;
m->e21 = 0.0f;
m->e22 = 1.0f;
m->e23 = 0.0f;
m->e31 = 0.0f;
m->e32 = 0.0f;
m->e33 = 1.0f;
}
static inline void mathTransform(Matrix* transform, Point* coord)
{
auto x = coord->x;
auto y = coord->y;
coord->x = x * transform->e11 + y * transform->e12 + transform->e13;
coord->y = x * transform->e21 + y * transform->e22 + transform->e23;
}
static inline void mathScale(Matrix* m, float sx, float sy)
{
m->e11 *= sx;
m->e22 *= sy;
}
static inline void mathScaleR(Matrix* m, float x, float y)
{
if (x != 1.0f) {
m->e11 *= x;
m->e21 *= x;
}
if (y != 1.0f) {
m->e22 *= y;
m->e12 *= y;
}
}
static inline void mathTranslate(Matrix* m, float x, float y)
{
m->e13 += x;
m->e23 += y;
}
static inline void mathTranslateR(Matrix* m, float x, float y)
{
if (x == 0.0f && y == 0.0f) return;
m->e13 += (x * m->e11 + y * m->e12);
m->e23 += (x * m->e21 + y * m->e22);
}
static inline void mathLog(Matrix* m)
{
TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33);
}
static inline float mathLength(const Point* a, const Point* b)
{
auto x = b->x - a->x;
auto y = b->y - a->y;
if (x < 0) x = -x;
if (y < 0) y = -y;
return (x > y) ? (x + 0.375f * y) : (y + 0.375f * x);
}
static inline Point operator-(const Point& lhs, const Point& rhs)
{
return {lhs.x - rhs.x, lhs.y - rhs.y};
}
static inline Point operator+(const Point& lhs, const Point& rhs)
{
return {lhs.x + rhs.x, lhs.y + rhs.y};
}
static inline Point operator*(const Point& lhs, float rhs)
{
return {lhs.x * rhs, lhs.y * rhs};
}
static inline Point operator*(const float& lhs, const Point& rhs)
{
return {lhs * rhs.x, lhs * rhs.y};
}
static inline Point operator/(const Point& lhs, const float rhs)
{
return {lhs.x / rhs, lhs.y / rhs};
}
template <typename T>
static inline T mathLerp(const T &start, const T &end, float t)
{
return static_cast<T>(start + (end - start) * t);
}
#endif //_TVG_MATH_H_

View File

@ -0,0 +1,242 @@
/*
* 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 "config.h"
#include <cmath>
#include <cstring>
#include <memory.h>
#include "tvgMath.h"
#include "tvgStr.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static inline bool _floatExact(float a, float b)
{
return memcmp(&a, &b, sizeof(float)) == 0;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
namespace tvg {
/*
* https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtof-strtof-l-wcstof-wcstof-l?view=msvc-160
*
* src should be one of the following form :
*
* [whitespace] [sign] {digits [radix digits] | radix digits} [{e | E} [sign] digits]
* [whitespace] [sign] {INF | INFINITY}
* [whitespace] [sign] NAN [sequence]
*
* No hexadecimal form supported
* no sequence supported after NAN
*/
float strToFloat(const char *nPtr, char **endPtr)
{
if (endPtr) *endPtr = (char *) (nPtr);
if (!nPtr) return 0.0f;
auto a = nPtr;
auto iter = nPtr;
auto val = 0.0f;
unsigned long long integerPart = 0;
int minus = 1;
//ignore leading whitespaces
while (isspace(*iter)) iter++;
//signed or not
if (*iter == '-') {
minus = -1;
iter++;
} else if (*iter == '+') {
iter++;
}
if (tolower(*iter) == 'i') {
if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'f')) iter += 3;
else goto error;
if (tolower(*(iter)) == 'i') {
if ((tolower(*(iter + 1)) == 'n') && (tolower(*(iter + 2)) == 'i') && (tolower(*(iter + 3)) == 't') &&
(tolower(*(iter + 4)) == 'y'))
iter += 5;
else goto error;
}
if (endPtr) *endPtr = (char *) (iter);
return (minus == -1) ? -INFINITY : INFINITY;
}
if (tolower(*iter) == 'n') {
if ((tolower(*(iter + 1)) == 'a') && (tolower(*(iter + 2)) == 'n')) iter += 3;
else goto error;
if (endPtr) *endPtr = (char *) (iter);
return (minus == -1) ? -NAN : NAN;
}
//Optional: integer part before dot
if (isdigit(*iter)) {
for (; isdigit(*iter); iter++) {
integerPart = integerPart * 10ULL + (unsigned long long) (*iter - '0');
}
a = iter;
} else if (*iter != '.') {
goto success;
}
val = static_cast<float>(integerPart);
//Optional: decimal part after dot
if (*iter == '.') {
unsigned long long decimalPart = 0;
unsigned long long pow10 = 1;
int count = 0;
iter++;
if (isdigit(*iter)) {
for (; isdigit(*iter); iter++, count++) {
if (count < 19) {
decimalPart = decimalPart * 10ULL + +static_cast<unsigned long long>(*iter - '0');
pow10 *= 10ULL;
}
}
} else if (isspace(*iter)) { //skip if there is a space after the dot.
a = iter;
goto success;
}
val += static_cast<float>(decimalPart) / static_cast<float>(pow10);
a = iter;
}
//Optional: exponent
if (*iter == 'e' || *iter == 'E') {
++iter;
//Exception: svg may have 'em' unit for fonts. ex) 5em, 10.5em
if ((*iter == 'm') || (*iter == 'M')) {
//TODO: We don't support font em unit now, but has to multiply val * font size later...
a = iter + 1;
goto success;
}
//signed or not
int minus_e = 1;
if (*iter == '-') {
minus_e = -1;
++iter;
} else if (*iter == '+') {
iter++;
}
unsigned int exponentPart = 0;
if (isdigit(*iter)) {
while (*iter == '0') iter++;
for (; isdigit(*iter); iter++) {
exponentPart = exponentPart * 10U + static_cast<unsigned int>(*iter - '0');
}
} else if (!isdigit(*(a - 1))) {
a = nPtr;
goto success;
} else if (*iter == 0) {
goto success;
}
//if ((_floatExact(val, 2.2250738585072011f)) && ((minus_e * static_cast<int>(exponentPart)) <= -308)) {
if ((_floatExact(val, 1.175494351f)) && ((minus_e * static_cast<int>(exponentPart)) <= -38)) {
//val *= 1.0e-308f;
val *= 1.0e-38f;
a = iter;
goto success;
}
a = iter;
auto scale = 1.0f;
while (exponentPart >= 8U) {
scale *= 1E8;
exponentPart -= 8U;
}
while (exponentPart > 0U) {
scale *= 10.0f;
exponentPart--;
}
val = (minus_e == -1) ? (val / scale) : (val * scale);
} else if ((iter > nPtr) && !isdigit(*(iter - 1))) {
a = nPtr;
goto success;
}
success:
if (endPtr) *endPtr = (char *)(a);
if (!std::isfinite(val)) return 0.0f;
return minus * val;
error:
if (endPtr) *endPtr = (char *)(nPtr);
return 0.0f;
}
int str2int(const char* str, size_t n)
{
int ret = 0;
for(size_t i = 0; i < n; ++i) {
ret = ret * 10 + (str[i] - '0');
}
return ret;
}
char* strDuplicate(const char *str, size_t n)
{
auto len = strlen(str);
if (len < n) n = len;
auto ret = (char *) malloc(n + 1);
if (!ret) return nullptr;
ret[n] = '\0';
return (char *) memcpy(ret, str, n);
}
char* strDirname(const char* path)
{
const char *ptr = strrchr(path, '/');
#ifdef _WIN32
if (ptr) ptr = strrchr(ptr + 1, '\\');
#endif
int len = int(ptr + 1 - path); // +1 to include '/'
return strDuplicate(path, len);
}
}

View File

@ -0,0 +1,37 @@
/*
* 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_STR_H_
#define _TVG_STR_H_
#include <cstddef>
namespace tvg
{
float strToFloat(const char *nPtr, char **endPtr); //convert to float
int str2int(const char* str, size_t n); //convert to integer
char* strDuplicate(const char *str, size_t n); //copy the string
char* strDirname(const char* path); //return the full directory name
}
#endif //_TVG_STR_H_

View File

@ -0,0 +1,8 @@
#ifndef config_h
#define config_h
#define THORVG_VERSION_STRING "1.0.0"
#define THORVG_SW_RASTER_SUPPORT true
#define THORVG_NEON_VECTOR_SUPPORT true
#endif /* config_h */

View File

@ -0,0 +1,82 @@
/*
* 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 <fstream>
#include <string.h>
#include "tvgLoader.h"
#include "tvgRawLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
RawLoader::RawLoader() : ImageLoader(FileType::Raw)
{
}
RawLoader::~RawLoader()
{
if (copy) free(surface.buf32);
}
bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy)
{
if (!LoadModule::read()) return true;
if (!data || w == 0 || h == 0) return false;
this->w = (float)w;
this->h = (float)h;
this->copy = copy;
if (copy) {
surface.buf32 = (uint32_t*)malloc(sizeof(uint32_t) * w * h);
if (!surface.buf32) return false;
memcpy((void*)surface.buf32, data, sizeof(uint32_t) * w * h);
}
else surface.buf32 = const_cast<uint32_t*>(data);
//setup the surface
surface.stride = w;
surface.w = w;
surface.h = h;
surface.cs = ColorSpace::ARGB8888;
surface.channelSize = sizeof(uint32_t);
surface.premultiplied = premultiplied;
return true;
}
bool RawLoader::read()
{
LoadModule::read();
return true;
}

View File

@ -0,0 +1,40 @@
/*
* 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_RAW_LOADER_H_
#define _TVG_RAW_LOADER_H_
class RawLoader : public ImageLoader
{
public:
bool copy = false;
RawLoader();
~RawLoader();
using LoadModule::open;
bool open(const uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy);
bool read() override;
};
#endif //_TVG_RAW_LOADER_H_

View File

@ -0,0 +1,502 @@
/*
* 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.
*/
#include <memory.h>
#ifdef _WIN32
#include <malloc.h>
#elif defined(__linux__)
#include <alloca.h>
#else
#include <stdlib.h>
#endif
#include "tvgTvgCommon.h"
#include "tvgShape.h"
#include "tvgFill.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct TvgBinBlock
{
TvgBinTag type;
TvgBinCounter length;
const char* data;
const char* end;
};
static Paint* _parsePaint(TvgBinBlock baseBlock);
static TvgBinBlock _readBlock(const char *ptr)
{
TvgBinBlock block;
block.type = *ptr;
READ_UI32(&block.length, ptr + SIZE(TvgBinTag));
block.data = ptr + SIZE(TvgBinTag) + SIZE(TvgBinCounter);
block.end = block.data + block.length;
return block;
}
static bool _parseCmpTarget(const char *ptr, const char *end, Paint *paint)
{
auto block = _readBlock(ptr);
if (block.end > end) return false;
if (block.type != TVG_TAG_PAINT_CMP_METHOD) return false;
if (block.length != SIZE(TvgBinFlag)) return false;
auto cmpMethod = static_cast<CompositeMethod>(*block.data);
ptr = block.end;
auto cmpBlock = _readBlock(ptr);
if (cmpBlock.end > end) return false;
paint->composite(unique_ptr<Paint>(_parsePaint(cmpBlock)), cmpMethod);
return true;
}
static bool _parsePaintProperty(TvgBinBlock block, Paint *paint)
{
switch (block.type) {
case TVG_TAG_PAINT_OPACITY: {
if (block.length != SIZE(uint8_t)) return false;
paint->opacity(*block.data);
return true;
}
case TVG_TAG_PAINT_TRANSFORM: {
if (block.length != SIZE(Matrix)) return false;
Matrix matrix;
memcpy(&matrix, block.data, SIZE(Matrix));
paint->transform(matrix);
return true;
}
case TVG_TAG_PAINT_CMP_TARGET: {
if (block.length < SIZE(TvgBinTag) + SIZE(TvgBinCounter)) return false;
return _parseCmpTarget(block.data, block.end, paint);
}
}
return false;
}
static bool _parseScene(TvgBinBlock block, Paint *paint)
{
auto scene = static_cast<Scene*>(paint);
//Case2: Base Paint Properties
if (_parsePaintProperty(block, scene)) return true;
//Case3: A Child paint
if (auto paint = _parsePaint(block)) {
scene->push(unique_ptr<Paint>(paint));
return true;
}
return false;
}
static bool _parseShapePath(const char *ptr, const char *end, Shape *shape)
{
uint32_t cmdCnt, ptsCnt;
READ_UI32(&cmdCnt, ptr);
ptr += SIZE(cmdCnt);
READ_UI32(&ptsCnt, ptr);
ptr += SIZE(ptsCnt);
auto cmds = (TvgBinFlag*) ptr;
ptr += SIZE(TvgBinFlag) * cmdCnt;
auto pts = (Point*) ptr;
ptr += SIZE(Point) * ptsCnt;
if (ptr > end) return false;
/* Recover to PathCommand(4 bytes) from TvgBinFlag(1 byte) */
PathCommand* inCmds = (PathCommand*)alloca(sizeof(PathCommand) * cmdCnt);
for (uint32_t i = 0; i < cmdCnt; ++i) {
inCmds[i] = static_cast<PathCommand>(cmds[i]);
}
shape->appendPath(inCmds, cmdCnt, pts, ptsCnt);
return true;
}
static unique_ptr<Fill> _parseShapeFill(const char *ptr, const char *end)
{
unique_ptr<Fill> fillGrad;
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) return nullptr;
switch (block.type) {
case TVG_TAG_FILL_RADIAL_GRADIENT: {
if (block.length != 3 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x, y, radius;
READ_FLOAT(&x, ptr);
ptr += SIZE(float);
READ_FLOAT(&y, ptr);
ptr += SIZE(float);
READ_FLOAT(&radius, ptr);
auto fillGradRadial = RadialGradient::gen();
fillGradRadial->radial(x, y, radius);
fillGrad = std::move(fillGradRadial);
break;
}
case TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL: {
if (block.length != 3 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x, y, radius;
READ_FLOAT(&x, ptr);
ptr += SIZE(float);
READ_FLOAT(&y, ptr);
ptr += SIZE(float);
READ_FLOAT(&radius, ptr);
if (auto fillGradRadial = static_cast<RadialGradient*>(fillGrad.get())) {
P(fillGradRadial)->fx = x;
P(fillGradRadial)->fy = y;
P(fillGradRadial)->fr = radius;
}
break;
}
case TVG_TAG_FILL_LINEAR_GRADIENT: {
if (block.length != 4 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x1, y1, x2, y2;
READ_FLOAT(&x1, ptr);
ptr += SIZE(float);
READ_FLOAT(&y1, ptr);
ptr += SIZE(float);
READ_FLOAT(&x2, ptr);
ptr += SIZE(float);
READ_FLOAT(&y2, ptr);
auto fillGradLinear = LinearGradient::gen();
fillGradLinear->linear(x1, y1, x2, y2);
fillGrad = std::move(fillGradLinear);
break;
}
case TVG_TAG_FILL_FILLSPREAD: {
if (!fillGrad) return nullptr;
if (block.length != SIZE(TvgBinFlag)) return nullptr;
fillGrad->spread((FillSpread) *block.data);
break;
}
case TVG_TAG_FILL_COLORSTOPS: {
if (!fillGrad) return nullptr;
if (block.length == 0 || block.length & 0x07) return nullptr;
uint32_t stopsCnt = block.length >> 3; // 8 bytes per ColorStop
if (stopsCnt > 1023) return nullptr;
Fill::ColorStop* stops = (Fill::ColorStop*)alloca(sizeof(Fill::ColorStop) * stopsCnt);
auto p = block.data;
for (uint32_t i = 0; i < stopsCnt; i++, p += 8) {
READ_FLOAT(&stops[i].offset, p);
stops[i].r = p[4];
stops[i].g = p[5];
stops[i].b = p[6];
stops[i].a = p[7];
}
fillGrad->colorStops(stops, stopsCnt);
break;
}
case TVG_TAG_FILL_TRANSFORM: {
if (!fillGrad || block.length != SIZE(Matrix)) return nullptr;
Matrix gradTransform;
memcpy(&gradTransform, block.data, SIZE(Matrix));
fillGrad->transform(gradTransform);
break;
}
default: {
TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of the fill properties, %d bytes skipped", block.type, block.type, block.length);
break;
}
}
ptr = block.end;
}
return fillGrad;
}
static bool _parseShapeStrokeDashPattern(const char *ptr, const char *end, Shape *shape)
{
uint32_t dashPatternCnt;
READ_UI32(&dashPatternCnt, ptr);
ptr += SIZE(uint32_t);
if (dashPatternCnt > 0) {
float* dashPattern = static_cast<float*>(malloc(sizeof(float) * dashPatternCnt));
if (!dashPattern) return false;
memcpy(dashPattern, ptr, sizeof(float) * dashPatternCnt);
ptr += SIZE(float) * dashPatternCnt;
if (ptr > end) {
free(dashPattern);
return false;
}
shape->strokeDash(dashPattern, dashPatternCnt);
free(dashPattern);
}
return true;
}
static bool _parseShapeStroke(const char *ptr, const char *end, Shape *shape)
{
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) return false;
switch (block.type) {
case TVG_TAG_SHAPE_STROKE_CAP: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->strokeCap((StrokeCap) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_JOIN: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->strokeJoin((StrokeJoin) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_ORDER: {
if (block.length != SIZE(TvgBinFlag)) return false;
P(shape)->strokeFirst((bool) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_WIDTH: {
if (block.length != SIZE(float)) return false;
float width;
READ_FLOAT(&width, block.data);
shape->strokeWidth(width);
break;
}
case TVG_TAG_SHAPE_STROKE_COLOR: {
if (block.length != 4) return false;
shape->strokeFill(block.data[0], block.data[1], block.data[2], block.data[3]);
break;
}
case TVG_TAG_SHAPE_STROKE_FILL: {
auto fill = _parseShapeFill(block.data, block.end);
if (!fill) return false;
shape->strokeFill(std::move(fill));
break;
}
case TVG_TAG_SHAPE_STROKE_DASHPTRN: {
if (!_parseShapeStrokeDashPattern(block.data, block.end, shape)) return false;
break;
}
case TVG_TAG_SHAPE_STROKE_MITERLIMIT: {
if (block.length != SIZE(float)) return false;
float miterlimit;
READ_FLOAT(&miterlimit, block.data);
shape->strokeMiterlimit(miterlimit);
break;
}
case TVG_TAG_SHAPE_STROKE_DASH_OFFSET: {
if (block.length != SIZE(float)) return false;
float offset;
READ_FLOAT(&offset, block.data);
P(shape)->rs.stroke->dashOffset = offset;
break;
}
default: {
TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of stroke properties, %d bytes skipped", block.type, block.type, block.length);
break;
}
}
ptr = block.end;
}
return true;
}
static bool _parseShape(TvgBinBlock block, Paint* paint)
{
auto shape = static_cast<Shape*>(paint);
//Case1: Shape specific properties
switch (block.type) {
case TVG_TAG_SHAPE_PATH: {
return _parseShapePath(block.data, block.end, shape);
}
case TVG_TAG_SHAPE_STROKE: {
return _parseShapeStroke(block.data, block.end, shape);
}
case TVG_TAG_SHAPE_FILL: {
auto fill = _parseShapeFill(block.data, block.end);
if (!fill) return false;
shape->fill(std::move(fill));
return true;
}
case TVG_TAG_SHAPE_COLOR: {
if (block.length != 4) return false;
shape->fill(block.data[0], block.data[1], block.data[2], block.data[3]);
return true;
}
case TVG_TAG_SHAPE_FILLRULE: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->fill((FillRule)*block.data);
return true;
}
}
//Case2: Base Paint Properties
return _parsePaintProperty(block, shape);
}
static bool _parsePicture(TvgBinBlock block, Paint* paint)
{
auto picture = static_cast<Picture*>(paint);
switch (block.type) {
case TVG_TAG_PICTURE_RAW_IMAGE: {
if (block.length < 2 * SIZE(uint32_t)) return false;
auto ptr = block.data;
uint32_t w, h;
READ_UI32(&w, ptr);
ptr += SIZE(uint32_t);
READ_UI32(&h, ptr);
ptr += SIZE(uint32_t);
auto size = w * h * SIZE(uint32_t);
if (block.length != 2 * SIZE(uint32_t) + size) return false;
picture->load((uint32_t*) ptr, w, h, true, true);
return true;
}
case TVG_TAG_PICTURE_MESH: {
if (block.length < 1 * SIZE(uint32_t)) return false;
auto ptr = block.data;
uint32_t meshCnt;
READ_UI32(&meshCnt, ptr);
ptr += SIZE(uint32_t);
auto size = meshCnt * SIZE(Polygon);
if (block.length != SIZE(uint32_t) + size) return false;
picture->mesh((Polygon*) ptr, meshCnt);
return true;
}
//Base Paint Properties
default: {
if (_parsePaintProperty(block, picture)) return true;
}
}
//Vector Picture won't be requested since Saver replaces it with the Scene
return false;
}
static Paint* _parsePaint(TvgBinBlock baseBlock)
{
bool (*parser)(TvgBinBlock, Paint*);
Paint *paint;
//1. Decide the type of paint.
switch (baseBlock.type) {
case TVG_TAG_CLASS_SCENE: {
paint = Scene::gen().release();
parser = _parseScene;
break;
}
case TVG_TAG_CLASS_SHAPE: {
paint = Shape::gen().release();
parser = _parseShape;
break;
}
case TVG_TAG_CLASS_PICTURE: {
paint = Picture::gen().release();
parser = _parsePicture;
break;
}
default: {
TVGERR("TVG", "Invalid Paint Type %d (0x%x)", baseBlock.type, baseBlock.type);
return nullptr;
}
}
auto ptr = baseBlock.data;
//2. Read Subsequent properties of the current paint.
while (ptr < baseBlock.end) {
auto block = _readBlock(ptr);
if (block.end > baseBlock.end) return paint;
if (!parser(block, paint)) {
TVGERR("TVG", "Encountered the wrong paint properties... Paint Class %d (0x%x)", baseBlock.type, baseBlock.type);
return paint;
}
ptr = block.end;
}
return paint;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Scene* TvgBinInterpreter::run(const char *ptr, const char* end)
{
auto scene = Scene::gen();
if (!scene) return nullptr;
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) {
TVGERR("TVG", "Corrupted tvg file.");
return nullptr;
}
scene->push(unique_ptr<Paint>(_parsePaint(block)));
ptr = block.end;
}
return scene.release();
}

View File

@ -0,0 +1,54 @@
/*
* 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.
*/
#ifndef _TVG_TVG_COMMON_H_
#define _TVG_TVG_COMMON_H_
#include "tvgCommon.h"
#include "tvgFormat.h"
#define SIZE(A) sizeof(A)
#define READ_UI32(dst, src) memcpy(dst, (src), sizeof(uint32_t))
#define READ_FLOAT(dst, src) memcpy(dst, (src), sizeof(float))
/* Interface for Tvg Binary Interpreter */
class TvgBinInterpreterBase
{
public:
virtual ~TvgBinInterpreterBase() {}
/* ptr: points the tvg binary body (after header)
end: end of the tvg binary data */
virtual Scene* run(const char* ptr, const char* end) = 0;
};
/* Version 0 */
class TvgBinInterpreter : public TvgBinInterpreterBase
{
public:
Scene* run(const char* ptr, const char* end) override;
};
#endif //_TVG_TVG_COMMON_H_

View File

@ -0,0 +1,233 @@
/*
* 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.
*/
#include <memory.h>
#include <fstream>
#include "tvgLoader.h"
#include "tvgTvgLoader.h"
#include "tvgCompressor.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void TvgLoader::clear(bool all)
{
if (copy) free((char*)data);
ptr = data = nullptr;
size = 0;
copy = false;
delete(interpreter);
interpreter = nullptr;
if (all) delete(root);
}
/* WARNING: Header format shall not change! */
bool TvgLoader::readHeader()
{
if (!ptr) return false;
//Make sure the size is large enough to hold the header
if (size < TVG_HEADER_SIZE) return false;
//1. Signature
if (memcmp(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH)) return false;
ptr += TVG_HEADER_SIGNATURE_LENGTH;
//2. Version
char version[TVG_HEADER_VERSION_LENGTH + 1];
memcpy(version, ptr, TVG_HEADER_VERSION_LENGTH);
version[TVG_HEADER_VERSION_LENGTH - 1] = '\0';
ptr += TVG_HEADER_VERSION_LENGTH;
this->version = atoi(version);
if (this->version > THORVG_VERSION_NUMBER()) {
TVGLOG("TVG", "This TVG file expects a higher version(%d) of ThorVG symbol(%d)", this->version, THORVG_VERSION_NUMBER());
}
//3. View Size
READ_FLOAT(&w, ptr);
ptr += SIZE(float);
READ_FLOAT(&h, ptr);
ptr += SIZE(float);
//4. Reserved
if (*ptr & TVG_HEAD_FLAG_COMPRESSED) compressed = true;
ptr += TVG_HEADER_RESERVED_LENGTH;
//5. Compressed Size if any
if (compressed) {
auto p = ptr;
//TVG_HEADER_UNCOMPRESSED_SIZE
memcpy(&uncompressedSize, p, sizeof(uint32_t));
p += SIZE(uint32_t);
//TVG_HEADER_COMPRESSED_SIZE
memcpy(&compressedSize, p, sizeof(uint32_t));
p += SIZE(uint32_t);
//TVG_HEADER_COMPRESSED_SIZE_BITS
memcpy(&compressedSizeBits, p, sizeof(uint32_t));
}
ptr += TVG_HEADER_COMPRESS_SIZE;
//Decide the proper Tvg Binary Interpreter based on the current file version
if (this->version >= 0) interpreter = new TvgBinInterpreter;
return true;
}
void TvgLoader::run(unsigned tid)
{
auto data = const_cast<char*>(ptr);
if (compressed) {
data = (char*) lzwDecode((uint8_t*) data, compressedSize, compressedSizeBits, uncompressedSize);
root = interpreter->run(data, data + uncompressedSize);
free(data);
} else {
root = interpreter->run(data, this->data + size);
}
clear(false);
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
TvgLoader::TvgLoader() : ImageLoader(FileType::Tvg)
{
}
TvgLoader::~TvgLoader()
{
this->done();
clear();
}
bool TvgLoader::open(const string &path)
{
clear();
ifstream f;
f.open(path, ifstream::in | ifstream::binary | ifstream::ate);
if (!f.is_open()) return false;
size = f.tellg();
f.seekg(0, ifstream::beg);
copy = true;
data = (char*)malloc(size);
if (!data) {
clear();
f.close();
return false;
}
if (!f.read((char*)data, size))
{
clear();
f.close();
return false;
}
f.close();
ptr = data;
return readHeader();
}
bool TvgLoader::open(const char *data, uint32_t size, TVG_UNUSED const string& rpath, bool copy)
{
clear();
if (copy) {
this->data = (char*)malloc(size);
if (!this->data) return false;
memcpy((char*)this->data, data, size);
} else this->data = data;
this->ptr = this->data;
this->size = size;
this->copy = copy;
return readHeader();
}
bool TvgLoader::resize(Paint* paint, float w, float h)
{
if (!paint) return false;
auto sx = w / this->w;
auto sy = h / this->h;
//Scale
auto scale = sx < sy ? sx : sy;
paint->scale(scale);
//Align
float tx = 0, ty = 0;
auto sw = this->w * scale;
auto sh = this->h * scale;
if (sw > sh) ty -= (h - sh) * 0.5f;
else tx -= (w - sw) * 0.5f;
paint->translate(-tx, -ty);
return true;
}
bool TvgLoader::read()
{
if (!ptr || size == 0) return false;
//the loading has been already completed
if (root || !LoadModule::read()) return true;
TaskScheduler::request(this);
return true;
}
Paint* TvgLoader::paint()
{
this->done();
auto ret = root;
root = nullptr;
return ret;
}

View File

@ -0,0 +1,61 @@
/*
* 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.
*/
#ifndef _TVG_TVG_LOADER_H_
#define _TVG_TVG_LOADER_H_
#include "tvgTaskScheduler.h"
#include "tvgTvgCommon.h"
class TvgLoader : public ImageLoader, public Task
{
public:
const char* data = nullptr;
const char* ptr = nullptr;
uint32_t size = 0;
uint16_t version = 0;
Scene* root = nullptr;
TvgBinInterpreterBase* interpreter = nullptr;
uint32_t uncompressedSize = 0;
uint32_t compressedSize = 0;
uint32_t compressedSizeBits = 0;
bool copy = false;
bool compressed = false;
TvgLoader();
~TvgLoader();
bool open(const string &path) override;
bool open(const char *data, uint32_t size, const string& rpath, bool copy) override;
bool read() override;
bool resize(Paint* paint, float w, float h) override;
Paint* paint() override;
private:
bool readHeader();
void run(unsigned tid) override;
void clear(bool all = true);
};
#endif //_TVG_TVG_LOADER_H_

View File

@ -0,0 +1,583 @@
/*
* 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 "tvgCommon.h"
#include "tvgRender.h"
#include <algorithm>
#if 0
#include <sys/time.h>
static double timeStamp()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec + tv.tv_usec / 1000000.0);
}
#endif
#define SW_CURVE_TYPE_POINT 0
#define SW_CURVE_TYPE_CUBIC 1
#define SW_ANGLE_PI (180L << 16)
#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;
SwFill* fill = nullptr;
SwStrokeBorder borders[2];
float sx, sy;
StrokeCap cap;
StrokeJoin join;
StrokeJoin joinSaved;
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,784 @@
/*
* 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 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;
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) {
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;
}
rgba = ALPHA_BLEND((rgba | 0xff000000), a);
for (; i < GRADIENT_STOP_SIZE; ++i)
fill->ctable[i] = rgba;
//Make sure the last color stop is represented at the end of the table
fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
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 < FLT_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 = mathMultiply(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 < FLT_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 = mathMultiply(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>(round(transform->e13));
image->oy = -static_cast<int32_t>(round(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(roundf((v.x * cosv - v.y * sinv) * 64.0f));
pt.y = SwCoord(roundf((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(atan2f(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>(round(xMin / 64.0f));
renderRegion.max.x = static_cast<SwCoord>(round(xMax / 64.0f));
renderRegion.min.y = static_cast<SwCoord>(round(yMin / 64.0f));
renderRegion.max.y = static_cast<SwCoord>(round(yMax / 64.0f));
} 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(sizeof(SwMpool), 1));
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,208 @@
/*
* 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)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
uint32_t ialpha = 255 - a;
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++;
}
}
return true;
}
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto span = rle->spans;
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
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;
}
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,178 @@
/*
* 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)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto span = rle->spans;
uint32_t src;
uint8x8_t *vDst = nullptr;
uint16_t align;
for (uint32_t i = 0; i < rle->size; ++i) {
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
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;
}
return true;
}
static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (surface->channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
return false;
}
auto color = surface->join(r, g, b, a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
auto ialpha = 255 - a;
auto vColor = vdup_n_u32(color);
auto vIalpha = vdup_n_u8((uint8_t) ialpha);
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);
}
return true;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,852 @@
/*
* 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 "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 cmpStroking = false;
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;
}
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::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::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->strokeFill(&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()
{
if (surface) return rasterClear(surface, 0, 0, surface->w, surface->h);
return false;
}
bool SwRenderer::sync()
{
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);
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;
vport.x = vport.y = 0;
vport.w = surface->w;
vport.h = surface->h;
return rasterCompositor(surface);
}
bool SwRenderer::preRender()
{
if (surface) {
vport.x = vport.y = 0;
vport.w = surface->w;
vport.h = surface->h;
}
return true;
}
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;
}
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;
//Finish previous task if it has duplicated request.
task->done();
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
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;
task->source = surface;
task->mesh = mesh;
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwSceneTask*>(data);
if (!task) task = new SwSceneTask;
task->scene = scene;
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto task = scene.begin(); task < scene.end(); ++task) {
static_cast<SwTask*>(*task)->done();
}
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
//prepare task
auto task = static_cast<SwShapeTask*>(data);
if (!task) {
task = new SwShapeTask;
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,84 @@
/*
* 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;
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,669 @@
/*
* 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 < FLT_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 < FLT_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 SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, float length, 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;
SwDashStroke dash;
auto offset = 0.0f;
auto trimmed = false;
dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset);
//dash by trimming.
if (length > 0.0f && dash.cnt == 0) {
auto begin = length * rshape->stroke->trim.begin;
auto end = length * rshape->stroke->trim.end;
//TODO: mix trimming + dash style
//default
if (end > begin) {
if (begin > 0.0f) dash.cnt += 4;
else dash.cnt += 2;
//looping
} else dash.cnt += 3;
dash.pattern = (float*)malloc(sizeof(float) * dash.cnt);
if (dash.cnt == 2) {
dash.pattern[0] = end - begin;
dash.pattern[1] = length - (end - begin);
} else if (dash.cnt == 3) {
dash.pattern[0] = end;
dash.pattern[1] = (begin - end);
dash.pattern[2] = length - begin;
} else {
dash.pattern[0] = 0; //zero dash to start with a space.
dash.pattern[1] = begin;
dash.pattern[2] = end - begin;
dash.pattern[3] = length - end;
}
trimmed = true;
//just a dasy style.
} else {
if (dash.cnt == 0) return nullptr;
}
//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) {
_dashMoveTo(dash, offIdx, offset, pts);
cmds++;
pts++;
}
while (--cmdCnt > 0) {
switch (*cmds) {
case PathCommand::Close: {
_dashClose(dash, transform);
break;
}
case PathCommand::MoveTo: {
if (rshape->stroke->trim.individual) _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 float _outlineLength(const RenderShape* rshape)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.pts.count;
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return 0.0f;
const Point* close = nullptr;
auto length = 0.0f;
auto slength = -1.0f;
auto simultaneous = !rshape->stroke->trim.individual;
//Compute the whole length
while (cmdCnt-- > 0) {
switch (*cmds) {
case PathCommand::Close: {
length += mathLength(pts - 1, close);
//retrieve the max length of the shape if the simultaneous mode.
if (simultaneous) {
if (slength < length) slength = length;
length = 0.0f;
}
break;
}
case PathCommand::MoveTo: {
close = pts;
++pts;
break;
}
case PathCommand::LineTo: {
length += mathLength(pts - 1, pts);
++pts;
break;
}
case PathCommand::CubicTo: {
length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)});
pts += 3;
break;
}
}
++cmds;
}
if (simultaneous && slength > length) return slength;
else return length;
}
static bool _axisAlignedRect(const SwOutline* outline)
{
//Fast Track: axis-aligned rectangle?
if (outline->pts.count != 5) return false;
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;
auto length = rshape->strokeTrim() ? _outlineLength(rshape) : 0.0f;
//Dash style (+trimming)
if (rshape->stroke->dashCnt > 0 || length > 0) {
shapeOutline = _genDashOutline(rshape, transform, length, mpool, tid);
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) ? true : false;
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()) << 16;
//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;
}

View File

@ -0,0 +1,85 @@
/*
* 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.
*/
#include "tvgIteratorAccessor.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static bool accessChildren(Iterator* it, function<bool(const Paint* paint)> func)
{
while (auto child = it->next()) {
//Access the child
if (!func(child)) return false;
//Access the children of the child
if (auto it2 = IteratorAccessor::iterator(child)) {
if (!accessChildren(it2, func)) {
delete(it2);
return false;
}
delete(it2);
}
}
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
unique_ptr<Picture> Accessor::set(unique_ptr<Picture> picture, function<bool(const Paint* paint)> func) noexcept
{
auto p = picture.get();
if (!p || !func) return picture;
//Use the Preorder Tree-Search
//Root
if (!func(p)) return picture;
//Children
if (auto it = IteratorAccessor::iterator(p)) {
accessChildren(it, func);
delete(it);
}
return picture;
}
Accessor::~Accessor()
{
}
Accessor::Accessor() : pImpl(nullptr)
{
}
unique_ptr<Accessor> Accessor::gen() noexcept
{
return unique_ptr<Accessor>(new Accessor);
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2023 - 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 "tvgFrameModule.h"
#include "tvgAnimation.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Animation::~Animation()
{
delete(pImpl);
}
Animation::Animation() : pImpl(new Impl)
{
}
Result Animation::frame(float no) noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return Result::InsufficientCondition;
if (!loader->animatable()) return Result::NonSupport;
if (static_cast<FrameModule*>(loader)->frame(no)) return Result::Success;
return Result::InsufficientCondition;
}
Picture* Animation::picture() const noexcept
{
return pImpl->picture;
}
float Animation::curFrame() const noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return 0;
if (!loader->animatable()) return 0;
return static_cast<FrameModule*>(loader)->curFrame();
}
float Animation::totalFrame() const noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return 0;
if (!loader->animatable()) return 0;
return static_cast<FrameModule*>(loader)->totalFrame();
}
float Animation::duration() const noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return 0;
if (!loader->animatable()) return 0;
return static_cast<FrameModule*>(loader)->duration();
}
Result Animation::segment(float begin, float end) noexcept
{
if (begin < 0.0 || end > 1.0 || begin >= end) return Result::InvalidArguments;
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return Result::InsufficientCondition;
if (!loader->animatable()) return Result::NonSupport;
static_cast<FrameModule*>(loader)->segment(begin, end);
return Result::Success;
}
Result Animation::segment(float *begin, float *end) noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return Result::InsufficientCondition;
if (!loader->animatable()) return Result::NonSupport;
if (!begin && !end) return Result::InvalidArguments;
static_cast<FrameModule*>(loader)->segment(begin, end);
return Result::Success;
}
unique_ptr<Animation> Animation::gen() noexcept
{
return unique_ptr<Animation>(new Animation);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_ANIMATION_H_
#define _TVG_ANIMATION_H_
#include "tvgCommon.h"
#include "tvgPaint.h"
#include "tvgPicture.h"
struct Animation::Impl
{
Picture* picture = nullptr;
Impl()
{
picture = Picture::gen().release();
PP(picture)->ref();
}
~Impl()
{
if (PP(picture)->unref() == 0) {
delete(picture);
}
}
};
#endif //_TVG_ANIMATION_H_

View File

@ -0,0 +1,81 @@
/*
* 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 "tvgCanvas.h"
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Canvas::Canvas(RenderMethod *pRenderer):pImpl(new Impl(pRenderer))
{
}
Canvas::~Canvas()
{
delete(pImpl);
}
list<Paint*>& Canvas::paints() noexcept
{
return pImpl->paints;
}
Result Canvas::push(unique_ptr<Paint> paint) noexcept
{
return pImpl->push(std::move(paint));
}
Result Canvas::clear(bool paints, bool buffer) noexcept
{
return pImpl->clear(paints, buffer);
}
Result Canvas::draw() noexcept
{
TVGLOG("RENDERER", "Draw S. -------------------------------- Canvas(%p)", this);
auto ret = pImpl->draw();
TVGLOG("RENDERER", "Draw E. -------------------------------- Canvas(%p)", this);
return ret;
}
Result Canvas::update(Paint* paint) noexcept
{
TVGLOG("RENDERER", "Update S. ------------------------------ Canvas(%p)", this);
auto ret = pImpl->update(paint, false);
TVGLOG("RENDERER", "Update E. ------------------------------ Canvas(%p)", this);
return ret;
}
Result Canvas::sync() noexcept
{
return pImpl->sync();
}

View File

@ -0,0 +1,139 @@
/*
* 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_CANVAS_H_
#define _TVG_CANVAS_H_
#include "tvgPaint.h"
struct Canvas::Impl
{
list<Paint*> paints;
RenderMethod* renderer;
bool refresh = false; //if all paints should be updated by force.
bool drawing = false; //on drawing condition?
Impl(RenderMethod* pRenderer) : renderer(pRenderer)
{
renderer->ref();
}
~Impl()
{
//make it sure any deffered jobs
if (renderer) renderer->sync();
clearPaints();
if (renderer && (renderer->unref() == 0)) delete(renderer);
}
void clearPaints()
{
for (auto paint : paints) {
if (P(paint)->unref() == 0) delete(paint);
}
paints.clear();
}
Result push(unique_ptr<Paint> paint)
{
//You can not push paints during rendering.
if (drawing) return Result::InsufficientCondition;
auto p = paint.release();
if (!p) return Result::MemoryCorruption;
PP(p)->ref();
paints.push_back(p);
return update(p, true);
}
Result clear(bool paints, bool buffer)
{
if (drawing) return Result::InsufficientCondition;
//Clear render target
if (buffer) {
if (!renderer || !renderer->clear()) return Result::InsufficientCondition;
}
if (paints) clearPaints();
return Result::Success;
}
void needRefresh()
{
refresh = true;
}
Result update(Paint* paint, bool force)
{
if (paints.empty() || drawing || !renderer) return Result::InsufficientCondition;
Array<RenderData> clips;
auto flag = RenderUpdateFlag::None;
if (refresh || force) flag = RenderUpdateFlag::All;
if (paint) {
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
} else {
for (auto paint : paints) {
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
}
refresh = false;
}
return Result::Success;
}
Result draw()
{
if (drawing || paints.empty() || !renderer || !renderer->preRender()) return Result::InsufficientCondition;
bool rendered = false;
for (auto paint : paints) {
if (paint->pImpl->render(renderer)) rendered = true;
}
if (!rendered || !renderer->postRender()) return Result::InsufficientCondition;
drawing = true;
return Result::Success;
}
Result sync()
{
if (!drawing) return Result::InsufficientCondition;
if (renderer->sync()) {
drawing = false;
return Result::Success;
}
return Result::InsufficientCondition;
}
};
#endif /* _TVG_CANVAS_H_ */

View File

@ -0,0 +1,90 @@
/*
* 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_COMMON_H_
#define _TVG_COMMON_H_
#include "config.h"
#include "thorvg.h"
using namespace std;
using namespace tvg;
//for MSVC Compat
#ifdef _MSC_VER
#define TVG_UNUSED
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else
#define TVG_UNUSED __attribute__ ((__unused__))
#endif
// Portable 'fallthrough' attribute
#if __has_cpp_attribute(fallthrough)
#ifdef _MSC_VER
#define TVG_FALLTHROUGH [[fallthrough]];
#else
#define TVG_FALLTHROUGH __attribute__ ((fallthrough));
#endif
#else
#define TVG_FALLTHROUGH
#endif
#if defined(_MSC_VER) && defined(__clang__)
#define strncpy strncpy_s
#define strdup _strdup
#endif
//TVG class identifier values
#define TVG_CLASS_ID_UNDEFINED 0
#define TVG_CLASS_ID_SHAPE 1
#define TVG_CLASS_ID_SCENE 2
#define TVG_CLASS_ID_PICTURE 3
#define TVG_CLASS_ID_LINEAR 4
#define TVG_CLASS_ID_RADIAL 5
#define TVG_CLASS_ID_TEXT 6
enum class FileType { Png = 0, Jpg, Webp, Tvg, Svg, Lottie, Ttf, Raw, Gif, Unknown };
using Size = Point;
#ifdef THORVG_LOG_ENABLED
constexpr auto ErrorColor = "\033[31m"; //red
constexpr auto ErrorBgColor = "\033[41m";//bg red
constexpr auto LogColor = "\033[32m"; //green
constexpr auto LogBgColor = "\033[42m"; //bg green
constexpr auto GreyColor = "\033[90m"; //grey
constexpr auto ResetColors = "\033[0m"; //default
#define TVGERR(tag, fmt, ...) fprintf(stderr, "%s[E]%s %s" tag "%s (%s %d): %s" fmt "\n", ErrorBgColor, ResetColors, ErrorColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__)
#define TVGLOG(tag, fmt, ...) fprintf(stdout, "%s[L]%s %s" tag "%s (%s %d): %s" fmt "\n", LogBgColor, ResetColors, LogColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__)
#else
#define TVGERR(...) do {} while(0)
#define TVGLOG(...) do {} while(0)
#endif
uint16_t THORVG_VERSION_NUMBER();
#define P(A) ((A)->pImpl) //Access to pimpl.
#define PP(A) (((Paint*)(A))->pImpl) //Access to pimpl.
#endif //_TVG_COMMON_H_

View File

@ -0,0 +1,250 @@
/*
* 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 "tvgFill.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
Fill* RadialGradient::Impl::duplicate()
{
auto ret = RadialGradient::gen();
if (!ret) return nullptr;
ret->pImpl->cx = cx;
ret->pImpl->cy = cy;
ret->pImpl->r = r;
ret->pImpl->fx = fx;
ret->pImpl->fy = fy;
ret->pImpl->fr = fr;
return ret.release();
}
Result RadialGradient::Impl::radial(float cx, float cy, float r, float fx, float fy, float fr)
{
if (r < 0 || fr < 0) return Result::InvalidArguments;
this->cx = cx;
this->cy = cy;
this->r = r;
this->fx = fx;
this->fy = fy;
this->fr = fr;
return Result::Success;
};
Fill* LinearGradient::Impl::duplicate()
{
auto ret = LinearGradient::gen();
if (!ret) return nullptr;
ret->pImpl->x1 = x1;
ret->pImpl->y1 = y1;
ret->pImpl->x2 = x2;
ret->pImpl->y2 = y2;
return ret.release();
};
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Fill::Fill():pImpl(new Impl())
{
}
Fill::~Fill()
{
delete(pImpl);
}
Result Fill::colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept
{
if ((!colorStops && cnt > 0) || (colorStops && cnt == 0)) return Result::InvalidArguments;
if (cnt == 0) {
if (pImpl->colorStops) {
free(pImpl->colorStops);
pImpl->colorStops = nullptr;
pImpl->cnt = 0;
}
return Result::Success;
}
if (pImpl->cnt != cnt) {
pImpl->colorStops = static_cast<ColorStop*>(realloc(pImpl->colorStops, cnt * sizeof(ColorStop)));
}
pImpl->cnt = cnt;
memcpy(pImpl->colorStops, colorStops, cnt * sizeof(ColorStop));
return Result::Success;
}
uint32_t Fill::colorStops(const ColorStop** colorStops) const noexcept
{
if (colorStops) *colorStops = pImpl->colorStops;
return pImpl->cnt;
}
Result Fill::spread(FillSpread s) noexcept
{
pImpl->spread = s;
return Result::Success;
}
FillSpread Fill::spread() const noexcept
{
return pImpl->spread;
}
Result Fill::transform(const Matrix& m) noexcept
{
if (!pImpl->transform) {
pImpl->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
}
*pImpl->transform = m;
return Result::Success;
}
Matrix Fill::transform() const noexcept
{
if (pImpl->transform) return *pImpl->transform;
return {1, 0, 0, 0, 1, 0, 0, 0, 1};
}
Fill* Fill::duplicate() const noexcept
{
return pImpl->duplicate();
}
uint32_t Fill::identifier() const noexcept
{
return pImpl->id;
}
RadialGradient::RadialGradient():pImpl(new Impl())
{
Fill::pImpl->id = TVG_CLASS_ID_RADIAL;
Fill::pImpl->method(new FillDup<RadialGradient::Impl>(pImpl));
}
RadialGradient::~RadialGradient()
{
delete(pImpl);
}
Result RadialGradient::radial(float cx, float cy, float r) noexcept
{
return pImpl->radial(cx, cy, r, cx, cy, 0.0f);
}
Result RadialGradient::radial(float* cx, float* cy, float* r) const noexcept
{
if (cx) *cx = pImpl->cx;
if (cy) *cy = pImpl->cy;
if (r) *r = pImpl->r;
return Result::Success;
}
unique_ptr<RadialGradient> RadialGradient::gen() noexcept
{
return unique_ptr<RadialGradient>(new RadialGradient);
}
uint32_t RadialGradient::identifier() noexcept
{
return TVG_CLASS_ID_RADIAL;
}
LinearGradient::LinearGradient():pImpl(new Impl())
{
Fill::pImpl->id = TVG_CLASS_ID_LINEAR;
Fill::pImpl->method(new FillDup<LinearGradient::Impl>(pImpl));
}
LinearGradient::~LinearGradient()
{
delete(pImpl);
}
Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept
{
pImpl->x1 = x1;
pImpl->y1 = y1;
pImpl->x2 = x2;
pImpl->y2 = y2;
return Result::Success;
}
Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept
{
if (x1) *x1 = pImpl->x1;
if (x2) *x2 = pImpl->x2;
if (y1) *y1 = pImpl->y1;
if (y2) *y2 = pImpl->y2;
return Result::Success;
}
unique_ptr<LinearGradient> LinearGradient::gen() noexcept
{
return unique_ptr<LinearGradient>(new LinearGradient);
}
uint32_t LinearGradient::identifier() noexcept
{
return TVG_CLASS_ID_LINEAR;
}

View File

@ -0,0 +1,112 @@
/*
* 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_FILL_H_
#define _TVG_FILL_H_
#include <cstdlib>
#include <cstring>
#include "tvgCommon.h"
template<typename T>
struct DuplicateMethod
{
virtual ~DuplicateMethod() {}
virtual T* duplicate() = 0;
};
template<class T>
struct FillDup : DuplicateMethod<Fill>
{
T* inst = nullptr;
FillDup(T* _inst) : inst(_inst) {}
~FillDup() {}
Fill* duplicate() override
{
return inst->duplicate();
}
};
struct Fill::Impl
{
ColorStop* colorStops = nullptr;
Matrix* transform = nullptr;
uint32_t cnt = 0;
FillSpread spread;
DuplicateMethod<Fill>* dup = nullptr;
uint8_t id;
~Impl()
{
delete(dup);
free(colorStops);
free(transform);
}
void method(DuplicateMethod<Fill>* dup)
{
this->dup = dup;
}
Fill* duplicate()
{
auto ret = dup->duplicate();
if (!ret) return nullptr;
ret->pImpl->cnt = cnt;
ret->pImpl->spread = spread;
ret->pImpl->colorStops = static_cast<ColorStop*>(malloc(sizeof(ColorStop) * cnt));
memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt);
if (transform) {
ret->pImpl->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
*ret->pImpl->transform = *transform;
}
return ret;
}
};
struct RadialGradient::Impl
{
float cx = 0.0f, cy = 0.0f;
float fx = 0.0f, fy = 0.0f;
float r = 0.0f, fr = 0.0f;
Fill* duplicate();
Result radial(float cx, float cy, float r, float fx, float fy, float fr);
};
struct LinearGradient::Impl
{
float x1 = 0.0f;
float y1 = 0.0f;
float x2 = 0.0f;
float y2 = 0.0f;
Fill* duplicate();
};
#endif //_TVG_FILL_H_

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 - 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_FRAME_MODULE_H_
#define _TVG_FRAME_MODULE_H_
#include "tvgLoadModule.h"
namespace tvg
{
class FrameModule: public ImageLoader
{
public:
float segmentBegin = 0.0f;
float segmentEnd = 1.0f;
FrameModule(FileType type) : ImageLoader(type) {}
virtual ~FrameModule() {}
virtual bool frame(float no) = 0; //set the current frame number
virtual float totalFrame() = 0; //return the total frame count
virtual float curFrame() = 0; //return the current frame number
virtual float duration() = 0; //return the animation duration in seconds
void segment(float* begin, float* end)
{
if (begin) *begin = segmentBegin;
if (end) *end = segmentEnd;
}
void segment(float begin, float end)
{
segmentBegin = begin;
segmentEnd = end;
}
virtual bool animatable() override { return true; }
};
}
#endif //_TVG_FRAME_MODULE_H_

View File

@ -0,0 +1,88 @@
/*
* 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 "tvgCanvas.h"
#ifdef THORVG_GL_RASTER_SUPPORT
#include "tvgGlRenderer.h"
#else
class GlRenderer : public RenderMethod
{
//Non Supported. Dummy Class */
};
#endif
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct GlCanvas::Impl
{
};
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
#ifdef THORVG_GL_RASTER_SUPPORT
GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(new Impl)
#else
GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(new Impl)
#endif
{
}
GlCanvas::~GlCanvas()
{
delete(pImpl);
}
Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
{
#ifdef THORVG_GL_RASTER_SUPPORT
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<GlRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target(id, w, h)) return Result::Unknown;
//Paints must be updated again with this new target.
Canvas::pImpl->needRefresh();
return Result::Success;
#endif
return Result::NonSupport;
}
unique_ptr<GlCanvas> GlCanvas::gen() noexcept
{
#ifdef THORVG_GL_RASTER_SUPPORT
if (GlRenderer::init() <= 0) return nullptr;
return unique_ptr<GlCanvas>(new GlCanvas);
#endif
return nullptr;
}

View File

@ -0,0 +1,177 @@
/*
* 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 "tvgCommon.h"
#include "tvgTaskScheduler.h"
#include "tvgLoader.h"
#ifdef _WIN32
#include <cstring>
#endif
#ifdef THORVG_SW_RASTER_SUPPORT
#include "tvgSwRenderer.h"
#endif
#ifdef THORVG_GL_RASTER_SUPPORT
#include "tvgGlRenderer.h"
#endif
#ifdef THORVG_WG_RASTER_SUPPORT
#include "tvgWgRenderer.h"
#endif
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static int _initCnt = 0;
static uint16_t _version = 0;
//enum class operation helper
static constexpr bool operator &(CanvasEngine a, CanvasEngine b)
{
return int(a) & int(b);
}
static bool _buildVersionInfo()
{
auto SRC = THORVG_VERSION_STRING; //ex) 0.3.99
auto p = SRC;
const char* x;
char major[3];
x = strchr(p, '.');
if (!x) return false;
memcpy(major, p, x - p);
major[x - p] = '\0';
p = x + 1;
char minor[3];
x = strchr(p, '.');
if (!x) return false;
memcpy(minor, p, x - p);
minor[x - p] = '\0';
p = x + 1;
char micro[3];
x = SRC + strlen(THORVG_VERSION_STRING);
memcpy(micro, p, x - p);
micro[x - p] = '\0';
char sum[7];
snprintf(sum, sizeof(sum), "%s%s%s", major, minor, micro);
_version = atoi(sum);
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Result Initializer::init(uint32_t threads, CanvasEngine engine) noexcept
{
auto nonSupport = true;
if (engine == CanvasEngine::All || engine & CanvasEngine::Sw) {
#ifdef THORVG_SW_RASTER_SUPPORT
if (!SwRenderer::init(threads)) return Result::FailedAllocation;
nonSupport = false;
#endif
}
if (engine == CanvasEngine::All || engine & CanvasEngine::Gl) {
#ifdef THORVG_GL_RASTER_SUPPORT
if (!GlRenderer::init(threads)) return Result::FailedAllocation;
nonSupport = false;
#endif
}
if (engine == CanvasEngine::All || engine & CanvasEngine::Wg) {
#ifdef THORVG_WG_RASTER_SUPPORT
if (!WgRenderer::init(threads)) return Result::FailedAllocation;
nonSupport = false;
#endif
}
if (nonSupport) return Result::NonSupport;
if (_initCnt++ > 0) return Result::Success;
if (!_buildVersionInfo()) return Result::Unknown;
if (!LoaderMgr::init()) return Result::Unknown;
TaskScheduler::init(threads);
return Result::Success;
}
Result Initializer::term(CanvasEngine engine) noexcept
{
if (_initCnt == 0) return Result::InsufficientCondition;
auto nonSupport = true;
if (engine == CanvasEngine::All || engine & CanvasEngine::Sw) {
#ifdef THORVG_SW_RASTER_SUPPORT
if (!SwRenderer::term()) return Result::InsufficientCondition;
nonSupport = false;
#endif
}
if (engine == CanvasEngine::All || engine & CanvasEngine::Gl) {
#ifdef THORVG_GL_RASTER_SUPPORT
if (!GlRenderer::term()) return Result::InsufficientCondition;
nonSupport = false;
#endif
}
if (engine == CanvasEngine::All || engine & CanvasEngine::Wg) {
#ifdef THORVG_WG_RASTER_SUPPORT
if (!WgRenderer::term()) return Result::InsufficientCondition;
nonSupport = false;
#endif
}
if (nonSupport) return Result::NonSupport;
if (--_initCnt > 0) return Result::Success;
TaskScheduler::term();
if (!LoaderMgr::term()) return Result::Unknown;
return Result::Success;
}
uint16_t THORVG_VERSION_NUMBER()
{
return _version;
}

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
#ifndef _TVG_ITERATOR_ACCESSOR_H_
#define _TVG_ITERATOR_ACCESSOR_H_
#include "tvgPaint.h"
namespace tvg
{
class IteratorAccessor
{
public:
//Utility Method: Iterator Accessor
static Iterator* iterator(const Paint* paint)
{
return paint->pImpl->iterator();
}
};
}
#endif //_TVG_ITERATOR_ACCESSOR_H_

View File

@ -0,0 +1,107 @@
/*
* 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_LOAD_MODULE_H_
#define _TVG_LOAD_MODULE_H_
#include "tvgRender.h"
#include "tvgInlist.h"
struct LoadModule
{
INLIST_ITEM(LoadModule);
//Use either hashkey(data) or hashpath(path)
union {
uintptr_t hashkey;
char* hashpath = nullptr;
};
FileType type; //current loader file type
uint16_t sharing = 0; //reference count
bool readied = false; //read done already.
bool pathcache = false; //cached by path
LoadModule(FileType type) : type(type) {}
virtual ~LoadModule()
{
if (pathcache) free(hashpath);
}
virtual bool open(const string& path) { return false; }
virtual bool open(const char* data, uint32_t size, const string& rpath, bool copy) { return false; }
virtual bool resize(Paint* paint, float w, float h) { return false; }
virtual void sync() {}; //finish immediately if any async update jobs.
virtual bool read()
{
if (readied) return false;
readied = true;
return true;
}
bool cached()
{
if (hashkey) return true;
return false;
}
virtual bool close()
{
if (sharing == 0) return true;
--sharing;
return false;
}
};
struct ImageLoader : LoadModule
{
static ColorSpace cs; //desired value
float w = 0, h = 0; //default image size
Surface surface;
ImageLoader(FileType type) : LoadModule(type) {}
virtual bool animatable() { return false; } //true if this loader supports animation.
virtual Paint* paint() { return nullptr; }
virtual Surface* bitmap()
{
if (surface.data) return &surface;
return nullptr;
}
};
struct FontLoader : LoadModule
{
float scale = 1.0f;
FontLoader(FileType type) : LoadModule(type) {}
virtual bool request(Shape* shape, char* text, bool italic = false) = 0;
};
#endif //_TVG_LOAD_MODULE_H_

View File

@ -0,0 +1,435 @@
/*
* 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 "tvgInlist.h"
#include "tvgLoader.h"
#include "tvgLock.h"
#ifdef THORVG_SVG_LOADER_SUPPORT
#include "tvgSvgLoader.h"
#endif
#ifdef THORVG_PNG_LOADER_SUPPORT
#include "tvgPngLoader.h"
#endif
#ifdef THORVG_TVG_LOADER_SUPPORT
#include "tvgTvgLoader.h"
#endif
#ifdef THORVG_JPG_LOADER_SUPPORT
#include "tvgJpgLoader.h"
#endif
#ifdef THORVG_WEBP_LOADER_SUPPORT
#include "tvgWebpLoader.h"
#endif
#ifdef THORVG_TTF_LOADER_SUPPORT
#include "tvgTtfLoader.h"
#endif
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
#include "tvgLottieLoader.h"
#endif
#include "tvgRawLoader.h"
uintptr_t HASH_KEY(const char* data)
{
return reinterpret_cast<uintptr_t>(data);
}
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
ColorSpace ImageLoader::cs = ColorSpace::ARGB8888;
static Key key;
static Inlist<LoadModule> _activeLoaders;
static LoadModule* _find(FileType type)
{
switch(type) {
case FileType::Png: {
#ifdef THORVG_PNG_LOADER_SUPPORT
return new PngLoader;
#endif
break;
}
case FileType::Jpg: {
#ifdef THORVG_JPG_LOADER_SUPPORT
return new JpgLoader;
#endif
break;
}
case FileType::Webp: {
#ifdef THORVG_WEBP_LOADER_SUPPORT
return new WebpLoader;
#endif
break;
}
case FileType::Tvg: {
#ifdef THORVG_TVG_LOADER_SUPPORT
return new TvgLoader;
#endif
break;
}
case FileType::Svg: {
#ifdef THORVG_SVG_LOADER_SUPPORT
return new SvgLoader;
#endif
break;
}
case FileType::Ttf: {
#ifdef THORVG_TTF_LOADER_SUPPORT
return new TtfLoader;
#endif
break;
}
case FileType::Lottie: {
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
return new LottieLoader;
#endif
break;
}
case FileType::Raw: {
return new RawLoader;
break;
}
default: {
break;
}
}
#ifdef THORVG_LOG_ENABLED
const char *format;
switch(type) {
case FileType::Tvg: {
format = "TVG";
break;
}
case FileType::Svg: {
format = "SVG";
break;
}
case FileType::Ttf: {
format = "TTF";
break;
}
case FileType::Lottie: {
format = "lottie(json)";
break;
}
case FileType::Raw: {
format = "RAW";
break;
}
case FileType::Png: {
format = "PNG";
break;
}
case FileType::Jpg: {
format = "JPG";
break;
}
case FileType::Webp: {
format = "WEBP";
break;
}
default: {
format = "???";
break;
}
}
TVGLOG("RENDERER", "%s format is not supported", format);
#endif
return nullptr;
}
static LoadModule* _findByPath(const string& path)
{
auto ext = path.substr(path.find_last_of(".") + 1);
if (!ext.compare("tvg")) return _find(FileType::Tvg);
if (!ext.compare("svg")) return _find(FileType::Svg);
if (!ext.compare("json")) return _find(FileType::Lottie);
if (!ext.compare("png")) return _find(FileType::Png);
if (!ext.compare("jpg")) return _find(FileType::Jpg);
if (!ext.compare("webp")) return _find(FileType::Webp);
if (!ext.compare("ttf") || !ext.compare("ttc")) return _find(FileType::Ttf);
if (!ext.compare("otf") || !ext.compare("otc")) return _find(FileType::Ttf);
return nullptr;
}
static FileType _convert(const string& mimeType)
{
auto type = FileType::Unknown;
if (mimeType == "tvg") type = FileType::Tvg;
else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg;
else if (mimeType == "ttf" || mimeType == "otf") type = FileType::Ttf;
else if (mimeType == "lottie") type = FileType::Lottie;
else if (mimeType == "raw") type = FileType::Raw;
else if (mimeType == "png") type = FileType::Png;
else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg;
else if (mimeType == "webp") type = FileType::Webp;
else TVGLOG("RENDERER", "Given mimetype is unknown = \"%s\".", mimeType.c_str());
return type;
}
static LoadModule* _findByType(const string& mimeType)
{
return _find(_convert(mimeType));
}
static LoadModule* _findFromCache(const string& path)
{
ScopedLock lock(key);
auto loader = _activeLoaders.head;
while (loader) {
if (loader->pathcache && !strcmp(loader->hashpath, path.c_str())) {
++loader->sharing;
return loader;
}
loader = loader->next;
}
return nullptr;
}
static LoadModule* _findFromCache(const char* data, uint32_t size, const string& mimeType)
{
auto type = _convert(mimeType);
if (type == FileType::Unknown) return nullptr;
ScopedLock lock(key);
auto loader = _activeLoaders.head;
auto key = HASH_KEY(data);
while (loader) {
if (loader->type == type && loader->hashkey == key) {
++loader->sharing;
return loader;
}
loader = loader->next;
}
return nullptr;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
bool LoaderMgr::init()
{
return true;
}
bool LoaderMgr::term()
{
auto loader = _activeLoaders.head;
//clean up the remained font loaders which is globally used.
while (loader && loader->type == FileType::Ttf) {
auto ret = loader->close();
auto tmp = loader;
loader = loader->next;
_activeLoaders.remove(tmp);
if (ret) delete(tmp);
}
return true;
}
bool LoaderMgr::retrieve(LoadModule* loader)
{
if (!loader) return false;
if (loader->close()) {
if (loader->cached()) {
ScopedLock lock(key);
_activeLoaders.remove(loader);
}
delete(loader);
}
return true;
}
LoadModule* LoaderMgr::loader(const string& path, bool* invalid)
{
*invalid = false;
//TODO: lottie is not sharable.
auto allowCache = true;
auto ext = path.substr(path.find_last_of(".") + 1);
if (!ext.compare("json")) allowCache = false;
if (allowCache) {
if (auto loader = _findFromCache(path)) return loader;
}
if (auto loader = _findByPath(path)) {
if (loader->open(path)) {
if (allowCache) {
loader->hashpath = strdup(path.c_str());
loader->pathcache = true;
{
ScopedLock lock(key);
_activeLoaders.back(loader);
}
}
return loader;
}
delete(loader);
}
//Unkown MimeType. Try with the candidates in the order
for (int i = 0; i < static_cast<int>(FileType::Raw); i++) {
if (auto loader = _find(static_cast<FileType>(i))) {
if (loader->open(path)) {
if (allowCache) {
loader->hashpath = strdup(path.c_str());
loader->pathcache = true;
{
ScopedLock lock(key);
_activeLoaders.back(loader);
}
}
return loader;
}
delete(loader);
}
}
*invalid = true;
return nullptr;
}
bool LoaderMgr::retrieve(const string& path)
{
return retrieve(_findFromCache(path));
}
LoadModule* LoaderMgr::loader(const char* key)
{
auto loader = _activeLoaders.head;
while (loader) {
if (loader->pathcache && strstr(loader->hashpath, key)) {
++loader->sharing;
return loader;
}
loader = loader->next;
}
return nullptr;
}
LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy)
{
//Note that users could use the same data pointer with the different content.
//Thus caching is only valid for shareable.
auto allowCache = !copy;
//TODO: lottie is not sharable.
if (allowCache) {
auto type = _convert(mimeType);
if (type == FileType::Lottie) allowCache = false;
}
if (allowCache) {
if (auto loader = _findFromCache(data, size, mimeType)) return loader;
}
//Try with the given MimeType
if (!mimeType.empty()) {
if (auto loader = _findByType(mimeType)) {
if (loader->open(data, size, rpath, copy)) {
if (allowCache) {
loader->hashkey = HASH_KEY(data);
ScopedLock lock(key);
_activeLoaders.back(loader);
}
return loader;
} else {
TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported.", mimeType.c_str());
delete(loader);
}
}
}
//Unkown MimeType. Try with the candidates in the order
for (int i = 0; i < static_cast<int>(FileType::Raw); i++) {
auto loader = _find(static_cast<FileType>(i));
if (loader) {
if (loader->open(data, size, rpath, copy)) {
if (allowCache) {
loader->hashkey = HASH_KEY(data);
ScopedLock lock(key);
_activeLoaders.back(loader);
}
return loader;
}
delete(loader);
}
}
return nullptr;
}
LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool premultiplied, bool copy)
{
//Note that users could use the same data pointer with the different content.
//Thus caching is only valid for shareable.
if (!copy) {
//TODO: should we check premultiplied??
if (auto loader = _findFromCache((const char*)(data), w * h, "raw")) return loader;
}
//function is dedicated for raw images only
auto loader = new RawLoader;
if (loader->open(data, w, h, premultiplied, copy)) {
if (!copy) {
loader->hashkey = HASH_KEY((const char*)data);
ScopedLock lock(key);
_activeLoaders.back(loader);
}
return loader;
}
delete(loader);
return nullptr;
}

View File

@ -0,0 +1,40 @@
/*
* 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_LOADER_H_
#define _TVG_LOADER_H_
#include "tvgLoadModule.h"
struct LoaderMgr
{
static bool init();
static bool term();
static LoadModule* loader(const string& path, bool* invalid);
static LoadModule* loader(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy);
static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy);
static LoadModule* loader(const char* key);
static bool retrieve(const string& path);
static bool retrieve(LoadModule* loader);
};
#endif //_TVG_LOADER_H_

View File

@ -0,0 +1,469 @@
/*
* 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 "tvgPaint.h"
#include "tvgShape.h"
#include "tvgPicture.h"
#include "tvgScene.h"
#include "tvgText.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
#define PAINT_METHOD(ret, METHOD) \
switch (id) { \
case TVG_CLASS_ID_SHAPE: ret = P((Shape*)paint)->METHOD; break; \
case TVG_CLASS_ID_SCENE: ret = P((Scene*)paint)->METHOD; break; \
case TVG_CLASS_ID_PICTURE: ret = P((Picture*)paint)->METHOD; break; \
case TVG_CLASS_ID_TEXT: ret = P((Text*)paint)->METHOD; break; \
default: ret = {}; \
}
static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport)
{
/* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
auto shape = static_cast<Shape*>(cmpTarget);
//Rectangle Candidates?
const Point* pts;
auto ptsCnt = shape->pathCoords(&pts);
//nothing to clip
if (ptsCnt == 0) return Result::InvalidArguments;
if (ptsCnt != 4) return Result::InsufficientCondition;
if (rTransform) rTransform->update();
//No rotation and no skewing
if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return Result::InsufficientCondition;
if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return Result::InsufficientCondition;
//Perpendicular Rectangle?
auto pt1 = pts + 0;
auto pt2 = pts + 1;
auto pt3 = pts + 2;
auto pt4 = pts + 3;
if ((mathEqual(pt1->x, pt2->x) && mathEqual(pt2->y, pt3->y) && mathEqual(pt3->x, pt4->x) && mathEqual(pt1->y, pt4->y)) ||
(mathEqual(pt2->x, pt3->x) && mathEqual(pt1->y, pt2->y) && mathEqual(pt1->x, pt4->x) && mathEqual(pt3->y, pt4->y))) {
auto v1 = *pt1;
auto v2 = *pt3;
if (rTransform) {
mathMultiply(&v1, &rTransform->m);
mathMultiply(&v2, &rTransform->m);
}
if (pTransform) {
mathMultiply(&v1, &pTransform->m);
mathMultiply(&v2, &pTransform->m);
}
//sorting
if (v1.x > v2.x) {
auto tmp = v2.x;
v2.x = v1.x;
v1.x = tmp;
}
if (v1.y > v2.y) {
auto tmp = v2.y;
v2.y = v1.y;
v1.y = tmp;
}
viewport.x = static_cast<int32_t>(v1.x);
viewport.y = static_cast<int32_t>(v1.y);
viewport.w = static_cast<int32_t>(ceil(v2.x - viewport.x));
viewport.h = static_cast<int32_t>(ceil(v2.y - viewport.y));
if (viewport.w < 0) viewport.w = 0;
if (viewport.h < 0) viewport.h = 0;
return Result::Success;
}
return Result::InsufficientCondition;
}
RenderRegion Paint::Impl::bounds(RenderMethod* renderer) const
{
RenderRegion ret;
PAINT_METHOD(ret, bounds(renderer));
return ret;
}
Iterator* Paint::Impl::iterator()
{
Iterator* ret;
PAINT_METHOD(ret, iterator());
return ret;
}
Paint* Paint::Impl::duplicate()
{
Paint* ret;
PAINT_METHOD(ret, duplicate());
//duplicate Transform
if (rTransform) {
ret->pImpl->rTransform = new RenderTransform();
*ret->pImpl->rTransform = *rTransform;
ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
}
ret->pImpl->opacity = opacity;
if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
return ret;
}
bool Paint::Impl::rotate(float degree)
{
if (rTransform) {
if (mathEqual(degree, rTransform->degree)) return true;
} else {
if (mathZero(degree)) return true;
rTransform = new RenderTransform();
}
rTransform->degree = degree;
if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
return true;
}
bool Paint::Impl::scale(float factor)
{
if (rTransform) {
if (mathEqual(factor, rTransform->scale)) return true;
} else {
if (mathEqual(factor, 1.0f)) return true;
rTransform = new RenderTransform();
}
rTransform->scale = factor;
if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
return true;
}
bool Paint::Impl::translate(float x, float y)
{
if (rTransform) {
if (mathEqual(x, rTransform->x) && mathEqual(y, rTransform->y)) return true;
} else {
if (mathZero(x) && mathZero(y)) return true;
rTransform = new RenderTransform();
}
rTransform->x = x;
rTransform->y = y;
if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform;
return true;
}
bool Paint::Impl::render(RenderMethod* renderer)
{
Compositor* cmp = nullptr;
/* Note: only ClipPath is processed in update() step.
Create a composition image. */
if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
RenderRegion region;
PAINT_METHOD(region, bounds(renderer));
if (MASK_REGION_MERGING(compData->method)) region.add(P(compData->target)->bounds(renderer));
if (region.w == 0 || region.h == 0) return true;
cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method));
if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) {
compData->target->pImpl->render(renderer);
}
}
if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
renderer->blend(blendMethod);
bool ret;
PAINT_METHOD(ret, render(renderer));
if (cmp) renderer->endComposite(cmp);
return ret;
}
RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (this->renderer != renderer) {
if (this->renderer) TVGERR("RENDERER", "paint's renderer has been changed!");
renderer->ref();
this->renderer = renderer;
}
if (renderFlag & RenderUpdateFlag::Transform) {
if (!rTransform) return nullptr;
rTransform->update();
}
/* 1. Composition Pre Processing */
RenderData trd = nullptr; //composite target render data
RenderRegion viewport;
Result compFastTrack = Result::InsufficientCondition;
bool childClipper = false;
if (compData) {
auto target = compData->target;
auto method = compData->method;
target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset
/* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle,
we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */
auto tryFastTrack = false;
if (target->identifier() == TVG_CLASS_ID_SHAPE) {
if (method == CompositeMethod::ClipPath) tryFastTrack = true;
else {
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
//no gradient fill & no compositions of the composition target.
if (!shape->fill() && !(PP(shape)->compData)) {
if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true;
else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true;
}
}
if (tryFastTrack) {
RenderRegion viewport2;
if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2)) == Result::Success) {
viewport = renderer->viewport();
viewport2.intersect(viewport);
renderer->viewport(viewport2);
target->pImpl->ctxFlag |= ContextFlag::FastTrack;
}
}
}
if (compFastTrack == Result::InsufficientCondition) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper);
if (childClipper) clips.push(trd);
}
}
/* 2. Main Update */
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
renderFlag = RenderUpdateFlag::None;
opacity = MULTIPLY(opacity, this->opacity);
RenderData rd = nullptr;
RenderTransform outTransform(pTransform, rTransform);
PAINT_METHOD(rd, update(renderer, &outTransform, clips, opacity, newFlag, clipper));
/* 3. Composition Post Processing */
if (compFastTrack == Result::Success) renderer->viewport(viewport);
else if (childClipper) clips.pop();
return rd;
}
bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking)
{
Matrix* m = nullptr;
bool ret;
//Case: No transformed, quick return!
if (!transformed || !(m = this->transform())) {
PAINT_METHOD(ret, bounds(x, y, w, h, stroking));
return ret;
}
//Case: Transformed
auto tx = 0.0f;
auto ty = 0.0f;
auto tw = 0.0f;
auto th = 0.0f;
PAINT_METHOD(ret, bounds(&tx, &ty, &tw, &th, stroking));
//Get vertices
Point pt[4] = {{tx, ty}, {tx + tw, ty}, {tx + tw, ty + th}, {tx, ty + th}};
//New bounding box
auto x1 = FLT_MAX;
auto y1 = FLT_MAX;
auto x2 = -FLT_MAX;
auto y2 = -FLT_MAX;
//Compute the AABB after transformation
for (int i = 0; i < 4; i++) {
mathMultiply(&pt[i], m);
if (pt[i].x < x1) x1 = pt[i].x;
if (pt[i].x > x2) x2 = pt[i].x;
if (pt[i].y < y1) y1 = pt[i].y;
if (pt[i].y > y2) y2 = pt[i].y;
}
if (x) *x = x1;
if (y) *y = y1;
if (w) *w = x2 - x1;
if (h) *h = y2 - y1;
return ret;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Paint :: Paint() : pImpl(new Impl(this))
{
}
Paint :: ~Paint()
{
delete(pImpl);
}
Result Paint::rotate(float degree) noexcept
{
if (pImpl->rotate(degree)) return Result::Success;
return Result::FailedAllocation;
}
Result Paint::scale(float factor) noexcept
{
if (pImpl->scale(factor)) return Result::Success;
return Result::FailedAllocation;
}
Result Paint::translate(float x, float y) noexcept
{
if (pImpl->translate(x, y)) return Result::Success;
return Result::FailedAllocation;
}
Result Paint::transform(const Matrix& m) noexcept
{
if (pImpl->transform(m)) return Result::Success;
return Result::FailedAllocation;
}
Matrix Paint::transform() noexcept
{
auto pTransform = pImpl->transform();
if (pTransform) return *pTransform;
return {1, 0, 0, 0, 1, 0, 0, 0, 1};
}
Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept
{
if (pImpl->bounds(x, y, w, h, transformed, true)) return Result::Success;
return Result::InsufficientCondition;
}
Paint* Paint::duplicate() const noexcept
{
return pImpl->duplicate();
}
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
{
auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success;
delete(p);
return Result::InvalidArguments;
}
CompositeMethod Paint::composite(const Paint** target) const noexcept
{
if (pImpl->compData) {
if (target) *target = pImpl->compData->target;
return pImpl->compData->method;
} else {
if (target) *target = nullptr;
return CompositeMethod::None;
}
}
Result Paint::opacity(uint8_t o) noexcept
{
if (pImpl->opacity == o) return Result::Success;
pImpl->opacity = o;
pImpl->renderFlag |= RenderUpdateFlag::Color;
return Result::Success;
}
uint8_t Paint::opacity() const noexcept
{
return pImpl->opacity;
}
uint32_t Paint::identifier() const noexcept
{
return pImpl->id;
}
Result Paint::blend(BlendMethod method) const noexcept
{
if (pImpl->blendMethod != method) {
pImpl->blendMethod = method;
pImpl->renderFlag |= RenderUpdateFlag::Blend;
}
return Result::Success;
}
BlendMethod Paint::blend() const noexcept
{
return pImpl->blendMethod;
}

View File

@ -0,0 +1,146 @@
/*
* 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_PAINT_H_
#define _TVG_PAINT_H_
#include "tvgRender.h"
#include "tvgMath.h"
namespace tvg
{
enum ContextFlag : uint8_t {Invalid = 0, FastTrack = 1};
struct Iterator
{
virtual ~Iterator() {}
virtual const Paint* next() = 0;
virtual uint32_t count() = 0;
virtual void begin() = 0;
};
struct Composite
{
Paint* target;
Paint* source;
CompositeMethod method;
};
struct Paint::Impl
{
Paint* paint = nullptr;
RenderTransform* rTransform = nullptr;
Composite* compData = nullptr;
RenderMethod* renderer = nullptr;
BlendMethod blendMethod = BlendMethod::Normal; //uint8_t
uint8_t renderFlag = RenderUpdateFlag::None;
uint8_t ctxFlag = ContextFlag::Invalid;
uint8_t id;
uint8_t opacity = 255;
uint8_t refCnt = 0; //reference count
Impl(Paint* pnt) : paint(pnt) {}
~Impl()
{
if (compData) {
if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData);
}
delete(rTransform);
if (renderer && (renderer->unref() == 0)) delete(renderer);
}
uint8_t ref()
{
if (refCnt == 255) TVGERR("RENDERER", "Corrupted reference count!");
return ++refCnt;
}
uint8_t unref()
{
if (refCnt == 0) TVGERR("RENDERER", "Corrupted reference count!");
return --refCnt;
}
bool transform(const Matrix& m)
{
if (!rTransform) {
if (mathIdentity(&m)) return true;
rTransform = new RenderTransform();
if (!rTransform) return false;
}
rTransform->override(m);
renderFlag |= RenderUpdateFlag::Transform;
return true;
}
Matrix* transform()
{
if (rTransform) {
rTransform->update();
return &rTransform->m;
}
return nullptr;
}
bool composite(Paint* source, Paint* target, CompositeMethod method)
{
//Invalid case
if ((!target && method != CompositeMethod::None) || (target && method == CompositeMethod::None)) return false;
if (compData) {
P(compData->target)->unref();
if ((compData->target != target) && P(compData->target)->refCnt == 0) {
delete(compData->target);
}
//Reset scenario
if (!target && method == CompositeMethod::None) {
free(compData);
compData = nullptr;
return true;
}
} else {
if (!target && method == CompositeMethod::None) return true;
compData = static_cast<Composite*>(calloc(1, sizeof(Composite)));
}
P(target)->ref();
compData->target = target;
compData->source = source;
compData->method = method;
return true;
}
RenderRegion bounds(RenderMethod* renderer) const;
Iterator* iterator();
bool rotate(float degree);
bool scale(float factor);
bool translate(float x, float y);
bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking);
RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod* renderer);
Paint* duplicate();
};
}
#endif //_TVG_PAINT_H_

View File

@ -0,0 +1,225 @@
/*
* 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 "tvgPicture.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
RenderUpdateFlag Picture::Impl::load()
{
if (loader) {
if (!paint) {
paint = loader->paint();
if (paint) {
if (w != loader->w || h != loader->h) {
if (!resizing) {
w = loader->w;
h = loader->h;
}
loader->resize(paint, w, h);
resizing = false;
}
return RenderUpdateFlag::None;
}
} else loader->sync();
if (!surface) {
if ((surface = loader->bitmap())) {
return RenderUpdateFlag::Image;
}
}
}
return RenderUpdateFlag::None;
}
bool Picture::Impl::needComposition(uint8_t opacity)
{
//In this case, paint(scene) would try composition itself.
if (opacity < 255) return false;
//Composition test
const Paint* target;
auto method = picture->composite(&target);
if (!target || method == tvg::CompositeMethod::ClipPath) return false;
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false;
return true;
}
bool Picture::Impl::render(RenderMethod* renderer)
{
bool ret = false;
if (surface) return renderer->renderImage(rd);
else if (paint) {
Compositor* cmp = nullptr;
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, 255);
}
ret = paint->pImpl->render(renderer);
if (cmp) renderer->endComposite(cmp);
}
return ret;
}
bool Picture::Impl::size(float w, float h)
{
this->w = w;
this->h = h;
resizing = true;
return true;
}
RenderRegion Picture::Impl::bounds(RenderMethod* renderer)
{
if (rd) return renderer->region(rd);
if (paint) return paint->pImpl->bounds(renderer);
return {0, 0, 0, 0};
}
RenderTransform Picture::Impl::resizeTransform(const RenderTransform* pTransform)
{
//Overriding Transformation by the desired image size
auto sx = w / loader->w;
auto sy = h / loader->h;
auto scale = sx < sy ? sx : sy;
RenderTransform tmp;
tmp.m = {scale, 0, 0, 0, scale, 0, 0, 0, 1};
if (!pTransform) return tmp;
else return RenderTransform(pTransform, &tmp);
}
Result Picture::Impl::load(ImageLoader* loader)
{
//Same resource has been loaded.
if (this->loader == loader) {
this->loader->sharing--; //make it sure the reference counting.
return Result::Success;
} else if (this->loader) {
LoaderMgr::retrieve(this->loader);
}
this->loader = loader;
if (!loader->read()) return Result::Unknown;
this->w = loader->w;
this->h = loader->h;
return Result::Success;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Picture::Picture() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_PICTURE;
}
Picture::~Picture()
{
delete(pImpl);
}
unique_ptr<Picture> Picture::gen() noexcept
{
return unique_ptr<Picture>(new Picture);
}
uint32_t Picture::identifier() noexcept
{
return TVG_CLASS_ID_PICTURE;
}
Result Picture::load(const std::string& path) noexcept
{
if (path.empty()) return Result::InvalidArguments;
return pImpl->load(path);
}
Result Picture::load(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy) noexcept
{
if (!data || size <= 0) return Result::InvalidArguments;
return pImpl->load(data, size, mimeType, rpath, copy);
}
Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy) noexcept
{
if (!data || w <= 0 || h <= 0) return Result::InvalidArguments;
return pImpl->load(data, w, h, premultiplied, copy);
}
Result Picture::size(float w, float h) noexcept
{
if (pImpl->size(w, h)) return Result::Success;
return Result::InsufficientCondition;
}
Result Picture::size(float* w, float* h) const noexcept
{
if (!pImpl->loader) return Result::InsufficientCondition;
if (w) *w = pImpl->w;
if (h) *h = pImpl->h;
return Result::Success;
}
Result Picture::mesh(const Polygon* triangles, uint32_t triangleCnt) noexcept
{
if (!triangles && triangleCnt > 0) return Result::InvalidArguments;
if (triangles && triangleCnt == 0) return Result::InvalidArguments;
pImpl->mesh(triangles, triangleCnt);
return Result::Success;
}
uint32_t Picture::mesh(const Polygon** triangles) const noexcept
{
if (triangles) *triangles = pImpl->rm.triangles;
return pImpl->rm.triangleCnt;
}

View File

@ -0,0 +1,244 @@
/*
* 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_PICTURE_H_
#define _TVG_PICTURE_H_
#include <string>
#include "tvgPaint.h"
#include "tvgLoader.h"
struct PictureIterator : Iterator
{
Paint* paint = nullptr;
Paint* ptr = nullptr;
PictureIterator(Paint* p) : paint(p) {}
const Paint* next() override
{
if (!ptr) ptr = paint;
else ptr = nullptr;
return ptr;
}
uint32_t count() override
{
if (paint) return 1;
else return 0;
}
void begin() override
{
ptr = nullptr;
}
};
struct Picture::Impl
{
ImageLoader* loader = nullptr;
Paint* paint = nullptr; //vector picture uses
Surface* surface = nullptr; //bitmap picture uses
RenderData rd = nullptr; //engine data
float w = 0, h = 0;
RenderMesh rm; //mesh data
Picture* picture = nullptr;
bool resizing = false;
bool needComp = false; //need composition
RenderTransform resizeTransform(const RenderTransform* pTransform);
bool needComposition(uint8_t opacity);
bool render(RenderMethod* renderer);
bool size(float w, float h);
RenderRegion bounds(RenderMethod* renderer);
Result load(ImageLoader* ploader);
Impl(Picture* p) : picture(p)
{
}
~Impl()
{
LoaderMgr::retrieve(loader);
if (surface) {
if (auto renderer = PP(picture)->renderer) {
renderer->dispose(rd);
}
}
delete(paint);
}
RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
auto flag = load();
if (surface) {
auto transform = resizeTransform(pTransform);
rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag));
} else if (paint) {
if (resizing) {
loader->resize(paint, w, h);
resizing = false;
}
needComp = needComposition(opacity) ? true : false;
rd = paint->pImpl->update(renderer, pTransform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
}
return rd;
}
bool bounds(float* x, float* y, float* w, float* h, bool stroking)
{
if (rm.triangleCnt > 0) {
auto triangles = rm.triangles;
auto min = triangles[0].vertex[0].pt;
auto max = triangles[0].vertex[0].pt;
for (uint32_t i = 0; i < rm.triangleCnt; ++i) {
if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x;
else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x;
if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y;
else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y;
if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x;
else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x;
if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y;
else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y;
if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x;
else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x;
if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y;
else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y;
}
if (x) *x = min.x;
if (y) *y = min.y;
if (w) *w = max.x - min.x;
if (h) *h = max.y - min.y;
} else {
if (x) *x = 0;
if (y) *y = 0;
if (w) *w = this->w;
if (h) *h = this->h;
}
return true;
}
Result load(const string& path)
{
if (paint || surface) return Result::InsufficientCondition;
bool invalid; //Invalid Path
auto loader = static_cast<ImageLoader*>(LoaderMgr::loader(path, &invalid));
if (!loader) {
if (invalid) return Result::InvalidArguments;
return Result::NonSupport;
}
return load(loader);
}
Result load(const char* data, uint32_t size, const string& mimeType, const string& rpath, bool copy)
{
if (paint || surface) return Result::InsufficientCondition;
auto loader = static_cast<ImageLoader*>(LoaderMgr::loader(data, size, mimeType, rpath, copy));
if (!loader) return Result::NonSupport;
return load(loader);
}
Result load(uint32_t* data, uint32_t w, uint32_t h, bool premultiplied, bool copy)
{
if (paint || surface) return Result::InsufficientCondition;
auto loader = static_cast<ImageLoader*>(LoaderMgr::loader(data, w, h, premultiplied, copy));
if (!loader) return Result::FailedAllocation;
return load(loader);
}
void mesh(const Polygon* triangles, const uint32_t triangleCnt)
{
if (triangles && triangleCnt > 0) {
this->rm.triangleCnt = triangleCnt;
this->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * triangleCnt);
memcpy(this->rm.triangles, triangles, sizeof(Polygon) * triangleCnt);
} else {
free(this->rm.triangles);
this->rm.triangles = nullptr;
this->rm.triangleCnt = 0;
}
}
Paint* duplicate()
{
load();
auto ret = Picture::gen().release();
auto dup = ret->pImpl;
if (paint) dup->paint = paint->duplicate();
if (loader) {
dup->loader = loader;
++dup->loader->sharing;
}
dup->surface = surface;
dup->w = w;
dup->h = h;
dup->resizing = resizing;
if (rm.triangleCnt > 0) {
dup->rm.triangleCnt = rm.triangleCnt;
dup->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * rm.triangleCnt);
memcpy(dup->rm.triangles, rm.triangles, sizeof(Polygon) * rm.triangleCnt);
}
return ret;
}
Iterator* iterator()
{
load();
return new PictureIterator(paint);
}
uint32_t* data(uint32_t* w, uint32_t* h)
{
//Try it, If not loaded yet.
load();
if (loader) {
if (w) *w = static_cast<uint32_t>(loader->w);
if (h) *h = static_cast<uint32_t>(loader->h);
} else {
if (w) *w = 0;
if (h) *h = 0;
}
if (surface) return surface->buf32;
else return nullptr;
}
RenderUpdateFlag load();
};
#endif //_TVG_PICTURE_H_

View File

@ -0,0 +1,62 @@
/*
* 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 "tvgRender.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
void RenderTransform::override(const Matrix& m)
{
this->m = m;
overriding = true;
}
void RenderTransform::update()
{
if (overriding) return;
mathIdentity(&m);
mathScale(&m, scale, scale);
if (!mathZero(degree)) mathRotate(&m, degree);
mathTranslate(&m, x, y);
}
RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs)
{
if (lhs && rhs) m = mathMultiply(&lhs->m, &rhs->m);
else if (lhs) m = lhs->m;
else if (rhs) m = rhs->m;
else mathIdentity(&m);
}

View File

@ -0,0 +1,368 @@
/*
* 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_RENDER_H_
#define _TVG_RENDER_H_
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgLock.h"
namespace tvg
{
using RenderData = void*;
using pixel_t = uint32_t;
enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255};
struct Surface;
enum ColorSpace : uint8_t
{
ABGR8888 = 0, //The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied.
ARGB8888, //The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied.
ABGR8888S, //The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied.
ARGB8888S, //The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied.
Grayscale8, //One single channel data.
Unsupported //TODO: Change to the default, At the moment, we put it in the last to align with SwCanvas::Colorspace.
};
struct Surface
{
union {
pixel_t* data = nullptr; //system based data pointer
uint32_t* buf32; //for explicit 32bits channels
uint8_t* buf8; //for explicit 8bits grayscale
};
Key key; //a reserved lock for the thread safety
uint32_t stride = 0;
uint32_t w = 0, h = 0;
ColorSpace cs = ColorSpace::Unsupported;
uint8_t channelSize = 0;
bool premultiplied = false; //Alpha-premultiplied
Surface()
{
}
Surface(const Surface* rhs)
{
data = rhs->data;
stride = rhs->stride;
w = rhs->w;
h = rhs->h;
cs = rhs->cs;
channelSize = rhs->channelSize;
premultiplied = rhs->premultiplied;
}
};
struct Compositor
{
CompositeMethod method;
uint8_t opacity;
};
struct RenderMesh
{
Polygon* triangles = nullptr;
uint32_t triangleCnt = 0;
~RenderMesh()
{
free(triangles);
}
};
struct RenderRegion
{
int32_t x, y, w, h;
void intersect(const RenderRegion& rhs)
{
auto x1 = x + w;
auto y1 = y + h;
auto x2 = rhs.x + rhs.w;
auto y2 = rhs.y + rhs.h;
x = (x > rhs.x) ? x : rhs.x;
y = (y > rhs.y) ? y : rhs.y;
w = ((x1 < x2) ? x1 : x2) - x;
h = ((y1 < y2) ? y1 : y2) - y;
if (w < 0) w = 0;
if (h < 0) h = 0;
}
void add(const RenderRegion& rhs)
{
if (rhs.x < x) {
w += (x - rhs.x);
x = rhs.x;
}
if (rhs.y < y) {
h += (y - rhs.y);
y = rhs.y;
}
if (rhs.x + rhs.w > x + w) w = (rhs.x + rhs.w) - x;
if (rhs.y + rhs.h > y + h) h = (rhs.y + rhs.h) - y;
}
};
struct RenderTransform
{
Matrix m; //3x3 Matrix Elements
float x = 0.0f;
float y = 0.0f;
float degree = 0.0f; //rotation degree
float scale = 1.0f; //scale factor
bool overriding = false; //user transform?
void update();
void override(const Matrix& m);
RenderTransform() {}
RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs);
};
struct RenderStroke
{
float width = 0.0f;
uint8_t color[4] = {0, 0, 0, 0};
Fill *fill = nullptr;
float* dashPattern = nullptr;
uint32_t dashCnt = 0;
float dashOffset = 0.0f;
float miterlimit = 4.0f;
StrokeCap cap = StrokeCap::Square;
StrokeJoin join = StrokeJoin::Bevel;
bool strokeFirst = false;
struct {
float begin = 0.0f;
float end = 1.0f;
bool individual = false;
} trim;
~RenderStroke()
{
free(dashPattern);
delete(fill);
}
};
struct RenderShape
{
struct
{
Array<PathCommand> cmds;
Array<Point> pts;
} path;
Fill *fill = nullptr;
uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a
RenderStroke *stroke = nullptr;
FillRule rule = FillRule::Winding;
~RenderShape()
{
delete(fill);
delete(stroke);
}
void fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const
{
if (r) *r = color[0];
if (g) *g = color[1];
if (b) *b = color[2];
if (a) *a = color[3];
}
float strokeWidth() const
{
if (!stroke) return 0;
return stroke->width;
}
bool strokeTrim() const
{
if (!stroke) return false;
if (stroke->trim.begin == 0.0f && stroke->trim.end == 1.0f) return false;
if (stroke->trim.begin == 1.0f && stroke->trim.end == 0.0f) return false;
return true;
}
bool strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const
{
if (!stroke) return false;
if (r) *r = stroke->color[0];
if (g) *g = stroke->color[1];
if (b) *b = stroke->color[2];
if (a) *a = stroke->color[3];
return true;
}
const Fill* strokeFill() const
{
if (!stroke) return nullptr;
return stroke->fill;
}
uint32_t strokeDash(const float** dashPattern, float* offset) const
{
if (!stroke) return 0;
if (dashPattern) *dashPattern = stroke->dashPattern;
if (offset) *offset = stroke->dashOffset;
return stroke->dashCnt;
}
StrokeCap strokeCap() const
{
if (!stroke) return StrokeCap::Square;
return stroke->cap;
}
StrokeJoin strokeJoin() const
{
if (!stroke) return StrokeJoin::Bevel;
return stroke->join;
}
float strokeMiterlimit() const
{
if (!stroke) return 4.0f;
return stroke->miterlimit;;
}
};
class RenderMethod
{
private:
uint32_t refCnt = 0; //reference count
Key key;
public:
uint32_t ref()
{
ScopedLock lock(key);
return (++refCnt);
}
uint32_t unref()
{
ScopedLock lock(key);
return (--refCnt);
}
virtual ~RenderMethod() {}
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual bool preRender() = 0;
virtual bool renderShape(RenderData data) = 0;
virtual bool renderImage(RenderData data) = 0;
virtual bool postRender() = 0;
virtual void dispose(RenderData data) = 0;
virtual RenderRegion region(RenderData data) = 0;
virtual RenderRegion viewport() = 0;
virtual bool viewport(const RenderRegion& vp) = 0;
virtual bool blend(BlendMethod method) = 0;
virtual ColorSpace colorSpace() = 0;
virtual bool clear() = 0;
virtual bool sync() = 0;
virtual Compositor* target(const RenderRegion& region, ColorSpace cs) = 0;
virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
virtual bool endComposite(Compositor* cmp) = 0;
};
static inline bool MASK_REGION_MERGING(CompositeMethod method)
{
switch(method) {
case CompositeMethod::AlphaMask:
case CompositeMethod::InvAlphaMask:
case CompositeMethod::LumaMask:
case CompositeMethod::InvLumaMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
return false;
//these might expand the rendering region
case CompositeMethod::AddMask:
case CompositeMethod::DifferenceMask:
return true;
default:
TVGERR("RENDERER", "Unsupported Composite Method! = %d", (int)method);
return false;
}
}
static inline uint8_t CHANNEL_SIZE(ColorSpace cs)
{
switch(cs) {
case ColorSpace::ABGR8888:
case ColorSpace::ABGR8888S:
case ColorSpace::ARGB8888:
case ColorSpace::ARGB8888S:
return sizeof(uint32_t);
case ColorSpace::Grayscale8:
return sizeof(uint8_t);
case ColorSpace::Unsupported:
default:
TVGERR("RENDERER", "Unsupported Channel Size! = %d", (int)cs);
return 0;
}
}
static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod* renderer, CompositeMethod method)
{
switch(method) {
case CompositeMethod::AlphaMask:
case CompositeMethod::InvAlphaMask:
case CompositeMethod::AddMask:
case CompositeMethod::DifferenceMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
return ColorSpace::Grayscale8;
//TODO: Optimize Luma/InvLuma colorspace to Grayscale8
case CompositeMethod::LumaMask:
case CompositeMethod::InvLumaMask:
return renderer->colorSpace();
default:
TVGERR("RENDERER", "Unsupported Composite Size! = %d", (int)method);
return ColorSpace::Unsupported;
}
}
static inline uint8_t MULTIPLY(uint8_t c, uint8_t a)
{
return (((c) * (a) + 0xff) >> 8);
}
}
#endif //_TVG_RENDER_H_

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
#ifndef _TVG_SAVE_MODULE_H_
#define _TVG_SAVE_MODULE_H_
#include "tvgIteratorAccessor.h"
namespace tvg
{
class SaveModule
{
public:
virtual ~SaveModule() {}
virtual bool save(Paint* paint, Paint* bg, const string& path, uint32_t quality) = 0;
virtual bool save(Animation* animation, Paint* bg, const string& path, uint32_t quality, uint32_t fps) = 0;
virtual bool close() = 0;
};
}
#endif //_TVG_SAVE_MODULE_H_

View File

@ -0,0 +1,203 @@
/*
* 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.
*/
#include "tvgCommon.h"
#include "tvgSaveModule.h"
#include "tvgPaint.h"
#ifdef THORVG_TVG_SAVER_SUPPORT
#include "tvgTvgSaver.h"
#endif
#ifdef THORVG_GIF_SAVER_SUPPORT
#include "tvgGifSaver.h"
#endif
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct Saver::Impl
{
SaveModule* saveModule = nullptr;
Paint* bg = nullptr;
~Impl()
{
delete(saveModule);
delete(bg);
}
};
static SaveModule* _find(FileType type)
{
switch(type) {
case FileType::Tvg: {
#ifdef THORVG_TVG_SAVER_SUPPORT
return new TvgSaver;
#endif
break;
}
case FileType::Gif: {
#ifdef THORVG_GIF_SAVER_SUPPORT
return new GifSaver;
#endif
break;
}
default: {
break;
}
}
#ifdef THORVG_LOG_ENABLED
const char *format;
switch(type) {
case FileType::Tvg: {
format = "TVG";
break;
}
case FileType::Gif: {
format = "GIF";
break;
}
default: {
format = "???";
break;
}
}
TVGLOG("RENDERER", "%s format is not supported", format);
#endif
return nullptr;
}
static SaveModule* _find(const string& path)
{
auto ext = path.substr(path.find_last_of(".") + 1);
if (!ext.compare("tvg")) {
return _find(FileType::Tvg);
} else if (!ext.compare("gif")) {
return _find(FileType::Gif);
}
return nullptr;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Saver::Saver() : pImpl(new Impl())
{
}
Saver::~Saver()
{
delete(pImpl);
}
Result Saver::save(unique_ptr<Paint> paint, const string& path, uint32_t quality) noexcept
{
auto p = paint.release();
if (!p) return Result::MemoryCorruption;
//Already on saving an other resource.
if (pImpl->saveModule) {
if (P(p)->refCnt == 0) delete(p);
return Result::InsufficientCondition;
}
if (auto saveModule = _find(path)) {
if (saveModule->save(p, pImpl->bg, path, quality)) {
pImpl->saveModule = saveModule;
return Result::Success;
} else {
if (P(p)->refCnt == 0) delete(p);
delete(saveModule);
return Result::Unknown;
}
}
if (P(p)->refCnt == 0) delete(p);
return Result::NonSupport;
}
Result Saver::background(unique_ptr<Paint> paint) noexcept
{
delete(pImpl->bg);
pImpl->bg = paint.release();
return Result::Success;
}
Result Saver::save(unique_ptr<Animation> animation, const string& path, uint32_t quality, uint32_t fps) noexcept
{
auto a = animation.release();
if (!a) return Result::MemoryCorruption;
//animation holds the picture, it must be 1 at the bottom.
auto remove = PP(a->picture())->refCnt <= 1 ? true : false;
if (mathZero(a->totalFrame())) {
if (remove) delete(a);
return Result::InsufficientCondition;
}
//Already on saving an other resource.
if (pImpl->saveModule) {
if (remove) delete(a);
return Result::InsufficientCondition;
}
if (auto saveModule = _find(path)) {
if (saveModule->save(a, pImpl->bg, path, quality, fps)) {
pImpl->saveModule = saveModule;
return Result::Success;
} else {
if (remove) delete(a);
delete(saveModule);
return Result::Unknown;
}
}
if (remove) delete(a);
return Result::NonSupport;
}
Result Saver::sync() noexcept
{
if (!pImpl->saveModule) return Result::InsufficientCondition;
pImpl->saveModule->close();
delete(pImpl->saveModule);
pImpl->saveModule = nullptr;
return Result::Success;
}
unique_ptr<Saver> Saver::gen() noexcept
{
return unique_ptr<Saver>(new Saver);
}

View File

@ -0,0 +1,75 @@
/*
* 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 "tvgScene.h"
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Scene::Scene() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_SCENE;
}
Scene::~Scene()
{
delete(pImpl);
}
unique_ptr<Scene> Scene::gen() noexcept
{
return unique_ptr<Scene>(new Scene);
}
uint32_t Scene::identifier() noexcept
{
return TVG_CLASS_ID_SCENE;
}
Result Scene::push(unique_ptr<Paint> paint) noexcept
{
auto p = paint.release();
if (!p) return Result::MemoryCorruption;
PP(p)->ref();
pImpl->paints.push_back(p);
return Result::Success;
}
Result Scene::clear(bool free) noexcept
{
pImpl->clear(free);
return Result::Success;
}
list<Paint*>& Scene::paints() noexcept
{
return pImpl->paints;
}

View File

@ -0,0 +1,229 @@
/*
* 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_SCENE_H_
#define _TVG_SCENE_H_
#include <float.h>
#include "tvgPaint.h"
struct SceneIterator : Iterator
{
list<Paint*>* paints;
list<Paint*>::iterator itr;
SceneIterator(list<Paint*>* p) : paints(p)
{
begin();
}
const Paint* next() override
{
if (itr == paints->end()) return nullptr;
auto paint = *itr;
++itr;
return paint;
}
uint32_t count() override
{
return paints->size();
}
void begin() override
{
itr = paints->begin();
}
};
struct Scene::Impl
{
list<Paint*> paints;
RenderData rd = nullptr;
Scene* scene = nullptr;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Impl(Scene* s) : scene(s)
{
}
~Impl()
{
for (auto paint : paints) {
if (P(paint)->unref() == 0) delete(paint);
}
if (auto renderer = PP(scene)->renderer) {
renderer->dispose(rd);
}
}
bool needComposition(uint8_t opacity)
{
if (opacity == 0 || paints.empty()) return false;
//Masking may require composition (even if opacity == 255)
auto compMethod = scene->composite(nullptr);
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true;
//Blending may require composition (even if opacity == 255)
if (scene->blend() != BlendMethod::Normal) return true;
//Half translucent requires intermediate composition.
if (opacity == 255) return false;
//If scene has several children or only scene, it may require composition.
//OPTIMIZE: the bitmap type of the picture would not need the composition.
//OPTIMIZE: a single paint of a scene would not need the composition.
if (paints.size() == 1 && paints.front()->identifier() == TVG_CLASS_ID_SHAPE) return false;
return true;
}
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper)
{
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermeidate composition with that opacity value. */
this->opacity = opacity;
opacity = 255;
}
if (clipper) {
Array<RenderData> rds(paints.size());
for (auto paint : paints) {
rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true));
}
rd = renderer->prepare(rds, rd, transform, clips, opacity, flag);
return rd;
} else {
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return nullptr;
}
}
bool render(RenderMethod* renderer)
{
Compositor* cmp = nullptr;
auto ret = true;
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
}
for (auto paint : paints) {
ret &= paint->pImpl->render(renderer);
}
if (cmp) renderer->endComposite(cmp);
return ret;
}
RenderRegion bounds(RenderMethod* renderer) const
{
if (paints.empty()) return {0, 0, 0, 0};
int32_t x1 = INT32_MAX;
int32_t y1 = INT32_MAX;
int32_t x2 = 0;
int32_t y2 = 0;
for (auto paint : paints) {
auto region = paint->pImpl->bounds(renderer);
//Merge regions
if (region.x < x1) x1 = region.x;
if (x2 < region.x + region.w) x2 = (region.x + region.w);
if (region.y < y1) y1 = region.y;
if (y2 < region.y + region.h) y2 = (region.y + region.h);
}
return {x1, y1, (x2 - x1), (y2 - y1)};
}
bool bounds(float* px, float* py, float* pw, float* ph, bool stroking)
{
if (paints.empty()) return false;
auto x1 = FLT_MAX;
auto y1 = FLT_MAX;
auto x2 = -FLT_MAX;
auto y2 = -FLT_MAX;
for (auto paint : paints) {
auto x = FLT_MAX;
auto y = FLT_MAX;
auto w = 0.0f;
auto h = 0.0f;
if (!P(paint)->bounds(&x, &y, &w, &h, true, stroking)) continue;
//Merge regions
if (x < x1) x1 = x;
if (x2 < x + w) x2 = (x + w);
if (y < y1) y1 = y;
if (y2 < y + h) y2 = (y + h);
}
if (px) *px = x1;
if (py) *py = y1;
if (pw) *pw = (x2 - x1);
if (ph) *ph = (y2 - y1);
return true;
}
Paint* duplicate()
{
auto ret = Scene::gen().release();
auto dup = ret->pImpl;
for (auto paint : paints) {
auto cdup = paint->duplicate();
P(cdup)->ref();
dup->paints.push_back(cdup);
}
return ret;
}
void clear(bool free)
{
for (auto paint : paints) {
if (P(paint)->unref() == 0 && free) delete(paint);
}
paints.clear();
}
Iterator* iterator()
{
return new SceneIterator(&paints);
}
};
#endif //_TVG_SCENE_H_

View File

@ -0,0 +1,405 @@
/*
* 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 "tvgShape.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Shape :: Shape() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_SHAPE;
}
Shape :: ~Shape()
{
delete(pImpl);
}
unique_ptr<Shape> Shape::gen() noexcept
{
return unique_ptr<Shape>(new Shape);
}
uint32_t Shape::identifier() noexcept
{
return TVG_CLASS_ID_SHAPE;
}
Result Shape::reset() noexcept
{
pImpl->rs.path.cmds.clear();
pImpl->rs.path.pts.clear();
pImpl->flag |= RenderUpdateFlag::Path;
return Result::Success;
}
uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept
{
if (cmds) *cmds = pImpl->rs.path.cmds.data;
return pImpl->rs.path.cmds.count;
}
uint32_t Shape::pathCoords(const Point** pts) const noexcept
{
if (pts) *pts = pImpl->rs.path.pts.data;
return pImpl->rs.path.pts.count;
}
Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept
{
if (cmdCnt == 0 || ptsCnt == 0 || !cmds || !pts) return Result::InvalidArguments;
pImpl->grow(cmdCnt, ptsCnt);
pImpl->append(cmds, cmdCnt, pts, ptsCnt);
return Result::Success;
}
Result Shape::moveTo(float x, float y) noexcept
{
pImpl->moveTo(x, y);
return Result::Success;
}
Result Shape::lineTo(float x, float y) noexcept
{
pImpl->lineTo(x, y);
return Result::Success;
}
Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept
{
pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y);
return Result::Success;
}
Result Shape::close() noexcept
{
pImpl->close();
return Result::Success;
}
Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept
{
auto rxKappa = rx * PATH_KAPPA;
auto ryKappa = ry * PATH_KAPPA;
pImpl->grow(6, 13);
pImpl->moveTo(cx + rx, cy);
pImpl->cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry);
pImpl->cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy);
pImpl->cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry);
pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy);
pImpl->close();
return Result::Success;
}
Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept
{
//just circle
if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius);
const float arcPrecision = 1e-5f;
startAngle = mathDeg2Rad(startAngle);
sweep = mathDeg2Rad(sweep);
auto nCurves = static_cast<int>(fabsf(sweep / MATH_PI2));
if (fabsf(sweep / MATH_PI2) - nCurves > arcPrecision) ++nCurves;
auto sweepSign = (sweep < 0 ? -1 : 1);
auto fract = fmodf(sweep, MATH_PI2);
fract = (fabsf(fract) < arcPrecision) ? MATH_PI2 * sweepSign : fract;
//Start from here
Point start = {radius * cosf(startAngle), radius * sinf(startAngle)};
if (pie) {
pImpl->moveTo(cx, cy);
pImpl->lineTo(start.x + cx, start.y + cy);
} else {
pImpl->moveTo(start.x + cx, start.y + cy);
}
for (int i = 0; i < nCurves; ++i) {
auto endAngle = startAngle + ((i != nCurves - 1) ? MATH_PI2 * sweepSign : fract);
Point end = {radius * cosf(endAngle), radius * sinf(endAngle)};
//variables needed to calculate bezier control points
//get bezier control points using article:
//(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479)
auto ax = start.x;
auto ay = start.y;
auto bx = end.x;
auto by = end.y;
auto q1 = ax * ax + ay * ay;
auto q2 = ax * bx + ay * by + q1;
auto k2 = (4.0f/3.0f) * ((sqrtf(2 * q1 * q2) - q2) / (ax * by - ay * bx));
start = end; //Next start point is the current end point
end.x += cx;
end.y += cy;
Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy};
Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy};
pImpl->cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y);
startAngle = endAngle;
}
if (pie) pImpl->close();
return Result::Success;
}
Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept
{
auto halfW = w * 0.5f;
auto halfH = h * 0.5f;
//clamping cornerRadius by minimum size
if (rx > halfW) rx = halfW;
if (ry > halfH) ry = halfH;
//rectangle
if (rx == 0 && ry == 0) {
pImpl->grow(5, 4);
pImpl->moveTo(x, y);
pImpl->lineTo(x + w, y);
pImpl->lineTo(x + w, y + h);
pImpl->lineTo(x, y + h);
pImpl->close();
//rounded rectangle or circle
} else {
auto hrx = rx * PATH_KAPPA;
auto hry = ry * PATH_KAPPA;
pImpl->grow(10, 17);
pImpl->moveTo(x + rx, y);
pImpl->lineTo(x + w - rx, y);
pImpl->cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry);
pImpl->lineTo(x + w, y + h - ry);
pImpl->cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h);
pImpl->lineTo(x + rx, y + h);
pImpl->cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry);
pImpl->lineTo(x, y + ry);
pImpl->cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y);
pImpl->close();
}
return Result::Success;
}
Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
{
if (pImpl->rs.fill) {
delete(pImpl->rs.fill);
pImpl->rs.fill = nullptr;
pImpl->flag |= RenderUpdateFlag::Gradient;
}
if (r == pImpl->rs.color[0] && g == pImpl->rs.color[1] && b == pImpl->rs.color[2] && a == pImpl->rs.color[3]) return Result::Success;
pImpl->rs.color[0] = r;
pImpl->rs.color[1] = g;
pImpl->rs.color[2] = b;
pImpl->rs.color[3] = a;
pImpl->flag |= RenderUpdateFlag::Color;
return Result::Success;
}
Result Shape::fill(unique_ptr<Fill> f) noexcept
{
auto p = f.release();
if (!p) return Result::MemoryCorruption;
if (pImpl->rs.fill && pImpl->rs.fill != p) delete(pImpl->rs.fill);
pImpl->rs.fill = p;
pImpl->flag |= RenderUpdateFlag::Gradient;
return Result::Success;
}
Result Shape::fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept
{
pImpl->rs.fillColor(r, g, b, a);
return Result::Success;
}
const Fill* Shape::fill() const noexcept
{
return pImpl->rs.fill;
}
Result Shape::order(bool strokeFirst) noexcept
{
if (!pImpl->strokeFirst(strokeFirst)) return Result::FailedAllocation;
return Result::Success;
}
Result Shape::strokeWidth(float width) noexcept
{
if (!pImpl->strokeWidth(width)) return Result::FailedAllocation;
return Result::Success;
}
float Shape::strokeWidth() const noexcept
{
return pImpl->rs.strokeWidth();
}
Result Shape::strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
{
if (!pImpl->strokeFill(r, g, b, a)) return Result::FailedAllocation;
return Result::Success;
}
Result Shape::strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept
{
if (!pImpl->rs.strokeFill(r, g, b, a)) return Result::InsufficientCondition;
return Result::Success;
}
Result Shape::strokeFill(unique_ptr<Fill> f) noexcept
{
return pImpl->strokeFill(std::move(f));
}
const Fill* Shape::strokeFill() const noexcept
{
return pImpl->rs.strokeFill();
}
Result Shape::strokeDash(const float* dashPattern, uint32_t cnt, float offset) noexcept
{
return pImpl->strokeDash(dashPattern, cnt, offset);
}
uint32_t Shape::strokeDash(const float** dashPattern, float* offset) const noexcept
{
return pImpl->rs.strokeDash(dashPattern, offset);
}
Result Shape::strokeCap(StrokeCap cap) noexcept
{
if (!pImpl->strokeCap(cap)) return Result::FailedAllocation;
return Result::Success;
}
Result Shape::strokeJoin(StrokeJoin join) noexcept
{
if (!pImpl->strokeJoin(join)) return Result::FailedAllocation;
return Result::Success;
}
Result Shape::strokeMiterlimit(float miterlimit) noexcept
{
// https://www.w3.org/TR/SVG2/painting.html#LineJoin
// - A negative value for stroke-miterlimit must be treated as an illegal value.
if (miterlimit < 0.0f) return Result::NonSupport;
// TODO Find out a reasonable max value.
if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation;
return Result::Success;
}
StrokeCap Shape::strokeCap() const noexcept
{
return pImpl->rs.strokeCap();
}
StrokeJoin Shape::strokeJoin() const noexcept
{
return pImpl->rs.strokeJoin();
}
float Shape::strokeMiterlimit() const noexcept
{
return pImpl->rs.strokeMiterlimit();
}
Result Shape::fill(FillRule r) noexcept
{
pImpl->rs.rule = r;
return Result::Success;
}
FillRule Shape::fillRule() const noexcept
{
return pImpl->rs.rule;
}

View File

@ -0,0 +1,401 @@
/*
* 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_SHAPE_H_
#define _TVG_SHAPE_H_
#include <memory.h>
#include "tvgMath.h"
#include "tvgPaint.h"
struct Shape::Impl
{
RenderShape rs; //shape data
RenderData rd = nullptr; //engine data
Shape* shape;
uint8_t flag = RenderUpdateFlag::None;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Impl(Shape* s) : shape(s)
{
}
~Impl()
{
if (auto renderer = PP(shape)->renderer) {
renderer->dispose(rd);
}
}
bool render(RenderMethod* renderer)
{
Compositor* cmp = nullptr;
bool ret;
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
}
ret = renderer->renderShape(rd);
if (cmp) renderer->endComposite(cmp);
return ret;
}
bool needComposition(uint8_t opacity)
{
if (opacity == 0) return false;
//Shape composition is only necessary when stroking & fill are valid.
if (!rs.stroke || rs.stroke->width < FLT_EPSILON || (!rs.stroke->fill && rs.stroke->color[3] == 0)) return false;
if (!rs.fill && rs.color[3] == 0) return false;
//translucent fill & stroke
if (opacity < 255) return true;
//Composition test
const Paint* target;
auto method = shape->composite(&target);
if (!target || method == CompositeMethod::ClipPath) return false;
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) {
if (target->identifier() == TVG_CLASS_ID_SHAPE) {
auto shape = static_cast<const Shape*>(target);
if (!shape->fill()) {
uint8_t r, g, b, a;
shape->fillColor(&r, &g, &b, &a);
if (a == 0 || a == 255) {
if (method == CompositeMethod::LumaMask || method == CompositeMethod::InvLumaMask) {
if ((r == 255 && g == 255 && b == 255) || (r == 0 && g == 0 && b == 0)) return false;
} else return false;
}
}
}
}
return true;
}
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermeidate composition with that opacity value. */
this->opacity = opacity;
opacity = 255;
}
rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
flag = RenderUpdateFlag::None;
return rd;
}
RenderRegion bounds(RenderMethod* renderer)
{
return renderer->region(rd);
}
bool bounds(float* x, float* y, float* w, float* h, bool stroking)
{
//Path bounding size
if (rs.path.pts.count > 0 ) {
auto pts = rs.path.pts.begin();
Point min = { pts->x, pts->y };
Point max = { pts->x, pts->y };
for (auto pts2 = pts + 1; pts2 < rs.path.pts.end(); ++pts2) {
if (pts2->x < min.x) min.x = pts2->x;
if (pts2->y < min.y) min.y = pts2->y;
if (pts2->x > max.x) max.x = pts2->x;
if (pts2->y > max.y) max.y = pts2->y;
}
if (x) *x = min.x;
if (y) *y = min.y;
if (w) *w = max.x - min.x;
if (h) *h = max.y - min.y;
}
//Stroke feathering
if (stroking && rs.stroke) {
if (x) *x -= rs.stroke->width * 0.5f;
if (y) *y -= rs.stroke->width * 0.5f;
if (w) *w += rs.stroke->width;
if (h) *h += rs.stroke->width;
}
return rs.path.pts.count > 0 ? true : false;
}
void reserveCmd(uint32_t cmdCnt)
{
rs.path.cmds.reserve(cmdCnt);
}
void reservePts(uint32_t ptsCnt)
{
rs.path.pts.reserve(ptsCnt);
}
void grow(uint32_t cmdCnt, uint32_t ptsCnt)
{
rs.path.cmds.grow(cmdCnt);
rs.path.pts.grow(ptsCnt);
}
void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt)
{
memcpy(rs.path.cmds.end(), cmds, sizeof(PathCommand) * cmdCnt);
memcpy(rs.path.pts.end(), pts, sizeof(Point) * ptsCnt);
rs.path.cmds.count += cmdCnt;
rs.path.pts.count += ptsCnt;
flag |= RenderUpdateFlag::Path;
}
void moveTo(float x, float y)
{
rs.path.cmds.push(PathCommand::MoveTo);
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void lineTo(float x, float y)
{
rs.path.cmds.push(PathCommand::LineTo);
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
{
rs.path.cmds.push(PathCommand::CubicTo);
rs.path.pts.push({cx1, cy1});
rs.path.pts.push({cx2, cy2});
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void close()
{
//Don't close multiple times.
if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return;
rs.path.cmds.push(PathCommand::Close);
flag |= RenderUpdateFlag::Path;
}
bool strokeWidth(float width)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->width = width;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeTrim(float begin, float end, bool individual)
{
if (!rs.stroke) {
if (begin == 0.0f && end == 1.0f) return true;
rs.stroke = new RenderStroke();
}
if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end)) return true;
rs.stroke->trim.begin = begin;
rs.stroke->trim.end = end;
rs.stroke->trim.individual = individual;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeCap(StrokeCap cap)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->cap = cap;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeJoin(StrokeJoin join)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->join = join;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeMiterlimit(float miterlimit)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->miterlimit = miterlimit;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->fill) {
delete(rs.stroke->fill);
rs.stroke->fill = nullptr;
flag |= RenderUpdateFlag::GradientStroke;
}
rs.stroke->color[0] = r;
rs.stroke->color[1] = g;
rs.stroke->color[2] = b;
rs.stroke->color[3] = a;
flag |= RenderUpdateFlag::Stroke;
return true;
}
Result strokeFill(unique_ptr<Fill> f)
{
auto p = f.release();
if (!p) return Result::MemoryCorruption;
if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill);
rs.stroke->fill = p;
flag |= RenderUpdateFlag::Stroke;
flag |= RenderUpdateFlag::GradientStroke;
return Result::Success;
}
Result strokeDash(const float* pattern, uint32_t cnt, float offset)
{
if ((cnt == 1) || (!pattern && cnt > 0) || (pattern && cnt == 0)) {
return Result::InvalidArguments;
}
for (uint32_t i = 0; i < cnt; i++) {
if (pattern[i] < FLT_EPSILON) return Result::InvalidArguments;
}
//Reset dash
if (!pattern && cnt == 0) {
free(rs.stroke->dashPattern);
rs.stroke->dashPattern = nullptr;
} else {
if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->dashCnt != cnt) {
free(rs.stroke->dashPattern);
rs.stroke->dashPattern = nullptr;
}
if (!rs.stroke->dashPattern) {
rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * cnt));
if (!rs.stroke->dashPattern) return Result::FailedAllocation;
}
for (uint32_t i = 0; i < cnt; ++i) {
rs.stroke->dashPattern[i] = pattern[i];
}
}
rs.stroke->dashCnt = cnt;
rs.stroke->dashOffset = offset;
flag |= RenderUpdateFlag::Stroke;
return Result::Success;
}
bool strokeFirst()
{
if (!rs.stroke) return true;
return rs.stroke->strokeFirst;
}
bool strokeFirst(bool strokeFirst)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->strokeFirst = strokeFirst;
flag |= RenderUpdateFlag::Stroke;
return true;
}
void update(RenderUpdateFlag flag)
{
this->flag |= flag;
}
Paint* duplicate()
{
auto ret = Shape::gen().release();
auto dup = ret->pImpl;
dup->rs.rule = rs.rule;
//Color
memcpy(dup->rs.color, rs.color, sizeof(rs.color));
dup->flag = RenderUpdateFlag::Color;
//Path
if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) {
dup->rs.path.cmds = rs.path.cmds;
dup->rs.path.pts = rs.path.pts;
dup->flag |= RenderUpdateFlag::Path;
}
//Stroke
if (rs.stroke) {
dup->rs.stroke = new RenderStroke();
*dup->rs.stroke = *rs.stroke;
memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color));
if (rs.stroke->dashCnt > 0) {
dup->rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * rs.stroke->dashCnt));
memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt);
}
if (rs.stroke->fill) {
dup->rs.stroke->fill = rs.stroke->fill->duplicate();
dup->flag |= RenderUpdateFlag::GradientStroke;
}
dup->flag |= RenderUpdateFlag::Stroke;
}
//Fill
if (rs.fill) {
dup->rs.fill = rs.fill->duplicate();
dup->flag |= RenderUpdateFlag::Gradient;
}
return ret;
}
Iterator* iterator()
{
return nullptr;
}
};
#endif //_TVG_SHAPE_H_

View File

@ -0,0 +1,110 @@
/*
* 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 "tvgCanvas.h"
#include "tvgLoadModule.h"
#ifdef THORVG_SW_RASTER_SUPPORT
#include "tvgSwRenderer.h"
#else
class SwRenderer : public RenderMethod
{
//Non Supported. Dummy Class */
};
#endif
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct SwCanvas::Impl
{
};
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
#ifdef THORVG_SW_RASTER_SUPPORT
SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(new Impl)
#else
SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(new Impl)
#endif
{
}
SwCanvas::~SwCanvas()
{
delete(pImpl);
}
Result SwCanvas::mempool(MempoolPolicy policy) noexcept
{
#ifdef THORVG_SW_RASTER_SUPPORT
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<SwRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
//It can't change the policy during the running.
if (!Canvas::pImpl->paints.empty()) return Result::InsufficientCondition;
if (policy == MempoolPolicy::Individual) renderer->mempool(false);
else renderer->mempool(true);
return Result::Success;
#endif
return Result::NonSupport;
}
Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept
{
#ifdef THORVG_SW_RASTER_SUPPORT
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<SwRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target(buffer, stride, w, h, static_cast<ColorSpace>(cs))) return Result::InvalidArguments;
//Paints must be updated again with this new target.
Canvas::pImpl->needRefresh();
//FIXME: The value must be associated with an individual canvas instance.
ImageLoader::cs = static_cast<ColorSpace>(cs);
return Result::Success;
#endif
return Result::NonSupport;
}
unique_ptr<SwCanvas> SwCanvas::gen() noexcept
{
#ifdef THORVG_SW_RASTER_SUPPORT
if (SwRenderer::init() <= 0) return nullptr;
return unique_ptr<SwCanvas>(new SwCanvas);
#endif
return nullptr;
}

View File

@ -0,0 +1,229 @@
/*
* 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 "tvgArray.h"
#include "tvgInlist.h"
#include "tvgTaskScheduler.h"
#ifdef THORVG_THREAD_SUPPORT
#include <thread>
#include <atomic>
#endif
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
namespace tvg {
struct TaskSchedulerImpl;
static TaskSchedulerImpl* inst = nullptr;
#ifdef THORVG_THREAD_SUPPORT
static thread_local bool _async = true;
struct TaskQueue {
Inlist<Task> taskDeque;
mutex mtx;
condition_variable ready;
bool done = false;
bool tryPop(Task** task)
{
unique_lock<mutex> lock{mtx, try_to_lock};
if (!lock || taskDeque.empty()) return false;
*task = taskDeque.front();
return true;
}
bool tryPush(Task* task)
{
{
unique_lock<mutex> lock{mtx, try_to_lock};
if (!lock) return false;
taskDeque.back(task);
}
ready.notify_one();
return true;
}
void complete()
{
{
lock_guard<mutex> lock{mtx};
done = true;
}
ready.notify_all();
}
bool pop(Task** task)
{
unique_lock<mutex> lock{mtx};
while (taskDeque.empty() && !done) {
ready.wait(lock);
}
if (taskDeque.empty()) return false;
*task = taskDeque.front();
return true;
}
void push(Task* task)
{
{
lock_guard<mutex> lock{mtx};
taskDeque.back(task);
}
ready.notify_one();
}
};
struct TaskSchedulerImpl
{
Array<thread*> threads;
Array<TaskQueue*> taskQueues;
atomic<uint32_t> idx{0};
TaskSchedulerImpl(uint32_t threadCnt)
{
threads.reserve(threadCnt);
taskQueues.reserve(threadCnt);
for (uint32_t i = 0; i < threadCnt; ++i) {
taskQueues.push(new TaskQueue);
threads.push(new thread);
}
for (uint32_t i = 0; i < threadCnt; ++i) {
*threads.data[i] = thread([&, i] { run(i); });
}
}
~TaskSchedulerImpl()
{
for (auto tq = taskQueues.begin(); tq < taskQueues.end(); ++tq) {
(*tq)->complete();
}
for (auto thread = threads.begin(); thread < threads.end(); ++thread) {
(*thread)->join();
delete(*thread);
}
for (auto tq = taskQueues.begin(); tq < taskQueues.end(); ++tq) {
delete(*tq);
}
}
void run(unsigned i)
{
Task* task;
//Thread Loop
while (true) {
auto success = false;
for (uint32_t x = 0; x < threads.count * 2; ++x) {
if (taskQueues[(i + x) % threads.count]->tryPop(&task)) {
success = true;
break;
}
}
if (!success && !taskQueues[i]->pop(&task)) break;
(*task)(i + 1);
}
}
void request(Task* task)
{
//Async
if (threads.count > 0 && _async) {
task->prepare();
auto i = idx++;
for (uint32_t n = 0; n < threads.count; ++n) {
if (taskQueues[(i + n) % threads.count]->tryPush(task)) return;
}
taskQueues[i % threads.count]->push(task);
//Sync
} else {
task->run(0);
}
}
uint32_t threadCnt()
{
return threads.count;
}
};
#else //THORVG_THREAD_SUPPORT
static bool _async = true;
struct TaskSchedulerImpl
{
TaskSchedulerImpl(TVG_UNUSED uint32_t threadCnt) {}
void request(Task* task) { task->run(0); }
uint32_t threadCnt() { return 0; }
};
#endif //THORVG_THREAD_SUPPORT
} //namespace
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
void TaskScheduler::init(uint32_t threads)
{
if (inst) return;
inst = new TaskSchedulerImpl(threads);
}
void TaskScheduler::term()
{
delete(inst);
inst = nullptr;
}
void TaskScheduler::request(Task* task)
{
if (inst) inst->request(task);
}
uint32_t TaskScheduler::threads()
{
if (inst) return inst->threadCnt();
return 0;
}
void TaskScheduler::async(bool on)
{
//toggle async tasking for each thread on/off
_async = on;
}

View File

@ -0,0 +1,112 @@
/*
* 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_TASK_SCHEDULER_H_
#define _TVG_TASK_SCHEDULER_H_
#include <mutex>
#include <condition_variable>
#include "tvgCommon.h"
#include "tvgInlist.h"
namespace tvg {
#ifdef THORVG_THREAD_SUPPORT
struct Task
{
private:
mutex mtx;
condition_variable cv;
bool ready = true;
bool pending = false;
public:
INLIST_ITEM(Task);
virtual ~Task() = default;
void done()
{
if (!pending) return;
unique_lock<mutex> lock(mtx);
while (!ready) cv.wait(lock);
pending = false;
}
protected:
virtual void run(unsigned tid) = 0;
private:
void operator()(unsigned tid)
{
run(tid);
lock_guard<mutex> lock(mtx);
ready = true;
cv.notify_one();
}
void prepare()
{
ready = false;
pending = true;
}
friend struct TaskSchedulerImpl;
};
#else //THORVG_THREAD_SUPPORT
struct Task
{
public:
INLIST_ITEM(Task);
virtual ~Task() = default;
void done() {}
protected:
virtual void run(unsigned tid) = 0;
private:
friend struct TaskSchedulerImpl;
};
#endif //THORVG_THREAD_SUPPORT
struct TaskScheduler
{
static uint32_t threads();
static void init(uint32_t threads);
static void term();
static void request(Task* task);
static void async(bool on);
};
} //namespace
#endif //_TVG_TASK_SCHEDULER_H_

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2023 - 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 "tvgText.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Text::Text() : pImpl(new Impl)
{
Paint::pImpl->id = TVG_CLASS_ID_TEXT;
}
Text::~Text()
{
delete(pImpl);
}
Result Text::text(const char* text) noexcept
{
return pImpl->text(text);
}
Result Text::font(const char* name, float size, const char* style) noexcept
{
return pImpl->font(name, size, style);
}
Result Text::load(const std::string& path) noexcept
{
bool invalid; //invalid path
if (!LoaderMgr::loader(path, &invalid)) {
if (invalid) return Result::InvalidArguments;
else return Result::NonSupport;
}
return Result::Success;
}
Result Text::unload(const std::string& path) noexcept
{
if (LoaderMgr::retrieve(path)) return Result::Success;
return Result::InsufficientCondition;
}
Result Text::fill(uint8_t r, uint8_t g, uint8_t b) noexcept
{
if (!pImpl->paint) return Result::InsufficientCondition;
return pImpl->fill(r, g, b);
}
Result Text::fill(unique_ptr<Fill> f) noexcept
{
if (!pImpl->paint) return Result::InsufficientCondition;
auto p = f.release();
if (!p) return Result::MemoryCorruption;
return pImpl->fill(p);
}
unique_ptr<Text> Text::gen() noexcept
{
return unique_ptr<Text>(new Text);
}
uint32_t Text::identifier() noexcept
{
return TVG_CLASS_ID_TEXT;
}

View File

@ -0,0 +1,183 @@
/*
* Copyright (c) 2023 - 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_TEXT_H
#define _TVG_TEXT_H
#include <cstring>
#include "tvgShape.h"
#include "tvgFill.h"
#ifdef THORVG_TTF_LOADER_SUPPORT
#include "tvgTtfLoader.h"
#else
#include "tvgLoader.h"
#endif
struct Text::Impl
{
FontLoader* loader = nullptr;
Shape* paint = nullptr;
char* utf8 = nullptr;
float fontSize;
bool italic = false;
bool changed = false;
~Impl()
{
free(utf8);
LoaderMgr::retrieve(loader);
delete(paint);
}
Result fill(uint8_t r, uint8_t g, uint8_t b)
{
return paint->fill(r, g, b);
}
Result fill(Fill* f)
{
return paint->fill(cast<Fill>(f));
}
Result text(const char* utf8)
{
free(this->utf8);
if (utf8) this->utf8 = strdup(utf8);
else this->utf8 = nullptr;
changed = true;
return Result::Success;
}
Result font(const char* name, float size, const char* style)
{
auto loader = LoaderMgr::loader(name);
if (!loader) return Result::InsufficientCondition;
//Same resource has been loaded.
if (this->loader == loader) {
this->loader->sharing--; //make it sure the reference counting.
return Result::Success;
} else if (this->loader) {
LoaderMgr::retrieve(this->loader);
}
this->loader = static_cast<FontLoader*>(loader);
if (!paint) paint = Shape::gen().release();
fontSize = size;
if (style && strstr(style, "italic")) italic = true;
changed = true;
return Result::Success;
}
RenderRegion bounds(RenderMethod* renderer)
{
if (paint) return P(paint)->bounds(renderer);
else return {0, 0, 0, 0};
}
bool render(RenderMethod* renderer)
{
if (paint) return PP(paint)->render(renderer);
return false;
}
bool load()
{
if (!loader) return false;
//reload
if (changed) {
loader->request(paint, utf8, italic);
loader->read();
changed = false;
}
if (paint) {
loader->resize(paint, fontSize, fontSize);
return true;
}
return false;
}
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (!load()) return nullptr;
//transform the gradient coordinates based on the final scaled font.
if (P(paint)->flag & RenderUpdateFlag::Gradient) {
auto fill = P(paint)->rs.fill;
auto scale = 1.0f / loader->scale;
if (fill->identifier() == TVG_CLASS_ID_LINEAR) {
P(static_cast<LinearGradient*>(fill))->x1 *= scale;
P(static_cast<LinearGradient*>(fill))->y1 *= scale;
P(static_cast<LinearGradient*>(fill))->x2 *= scale;
P(static_cast<LinearGradient*>(fill))->y2 *= scale;
} else {
P(static_cast<RadialGradient*>(fill))->cx *= scale;
P(static_cast<RadialGradient*>(fill))->cy *= scale;
P(static_cast<RadialGradient*>(fill))->r *= scale;
P(static_cast<RadialGradient*>(fill))->fx *= scale;
P(static_cast<RadialGradient*>(fill))->fy *= scale;
P(static_cast<RadialGradient*>(fill))->fr *= scale;
}
}
return PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper);
}
bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking)
{
if (!load() || !paint) return false;
paint->bounds(x, y, w, h, true);
return true;
}
Paint* duplicate()
{
load();
auto ret = Text::gen().release();
auto dup = ret->pImpl;
if (paint) dup->paint = static_cast<Shape*>(paint->duplicate());
if (loader) {
dup->loader = loader;
++dup->loader->sharing;
}
dup->utf8 = strdup(utf8);
dup->italic = italic;
dup->fontSize = fontSize;
return ret;
}
Iterator* iterator()
{
return nullptr;
}
};
#endif //_TVG_TEXT_H

View File

@ -28,7 +28,7 @@ struct CurveVertexSplitResult {
};
/// A single vertex with an in and out tangent
struct CurveVertex {
struct __attribute__((packed)) CurveVertex {
private:
/// Initializes a curve point with absolute or relative values
explicit CurveVertex(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_, bool isRelative_) :
@ -70,7 +70,6 @@ public:
public:
Vector2D point = Vector2D::Zero();
Vector2D inTangent = Vector2D::Zero();
Vector2D outTangent = Vector2D::Zero();

View File

@ -34,7 +34,7 @@ struct PathSplitResult {
/// We don't do this however, as it would effectively double the memory footprint
/// of path data.
///
struct PathElement {
struct __attribute__((packed)) PathElement {
/// Initializes a new path with length of 0
explicit PathElement(CurveVertex const &vertex_) :
vertex(vertex_) {

View File

@ -38,7 +38,7 @@ Vector1D interpolate(
double amount
);
struct Vector2D {
struct __attribute__((packed)) Vector2D {
static Vector2D Zero() {
return Vector2D(0.0, 0.0);
}

View File

@ -1,5 +1,28 @@
#include "ValueInterpolators.hpp"
#include <Accelerate/Accelerate.h>
namespace lottie {
void batchInterpolate(std::vector<PathElement> const &from, std::vector<PathElement> const &to, BezierPath &resultPath, double amount) {
int elementCount = (int)from.size();
if (elementCount > (int)to.size()) {
elementCount = (int)to.size();
}
if (sizeof(PathElement) == 8 * 2 * 3) {
resultPath.setElementCount(elementCount);
vDSP_vintbD((double *)&from[0], 1, (double *)&to[0], 1, &amount, (double *)&resultPath.elements()[0], 1, elementCount * 2 * 3);
} else {
for (int i = 0; i < elementCount; i++) {
const auto &fromVertex = from[i].vertex;
const auto &toVertex = to[i].vertex;
auto vertex = ValueInterpolator<CurveVertex>::interpolate(fromVertex, toVertex, amount);
resultPath.updateVertex(vertex, i, false);
}
}
}
}

View File

@ -89,6 +89,8 @@ public:
}
};
void batchInterpolate(std::vector<PathElement> const &from, std::vector<PathElement> const &to, BezierPath &resultPath, double amount);
template<>
struct ValueInterpolator<CurveVertex> {
public:
@ -155,6 +157,7 @@ public:
//TODO:probably a bug in the upstream code, uncomment
//newPath.setClosed(value.closed());
int elementCount = (int)std::min(value.elements().size(), to.elements().size());
resultPath.reserveCapacity(std::max(value.elements().size(), to.elements().size()));
@ -174,14 +177,16 @@ public:
resultPath.updateVertex(vertex, i, false);
}
} else {
for (int i = 0; i < elementCount; i++) {
batchInterpolate(value.elements(), to.elements(), resultPath, amount);
/*for (int i = 0; i < elementCount; i++) {
const auto &fromVertex = value.elements()[i].vertex;
const auto &toVertex = to.elements()[i].vertex;
auto vertex = ValueInterpolator<CurveVertex>::interpolate(fromVertex, toVertex, amount);
resultPath.updateVertex(vertex, i, false);
}
}*/
}
}
};