initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
41
thirdparty/thorvg/AUTHORS
vendored
Normal file
41
thirdparty/thorvg/AUTHORS
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
Hermet Park <hermet@lottiefiles.com>
|
||||
Prudhvi Raj Vasireddi <prudhvi.raj@samsung.com>
|
||||
Junsu Choi <jsuya.choi@samsung.com>
|
||||
Pranay Samanta <pranay.ks@samsung.com>
|
||||
Mateusz Palkowski <m.palkowski@samsung.com>
|
||||
Subhransu Mohanty <sub.mohanty@samsung.com>
|
||||
Mira Grudzinska <mira@lottiefiles.com>
|
||||
Michal Szczecinski <m.szczecinsk@partner.samsung.com>
|
||||
Shinwoo Kim <cinoo.kim@samsung.com>
|
||||
Piotr Kalota <p.kalota@samsung.com>
|
||||
Vincent Torri <vincent.torri@gmail.com>
|
||||
Pankaj Kumar <pankaj.m1@samsung.com>
|
||||
Patryk Kaczmarek <patryk.k@partner.samsung.com>
|
||||
Michal Maciola <m.maciola@samsung.com>
|
||||
Peter Vullings <peter@projectitis.com>
|
||||
K. S. Ernest (iFire) Lee <ernest.lee@chibifire.com>
|
||||
Rémi Verschelde <rverschelde@gmail.com>
|
||||
Martin Liska <mliksa@suse.cz>
|
||||
Vincenzo Pupillo <vincenzo.pupillo@unimi.it>
|
||||
EunSik Jeong <rinechran@outlook.jp>
|
||||
Rafał Mikrut <mikrutrafal@protonmail.com>
|
||||
Martin Capitanio <capnm@capitanio.org>
|
||||
RuiwenTang <tangruiwen1989@gmail.com>
|
||||
YouJin Lee <ol-of@naver.com>
|
||||
Sergii Liebodkin <sergii@lottiefiles.com>
|
||||
Jinny You <jinny@lottiefiles.com>
|
||||
Nattu Adnan <nattu@reallynattu.com>
|
||||
Gabor Kiss-Vamosi <kisvegabor@gmail.com>
|
||||
Lorcán Mc Donagh <lorcan@lmdsp.com>
|
||||
Lucas Niu <hoiyu3twon9@gmail.com>
|
||||
Francisco Ramírez <franchuti688@gmail.com>
|
||||
Abdelrahman Ashraf <a.theashraf@gmail.com>
|
||||
Neo Xu <neo.xu1990@gmail.com>
|
||||
Thaddeus Crews <repiteo@outlook.com>
|
||||
Josh Soref <jsoref@gmail.com>
|
||||
Elliott Sales de Andrade <quantum.analyst@gmail.com>
|
||||
Łukasz Pomietło <oficjalnyadreslukasza@gmail.com>
|
||||
Kelly Loh <kelly@lottiefiles.com>
|
||||
Dragoș Tiselice <dragos@lottiefiles.com>
|
||||
Marcin Baszczewski <marcin@baszczewski.pl>
|
||||
Fabian Blatz <fabianblatz@gmail.com>
|
||||
7
thirdparty/thorvg/LICENSE
vendored
Normal file
7
thirdparty/thorvg/LICENSE
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2020 - 2024 notice for the ThorVG Project (see AUTHORS)
|
||||
|
||||
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.
|
||||
19
thirdparty/thorvg/inc/config.h
vendored
Normal file
19
thirdparty/thorvg/inc/config.h
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef THORVG_CONFIG_H
|
||||
#define THORVG_CONFIG_H
|
||||
|
||||
#define THORVG_SW_RASTER_SUPPORT
|
||||
#define THORVG_SVG_LOADER_SUPPORT
|
||||
#define THORVG_PNG_LOADER_SUPPORT
|
||||
#ifndef WEB_ENABLED
|
||||
#define THORVG_THREAD_SUPPORT
|
||||
#endif
|
||||
|
||||
// Added conditionally if respective modules are enabled.
|
||||
//#define THORVG_WEBP_LOADER_SUPPORT
|
||||
//#define THORVG_JPG_LOADER_SUPPORT
|
||||
|
||||
// For internal debugging:
|
||||
//#define THORVG_LOG_ENABLED
|
||||
|
||||
#define THORVG_VERSION_STRING "0.15.13"
|
||||
#endif
|
||||
2180
thirdparty/thorvg/inc/thorvg.h
vendored
Normal file
2180
thirdparty/thorvg/inc/thorvg.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13
thirdparty/thorvg/patches/0001-revert-tvglines-bezier-precision.patch
vendored
Normal file
13
thirdparty/thorvg/patches/0001-revert-tvglines-bezier-precision.patch
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp
|
||||
index cb7f24ff40..f27f69faeb 100644
|
||||
--- a/thirdparty/thorvg/src/common/tvgMath.cpp
|
||||
+++ b/thirdparty/thorvg/src/common/tvgMath.cpp
|
||||
@@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc
|
||||
Bezier left;
|
||||
right.split(t, left);
|
||||
length = _bezLength(left, lineLengthFunc);
|
||||
- if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
|
||||
+ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
|
||||
break;
|
||||
}
|
||||
if (length < at) {
|
||||
44
thirdparty/thorvg/patches/0002-use-heap-alloc.patch
vendored
Normal file
44
thirdparty/thorvg/patches/0002-use-heap-alloc.patch
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
|
||||
index 81d5c098a2..4c0a0f53db 100644
|
||||
--- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
|
||||
+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
|
||||
@@ -475,11 +475,14 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt
|
||||
if (!buf) return false;
|
||||
|
||||
end = buf + bufLength;
|
||||
- key = (char*)alloca(end - buf + 1);
|
||||
- val = (char*)alloca(end - buf + 1);
|
||||
|
||||
if (buf == end) return true;
|
||||
|
||||
+ char* key_buf = (char*)malloc(end - buf + 1);
|
||||
+ char* val_buf = (char*)malloc(end - buf + 1);
|
||||
+
|
||||
+ key = key_buf;
|
||||
+ val = val_buf;
|
||||
do {
|
||||
char* sep = (char*)strchr(buf, ':');
|
||||
next = (char*)strchr(buf, ';');
|
||||
@@ -487,7 +490,11 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt
|
||||
if (auto src = strstr(buf, "src")) {//src tag from css font-face contains extra semicolon
|
||||
if (src < sep) {
|
||||
if (next + 1 < end) next = (char*)strchr(next + 1, ';');
|
||||
- else return true;
|
||||
+ else {
|
||||
+ free(key_buf);
|
||||
+ free(val_buf);
|
||||
+ return true;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +541,9 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt
|
||||
buf = next + 1;
|
||||
} while (true);
|
||||
|
||||
+ free(key_buf);
|
||||
+ free(val_buf);
|
||||
+
|
||||
return true;
|
||||
}
|
||||
|
||||
203
thirdparty/thorvg/src/common/tvgArray.h
vendored
Normal file
203
thirdparty/thorvg/src/common/tvgArray.h
vendored
Normal 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(const 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_
|
||||
492
thirdparty/thorvg/src/common/tvgCompressor.cpp
vendored
Normal file
492
thirdparty/thorvg/src/common/tvgCompressor.cpp
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Lempel–Ziv–Welch (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 idx;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* DJB2 Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
unsigned long djb2Encode(const char* str)
|
||||
{
|
||||
if (!str) return 0;
|
||||
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++)) {
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
36
thirdparty/thorvg/src/common/tvgCompressor.h
vendored
Normal file
36
thirdparty/thorvg/src/common/tvgCompressor.h
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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);
|
||||
unsigned long djb2Encode(const char* str);
|
||||
}
|
||||
|
||||
#endif //_TVG_COMPRESSOR_H_
|
||||
111
thirdparty/thorvg/src/common/tvgInlist.h
vendored
Normal file
111
thirdparty/thorvg/src/common/tvgInlist.h
vendored
Normal 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() const
|
||||
{
|
||||
return head ? false : true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // _TVG_INLIST_H_
|
||||
76
thirdparty/thorvg/src/common/tvgLock.h
vendored
Normal file
76
thirdparty/thorvg/src/common/tvgLock.h
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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>
|
||||
#include "tvgTaskScheduler.h"
|
||||
|
||||
namespace tvg {
|
||||
|
||||
struct Key
|
||||
{
|
||||
std::mutex mtx;
|
||||
};
|
||||
|
||||
struct ScopedLock
|
||||
{
|
||||
Key* key = nullptr;
|
||||
|
||||
ScopedLock(Key& k)
|
||||
{
|
||||
if (TaskScheduler::threads() > 0) {
|
||||
k.mtx.lock();
|
||||
key = &k;
|
||||
}
|
||||
}
|
||||
|
||||
~ScopedLock()
|
||||
{
|
||||
if (TaskScheduler::threads() > 0) {
|
||||
key->mtx.unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#else //THORVG_THREAD_SUPPORT
|
||||
|
||||
namespace tvg {
|
||||
|
||||
struct Key {};
|
||||
|
||||
struct ScopedLock
|
||||
{
|
||||
ScopedLock(Key& key) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //THORVG_THREAD_SUPPORT
|
||||
|
||||
#endif //_TVG_LOCK_H_
|
||||
|
||||
374
thirdparty/thorvg/src/common/tvgMath.cpp
vendored
Normal file
374
thirdparty/thorvg/src/common/tvgMath.cpp
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#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) {
|
||||
cur.split(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;
|
||||
right.split(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 {
|
||||
|
||||
//https://en.wikipedia.org/wiki/Remez_algorithm
|
||||
float atan2(float y, float x)
|
||||
{
|
||||
if (y == 0.0f && x == 0.0f) return 0.0f;
|
||||
auto a = std::min(fabsf(x), fabsf(y)) / std::max(fabsf(x), fabsf(y));
|
||||
auto s = a * a;
|
||||
auto r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a;
|
||||
if (fabsf(y) > fabsf(x)) r = 1.57079637f - r;
|
||||
if (x < 0) r = 3.14159274f - r;
|
||||
if (y < 0) return -r;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
bool inverse(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);
|
||||
|
||||
auto invDet = 1.0f / det;
|
||||
if (std::isinf(invDet)) return false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
bool identity(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 rotate(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;
|
||||
}
|
||||
|
||||
|
||||
Matrix operator*(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;
|
||||
}
|
||||
|
||||
|
||||
bool operator==(const Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
if (!tvg::equal(lhs.e11, rhs.e11) || !tvg::equal(lhs.e12, rhs.e12) || !tvg::equal(lhs.e13, rhs.e13) ||
|
||||
!tvg::equal(lhs.e21, rhs.e21) || !tvg::equal(lhs.e22, rhs.e22) || !tvg::equal(lhs.e23, rhs.e23) ||
|
||||
!tvg::equal(lhs.e31, rhs.e31) || !tvg::equal(lhs.e32, rhs.e32) || !tvg::equal(lhs.e33, rhs.e33)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void operator*=(Point& pt, const Matrix& m)
|
||||
{
|
||||
auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13;
|
||||
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
|
||||
pt.x = tx;
|
||||
pt.y = ty;
|
||||
}
|
||||
|
||||
|
||||
Point operator*(const Point& pt, const Matrix& m)
|
||||
{
|
||||
auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13;
|
||||
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
|
||||
return {tx, ty};
|
||||
}
|
||||
|
||||
|
||||
Point normal(const Point& p1, const Point& p2)
|
||||
{
|
||||
auto dir = p2 - p1;
|
||||
auto len = length(dir);
|
||||
if (tvg::zero(len)) return {};
|
||||
|
||||
auto unitDir = dir / len;
|
||||
return {-unitDir.y, unitDir.x};
|
||||
}
|
||||
|
||||
|
||||
float Line::length() const
|
||||
{
|
||||
return _lineLength(pt1, pt2);
|
||||
}
|
||||
|
||||
|
||||
void Line::split(float at, Line& left, Line& right) const
|
||||
{
|
||||
auto len = length();
|
||||
auto dx = ((pt2.x - pt1.x) / len) * at;
|
||||
auto dy = ((pt2.y - pt1.y) / len) * at;
|
||||
left.pt1 = pt1;
|
||||
left.pt2.x = left.pt1.x + dx;
|
||||
left.pt2.y = left.pt1.y + dy;
|
||||
right.pt1 = left.pt2;
|
||||
right.pt2 = pt2;
|
||||
}
|
||||
|
||||
|
||||
void Bezier::split(Bezier& left, Bezier& right) const
|
||||
{
|
||||
auto c = (ctrl1.x + ctrl2.x) * 0.5f;
|
||||
left.ctrl1.x = (start.x + ctrl1.x) * 0.5f;
|
||||
right.ctrl2.x = (ctrl2.x + end.x) * 0.5f;
|
||||
left.start.x = start.x;
|
||||
right.end.x = 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 = (ctrl1.y + ctrl2.y) * 0.5f;
|
||||
left.ctrl1.y = (start.y + ctrl1.y) * 0.5f;
|
||||
right.ctrl2.y = (ctrl2.y + end.y) * 0.5f;
|
||||
left.start.y = start.y;
|
||||
right.end.y = 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;
|
||||
}
|
||||
|
||||
|
||||
void Bezier::split(float at, Bezier& left, Bezier& right) const
|
||||
{
|
||||
right = *this;
|
||||
auto t = right.at(at, right.length());
|
||||
right.split(t, left);
|
||||
}
|
||||
|
||||
|
||||
float Bezier::length() const
|
||||
{
|
||||
return _bezLength(*this, _lineLength);
|
||||
}
|
||||
|
||||
|
||||
float Bezier::lengthApprox() const
|
||||
{
|
||||
return _bezLength(*this, _lineLengthApprox);
|
||||
}
|
||||
|
||||
|
||||
void Bezier::split(float t, Bezier& left)
|
||||
{
|
||||
left.start = start;
|
||||
|
||||
left.ctrl1.x = start.x + t * (ctrl1.x - start.x);
|
||||
left.ctrl1.y = start.y + t * (ctrl1.y - start.y);
|
||||
|
||||
left.ctrl2.x = ctrl1.x + t * (ctrl2.x - ctrl1.x); //temporary holding spot
|
||||
left.ctrl2.y = ctrl1.y + t * (ctrl2.y - ctrl1.y); //temporary holding spot
|
||||
|
||||
ctrl2.x = ctrl2.x + t * (end.x - ctrl2.x);
|
||||
ctrl2.y = ctrl2.y + t * (end.y - ctrl2.y);
|
||||
|
||||
ctrl1.x = left.ctrl2.x + t * (ctrl2.x - left.ctrl2.x);
|
||||
ctrl1.y = left.ctrl2.y + t * (ctrl2.y - left.ctrl2.y);
|
||||
|
||||
left.ctrl2.x = left.ctrl1.x + t * (left.ctrl2.x - left.ctrl1.x);
|
||||
left.ctrl2.y = left.ctrl1.y + t * (left.ctrl2.y - left.ctrl1.y);
|
||||
|
||||
left.end.x = start.x = left.ctrl2.x + t * (ctrl1.x - left.ctrl2.x);
|
||||
left.end.y = start.y = left.ctrl2.y + t * (ctrl1.y - left.ctrl2.y);
|
||||
}
|
||||
|
||||
|
||||
float Bezier::at(float at, float length) const
|
||||
{
|
||||
return _bezAt(*this, at, length, _lineLength);
|
||||
}
|
||||
|
||||
|
||||
float Bezier::atApprox(float at, float length) const
|
||||
{
|
||||
return _bezAt(*this, at, length, _lineLengthApprox);
|
||||
}
|
||||
|
||||
|
||||
Point Bezier::at(float t) const
|
||||
{
|
||||
Point cur;
|
||||
auto it = 1.0f - t;
|
||||
|
||||
auto ax = start.x * it + ctrl1.x * t;
|
||||
auto bx = ctrl1.x * it + ctrl2.x * t;
|
||||
auto cx = ctrl2.x * it + end.x * t;
|
||||
ax = ax * it + bx * t;
|
||||
bx = bx * it + cx * t;
|
||||
cur.x = ax * it + bx * t;
|
||||
|
||||
float ay = start.y * it + ctrl1.y * t;
|
||||
float by = ctrl1.y * it + ctrl2.y * t;
|
||||
float cy = ctrl2.y * it + end.y * t;
|
||||
ay = ay * it + by * t;
|
||||
by = by * it + cy * t;
|
||||
cur.y = ay * it + by * t;
|
||||
|
||||
return cur;
|
||||
}
|
||||
|
||||
|
||||
float Bezier::angle(float t) const
|
||||
{
|
||||
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 * start.x + b * ctrl1.x + c * ctrl2.x + d * end.x, a * start.y + b * ctrl1.y + c * ctrl2.y + d * end.y};
|
||||
pt.x *= 3;
|
||||
pt.y *= 3;
|
||||
|
||||
return rad2deg(tvg::atan2(pt.y, pt.x));
|
||||
}
|
||||
|
||||
|
||||
uint8_t lerp(const uint8_t &start, const uint8_t &end, float t)
|
||||
{
|
||||
auto result = static_cast<int>(start + (end - start) * t);
|
||||
tvg::clamp(result, 0, 255);
|
||||
return static_cast<uint8_t>(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
306
thirdparty/thorvg/src/common/tvgMath.h
vendored
Normal file
306
thirdparty/thorvg/src/common/tvgMath.h
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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 <cmath>
|
||||
#include "tvgCommon.h"
|
||||
|
||||
namespace tvg
|
||||
{
|
||||
|
||||
#define MATH_PI 3.14159265358979323846f
|
||||
#define MATH_PI2 1.57079632679489661923f
|
||||
#define FLOAT_EPSILON 1.0e-06f //1.192092896e-07f
|
||||
#define PATH_KAPPA 0.552284f
|
||||
|
||||
/************************************************************************/
|
||||
/* General functions */
|
||||
/************************************************************************/
|
||||
|
||||
float atan2(float y, float x);
|
||||
|
||||
|
||||
static inline float deg2rad(float degree)
|
||||
{
|
||||
return degree * (MATH_PI / 180.0f);
|
||||
}
|
||||
|
||||
|
||||
static inline float rad2deg(float radian)
|
||||
{
|
||||
return radian * (180.0f / MATH_PI);
|
||||
}
|
||||
|
||||
|
||||
static inline bool zero(float a)
|
||||
{
|
||||
return (fabsf(a) <= FLOAT_EPSILON) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
static inline bool equal(float a, float b)
|
||||
{
|
||||
return tvg::zero(a - b);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
static inline void clamp(T& v, const T& min, const T& max)
|
||||
{
|
||||
if (v < min) v = min;
|
||||
else if (v > max) v = max;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* Matrix functions */
|
||||
/************************************************************************/
|
||||
|
||||
void rotate(Matrix* m, float degree);
|
||||
bool inverse(const Matrix* m, Matrix* out);
|
||||
bool identity(const Matrix* m);
|
||||
Matrix operator*(const Matrix& lhs, const Matrix& rhs);
|
||||
bool operator==(const Matrix& lhs, const Matrix& rhs);
|
||||
|
||||
static inline bool rightAngle(const Matrix& m)
|
||||
{
|
||||
auto radian = fabsf(tvg::atan2(m.e21, m.e11));
|
||||
if (radian < FLOAT_EPSILON || tvg::equal(radian, MATH_PI2) || tvg::equal(radian, MATH_PI)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static inline bool skewed(const Matrix& m)
|
||||
{
|
||||
return !tvg::zero(m.e21 + m.e12);
|
||||
}
|
||||
|
||||
|
||||
static inline void identity(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 scale(Matrix* m, float sx, float sy)
|
||||
{
|
||||
m->e11 *= sx;
|
||||
m->e22 *= sy;
|
||||
}
|
||||
|
||||
|
||||
static inline void scaleR(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 translate(Matrix* m, float x, float y)
|
||||
{
|
||||
m->e13 += x;
|
||||
m->e23 += y;
|
||||
}
|
||||
|
||||
|
||||
static inline void translateR(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 bool operator!=(const Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
static inline void operator*=(Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = lhs * rhs;
|
||||
}
|
||||
|
||||
|
||||
static inline void log(const Matrix& m)
|
||||
{
|
||||
TVGLOG("COMMON", "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);
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Point functions */
|
||||
/************************************************************************/
|
||||
|
||||
void operator*=(Point& pt, const Matrix& m);
|
||||
Point operator*(const Point& pt, const Matrix& m);
|
||||
Point normal(const Point& p1, const Point& p2);
|
||||
|
||||
static inline float cross(const Point& lhs, const Point& rhs)
|
||||
{
|
||||
return lhs.x * rhs.y - rhs.x * lhs.y;
|
||||
}
|
||||
|
||||
|
||||
static inline bool zero(const Point& p)
|
||||
{
|
||||
return tvg::zero(p.x) && tvg::zero(p.y);
|
||||
}
|
||||
|
||||
|
||||
static inline float length(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 float length(const Point& a)
|
||||
{
|
||||
return sqrtf(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
|
||||
static inline bool operator==(const Point& lhs, const Point& rhs)
|
||||
{
|
||||
return tvg::equal(lhs.x, rhs.x) && tvg::equal(lhs.y, rhs.y);
|
||||
}
|
||||
|
||||
|
||||
static inline bool operator!=(const Point& lhs, const Point& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
|
||||
static inline void log(const Point& pt)
|
||||
{
|
||||
TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y);
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Line functions */
|
||||
/************************************************************************/
|
||||
|
||||
struct Line
|
||||
{
|
||||
Point pt1;
|
||||
Point pt2;
|
||||
|
||||
void split(float at, Line& left, Line& right) const;
|
||||
float length() const;
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Bezier functions */
|
||||
/************************************************************************/
|
||||
|
||||
struct Bezier
|
||||
{
|
||||
Point start;
|
||||
Point ctrl1;
|
||||
Point ctrl2;
|
||||
Point end;
|
||||
|
||||
void split(float t, Bezier& left);
|
||||
void split(Bezier& left, Bezier& right) const;
|
||||
void split(float at, Bezier& left, Bezier& right) const;
|
||||
float length() const;
|
||||
float lengthApprox() const;
|
||||
float at(float at, float length) const;
|
||||
float atApprox(float at, float length) const;
|
||||
Point at(float t) const;
|
||||
float angle(float t) const;
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Interpolation functions */
|
||||
/************************************************************************/
|
||||
|
||||
template <typename T>
|
||||
static inline T lerp(const T &start, const T &end, float t)
|
||||
{
|
||||
return static_cast<T>(start + (end - start) * t);
|
||||
}
|
||||
|
||||
uint8_t lerp(const uint8_t &start, const uint8_t &end, float t);
|
||||
|
||||
}
|
||||
|
||||
#endif //_TVG_MATH_H_
|
||||
240
thirdparty/thorvg/src/common/tvgStr.cpp
vendored
Normal file
240
thirdparty/thorvg/src/common/tvgStr.cpp
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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 *= 1E8f;
|
||||
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;
|
||||
}
|
||||
|
||||
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* strAppend(char* lhs, const char* rhs, size_t n)
|
||||
{
|
||||
if (!rhs) return lhs;
|
||||
if (!lhs) return strDuplicate(rhs, n);
|
||||
lhs = (char*)realloc(lhs, strlen(lhs) + n + 1);
|
||||
return strncat(lhs, rhs, 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);
|
||||
}
|
||||
|
||||
}
|
||||
37
thirdparty/thorvg/src/common/tvgStr.h
vendored
Normal file
37
thirdparty/thorvg/src/common/tvgStr.h
vendored
Normal 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
|
||||
char* strDuplicate(const char *str, size_t n); //copy the string
|
||||
char* strAppend(char* lhs, const char* rhs, size_t n); //append the rhs to the lhs
|
||||
char* strDirname(const char* path); //return the full directory name
|
||||
|
||||
}
|
||||
#endif //_TVG_STR_H_
|
||||
164
thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp
vendored
Normal file
164
thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.cpp
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 <turbojpeg.h>
|
||||
#include "tvgJpgLoader.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
void JpgLoader::clear()
|
||||
{
|
||||
if (freeData) free(data);
|
||||
data = nullptr;
|
||||
size = 0;
|
||||
freeData = false;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
JpgLoader::JpgLoader() : ImageLoader(FileType::Jpg)
|
||||
{
|
||||
jpegDecompressor = tjInitDecompress();
|
||||
}
|
||||
|
||||
|
||||
JpgLoader::~JpgLoader()
|
||||
{
|
||||
clear();
|
||||
tjDestroy(jpegDecompressor);
|
||||
|
||||
//This image is shared with raster engine.
|
||||
tjFree(surface.buf8);
|
||||
}
|
||||
|
||||
|
||||
bool JpgLoader::open(const string& path)
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
auto jpegFile = fopen(path.c_str(), "rb");
|
||||
if (!jpegFile) return false;
|
||||
|
||||
auto ret = false;
|
||||
|
||||
//determine size
|
||||
if (fseek(jpegFile, 0, SEEK_END) < 0) goto finalize;
|
||||
if (((size = ftell(jpegFile)) < 1)) goto finalize;
|
||||
if (fseek(jpegFile, 0, SEEK_SET)) goto finalize;
|
||||
|
||||
data = (unsigned char *) malloc(size);
|
||||
if (!data) goto finalize;
|
||||
|
||||
freeData = true;
|
||||
|
||||
if (fread(data, size, 1, jpegFile) < 1) goto failure;
|
||||
|
||||
int width, height, subSample, colorSpace;
|
||||
if (tjDecompressHeader3(jpegDecompressor, data, size, &width, &height, &subSample, &colorSpace) < 0) {
|
||||
TVGERR("JPG LOADER", "%s", tjGetErrorStr());
|
||||
goto failure;
|
||||
}
|
||||
|
||||
w = static_cast<float>(width);
|
||||
h = static_cast<float>(height);
|
||||
ret = true;
|
||||
|
||||
goto finalize;
|
||||
|
||||
failure:
|
||||
clear();
|
||||
|
||||
finalize:
|
||||
fclose(jpegFile);
|
||||
return ret;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool JpgLoader::open(const char* data, uint32_t size, bool copy)
|
||||
{
|
||||
int width, height, subSample, colorSpace;
|
||||
if (tjDecompressHeader3(jpegDecompressor, (unsigned char *) data, size, &width, &height, &subSample, &colorSpace) < 0) return false;
|
||||
|
||||
if (copy) {
|
||||
this->data = (unsigned char *) malloc(size);
|
||||
if (!this->data) return false;
|
||||
memcpy((unsigned char *)this->data, data, size);
|
||||
freeData = true;
|
||||
} else {
|
||||
this->data = (unsigned char *) data;
|
||||
freeData = false;
|
||||
}
|
||||
|
||||
w = static_cast<float>(width);
|
||||
h = static_cast<float>(height);
|
||||
this->size = size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool JpgLoader::read()
|
||||
{
|
||||
if (!LoadModule::read()) return true;
|
||||
|
||||
if (w == 0 || h == 0) return false;
|
||||
|
||||
//determine the image format
|
||||
TJPF format;
|
||||
if (ImageLoader::cs == ColorSpace::ARGB8888 || ImageLoader::cs == ColorSpace::ARGB8888S) {
|
||||
format = TJPF_BGRX;
|
||||
surface.cs = ColorSpace::ARGB8888;
|
||||
} else {
|
||||
format = TJPF_RGBX;
|
||||
surface.cs = ColorSpace::ABGR8888;
|
||||
}
|
||||
|
||||
auto image = (unsigned char *)tjAlloc(static_cast<int>(w) * static_cast<int>(h) * tjPixelSize[format]);
|
||||
if (!image) return false;
|
||||
|
||||
//decompress jpg image
|
||||
if (tjDecompress2(jpegDecompressor, data, size, image, static_cast<int>(w), 0, static_cast<int>(h), format, 0) < 0) {
|
||||
TVGERR("JPG LOADER", "%s", tjGetErrorStr());
|
||||
tjFree(image);
|
||||
image = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
//setup the surface
|
||||
surface.buf8 = image;
|
||||
surface.stride = w;
|
||||
surface.w = w;
|
||||
surface.h = h;
|
||||
surface.channelSize = sizeof(uint32_t);
|
||||
surface.premultiplied = true;
|
||||
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
50
thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h
vendored
Normal file
50
thirdparty/thorvg/src/loaders/external_jpg/tvgJpgLoader.h
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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_JPG_LOADER_H_
|
||||
#define _TVG_JPG_LOADER_H_
|
||||
|
||||
#include "tvgLoader.h"
|
||||
|
||||
using tjhandle = void*;
|
||||
|
||||
//TODO: Use Task?
|
||||
class JpgLoader : public ImageLoader
|
||||
{
|
||||
public:
|
||||
JpgLoader();
|
||||
~JpgLoader();
|
||||
|
||||
bool open(const string& path) override;
|
||||
bool open(const char* data, uint32_t size, bool copy) override;
|
||||
bool read() override;
|
||||
|
||||
private:
|
||||
void clear();
|
||||
|
||||
tjhandle jpegDecompressor;
|
||||
unsigned char* data = nullptr;
|
||||
unsigned long size = 0;
|
||||
bool freeData = false;
|
||||
};
|
||||
|
||||
#endif //_TVG_JPG_LOADER_H_
|
||||
117
thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp
vendored
Normal file
117
thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.cpp
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 "tvgPngLoader.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
void PngLoader::clear()
|
||||
{
|
||||
png_image_free(image);
|
||||
free(image);
|
||||
image = nullptr;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
PngLoader::PngLoader() : ImageLoader(FileType::Png)
|
||||
{
|
||||
image = static_cast<png_imagep>(calloc(1, sizeof(png_image)));
|
||||
image->version = PNG_IMAGE_VERSION;
|
||||
image->opaque = NULL;
|
||||
}
|
||||
|
||||
PngLoader::~PngLoader()
|
||||
{
|
||||
clear();
|
||||
free((void*)surface.buf32);
|
||||
}
|
||||
|
||||
|
||||
bool PngLoader::open(const string& path)
|
||||
{
|
||||
image->opaque = NULL;
|
||||
|
||||
if (!png_image_begin_read_from_file(image, path.c_str())) return false;
|
||||
|
||||
w = (float)image->width;
|
||||
h = (float)image->height;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool PngLoader::open(const char* data, uint32_t size, bool copy)
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
image->opaque = NULL;
|
||||
|
||||
if (!png_image_begin_read_from_memory(image, data, size)) return false;
|
||||
|
||||
w = (float)image->width;
|
||||
h = (float)image->height;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool PngLoader::read()
|
||||
{
|
||||
if (!LoadModule::read()) return true;
|
||||
|
||||
if (w == 0 || h == 0) return false;
|
||||
|
||||
if (ImageLoader::cs == ColorSpace::ARGB8888 || ImageLoader::cs == ColorSpace::ARGB8888S) {
|
||||
image->format = PNG_FORMAT_BGRA;
|
||||
surface.cs = ColorSpace::ARGB8888S;
|
||||
} else {
|
||||
image->format = PNG_FORMAT_RGBA;
|
||||
surface.cs = ColorSpace::ABGR8888S;
|
||||
}
|
||||
|
||||
auto buffer = static_cast<png_bytep>(malloc(PNG_IMAGE_SIZE((*image))));
|
||||
if (!png_image_finish_read(image, NULL, buffer, 0, NULL)) {
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
//setup the surface
|
||||
surface.buf32 = reinterpret_cast<uint32_t*>(buffer);
|
||||
surface.stride = (uint32_t)w;
|
||||
surface.w = (uint32_t)w;
|
||||
surface.h = (uint32_t)h;
|
||||
surface.channelSize = sizeof(uint32_t);
|
||||
//TODO: we can acquire a pre-multiplied image. See "png_structrp"
|
||||
surface.premultiplied = false;
|
||||
|
||||
clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
45
thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h
vendored
Normal file
45
thirdparty/thorvg/src/loaders/external_png/tvgPngLoader.h
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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_PNG_LOADER_H_
|
||||
#define _TVG_PNG_LOADER_H_
|
||||
|
||||
#include <png.h>
|
||||
#include "tvgLoader.h"
|
||||
|
||||
class PngLoader : public ImageLoader
|
||||
{
|
||||
public:
|
||||
PngLoader();
|
||||
~PngLoader();
|
||||
|
||||
bool open(const string& path) override;
|
||||
bool open(const char* data, uint32_t size, bool copy) override;
|
||||
bool read() override;
|
||||
|
||||
private:
|
||||
void clear();
|
||||
|
||||
png_imagep image = nullptr;
|
||||
};
|
||||
|
||||
#endif //_TVG_PNG_LOADER_H_
|
||||
146
thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp
vendored
Normal file
146
thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.cpp
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 <memory.h>
|
||||
#include <webp/decode.h>
|
||||
|
||||
#include "tvgWebpLoader.h"
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
|
||||
void WebpLoader::run(unsigned tid)
|
||||
{
|
||||
//TODO: acquire the current colorspace format & pre-multiplied alpha image.
|
||||
surface.buf8 = WebPDecodeBGRA(data, size, nullptr, nullptr);
|
||||
surface.stride = (uint32_t)w;
|
||||
surface.w = (uint32_t)w;
|
||||
surface.h = (uint32_t)h;
|
||||
surface.channelSize = sizeof(uint32_t);
|
||||
surface.cs = ColorSpace::ARGB8888;
|
||||
surface.premultiplied = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
WebpLoader::WebpLoader() : ImageLoader(FileType::Webp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
WebpLoader::~WebpLoader()
|
||||
{
|
||||
done();
|
||||
|
||||
if (freeData) free(data);
|
||||
data = nullptr;
|
||||
size = 0;
|
||||
freeData = false;
|
||||
WebPFree(surface.buf8);
|
||||
}
|
||||
|
||||
|
||||
bool WebpLoader::open(const string& path)
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
auto webpFile = fopen(path.c_str(), "rb");
|
||||
if (!webpFile) return false;
|
||||
|
||||
auto ret = false;
|
||||
|
||||
//determine size
|
||||
if (fseek(webpFile, 0, SEEK_END) < 0) goto finalize;
|
||||
if (((size = ftell(webpFile)) < 1)) goto finalize;
|
||||
if (fseek(webpFile, 0, SEEK_SET)) goto finalize;
|
||||
|
||||
data = (unsigned char *) malloc(size);
|
||||
if (!data) goto finalize;
|
||||
|
||||
freeData = true;
|
||||
|
||||
if (fread(data, size, 1, webpFile) < 1) goto finalize;
|
||||
|
||||
int width, height;
|
||||
if (!WebPGetInfo(data, size, &width, &height)) goto finalize;
|
||||
|
||||
w = static_cast<float>(width);
|
||||
h = static_cast<float>(height);
|
||||
|
||||
ret = true;
|
||||
|
||||
finalize:
|
||||
fclose(webpFile);
|
||||
return ret;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool WebpLoader::open(const char* data, uint32_t size, bool copy)
|
||||
{
|
||||
if (copy) {
|
||||
this->data = (unsigned char *) malloc(size);
|
||||
if (!this->data) return false;
|
||||
memcpy((unsigned char *)this->data, data, size);
|
||||
freeData = true;
|
||||
} else {
|
||||
this->data = (unsigned char *) data;
|
||||
freeData = false;
|
||||
}
|
||||
|
||||
int width, height;
|
||||
if (!WebPGetInfo(this->data, size, &width, &height)) return false;
|
||||
|
||||
w = static_cast<float>(width);
|
||||
h = static_cast<float>(height);
|
||||
surface.cs = ColorSpace::ARGB8888;
|
||||
this->size = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WebpLoader::read()
|
||||
{
|
||||
if (!LoadModule::read()) return true;
|
||||
|
||||
if (!data || w == 0 || h == 0) return false;
|
||||
|
||||
TaskScheduler::request(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
RenderSurface* WebpLoader::bitmap()
|
||||
{
|
||||
this->done();
|
||||
|
||||
return ImageLoader::bitmap();
|
||||
}
|
||||
49
thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.h
vendored
Normal file
49
thirdparty/thorvg/src/loaders/external_webp/tvgWebpLoader.h
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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_WEBP_LOADER_H_
|
||||
#define _TVG_WEBP_LOADER_H_
|
||||
|
||||
#include "tvgLoader.h"
|
||||
#include "tvgTaskScheduler.h"
|
||||
|
||||
class WebpLoader : public ImageLoader, public Task
|
||||
{
|
||||
public:
|
||||
WebpLoader();
|
||||
~WebpLoader();
|
||||
|
||||
bool open(const string& path) override;
|
||||
bool open(const char* data, uint32_t size, bool copy) override;
|
||||
bool read() override;
|
||||
|
||||
RenderSurface* bitmap() override;
|
||||
|
||||
private:
|
||||
void run(unsigned tid) override;
|
||||
|
||||
unsigned char* data = nullptr;
|
||||
unsigned long size = 0;
|
||||
bool freeData = false;
|
||||
};
|
||||
|
||||
#endif //_TVG_WEBP_LOADER_H_
|
||||
82
thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
vendored
Normal file
82
thirdparty/thorvg/src/loaders/raw/tvgRawLoader.cpp
vendored
Normal 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 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 = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RawLoader::read()
|
||||
{
|
||||
LoadModule::read();
|
||||
|
||||
return true;
|
||||
}
|
||||
40
thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h
vendored
Normal file
40
thirdparty/thorvg/src/loaders/raw/tvgRawLoader.h
vendored
Normal 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 copy);
|
||||
bool read() override;
|
||||
};
|
||||
|
||||
|
||||
#endif //_TVG_RAW_LOADER_H_
|
||||
265
thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp
vendored
Normal file
265
thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (c) 2022 - 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 "tvgSvgCssStyle.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static bool _isImportanceApplicable(SvgStyleFlags &toFlagsImportance, SvgStyleFlags fromFlagsImportance, SvgStyleFlags flag)
|
||||
{
|
||||
if (!(toFlagsImportance & flag) && (fromFlagsImportance & flag)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
|
||||
{
|
||||
if (from == nullptr) return;
|
||||
//Copy the properties of 'from' only if they were explicitly set (not the default ones).
|
||||
if ((from->curColorSet && !(to->flags & SvgStyleFlags::Color)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Color)) {
|
||||
to->color = from->color;
|
||||
to->curColorSet = true;
|
||||
to->flags = (to->flags | SvgStyleFlags::Color);
|
||||
if (from->flagsImportance & SvgStyleFlags::Color) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Color);
|
||||
}
|
||||
}
|
||||
if (((from->flags & SvgStyleFlags::PaintOrder) && !(to->flags & SvgStyleFlags::PaintOrder)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::PaintOrder)) {
|
||||
to->paintOrder = from->paintOrder;
|
||||
to->flags = (to->flags | SvgStyleFlags::PaintOrder);
|
||||
if (from->flagsImportance & SvgStyleFlags::PaintOrder) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::PaintOrder);
|
||||
}
|
||||
}
|
||||
if (((from->flags & SvgStyleFlags::Display) && !(to->flags & SvgStyleFlags::Display)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Display)) {
|
||||
to->display = from->display;
|
||||
to->flags = (to->flags | SvgStyleFlags::Display);
|
||||
if (from->flagsImportance & SvgStyleFlags::Display) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Display);
|
||||
}
|
||||
}
|
||||
//Fill
|
||||
if (((from->fill.flags & SvgFillFlags::Paint) && !(to->flags & SvgStyleFlags::Fill)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Fill)) {
|
||||
to->fill.paint.color = from->fill.paint.color;
|
||||
to->fill.paint.none = from->fill.paint.none;
|
||||
to->fill.paint.curColor = from->fill.paint.curColor;
|
||||
if (from->fill.paint.url) {
|
||||
if (to->fill.paint.url) free(to->fill.paint.url);
|
||||
to->fill.paint.url = strdup(from->fill.paint.url);
|
||||
}
|
||||
to->fill.flags = (to->fill.flags | SvgFillFlags::Paint);
|
||||
to->flags = (to->flags | SvgStyleFlags::Fill);
|
||||
if (from->flagsImportance & SvgStyleFlags::Fill) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Fill);
|
||||
}
|
||||
}
|
||||
if (((from->fill.flags & SvgFillFlags::Opacity) && !(to->flags & SvgStyleFlags::FillOpacity)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::FillOpacity)) {
|
||||
to->fill.opacity = from->fill.opacity;
|
||||
to->fill.flags = (to->fill.flags | SvgFillFlags::Opacity);
|
||||
to->flags = (to->flags | SvgStyleFlags::FillOpacity);
|
||||
if (from->flagsImportance & SvgStyleFlags::FillOpacity) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillOpacity);
|
||||
}
|
||||
}
|
||||
if (((from->fill.flags & SvgFillFlags::FillRule) && !(to->flags & SvgStyleFlags::FillRule)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::FillRule)) {
|
||||
to->fill.fillRule = from->fill.fillRule;
|
||||
to->fill.flags = (to->fill.flags | SvgFillFlags::FillRule);
|
||||
to->flags = (to->flags | SvgStyleFlags::FillRule);
|
||||
if (from->flagsImportance & SvgStyleFlags::FillRule) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillRule);
|
||||
}
|
||||
}
|
||||
//Stroke
|
||||
if (((from->stroke.flags & SvgStrokeFlags::Paint) && !(to->flags & SvgStyleFlags::Stroke)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Stroke)) {
|
||||
to->stroke.paint.color = from->stroke.paint.color;
|
||||
to->stroke.paint.none = from->stroke.paint.none;
|
||||
to->stroke.paint.curColor = from->stroke.paint.curColor;
|
||||
if (from->stroke.paint.url) {
|
||||
if (to->stroke.paint.url) free(to->stroke.paint.url);
|
||||
to->stroke.paint.url = strdup(from->stroke.paint.url);
|
||||
}
|
||||
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Paint);
|
||||
to->flags = (to->flags | SvgStyleFlags::Stroke);
|
||||
if (from->flagsImportance & SvgStyleFlags::Stroke) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Stroke);
|
||||
}
|
||||
}
|
||||
if (((from->stroke.flags & SvgStrokeFlags::Opacity) && !(to->flags & SvgStyleFlags::StrokeOpacity)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeOpacity)) {
|
||||
to->stroke.opacity = from->stroke.opacity;
|
||||
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Opacity);
|
||||
to->flags = (to->flags | SvgStyleFlags::StrokeOpacity);
|
||||
if (from->flagsImportance & SvgStyleFlags::StrokeOpacity) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeOpacity);
|
||||
}
|
||||
}
|
||||
if (((from->stroke.flags & SvgStrokeFlags::Width) && !(to->flags & SvgStyleFlags::StrokeWidth)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeWidth)) {
|
||||
to->stroke.width = from->stroke.width;
|
||||
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Width);
|
||||
to->flags = (to->flags | SvgStyleFlags::StrokeWidth);
|
||||
if (from->flagsImportance & SvgStyleFlags::StrokeWidth) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeWidth);
|
||||
}
|
||||
}
|
||||
if (((from->stroke.flags & SvgStrokeFlags::Dash) && !(to->flags & SvgStyleFlags::StrokeDashArray)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeDashArray)) {
|
||||
if (from->stroke.dash.array.count > 0) {
|
||||
to->stroke.dash.array.clear();
|
||||
to->stroke.dash.array.reserve(from->stroke.dash.array.count);
|
||||
for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) {
|
||||
to->stroke.dash.array.push(from->stroke.dash.array[i]);
|
||||
}
|
||||
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Dash);
|
||||
to->flags = (to->flags | SvgStyleFlags::StrokeDashArray);
|
||||
if (from->flagsImportance & SvgStyleFlags::StrokeDashArray) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeDashArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (((from->stroke.flags & SvgStrokeFlags::Cap) && !(to->flags & SvgStyleFlags::StrokeLineCap)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeLineCap)) {
|
||||
to->stroke.cap = from->stroke.cap;
|
||||
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Cap);
|
||||
to->flags = (to->flags | SvgStyleFlags::StrokeLineCap);
|
||||
if (from->flagsImportance & SvgStyleFlags::StrokeLineCap) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineCap);
|
||||
}
|
||||
}
|
||||
if (((from->stroke.flags & SvgStrokeFlags::Join) && !(to->flags & SvgStyleFlags::StrokeLineJoin)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeLineJoin)) {
|
||||
to->stroke.join = from->stroke.join;
|
||||
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Join);
|
||||
to->flags = (to->flags | SvgStyleFlags::StrokeLineJoin);
|
||||
if (from->flagsImportance & SvgStyleFlags::StrokeLineJoin) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineJoin);
|
||||
}
|
||||
}
|
||||
//Opacity
|
||||
//TODO: it can be set to be 255 and shouldn't be changed by attribute 'opacity'
|
||||
if ((from->opacity < 255 && !(to->flags & SvgStyleFlags::Opacity)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Opacity)) {
|
||||
to->opacity = from->opacity;
|
||||
to->flags = (to->flags | SvgStyleFlags::Opacity);
|
||||
if (from->flagsImportance & SvgStyleFlags::Opacity) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
void cssCopyStyleAttr(SvgNode* to, const SvgNode* from)
|
||||
{
|
||||
//Copy matrix attribute
|
||||
if (from->transform && !(to->style->flags & SvgStyleFlags::Transform)) {
|
||||
to->transform = (Matrix*)malloc(sizeof(Matrix));
|
||||
if (to->transform) {
|
||||
*to->transform = *from->transform;
|
||||
to->style->flags = (to->style->flags | SvgStyleFlags::Transform);
|
||||
}
|
||||
}
|
||||
//Copy style attribute
|
||||
_copyStyle(to->style, from->style);
|
||||
|
||||
if (from->style->clipPath.url) {
|
||||
if (to->style->clipPath.url) free(to->style->clipPath.url);
|
||||
to->style->clipPath.url = strdup(from->style->clipPath.url);
|
||||
}
|
||||
if (from->style->mask.url) {
|
||||
if (to->style->mask.url) free(to->style->mask.url);
|
||||
to->style->mask.url = strdup(from->style->mask.url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title, SvgNodeType type)
|
||||
{
|
||||
if (!style) return nullptr;
|
||||
|
||||
auto child = style->child.data;
|
||||
for (uint32_t i = 0; i < style->child.count; ++i, ++child) {
|
||||
if ((*child)->type == type) {
|
||||
if ((!title && !(*child)->id) || (title && (*child)->id && !strcmp((*child)->id, title))) return (*child);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title)
|
||||
{
|
||||
if (!style || !title) return nullptr;
|
||||
|
||||
auto child = style->child.data;
|
||||
for (uint32_t i = 0; i < style->child.count; ++i, ++child) {
|
||||
if ((*child)->type == SvgNodeType::CssStyle) {
|
||||
if ((*child)->id && !strcmp((*child)->id, title)) return (*child);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void cssUpdateStyle(SvgNode* doc, SvgNode* style)
|
||||
{
|
||||
if (doc->child.count > 0) {
|
||||
auto child = doc->child.data;
|
||||
for (uint32_t i = 0; i < doc->child.count; ++i, ++child) {
|
||||
if (auto cssNode = cssFindStyleNode(style, nullptr, (*child)->type)) {
|
||||
cssCopyStyleAttr(*child, cssNode);
|
||||
}
|
||||
cssUpdateStyle(*child, style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void cssApplyStyleToPostponeds(Array<SvgNodeIdPair>& postponeds, SvgNode* style)
|
||||
{
|
||||
for (uint32_t i = 0; i < postponeds.count; ++i) {
|
||||
auto nodeIdPair = postponeds[i];
|
||||
|
||||
//css styling: tag.name has higher priority than .name
|
||||
if (auto cssNode = cssFindStyleNode(style, nodeIdPair.id, nodeIdPair.node->type)) {
|
||||
cssCopyStyleAttr(nodeIdPair.node, cssNode);
|
||||
}
|
||||
if (auto cssNode = cssFindStyleNode(style, nodeIdPair.id)) {
|
||||
cssCopyStyleAttr(nodeIdPair.node, cssNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.h
vendored
Normal file
34
thirdparty/thorvg/src/loaders/svg/tvgSvgCssStyle.h
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2022 - 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_SVG_CSS_STYLE_H_
|
||||
#define _TVG_SVG_CSS_STYLE_H_
|
||||
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
|
||||
void cssCopyStyleAttr(SvgNode* to, const SvgNode* from);
|
||||
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title, SvgNodeType type);
|
||||
SvgNode* cssFindStyleNode(const SvgNode* style, const char* title);
|
||||
void cssUpdateStyle(SvgNode* doc, SvgNode* style);
|
||||
void cssApplyStyleToPostponeds(Array<SvgNodeIdPair>& postponeds, SvgNode* style);
|
||||
|
||||
#endif //_TVG_SVG_CSS_STYLE_H_
|
||||
4110
thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
vendored
Normal file
4110
thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
68
thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
vendored
Normal file
68
thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.h
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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_SVG_LOADER_H_
|
||||
#define _TVG_SVG_LOADER_H_
|
||||
|
||||
#include "tvgTaskScheduler.h"
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
|
||||
class SvgLoader : public ImageLoader, public Task
|
||||
{
|
||||
public:
|
||||
string filePath;
|
||||
string svgPath = "";
|
||||
char* content = nullptr;
|
||||
uint32_t size = 0;
|
||||
|
||||
SvgLoaderData loaderData;
|
||||
Scene* root = nullptr;
|
||||
|
||||
bool copy = false;
|
||||
|
||||
SvgLoader();
|
||||
~SvgLoader();
|
||||
|
||||
bool open(const string& path) override;
|
||||
bool open(const char* data, uint32_t size, bool copy) override;
|
||||
bool resize(Paint* paint, float w, float h) override;
|
||||
bool read() override;
|
||||
bool close() override;
|
||||
|
||||
Paint* paint() override;
|
||||
|
||||
private:
|
||||
SvgViewFlag viewFlag = SvgViewFlag::None;
|
||||
AspectRatioAlign align = AspectRatioAlign::XMidYMid;
|
||||
AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;
|
||||
float vx = 0;
|
||||
float vy = 0;
|
||||
float vw = 0;
|
||||
float vh = 0;
|
||||
|
||||
bool header();
|
||||
void clear(bool all = true);
|
||||
void run(unsigned tid) override;
|
||||
};
|
||||
|
||||
|
||||
#endif //_TVG_SVG_LOADER_H_
|
||||
597
thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
vendored
Normal file
597
thirdparty/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h
vendored
Normal file
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* 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_SVG_LOADER_COMMON_H_
|
||||
#define _TVG_SVG_LOADER_COMMON_H_
|
||||
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgArray.h"
|
||||
|
||||
struct SvgNode;
|
||||
struct SvgStyleGradient;
|
||||
|
||||
//NOTE: Please update simpleXmlNodeTypeToString() as well.
|
||||
enum class SvgNodeType
|
||||
{
|
||||
Doc,
|
||||
G,
|
||||
Defs,
|
||||
Animation,
|
||||
Arc,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Image,
|
||||
Line,
|
||||
Path,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Rect,
|
||||
Text,
|
||||
TextArea,
|
||||
Tspan,
|
||||
Use,
|
||||
Video,
|
||||
ClipPath,
|
||||
Mask,
|
||||
CssStyle,
|
||||
Symbol,
|
||||
Unknown
|
||||
};
|
||||
|
||||
/*
|
||||
// TODO - remove?
|
||||
enum class SvgLengthType
|
||||
{
|
||||
Percent,
|
||||
Px,
|
||||
Pc,
|
||||
Pt,
|
||||
Mm,
|
||||
Cm,
|
||||
In,
|
||||
};
|
||||
*/
|
||||
|
||||
enum class SvgFillFlags
|
||||
{
|
||||
Paint = 0x01,
|
||||
Opacity = 0x02,
|
||||
Gradient = 0x04,
|
||||
FillRule = 0x08,
|
||||
ClipPath = 0x16
|
||||
};
|
||||
|
||||
constexpr bool operator &(SvgFillFlags a, SvgFillFlags b)
|
||||
{
|
||||
return int(a) & int(b);
|
||||
}
|
||||
|
||||
constexpr SvgFillFlags operator |(SvgFillFlags a, SvgFillFlags b)
|
||||
{
|
||||
return SvgFillFlags(int(a) | int(b));
|
||||
}
|
||||
|
||||
enum class SvgStrokeFlags
|
||||
{
|
||||
Paint = 0x1,
|
||||
Opacity = 0x2,
|
||||
Gradient = 0x4,
|
||||
Scale = 0x8,
|
||||
Width = 0x10,
|
||||
Cap = 0x20,
|
||||
Join = 0x40,
|
||||
Dash = 0x80,
|
||||
Miterlimit = 0x100,
|
||||
DashOffset = 0x200
|
||||
};
|
||||
|
||||
constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b)
|
||||
{
|
||||
return int(a) & int(b);
|
||||
}
|
||||
|
||||
constexpr SvgStrokeFlags operator |(SvgStrokeFlags a, SvgStrokeFlags b)
|
||||
{
|
||||
return SvgStrokeFlags(int(a) | int(b));
|
||||
}
|
||||
|
||||
|
||||
enum class SvgGradientType
|
||||
{
|
||||
Linear,
|
||||
Radial
|
||||
};
|
||||
|
||||
enum class SvgStyleFlags
|
||||
{
|
||||
Color = 0x01,
|
||||
Fill = 0x02,
|
||||
FillRule = 0x04,
|
||||
FillOpacity = 0x08,
|
||||
Opacity = 0x010,
|
||||
Stroke = 0x20,
|
||||
StrokeWidth = 0x40,
|
||||
StrokeLineJoin = 0x80,
|
||||
StrokeLineCap = 0x100,
|
||||
StrokeOpacity = 0x200,
|
||||
StrokeDashArray = 0x400,
|
||||
Transform = 0x800,
|
||||
ClipPath = 0x1000,
|
||||
Mask = 0x2000,
|
||||
MaskType = 0x4000,
|
||||
Display = 0x8000,
|
||||
PaintOrder = 0x10000,
|
||||
StrokeMiterlimit = 0x20000,
|
||||
StrokeDashOffset = 0x40000,
|
||||
};
|
||||
|
||||
constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b)
|
||||
{
|
||||
return int(a) & int(b);
|
||||
}
|
||||
|
||||
constexpr SvgStyleFlags operator |(SvgStyleFlags a, SvgStyleFlags b)
|
||||
{
|
||||
return SvgStyleFlags(int(a) | int(b));
|
||||
}
|
||||
|
||||
enum class SvgStopStyleFlags
|
||||
{
|
||||
StopDefault = 0x0,
|
||||
StopOpacity = 0x01,
|
||||
StopColor = 0x02
|
||||
};
|
||||
|
||||
constexpr bool operator &(SvgStopStyleFlags a, SvgStopStyleFlags b)
|
||||
{
|
||||
return int(a) & int(b);
|
||||
}
|
||||
|
||||
constexpr SvgStopStyleFlags operator |(SvgStopStyleFlags a, SvgStopStyleFlags b)
|
||||
{
|
||||
return SvgStopStyleFlags(int(a) | int(b));
|
||||
}
|
||||
|
||||
enum class SvgGradientFlags
|
||||
{
|
||||
None = 0x0,
|
||||
GradientUnits = 0x1,
|
||||
SpreadMethod = 0x2,
|
||||
X1 = 0x4,
|
||||
X2 = 0x8,
|
||||
Y1 = 0x10,
|
||||
Y2 = 0x20,
|
||||
Cx = 0x40,
|
||||
Cy = 0x80,
|
||||
R = 0x100,
|
||||
Fx = 0x200,
|
||||
Fy = 0x400,
|
||||
Fr = 0x800
|
||||
};
|
||||
|
||||
constexpr bool operator &(SvgGradientFlags a, SvgGradientFlags b)
|
||||
{
|
||||
return int(a) & int(b);
|
||||
}
|
||||
|
||||
constexpr SvgGradientFlags operator |(SvgGradientFlags a, SvgGradientFlags b)
|
||||
{
|
||||
return SvgGradientFlags(int(a) | int(b));
|
||||
}
|
||||
|
||||
enum class SvgFillRule
|
||||
{
|
||||
Winding = 0,
|
||||
OddEven = 1
|
||||
};
|
||||
|
||||
enum class SvgMaskType
|
||||
{
|
||||
Luminance = 0,
|
||||
Alpha
|
||||
};
|
||||
|
||||
//Length type to recalculate %, pt, pc, mm, cm etc
|
||||
enum class SvgParserLengthType
|
||||
{
|
||||
Vertical,
|
||||
Horizontal,
|
||||
Diagonal,
|
||||
//In case of, for example, radius of radial gradient
|
||||
Other
|
||||
};
|
||||
|
||||
enum class SvgViewFlag
|
||||
{
|
||||
None = 0x0,
|
||||
Width = 0x01, //viewPort width
|
||||
Height = 0x02, //viewPort height
|
||||
Viewbox = 0x04, //viewBox x,y,w,h - used only if all 4 are correctly set
|
||||
WidthInPercent = 0x08,
|
||||
HeightInPercent = 0x10
|
||||
};
|
||||
|
||||
constexpr bool operator &(SvgViewFlag a, SvgViewFlag b)
|
||||
{
|
||||
return static_cast<int>(a) & static_cast<int>(b);
|
||||
}
|
||||
|
||||
constexpr SvgViewFlag operator |(SvgViewFlag a, SvgViewFlag b)
|
||||
{
|
||||
return SvgViewFlag(int(a) | int(b));
|
||||
}
|
||||
|
||||
constexpr SvgViewFlag operator ^(SvgViewFlag a, SvgViewFlag b)
|
||||
{
|
||||
return SvgViewFlag(int(a) ^ int(b));
|
||||
}
|
||||
|
||||
enum class AspectRatioAlign
|
||||
{
|
||||
None,
|
||||
XMinYMin,
|
||||
XMidYMin,
|
||||
XMaxYMin,
|
||||
XMinYMid,
|
||||
XMidYMid,
|
||||
XMaxYMid,
|
||||
XMinYMax,
|
||||
XMidYMax,
|
||||
XMaxYMax
|
||||
};
|
||||
|
||||
enum class AspectRatioMeetOrSlice
|
||||
{
|
||||
Meet,
|
||||
Slice
|
||||
};
|
||||
|
||||
struct SvgDocNode
|
||||
{
|
||||
float w; //unit: point or in percentage see: SvgViewFlag
|
||||
float h; //unit: point or in percentage see: SvgViewFlag
|
||||
float vx;
|
||||
float vy;
|
||||
float vw;
|
||||
float vh;
|
||||
SvgViewFlag viewFlag;
|
||||
SvgNode* defs;
|
||||
SvgNode* style;
|
||||
AspectRatioAlign align;
|
||||
AspectRatioMeetOrSlice meetOrSlice;
|
||||
};
|
||||
|
||||
struct SvgGNode
|
||||
{
|
||||
};
|
||||
|
||||
struct SvgDefsNode
|
||||
{
|
||||
Array<SvgStyleGradient*> gradients;
|
||||
};
|
||||
|
||||
struct SvgSymbolNode
|
||||
{
|
||||
float w, h;
|
||||
float vx, vy, vw, vh;
|
||||
AspectRatioAlign align;
|
||||
AspectRatioMeetOrSlice meetOrSlice;
|
||||
bool overflowVisible;
|
||||
bool hasViewBox;
|
||||
bool hasWidth;
|
||||
bool hasHeight;
|
||||
};
|
||||
|
||||
struct SvgUseNode
|
||||
{
|
||||
float x, y, w, h;
|
||||
bool isWidthSet;
|
||||
bool isHeightSet;
|
||||
SvgNode* symbol;
|
||||
};
|
||||
|
||||
struct SvgEllipseNode
|
||||
{
|
||||
float cx;
|
||||
float cy;
|
||||
float rx;
|
||||
float ry;
|
||||
};
|
||||
|
||||
struct SvgCircleNode
|
||||
{
|
||||
float cx;
|
||||
float cy;
|
||||
float r;
|
||||
};
|
||||
|
||||
struct SvgRectNode
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float w;
|
||||
float h;
|
||||
float rx;
|
||||
float ry;
|
||||
bool hasRx;
|
||||
bool hasRy;
|
||||
};
|
||||
|
||||
struct SvgLineNode
|
||||
{
|
||||
float x1;
|
||||
float y1;
|
||||
float x2;
|
||||
float y2;
|
||||
};
|
||||
|
||||
struct SvgImageNode
|
||||
{
|
||||
float x, y, w, h;
|
||||
char* href;
|
||||
};
|
||||
|
||||
struct SvgPathNode
|
||||
{
|
||||
char* path;
|
||||
};
|
||||
|
||||
struct SvgPolygonNode
|
||||
{
|
||||
Array<float> pts;
|
||||
};
|
||||
|
||||
struct SvgClipNode
|
||||
{
|
||||
bool userSpace;
|
||||
};
|
||||
|
||||
struct SvgMaskNode
|
||||
{
|
||||
SvgMaskType type;
|
||||
bool userSpace;
|
||||
};
|
||||
|
||||
struct SvgCssStyleNode
|
||||
{
|
||||
};
|
||||
|
||||
struct SvgTextNode
|
||||
{
|
||||
char* text;
|
||||
char* fontFamily;
|
||||
float x, y;
|
||||
float fontSize;
|
||||
};
|
||||
|
||||
struct SvgLinearGradient
|
||||
{
|
||||
float x1;
|
||||
float y1;
|
||||
float x2;
|
||||
float y2;
|
||||
bool isX1Percentage;
|
||||
bool isY1Percentage;
|
||||
bool isX2Percentage;
|
||||
bool isY2Percentage;
|
||||
};
|
||||
|
||||
struct SvgRadialGradient
|
||||
{
|
||||
float cx;
|
||||
float cy;
|
||||
float fx;
|
||||
float fy;
|
||||
float r;
|
||||
float fr;
|
||||
bool isCxPercentage;
|
||||
bool isCyPercentage;
|
||||
bool isFxPercentage;
|
||||
bool isFyPercentage;
|
||||
bool isRPercentage;
|
||||
bool isFrPercentage;
|
||||
};
|
||||
|
||||
struct SvgComposite
|
||||
{
|
||||
char *url;
|
||||
SvgNode* node;
|
||||
bool applying; //flag for checking circular dependency.
|
||||
};
|
||||
|
||||
struct SvgColor
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
struct SvgPaint
|
||||
{
|
||||
SvgStyleGradient* gradient;
|
||||
char *url;
|
||||
SvgColor color;
|
||||
bool none;
|
||||
bool curColor;
|
||||
};
|
||||
|
||||
struct SvgDash
|
||||
{
|
||||
Array<float> array;
|
||||
float offset;
|
||||
};
|
||||
|
||||
struct SvgStyleGradient
|
||||
{
|
||||
SvgGradientType type;
|
||||
char* id;
|
||||
char* ref;
|
||||
FillSpread spread;
|
||||
SvgRadialGradient* radial;
|
||||
SvgLinearGradient* linear;
|
||||
Matrix* transform;
|
||||
Array<Fill::ColorStop> stops;
|
||||
SvgGradientFlags flags;
|
||||
bool userSpace;
|
||||
|
||||
void clear()
|
||||
{
|
||||
stops.reset();
|
||||
free(transform);
|
||||
free(radial);
|
||||
free(linear);
|
||||
free(ref);
|
||||
free(id);
|
||||
}
|
||||
};
|
||||
|
||||
struct SvgStyleFill
|
||||
{
|
||||
SvgFillFlags flags;
|
||||
SvgPaint paint;
|
||||
int opacity;
|
||||
FillRule fillRule;
|
||||
};
|
||||
|
||||
struct SvgStyleStroke
|
||||
{
|
||||
SvgStrokeFlags flags;
|
||||
SvgPaint paint;
|
||||
int opacity;
|
||||
float scale;
|
||||
float width;
|
||||
float centered;
|
||||
StrokeCap cap;
|
||||
StrokeJoin join;
|
||||
float miterlimit;
|
||||
SvgDash dash;
|
||||
};
|
||||
|
||||
struct SvgStyleProperty
|
||||
{
|
||||
SvgStyleFill fill;
|
||||
SvgStyleStroke stroke;
|
||||
SvgComposite clipPath;
|
||||
SvgComposite mask;
|
||||
int opacity;
|
||||
SvgColor color;
|
||||
char* cssClass;
|
||||
SvgStyleFlags flags;
|
||||
SvgStyleFlags flagsImportance; //indicates the importance of the flag - if set, higher priority is applied (https://drafts.csswg.org/css-cascade-4/#importance)
|
||||
bool curColorSet;
|
||||
bool paintOrder; //true if default (fill, stroke), false otherwise
|
||||
bool display;
|
||||
};
|
||||
|
||||
struct SvgNode
|
||||
{
|
||||
SvgNodeType type;
|
||||
SvgNode* parent;
|
||||
Array<SvgNode*> child;
|
||||
char *id;
|
||||
SvgStyleProperty *style;
|
||||
Matrix* transform;
|
||||
union {
|
||||
SvgGNode g;
|
||||
SvgDocNode doc;
|
||||
SvgDefsNode defs;
|
||||
SvgUseNode use;
|
||||
SvgCircleNode circle;
|
||||
SvgEllipseNode ellipse;
|
||||
SvgPolygonNode polygon;
|
||||
SvgPolygonNode polyline;
|
||||
SvgRectNode rect;
|
||||
SvgPathNode path;
|
||||
SvgLineNode line;
|
||||
SvgImageNode image;
|
||||
SvgMaskNode mask;
|
||||
SvgClipNode clip;
|
||||
SvgCssStyleNode cssStyle;
|
||||
SvgSymbolNode symbol;
|
||||
SvgTextNode text;
|
||||
} node;
|
||||
~SvgNode();
|
||||
};
|
||||
|
||||
struct SvgParser
|
||||
{
|
||||
SvgNode* node;
|
||||
SvgStyleGradient* styleGrad;
|
||||
Fill::ColorStop gradStop;
|
||||
SvgStopStyleFlags flags;
|
||||
struct
|
||||
{
|
||||
float x, y, w, h;
|
||||
} global;
|
||||
struct
|
||||
{
|
||||
bool parsedFx;
|
||||
bool parsedFy;
|
||||
} gradient;
|
||||
};
|
||||
|
||||
struct SvgNodeIdPair
|
||||
{
|
||||
SvgNode* node;
|
||||
char *id;
|
||||
};
|
||||
|
||||
struct FontFace
|
||||
{
|
||||
char* name = nullptr;
|
||||
char* src = nullptr;
|
||||
size_t srcLen = 0;
|
||||
char* decoded = nullptr;
|
||||
};
|
||||
|
||||
enum class OpenedTagType : uint8_t
|
||||
{
|
||||
Other = 0,
|
||||
Style,
|
||||
Text
|
||||
};
|
||||
|
||||
struct SvgLoaderData
|
||||
{
|
||||
Array<SvgNode*> stack;
|
||||
SvgNode* doc = nullptr;
|
||||
SvgNode* def = nullptr; //also used to store nested graphic nodes
|
||||
SvgNode* cssStyle = nullptr;
|
||||
Array<SvgStyleGradient*> gradients;
|
||||
Array<SvgStyleGradient*> gradientStack; //For stops
|
||||
SvgParser* svgParse = nullptr;
|
||||
Array<SvgNodeIdPair> cloneNodes;
|
||||
Array<SvgNodeIdPair> nodesToStyle;
|
||||
Array<char*> images; //embedded images
|
||||
Array<FontFace> fonts;
|
||||
int level = 0;
|
||||
bool result = false;
|
||||
OpenedTagType openedTag = OpenedTagType::Other;
|
||||
SvgNode* currentGraphicsNode = nullptr;
|
||||
};
|
||||
|
||||
struct Box
|
||||
{
|
||||
float x, y, w, h;
|
||||
};
|
||||
|
||||
#endif
|
||||
571
thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
vendored
Normal file
571
thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright notice for the EFL:
|
||||
|
||||
* Copyright (C) EFL developers (see AUTHORS)
|
||||
|
||||
* All rights reserved.
|
||||
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++.
|
||||
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgShape.h"
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
#include "tvgSvgPath.h"
|
||||
#include "tvgStr.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static char* _skipComma(const char* content)
|
||||
{
|
||||
while (*content && isspace(*content)) {
|
||||
content++;
|
||||
}
|
||||
if (*content == ',') return (char*)content + 1;
|
||||
return (char*)content;
|
||||
}
|
||||
|
||||
|
||||
static bool _parseNumber(char** content, float* number)
|
||||
{
|
||||
char* end = NULL;
|
||||
*number = strToFloat(*content, &end);
|
||||
//If the start of string is not number
|
||||
if ((*content) == end) return false;
|
||||
//Skip comma if any
|
||||
*content = _skipComma(end);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _parseFlag(char** content, int* number)
|
||||
{
|
||||
char* end = NULL;
|
||||
if (*(*content) != '0' && *(*content) != '1') return false;
|
||||
*number = *(*content) - '0';
|
||||
*content += 1;
|
||||
end = *content;
|
||||
*content = _skipComma(end);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep)
|
||||
{
|
||||
float cxp, cyp, cx, cy;
|
||||
float sx, sy;
|
||||
float cosPhi, sinPhi;
|
||||
float dx2, dy2;
|
||||
float x1p, y1p;
|
||||
float x1p2, y1p2;
|
||||
float rx2, ry2;
|
||||
float lambda;
|
||||
float c;
|
||||
float at;
|
||||
float theta1, deltaTheta;
|
||||
float nat;
|
||||
float delta, bcp;
|
||||
float cosPhiRx, cosPhiRy;
|
||||
float sinPhiRx, sinPhiRy;
|
||||
float cosTheta1, sinTheta1;
|
||||
int segments;
|
||||
|
||||
//Some helpful stuff is available here:
|
||||
//http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
||||
sx = cur->x;
|
||||
sy = cur->y;
|
||||
|
||||
//Correction of out-of-range radii, see F6.6.1 (step 2)
|
||||
rx = fabsf(rx);
|
||||
ry = fabsf(ry);
|
||||
|
||||
angle = deg2rad(angle);
|
||||
cosPhi = cosf(angle);
|
||||
sinPhi = sinf(angle);
|
||||
dx2 = (sx - x) / 2.0f;
|
||||
dy2 = (sy - y) / 2.0f;
|
||||
x1p = cosPhi * dx2 + sinPhi * dy2;
|
||||
y1p = cosPhi * dy2 - sinPhi * dx2;
|
||||
x1p2 = x1p * x1p;
|
||||
y1p2 = y1p * y1p;
|
||||
rx2 = rx * rx;
|
||||
ry2 = ry * ry;
|
||||
lambda = (x1p2 / rx2) + (y1p2 / ry2);
|
||||
|
||||
//Correction of out-of-range radii, see F6.6.2 (step 4)
|
||||
if (lambda > 1.0f) {
|
||||
//See F6.6.3
|
||||
float lambdaRoot = sqrtf(lambda);
|
||||
|
||||
rx *= lambdaRoot;
|
||||
ry *= lambdaRoot;
|
||||
//Update rx2 and ry2
|
||||
rx2 = rx * rx;
|
||||
ry2 = ry * ry;
|
||||
}
|
||||
|
||||
c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2);
|
||||
|
||||
//Check if there is no possible solution
|
||||
//(i.e. we can't do a square root of a negative value)
|
||||
if (c < 0.0f) {
|
||||
//Scale uniformly until we have a single solution
|
||||
//(see F6.2) i.e. when c == 0.0
|
||||
float scale = sqrtf(1.0f - c / (rx2 * ry2));
|
||||
rx *= scale;
|
||||
ry *= scale;
|
||||
//Update rx2 and ry2
|
||||
rx2 = rx * rx;
|
||||
ry2 = ry * ry;
|
||||
|
||||
//Step 2 (F6.5.2) - simplified since c == 0.0
|
||||
cxp = 0.0f;
|
||||
cyp = 0.0f;
|
||||
//Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0
|
||||
cx = 0.0f;
|
||||
cy = 0.0f;
|
||||
} else {
|
||||
//Complete c calculation
|
||||
c = sqrtf(c / ((rx2 * y1p2) + (ry2 * x1p2)));
|
||||
//Inverse sign if Fa == Fs
|
||||
if (largeArc == sweep) c = -c;
|
||||
|
||||
//Step 2 (F6.5.2)
|
||||
cxp = c * (rx * y1p / ry);
|
||||
cyp = c * (-ry * x1p / rx);
|
||||
|
||||
//Step 3 (F6.5.3 first part)
|
||||
cx = cosPhi * cxp - sinPhi * cyp;
|
||||
cy = sinPhi * cxp + cosPhi * cyp;
|
||||
}
|
||||
|
||||
//Step 3 (F6.5.3 second part) we now have the center point of the ellipse
|
||||
cx += (sx + x) / 2.0f;
|
||||
cy += (sy + y) / 2.0f;
|
||||
|
||||
//Step 4 (F6.5.4)
|
||||
//We dont' use arccos (as per w3c doc), see
|
||||
//http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
|
||||
//Note: atan2 (0.0, 1.0) == 0.0
|
||||
at = tvg::atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
|
||||
theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
|
||||
|
||||
nat = tvg::atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
|
||||
deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
|
||||
|
||||
if (sweep) {
|
||||
//Ensure delta theta < 0 or else add 360 degrees
|
||||
if (deltaTheta < 0.0f) deltaTheta += 2.0f * MATH_PI;
|
||||
} else {
|
||||
//Ensure delta theta > 0 or else substract 360 degrees
|
||||
if (deltaTheta > 0.0f) deltaTheta -= 2.0f * MATH_PI;
|
||||
}
|
||||
|
||||
//Add several cubic bezier to approximate the arc
|
||||
//(smaller than 90 degrees)
|
||||
//We add one extra segment because we want something
|
||||
//Smaller than 90deg (i.e. not 90 itself)
|
||||
segments = static_cast<int>(fabsf(deltaTheta / MATH_PI2) + 1.0f);
|
||||
delta = deltaTheta / segments;
|
||||
|
||||
//http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
|
||||
bcp = 4.0f / 3.0f * (1.0f - cosf(delta / 2.0f)) / sinf(delta / 2.0f);
|
||||
|
||||
cosPhiRx = cosPhi * rx;
|
||||
cosPhiRy = cosPhi * ry;
|
||||
sinPhiRx = sinPhi * rx;
|
||||
sinPhiRy = sinPhi * ry;
|
||||
|
||||
cosTheta1 = cosf(theta1);
|
||||
sinTheta1 = sinf(theta1);
|
||||
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
//End angle (for this segment) = current + delta
|
||||
float c1x, c1y, ex, ey, c2x, c2y;
|
||||
float theta2 = theta1 + delta;
|
||||
float cosTheta2 = cosf(theta2);
|
||||
float sinTheta2 = sinf(theta2);
|
||||
Point p[3];
|
||||
|
||||
//First control point (based on start point sx,sy)
|
||||
c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1);
|
||||
c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1);
|
||||
|
||||
//End point (for this segment)
|
||||
ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2);
|
||||
ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2);
|
||||
|
||||
//Second control point (based on end point ex,ey)
|
||||
c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2);
|
||||
c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2);
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {c1x, c1y};
|
||||
p[1] = {c2x, c2y};
|
||||
p[2] = {ex, ey};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = p[1];
|
||||
*cur = p[2];
|
||||
|
||||
//Next start point is the current end point (same for angle)
|
||||
sx = ex;
|
||||
sy = ey;
|
||||
theta1 = theta2;
|
||||
//Avoid recomputations
|
||||
cosTheta1 = cosTheta2;
|
||||
sinTheta1 = sinTheta2;
|
||||
}
|
||||
}
|
||||
|
||||
static int _numberCount(char cmd)
|
||||
{
|
||||
int count = 0;
|
||||
switch (cmd) {
|
||||
case 'M':
|
||||
case 'm':
|
||||
case 'L':
|
||||
case 'l':
|
||||
case 'T':
|
||||
case 't': {
|
||||
count = 2;
|
||||
break;
|
||||
}
|
||||
case 'C':
|
||||
case 'c':
|
||||
case 'E':
|
||||
case 'e': {
|
||||
count = 6;
|
||||
break;
|
||||
}
|
||||
case 'H':
|
||||
case 'h':
|
||||
case 'V':
|
||||
case 'v': {
|
||||
count = 1;
|
||||
break;
|
||||
}
|
||||
case 'S':
|
||||
case 's':
|
||||
case 'Q':
|
||||
case 'q': {
|
||||
count = 4;
|
||||
break;
|
||||
}
|
||||
case 'A':
|
||||
case 'a': {
|
||||
count = 7;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic, bool* closed)
|
||||
{
|
||||
switch (cmd) {
|
||||
case 'm':
|
||||
case 'l':
|
||||
case 'c':
|
||||
case 's':
|
||||
case 'q':
|
||||
case 't': {
|
||||
for (int i = 0; i < count - 1; i += 2) {
|
||||
arr[i] = arr[i] + cur->x;
|
||||
arr[i + 1] = arr[i + 1] + cur->y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
arr[0] = arr[0] + cur->x;
|
||||
break;
|
||||
}
|
||||
case 'v': {
|
||||
arr[0] = arr[0] + cur->y;
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
arr[5] = arr[5] + cur->x;
|
||||
arr[6] = arr[6] + cur->y;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 'm':
|
||||
case 'M': {
|
||||
Point p = {arr[0], arr[1]};
|
||||
cmds->push(PathCommand::MoveTo);
|
||||
pts->push(p);
|
||||
*cur = {arr[0], arr[1]};
|
||||
*startPoint = {arr[0], arr[1]};
|
||||
break;
|
||||
}
|
||||
case 'l':
|
||||
case 'L': {
|
||||
Point p = {arr[0], arr[1]};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
*cur = {arr[0], arr[1]};
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
case 'C': {
|
||||
Point p[3];
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {arr[0], arr[1]};
|
||||
p[1] = {arr[2], arr[3]};
|
||||
p[2] = {arr[4], arr[5]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = p[1];
|
||||
*cur = p[2];
|
||||
*isQuadratic = false;
|
||||
break;
|
||||
}
|
||||
case 's':
|
||||
case 'S': {
|
||||
Point p[3], ctrl;
|
||||
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
|
||||
!(*isQuadratic)) {
|
||||
ctrl.x = 2 * cur->x - curCtl->x;
|
||||
ctrl.y = 2 * cur->y - curCtl->y;
|
||||
} else {
|
||||
ctrl = *cur;
|
||||
}
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = ctrl;
|
||||
p[1] = {arr[0], arr[1]};
|
||||
p[2] = {arr[2], arr[3]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = p[1];
|
||||
*cur = p[2];
|
||||
*isQuadratic = false;
|
||||
break;
|
||||
}
|
||||
case 'q':
|
||||
case 'Q': {
|
||||
Point p[3];
|
||||
float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0f / 3.0f);
|
||||
float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0f / 3.0f);
|
||||
float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0f / 3.0f);
|
||||
float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0f / 3.0f);
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {ctrl_x0, ctrl_y0};
|
||||
p[1] = {ctrl_x1, ctrl_y1};
|
||||
p[2] = {arr[2], arr[3]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = {arr[0], arr[1]};
|
||||
*cur = p[2];
|
||||
*isQuadratic = true;
|
||||
break;
|
||||
}
|
||||
case 't':
|
||||
case 'T': {
|
||||
Point p[3], ctrl;
|
||||
if ((cmds->count > 1) && (cmds->last() == PathCommand::CubicTo) &&
|
||||
*isQuadratic) {
|
||||
ctrl.x = 2 * cur->x - curCtl->x;
|
||||
ctrl.y = 2 * cur->y - curCtl->y;
|
||||
} else {
|
||||
ctrl = *cur;
|
||||
}
|
||||
float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0f / 3.0f);
|
||||
float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0f / 3.0f);
|
||||
float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0f / 3.0f);
|
||||
float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0f / 3.0f);
|
||||
cmds->push(PathCommand::CubicTo);
|
||||
p[0] = {ctrl_x0, ctrl_y0};
|
||||
p[1] = {ctrl_x1, ctrl_y1};
|
||||
p[2] = {arr[0], arr[1]};
|
||||
pts->push(p[0]);
|
||||
pts->push(p[1]);
|
||||
pts->push(p[2]);
|
||||
*curCtl = {ctrl.x, ctrl.y};
|
||||
*cur = p[2];
|
||||
*isQuadratic = true;
|
||||
break;
|
||||
}
|
||||
case 'h':
|
||||
case 'H': {
|
||||
Point p = {arr[0], cur->y};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
cur->x = arr[0];
|
||||
break;
|
||||
}
|
||||
case 'v':
|
||||
case 'V': {
|
||||
Point p = {cur->x, arr[0]};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
cur->y = arr[0];
|
||||
break;
|
||||
}
|
||||
case 'z':
|
||||
case 'Z': {
|
||||
cmds->push(PathCommand::Close);
|
||||
*cur = *startPoint;
|
||||
*closed = true;
|
||||
break;
|
||||
}
|
||||
case 'a':
|
||||
case 'A': {
|
||||
if (tvg::zero(arr[0]) || tvg::zero(arr[1])) {
|
||||
Point p = {arr[5], arr[6]};
|
||||
cmds->push(PathCommand::LineTo);
|
||||
pts->push(p);
|
||||
*cur = {arr[5], arr[6]};
|
||||
} else if (!tvg::equal(cur->x, arr[5]) || !tvg::equal(cur->y, arr[6])) {
|
||||
_pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], fabsf(arr[0]), fabsf(arr[1]), arr[2], arr[3], arr[4]);
|
||||
*cur = *curCtl = {arr[5], arr[6]};
|
||||
*isQuadratic = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* closed)
|
||||
{
|
||||
int large, sweep;
|
||||
|
||||
path = _skipComma(path);
|
||||
if (isalpha(*path)) {
|
||||
*cmd = *path;
|
||||
path++;
|
||||
*count = _numberCount(*cmd);
|
||||
} else {
|
||||
if (*cmd == 'm') *cmd = 'l';
|
||||
else if (*cmd == 'M') *cmd = 'L';
|
||||
else {
|
||||
if (*closed) return nullptr;
|
||||
}
|
||||
}
|
||||
if (*count == 7) {
|
||||
//Special case for arc command
|
||||
if (_parseNumber(&path, &arr[0])) {
|
||||
if (_parseNumber(&path, &arr[1])) {
|
||||
if (_parseNumber(&path, &arr[2])) {
|
||||
if (_parseFlag(&path, &large)) {
|
||||
if (_parseFlag(&path, &sweep)) {
|
||||
if (_parseNumber(&path, &arr[5])) {
|
||||
if (_parseNumber(&path, &arr[6])) {
|
||||
arr[3] = (float)large;
|
||||
arr[4] = (float)sweep;
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*count = 0;
|
||||
return NULL;
|
||||
}
|
||||
for (int i = 0; i < *count; i++) {
|
||||
if (!_parseNumber(&path, &arr[i])) {
|
||||
*count = 0;
|
||||
return NULL;
|
||||
}
|
||||
path = _skipComma(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
|
||||
bool svgPathToShape(const char* svgPath, Shape* shape)
|
||||
{
|
||||
float numberArray[7];
|
||||
int numberCount = 0;
|
||||
Point cur = { 0, 0 };
|
||||
Point curCtl = { 0, 0 };
|
||||
Point startPoint = { 0, 0 };
|
||||
char cmd = 0;
|
||||
bool isQuadratic = false;
|
||||
bool closed = false;
|
||||
char* path = (char*)svgPath;
|
||||
|
||||
auto& pts = P(shape)->rs.path.pts;
|
||||
auto& cmds = P(shape)->rs.path.cmds;
|
||||
auto lastCmds = cmds.count;
|
||||
|
||||
while ((path[0] != '\0')) {
|
||||
path = _nextCommand(path, &cmd, numberArray, &numberCount, &closed);
|
||||
if (!path) break;
|
||||
closed = false;
|
||||
if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic, &closed)) break;
|
||||
}
|
||||
|
||||
if (cmds.count > lastCmds && cmds[lastCmds] != PathCommand::MoveTo) return false;
|
||||
return true;
|
||||
}
|
||||
30
thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h
vendored
Normal file
30
thirdparty/thorvg/src/loaders/svg/tvgSvgPath.h
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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_SVG_PATH_H_
|
||||
#define _TVG_SVG_PATH_H_
|
||||
|
||||
#include <tvgCommon.h>
|
||||
|
||||
bool svgPathToShape(const char* svgPath, Shape* shape);
|
||||
|
||||
#endif //_TVG_SVG_PATH_H_
|
||||
981
thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
vendored
Normal file
981
thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp
vendored
Normal file
@@ -0,0 +1,981 @@
|
||||
/*
|
||||
* 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" /* to include math.h before cstring */
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "tvgShape.h"
|
||||
#include "tvgCompressor.h"
|
||||
#include "tvgPaint.h"
|
||||
#include "tvgFill.h"
|
||||
#include "tvgStr.h"
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
#include "tvgSvgSceneBuilder.h"
|
||||
#include "tvgSvgPath.h"
|
||||
#include "tvgSvgUtil.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath);
|
||||
static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform);
|
||||
static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr);
|
||||
|
||||
|
||||
static inline bool _isGroupType(SvgNodeType type)
|
||||
{
|
||||
if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath || type == SvgNodeType::Symbol) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
|
||||
//a stroke width should be ignored for bounding box calculations
|
||||
static Box _boundingBox(const Shape* shape)
|
||||
{
|
||||
float x, y, w, h;
|
||||
shape->bounds(&x, &y, &w, &h, false);
|
||||
|
||||
if (auto strokeW = shape->strokeWidth()) {
|
||||
x += 0.5f * strokeW;
|
||||
y += 0.5f * strokeW;
|
||||
w -= strokeW;
|
||||
h -= strokeW;
|
||||
}
|
||||
|
||||
return {x, y, w, h};
|
||||
}
|
||||
|
||||
|
||||
static Box _boundingBox(const Text* text)
|
||||
{
|
||||
float x, y, w, h;
|
||||
text->bounds(&x, &y, &w, &h, false);
|
||||
return {x, y, w, h};
|
||||
}
|
||||
|
||||
|
||||
static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
|
||||
{
|
||||
gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
|
||||
gradTransf->e12 *= mBBox->e11;
|
||||
gradTransf->e11 *= mBBox->e11;
|
||||
|
||||
gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
|
||||
gradTransf->e22 *= mBBox->e22;
|
||||
gradTransf->e21 *= mBBox->e22;
|
||||
}
|
||||
|
||||
|
||||
static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
|
||||
{
|
||||
Fill::ColorStop* stops;
|
||||
int stopCount = 0;
|
||||
auto fillGrad = LinearGradient::gen();
|
||||
|
||||
bool isTransform = (g->transform ? true : false);
|
||||
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if (isTransform) finalTransform = *g->transform;
|
||||
|
||||
if (g->userSpace) {
|
||||
g->linear->x1 = g->linear->x1 * vBox.w;
|
||||
g->linear->y1 = g->linear->y1 * vBox.h;
|
||||
g->linear->x2 = g->linear->x2 * vBox.w;
|
||||
g->linear->y2 = g->linear->y2 * vBox.h;
|
||||
} else {
|
||||
Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
|
||||
if (isTransform) _transformMultiply(&m, &finalTransform);
|
||||
else {
|
||||
finalTransform = m;
|
||||
isTransform = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTransform) fillGrad->transform(finalTransform);
|
||||
|
||||
fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
|
||||
fillGrad->spread(g->spread);
|
||||
|
||||
//Update the stops
|
||||
stopCount = g->stops.count;
|
||||
if (stopCount > 0) {
|
||||
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
|
||||
if (!stops) return fillGrad;
|
||||
auto prevOffset = 0.0f;
|
||||
for (uint32_t i = 0; i < g->stops.count; ++i) {
|
||||
auto colorStop = &g->stops[i];
|
||||
//Use premultiplied color
|
||||
stops[i].r = colorStop->r;
|
||||
stops[i].g = colorStop->g;
|
||||
stops[i].b = colorStop->b;
|
||||
stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
|
||||
stops[i].offset = colorStop->offset;
|
||||
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
|
||||
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
|
||||
else if (colorStop->offset > 1) stops[i].offset = 1;
|
||||
prevOffset = stops[i].offset;
|
||||
}
|
||||
fillGrad->colorStops(stops, stopCount);
|
||||
free(stops);
|
||||
}
|
||||
return fillGrad;
|
||||
}
|
||||
|
||||
|
||||
static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity)
|
||||
{
|
||||
Fill::ColorStop *stops;
|
||||
int stopCount = 0;
|
||||
auto fillGrad = RadialGradient::gen();
|
||||
|
||||
bool isTransform = (g->transform ? true : false);
|
||||
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if (isTransform) finalTransform = *g->transform;
|
||||
|
||||
if (g->userSpace) {
|
||||
//The radius scaling is done according to the Units section:
|
||||
//https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
|
||||
g->radial->cx = g->radial->cx * vBox.w;
|
||||
g->radial->cy = g->radial->cy * vBox.h;
|
||||
g->radial->r = g->radial->r * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
|
||||
g->radial->fx = g->radial->fx * vBox.w;
|
||||
g->radial->fy = g->radial->fy * vBox.h;
|
||||
g->radial->fr = g->radial->fr * sqrtf(powf(vBox.w, 2.0f) + powf(vBox.h, 2.0f)) / sqrtf(2.0f);
|
||||
} else {
|
||||
Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1};
|
||||
if (isTransform) _transformMultiply(&m, &finalTransform);
|
||||
else {
|
||||
finalTransform = m;
|
||||
isTransform = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTransform) fillGrad->transform(finalTransform);
|
||||
|
||||
P(fillGrad)->radial(g->radial->cx, g->radial->cy, g->radial->r, g->radial->fx, g->radial->fy, g->radial->fr);
|
||||
fillGrad->spread(g->spread);
|
||||
|
||||
//Update the stops
|
||||
stopCount = g->stops.count;
|
||||
if (stopCount > 0) {
|
||||
stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
|
||||
if (!stops) return fillGrad;
|
||||
auto prevOffset = 0.0f;
|
||||
for (uint32_t i = 0; i < g->stops.count; ++i) {
|
||||
auto colorStop = &g->stops[i];
|
||||
//Use premultiplied color
|
||||
stops[i].r = colorStop->r;
|
||||
stops[i].g = colorStop->g;
|
||||
stops[i].b = colorStop->b;
|
||||
stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
|
||||
stops[i].offset = colorStop->offset;
|
||||
//check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
|
||||
if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
|
||||
else if (colorStop->offset > 1) stops[i].offset = 1;
|
||||
prevOffset = stops[i].offset;
|
||||
}
|
||||
fillGrad->colorStops(stops, stopCount);
|
||||
free(stops);
|
||||
}
|
||||
return fillGrad;
|
||||
}
|
||||
|
||||
|
||||
//The SVG standard allows only for 'use' nodes that point directly to a basic shape.
|
||||
static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
if (node->child.count != 1) return false;
|
||||
auto child = *(node->child.data);
|
||||
|
||||
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if (node->transform) finalTransform = *node->transform;
|
||||
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
|
||||
Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
|
||||
finalTransform *= m;
|
||||
}
|
||||
if (child->transform) finalTransform *= *child->transform;
|
||||
|
||||
return _appendClipShape(loaderData, child, shape, vBox, svgPath, identity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform);
|
||||
}
|
||||
|
||||
|
||||
static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip)
|
||||
{
|
||||
if (node->type == SvgNodeType::Use) {
|
||||
return _appendClipUseNode(loaderData, node, shape, vBox, svgPath);
|
||||
}
|
||||
return _appendClipShape(loaderData, node, shape, vBox, svgPath, nullptr);
|
||||
}
|
||||
|
||||
|
||||
static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const SvgNode* compNode, SvgNodeType type)
|
||||
{
|
||||
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
//The initial mask transformation ignored according to the SVG standard.
|
||||
if (node->transform && type != SvgNodeType::Mask) {
|
||||
m = *node->transform;
|
||||
}
|
||||
if (compNode->transform) {
|
||||
m *= *compNode->transform;
|
||||
}
|
||||
if (!compNode->node.clip.userSpace) {
|
||||
float x, y, w, h;
|
||||
P(paint)->bounds(&x, &y, &w, &h, false, false);
|
||||
Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1};
|
||||
m *= mBBox;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
/* ClipPath */
|
||||
/* Do not drop in Circular Dependency for ClipPath.
|
||||
Composition can be applied recursively if its children nodes have composition target to this one. */
|
||||
if (node->style->clipPath.applying) {
|
||||
TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
|
||||
} else {
|
||||
auto compNode = node->style->clipPath.node;
|
||||
if (compNode && compNode->child.count > 0) {
|
||||
node->style->clipPath.applying = true;
|
||||
|
||||
auto comp = Shape::gen();
|
||||
|
||||
auto child = compNode->child.data;
|
||||
auto valid = false; //Composite only when valid shapes exist
|
||||
|
||||
for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
|
||||
if (_appendClipChild(loaderData, *child, comp.get(), vBox, svgPath, compNode->child.count > 1)) valid = true;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath);
|
||||
comp->transform(finalTransform);
|
||||
paint->clip(std::move(comp));
|
||||
}
|
||||
|
||||
node->style->clipPath.applying = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mask */
|
||||
/* Do not drop in Circular Dependency for Mask.
|
||||
Composition can be applied recursively if its children nodes have composition target to this one. */
|
||||
if (node->style->mask.applying) {
|
||||
TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
|
||||
} else {
|
||||
auto compNode = node->style->mask.node;
|
||||
if (compNode && compNode->child.count > 0) {
|
||||
node->style->mask.applying = true;
|
||||
|
||||
bool isMaskWhite = true;
|
||||
if (auto comp = _sceneBuildHelper(loaderData, compNode, vBox, svgPath, true, 0, &isMaskWhite)) {
|
||||
if (!compNode->node.mask.userSpace) {
|
||||
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask);
|
||||
comp->transform(finalTransform);
|
||||
} else {
|
||||
if (node->transform) comp->transform(*node->transform);
|
||||
}
|
||||
|
||||
if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) {
|
||||
paint->composite(std::move(comp), CompositeMethod::LumaMask);
|
||||
} else {
|
||||
paint->composite(std::move(comp), CompositeMethod::AlphaMask);
|
||||
}
|
||||
}
|
||||
|
||||
node->style->mask.applying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip)
|
||||
{
|
||||
SvgStyleProperty* style = node->style;
|
||||
|
||||
//Clip transformation is applied directly to the path in the _appendClipShape function
|
||||
if (node->transform && !clip) vg->transform(*node->transform);
|
||||
if (node->type == SvgNodeType::Doc || !node->style->display) return;
|
||||
|
||||
//If fill property is nullptr then do nothing
|
||||
if (style->fill.paint.none) {
|
||||
//Do nothing
|
||||
} else if (style->fill.paint.gradient) {
|
||||
Box bBox = vBox;
|
||||
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg);
|
||||
|
||||
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
|
||||
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||
vg->fill(std::move(linear));
|
||||
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
|
||||
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||
vg->fill(std::move(radial));
|
||||
}
|
||||
} else if (style->fill.paint.url) {
|
||||
//TODO: Apply the color pointed by url
|
||||
TVGLOG("SVG", "The fill's url not supported.");
|
||||
} else if (style->fill.paint.curColor) {
|
||||
//Apply the current style color
|
||||
vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
|
||||
} else {
|
||||
//Apply the fill color
|
||||
vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
|
||||
}
|
||||
|
||||
//Apply the fill rule
|
||||
vg->fill((tvg::FillRule)style->fill.fillRule);
|
||||
//Rendering order
|
||||
vg->order(!style->paintOrder);
|
||||
|
||||
//Apply node opacity
|
||||
if (style->opacity < 255) vg->opacity(style->opacity);
|
||||
|
||||
if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;
|
||||
|
||||
//Apply the stroke style property
|
||||
vg->stroke(style->stroke.width);
|
||||
vg->stroke(style->stroke.cap);
|
||||
vg->stroke(style->stroke.join);
|
||||
vg->strokeMiterlimit(style->stroke.miterlimit);
|
||||
if (style->stroke.dash.array.count > 0) {
|
||||
P(vg)->strokeDash(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset);
|
||||
}
|
||||
|
||||
//If stroke property is nullptr then do nothing
|
||||
if (style->stroke.paint.none) {
|
||||
vg->stroke(0.0f);
|
||||
} else if (style->stroke.paint.gradient) {
|
||||
Box bBox = vBox;
|
||||
if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg);
|
||||
|
||||
if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
|
||||
auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
|
||||
vg->stroke(std::move(linear));
|
||||
} else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
|
||||
auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity);
|
||||
vg->stroke(std::move(radial));
|
||||
}
|
||||
} else if (style->stroke.paint.url) {
|
||||
//TODO: Apply the color pointed by url
|
||||
TVGLOG("SVG", "The stroke's url not supported.");
|
||||
} else if (style->stroke.paint.curColor) {
|
||||
//Apply the current style color
|
||||
vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
|
||||
} else {
|
||||
//Apply the stroke color
|
||||
vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
|
||||
}
|
||||
|
||||
_applyComposition(loaderData, vg, node, vBox, svgPath);
|
||||
}
|
||||
|
||||
|
||||
static unique_ptr<Shape> _shapeBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
auto shape = Shape::gen();
|
||||
if (_appendShape(loaderData, node, shape.get(), vBox, svgPath)) return shape;
|
||||
else return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static bool _recognizeShape(SvgNode* node, Shape* shape)
|
||||
{
|
||||
switch (node->type) {
|
||||
case SvgNodeType::Path: {
|
||||
if (node->node.path.path) {
|
||||
if (!svgPathToShape(node->node.path.path, shape)) {
|
||||
TVGERR("SVG", "Invalid path information.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SvgNodeType::Ellipse: {
|
||||
shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
|
||||
break;
|
||||
}
|
||||
case SvgNodeType::Polygon: {
|
||||
if (node->node.polygon.pts.count < 2) break;
|
||||
auto pts = node->node.polygon.pts.begin();
|
||||
shape->moveTo(pts[0], pts[1]);
|
||||
for (pts += 2; pts < node->node.polygon.pts.end(); pts += 2) {
|
||||
shape->lineTo(pts[0], pts[1]);
|
||||
}
|
||||
shape->close();
|
||||
break;
|
||||
}
|
||||
case SvgNodeType::Polyline: {
|
||||
if (node->node.polyline.pts.count < 2) break;
|
||||
auto pts = node->node.polyline.pts.begin();
|
||||
shape->moveTo(pts[0], pts[1]);
|
||||
for (pts += 2; pts < node->node.polyline.pts.end(); pts += 2) {
|
||||
shape->lineTo(pts[0], pts[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SvgNodeType::Circle: {
|
||||
shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
|
||||
break;
|
||||
}
|
||||
case SvgNodeType::Rect: {
|
||||
shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
|
||||
break;
|
||||
}
|
||||
case SvgNodeType::Line: {
|
||||
shape->moveTo(node->node.line.x1, node->node.line.y1);
|
||||
shape->lineTo(node->node.line.x2, node->node.line.y2);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
if (!_recognizeShape(node, shape)) return false;
|
||||
|
||||
_applyProperty(loaderData, node, shape, vBox, svgPath, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform)
|
||||
{
|
||||
//The 'transform' matrix has higher priority than the node->transform, since it already contains it
|
||||
auto m = transform ? transform : (node->transform ? node->transform : nullptr);
|
||||
|
||||
uint32_t currentPtsCnt = 0;
|
||||
if (m) {
|
||||
const Point *tmp = nullptr;
|
||||
currentPtsCnt = shape->pathCoords(&tmp);
|
||||
}
|
||||
|
||||
if (!_recognizeShape(node, shape)) return false;
|
||||
|
||||
if (m) {
|
||||
const Point *pts = nullptr;
|
||||
auto ptsCnt = shape->pathCoords(&pts);
|
||||
|
||||
auto p = const_cast<Point*>(pts) + currentPtsCnt;
|
||||
while (currentPtsCnt++ < ptsCnt) {
|
||||
*p *= *m;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
_applyProperty(loaderData, node, shape, vBox, svgPath, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
enum class imageMimeTypeEncoding
|
||||
{
|
||||
base64 = 0x1,
|
||||
utf8 = 0x2
|
||||
};
|
||||
|
||||
constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
|
||||
return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
|
||||
return (static_cast<int>(a) & static_cast<int>(b));
|
||||
}
|
||||
|
||||
|
||||
static constexpr struct
|
||||
{
|
||||
const char* name;
|
||||
int sz;
|
||||
imageMimeTypeEncoding encoding;
|
||||
} imageMimeTypes[] = {
|
||||
{"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
|
||||
{"png", sizeof("png"), imageMimeTypeEncoding::base64},
|
||||
{"webp", sizeof("webp"), imageMimeTypeEncoding::base64},
|
||||
{"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
|
||||
};
|
||||
|
||||
|
||||
static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
|
||||
if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
|
||||
*href += sizeof("image/") - 1;
|
||||
|
||||
//RFC2397 data:[<mediatype>][;base64],<data>
|
||||
//mediatype := [ type "/" subtype ] *( ";" parameter )
|
||||
//parameter := attribute "=" value
|
||||
for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
|
||||
if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
|
||||
*href += imageMimeTypes[i].sz - 1;
|
||||
*mimetype = imageMimeTypes[i].name;
|
||||
|
||||
while (**href && **href != ',') {
|
||||
while (**href && **href != ';') ++(*href);
|
||||
if (!**href) return false;
|
||||
++(*href);
|
||||
|
||||
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
|
||||
if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
|
||||
*href += sizeof("base64,") - 1;
|
||||
*encoding = imageMimeTypeEncoding::base64;
|
||||
return true; //valid base64
|
||||
}
|
||||
}
|
||||
if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
|
||||
if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
|
||||
*href += sizeof("utf8,") - 1;
|
||||
*encoding = imageMimeTypeEncoding::utf8;
|
||||
return true; //valid utf8
|
||||
}
|
||||
}
|
||||
}
|
||||
//no encoding defined
|
||||
if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
|
||||
++(*href);
|
||||
*encoding = imageMimeTypeEncoding::utf8;
|
||||
return true; //allow no encoding defined if utf8 expected
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "tvgTaskScheduler.h"
|
||||
|
||||
static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
if (!node->node.image.href || !strlen(node->node.image.href)) return nullptr;
|
||||
auto picture = Picture::gen();
|
||||
|
||||
const char* href = node->node.image.href;
|
||||
if (!strncmp(href, "data:", sizeof("data:") - 1)) {
|
||||
href += sizeof("data:") - 1;
|
||||
const char* mimetype;
|
||||
imageMimeTypeEncoding encoding;
|
||||
if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
|
||||
char *decoded = nullptr;
|
||||
if (encoding == imageMimeTypeEncoding::base64) {
|
||||
auto size = b64Decode(href, strlen(href), &decoded);
|
||||
if (picture->load(decoded, size, mimetype, false) != Result::Success) {
|
||||
free(decoded);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
auto size = svgUtilURLDecode(href, &decoded);
|
||||
if (picture->load(decoded, size, mimetype, false) != Result::Success) {
|
||||
free(decoded);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
loaderData.images.push(decoded);
|
||||
} else {
|
||||
if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
|
||||
//TODO: protect against recursive svg image loading
|
||||
//Temporarily disable embedded svg:
|
||||
const char *dot = strrchr(href, '.');
|
||||
if (dot && !strcmp(dot, ".svg")) {
|
||||
TVGLOG("SVG", "Embedded svg file is disabled.");
|
||||
return nullptr;
|
||||
}
|
||||
string imagePath = href;
|
||||
if (strncmp(href, "/", 1)) {
|
||||
auto last = svgPath.find_last_of("/");
|
||||
imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath;
|
||||
}
|
||||
if (picture->load(imagePath) != Result::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
float w, h;
|
||||
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) {
|
||||
auto sx = node->node.image.w / w;
|
||||
auto sy = node->node.image.h / h;
|
||||
m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
|
||||
}
|
||||
if (node->transform) m = *node->transform * m;
|
||||
picture->transform(m);
|
||||
|
||||
_applyComposition(loaderData, picture.get(), node, vBox, svgPath);
|
||||
|
||||
return picture;
|
||||
}
|
||||
|
||||
|
||||
static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, float width, float height, const Box& box)
|
||||
{
|
||||
auto sx = width / box.w;
|
||||
auto sy = height / box.h;
|
||||
auto tvx = box.x * sx;
|
||||
auto tvy = box.y * sy;
|
||||
|
||||
if (align == AspectRatioAlign::None)
|
||||
return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
|
||||
|
||||
//Scale
|
||||
if (meetOrSlice == AspectRatioMeetOrSlice::Meet) {
|
||||
if (sx < sy) sy = sx;
|
||||
else sx = sy;
|
||||
} else {
|
||||
if (sx < sy) sx = sy;
|
||||
else sy = sx;
|
||||
}
|
||||
|
||||
//Align
|
||||
tvx = box.x * sx;
|
||||
tvy = box.y * sy;
|
||||
auto tvw = box.w * sx;
|
||||
auto tvh = box.h * sy;
|
||||
|
||||
switch (align) {
|
||||
case AspectRatioAlign::XMinYMin: {
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMidYMin: {
|
||||
tvx -= (width - tvw) * 0.5f;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMaxYMin: {
|
||||
tvx -= width - tvw;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMinYMid: {
|
||||
tvy -= (height - tvh) * 0.5f;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMidYMid: {
|
||||
tvx -= (width - tvw) * 0.5f;
|
||||
tvy -= (height - tvh) * 0.5f;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMaxYMid: {
|
||||
tvx -= width - tvw;
|
||||
tvy -= (height - tvh) * 0.5f;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMinYMax: {
|
||||
tvy -= height - tvh;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMidYMax: {
|
||||
tvx -= (width - tvw) * 0.5f;
|
||||
tvy -= height - tvh;
|
||||
break;
|
||||
}
|
||||
case AspectRatioAlign::XMaxYMax: {
|
||||
tvx -= width - tvw;
|
||||
tvy -= height - tvh;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
|
||||
}
|
||||
|
||||
|
||||
static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
|
||||
{
|
||||
auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite);
|
||||
|
||||
// mUseTransform = mUseTransform * mTranslate
|
||||
Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if (node->transform) mUseTransform = *node->transform;
|
||||
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
|
||||
Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
|
||||
mUseTransform *= mTranslate;
|
||||
}
|
||||
|
||||
if (node->node.use.symbol) {
|
||||
auto symbol = node->node.use.symbol->node.symbol;
|
||||
|
||||
auto width = (symbol.hasWidth ? symbol.w : vBox.w);
|
||||
if (node->node.use.isWidthSet) width = node->node.use.w;
|
||||
auto height = (symbol.hasHeight ? symbol.h : vBox.h);;
|
||||
if (node->node.use.isHeightSet) height = node->node.use.h;
|
||||
auto vw = (symbol.hasViewBox ? symbol.vw : width);
|
||||
auto vh = (symbol.hasViewBox ? symbol.vh : height);
|
||||
|
||||
Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if ((!tvg::equal(width, vw) || !tvg::equal(height, vh)) && vw > 0 && vh > 0) {
|
||||
Box box = {symbol.vx, symbol.vy, vw, vh};
|
||||
mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
|
||||
} else if (!tvg::zero(symbol.vx) || !tvg::zero(symbol.vy)) {
|
||||
mViewBox = {1, 0, -symbol.vx, 0, 1, -symbol.vy, 0, 0, 1};
|
||||
}
|
||||
|
||||
// mSceneTransform = mUseTransform * mSymbolTransform * mViewBox
|
||||
Matrix mSceneTransform = mViewBox;
|
||||
if (node->node.use.symbol->transform) {
|
||||
mSceneTransform = *node->node.use.symbol->transform * mViewBox;
|
||||
}
|
||||
mSceneTransform = mUseTransform * mSceneTransform;
|
||||
scene->transform(mSceneTransform);
|
||||
|
||||
if (!node->node.use.symbol->node.symbol.overflowVisible) {
|
||||
auto viewBoxClip = Shape::gen();
|
||||
viewBoxClip->appendRect(0, 0, width, height, 0, 0);
|
||||
|
||||
// mClipTransform = mUseTransform * mSymbolTransform
|
||||
Matrix mClipTransform = mUseTransform;
|
||||
if (node->node.use.symbol->transform) {
|
||||
mClipTransform = mUseTransform * *node->node.use.symbol->transform;
|
||||
}
|
||||
viewBoxClip->transform(mClipTransform);
|
||||
|
||||
auto clippingLayer = Scene::gen();
|
||||
clippingLayer->clip(std::move(viewBoxClip));
|
||||
clippingLayer->push(std::move(scene));
|
||||
return clippingLayer;
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
if (auto clipper = scene->Paint::pImpl->clipper) {
|
||||
auto clipTransform = clipper->transform();
|
||||
Matrix inv;
|
||||
if (node->transform && inverse(node->transform, &inv)) clipTransform = inv * clipTransform;
|
||||
clipper->transform(mUseTransform * clipTransform);
|
||||
}
|
||||
|
||||
scene->transform(mUseTransform);
|
||||
return scene;
|
||||
}
|
||||
|
||||
|
||||
static void _applyTextFill(SvgStyleProperty* style, Text* text, const Box& vBox)
|
||||
{
|
||||
//If fill property is nullptr then do nothing
|
||||
if (style->fill.paint.none) {
|
||||
//Do nothing
|
||||
} else if (style->fill.paint.gradient) {
|
||||
Box bBox = vBox;
|
||||
if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(text);
|
||||
|
||||
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
|
||||
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||
text->fill(std::move(linear));
|
||||
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
|
||||
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity);
|
||||
text->fill(std::move(radial));
|
||||
}
|
||||
} else if (style->fill.paint.url) {
|
||||
//TODO: Apply the color pointed by url
|
||||
TVGLOG("SVG", "The fill's url not supported.");
|
||||
} else if (style->fill.paint.curColor) {
|
||||
//Apply the current style color
|
||||
text->fill(style->color.r, style->color.g, style->color.b);
|
||||
text->opacity(style->fill.opacity);
|
||||
} else {
|
||||
//Apply the fill color
|
||||
text->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b);
|
||||
text->opacity(style->fill.opacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static unique_ptr<Text> _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
auto textNode = &node->node.text;
|
||||
if (!textNode->text) return nullptr;
|
||||
auto text = Text::gen();
|
||||
|
||||
Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
if (node->transform) textTransform = *node->transform;
|
||||
translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize);
|
||||
text->transform(textTransform);
|
||||
|
||||
//TODO: handle def values of font and size as used in a system?
|
||||
const float ptPerPx = 0.75f; //1 pt = 1/72; 1 in = 96 px; -> 72/96 = 0.75
|
||||
auto fontSizePt = textNode->fontSize * ptPerPx;
|
||||
if (textNode->fontFamily) text->font(textNode->fontFamily, fontSizePt);
|
||||
text->text(textNode->text);
|
||||
|
||||
_applyTextFill(node->style, text.get(), vBox);
|
||||
_applyComposition(loaderData, text.get(), node, vBox, svgPath);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite)
|
||||
{
|
||||
/* Exception handling: Prevent invalid SVG data input.
|
||||
The size is the arbitrary value, we need an experimental size. */
|
||||
if (depth > 2192) {
|
||||
TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (_isGroupType(node->type) || mask) {
|
||||
auto scene = Scene::gen();
|
||||
// For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
|
||||
if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform);
|
||||
|
||||
if (node->style->display && node->style->opacity != 0) {
|
||||
auto child = node->child.data;
|
||||
for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
|
||||
if (_isGroupType((*child)->type)) {
|
||||
if ((*child)->type == SvgNodeType::Use)
|
||||
scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1, isMaskWhite));
|
||||
else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use))
|
||||
scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1, isMaskWhite));
|
||||
} else if ((*child)->type == SvgNodeType::Image) {
|
||||
auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath);
|
||||
if (image) {
|
||||
scene->push(std::move(image));
|
||||
if (isMaskWhite) *isMaskWhite = false;
|
||||
}
|
||||
} else if ((*child)->type == SvgNodeType::Text) {
|
||||
auto text = _textBuildHelper(loaderData, *child, vBox, svgPath);
|
||||
if (text) scene->push(std::move(text));
|
||||
} else if ((*child)->type != SvgNodeType::Mask) {
|
||||
auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath);
|
||||
if (shape) {
|
||||
if (isMaskWhite) {
|
||||
uint8_t r, g, b;
|
||||
shape->fillColor(&r, &g, &b);
|
||||
if (shape->fill() || r < 255 || g < 255 || b < 255 || shape->strokeFill() ||
|
||||
(shape->strokeColor(&r, &g, &b) == Result::Success && (r < 255 || g < 255 || b < 255))) {
|
||||
*isMaskWhite = false;
|
||||
}
|
||||
}
|
||||
scene->push(std::move(shape));
|
||||
}
|
||||
}
|
||||
}
|
||||
_applyComposition(loaderData, scene.get(), node, vBox, svgPath);
|
||||
scene->opacity(node->style->opacity);
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static void _updateInvalidViewSize(const Scene* scene, Box& vBox, float& w, float& h, SvgViewFlag viewFlag)
|
||||
{
|
||||
bool validWidth = (viewFlag & SvgViewFlag::Width);
|
||||
bool validHeight = (viewFlag & SvgViewFlag::Height);
|
||||
|
||||
float x, y;
|
||||
scene->bounds(&x, &y, &vBox.w, &vBox.h, false);
|
||||
if (!validWidth && !validHeight) {
|
||||
vBox.x = x;
|
||||
vBox.y = y;
|
||||
} else {
|
||||
if (validWidth) vBox.w = w;
|
||||
if (validHeight) vBox.h = h;
|
||||
}
|
||||
|
||||
//the size would have 1x1 or percentage values.
|
||||
if (!validWidth) w *= vBox.w;
|
||||
if (!validHeight) h *= vBox.h;
|
||||
}
|
||||
|
||||
|
||||
static void _loadFonts(Array<FontFace>& fonts)
|
||||
{
|
||||
if (fonts.empty()) return;
|
||||
|
||||
static constexpr struct {
|
||||
const char* prefix;
|
||||
size_t len;
|
||||
} prefixes[] = {
|
||||
{"data:font/ttf;base64,", sizeof("data:font/ttf;base64,") - 1},
|
||||
{"data:application/font-ttf;base64,", sizeof("data:application/font-ttf;base64,") - 1}
|
||||
};
|
||||
|
||||
for (uint32_t i = 0; i < fonts.count; ++i) {
|
||||
auto p = &fonts[i];
|
||||
if (!p->name) continue;
|
||||
|
||||
size_t shift = 0;
|
||||
for (const auto& prefix : prefixes) {
|
||||
if (p->srcLen > prefix.len && !memcmp(p->src, prefix.prefix, prefix.len)) {
|
||||
shift = prefix.len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shift == 0) {
|
||||
TVGLOG("SVG", "The embedded font \"%s\" data not loaded properly.", p->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto size = b64Decode(p->src + shift, p->srcLen - shift, &p->decoded);
|
||||
|
||||
if (Text::load(p->name, p->decoded, size) != Result::Success) TVGERR("SVG", "Error while loading the ttf font named \"%s\".", p->name);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag)
|
||||
{
|
||||
//TODO: aspect ratio is valid only if viewBox was set
|
||||
|
||||
if (!loaderData.doc || (loaderData.doc->type != SvgNodeType::Doc)) return nullptr;
|
||||
|
||||
_loadFonts(loaderData.fonts);
|
||||
|
||||
auto docNode = _sceneBuildHelper(loaderData, loaderData.doc, vBox, svgPath, false, 0);
|
||||
|
||||
if (!(viewFlag & SvgViewFlag::Viewbox)) _updateInvalidViewSize(docNode.get(), vBox, w, h, viewFlag);
|
||||
|
||||
if (!tvg::equal(w, vBox.w) || !tvg::equal(h, vBox.h)) {
|
||||
Matrix m = _calculateAspectRatioMatrix(align, meetOrSlice, w, h, vBox);
|
||||
docNode->transform(m);
|
||||
} else if (!tvg::zero(vBox.x) || !tvg::zero(vBox.y)) {
|
||||
docNode->translate(-vBox.x, -vBox.y);
|
||||
}
|
||||
|
||||
auto viewBoxClip = Shape::gen();
|
||||
viewBoxClip->appendRect(0, 0, w, h);
|
||||
|
||||
auto compositeLayer = Scene::gen();
|
||||
compositeLayer->clip(std::move(viewBoxClip));
|
||||
compositeLayer->push(std::move(docNode));
|
||||
|
||||
auto root = Scene::gen();
|
||||
root->push(std::move(compositeLayer));
|
||||
|
||||
loaderData.doc->node.doc.vx = vBox.x;
|
||||
loaderData.doc->node.doc.vy = vBox.y;
|
||||
loaderData.doc->node.doc.vw = vBox.w;
|
||||
loaderData.doc->node.doc.vh = vBox.h;
|
||||
loaderData.doc->node.doc.w = w;
|
||||
loaderData.doc->node.doc.h = h;
|
||||
|
||||
return root.release();
|
||||
}
|
||||
30
thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
vendored
Normal file
30
thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.h
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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_SVG_SCENE_BUILDER_H_
|
||||
#define _TVG_SVG_SCENE_BUILDER_H_
|
||||
|
||||
#include "tvgCommon.h"
|
||||
|
||||
Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, AspectRatioAlign align, AspectRatioMeetOrSlice meetOrSlice, const string& svgPath, SvgViewFlag viewFlag);
|
||||
|
||||
#endif //_TVG_SVG_SCENE_BUILDER_H_
|
||||
71
thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
vendored
Normal file
71
thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include "tvgSvgUtil.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static uint8_t _hexCharToDec(const char c)
|
||||
{
|
||||
if (c >= 'a') return c - 'a' + 10;
|
||||
else if (c >= 'A') return c - 'A' + 10;
|
||||
else return c - '0';
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
size_t svgUtilURLDecode(const char *src, char** dst)
|
||||
{
|
||||
if (!src) return 0;
|
||||
|
||||
auto length = strlen(src);
|
||||
if (length == 0) return 0;
|
||||
|
||||
char* decoded = (char*)malloc(sizeof(char) * length + 1);
|
||||
|
||||
char a, b;
|
||||
int idx =0;
|
||||
while (*src) {
|
||||
if (*src == '%' &&
|
||||
((a = src[1]) && (b = src[2])) &&
|
||||
(isxdigit(a) && isxdigit(b))) {
|
||||
decoded[idx++] = (_hexCharToDec(a) << 4) + _hexCharToDec(b);
|
||||
src+=3;
|
||||
} else if (*src == '+') {
|
||||
decoded[idx++] = ' ';
|
||||
src++;
|
||||
} else {
|
||||
decoded[idx++] = *src++;
|
||||
}
|
||||
}
|
||||
decoded[idx] = '\0';
|
||||
|
||||
*dst = decoded;
|
||||
return idx + 1;
|
||||
}
|
||||
|
||||
30
thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h
vendored
Normal file
30
thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.h
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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_SVG_UTIL_H_
|
||||
#define _TVG_SVG_UTIL_H_
|
||||
|
||||
#include "tvgCommon.h"
|
||||
|
||||
size_t svgUtilURLDecode(const char *src, char** dst);
|
||||
|
||||
#endif //_TVG_SVG_UTIL_H_
|
||||
608
thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
vendored
Normal file
608
thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp
vendored
Normal file
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <ctype.h>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <malloc.h>
|
||||
#elif defined(__linux__) || defined(__ZEPHYR__)
|
||||
#include <alloca.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "tvgXmlParser.h"
|
||||
#include "tvgStr.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
|
||||
{
|
||||
#ifdef THORVG_LOG_ENABLED
|
||||
const auto attributesNum = 6;
|
||||
const struct
|
||||
{
|
||||
const char* tag;
|
||||
bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
|
||||
const char* value;
|
||||
} attributes[] = {
|
||||
{"id", false, nullptr},
|
||||
{"data-name", false, nullptr},
|
||||
{"overflow", false, "visible"},
|
||||
{"version", false, nullptr},
|
||||
{"xmlns", true, nullptr},
|
||||
{"xml:space", false, nullptr},
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < attributesNum; ++i) {
|
||||
if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
|
||||
if (attributes[i].value && tagValue) {
|
||||
if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
|
||||
return true;
|
||||
} else continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
|
||||
{
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if (isspace((unsigned char)*itr)) break;
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
|
||||
{
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if (!isspace((unsigned char)*itr)) break;
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
|
||||
{
|
||||
for (itr--; itr > itrStart; itr--) {
|
||||
if (!isspace((unsigned char)*itr)) break;
|
||||
}
|
||||
return itr + 1;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
|
||||
{
|
||||
auto p = itr;
|
||||
while (itr < itrEnd && *itr == '&') {
|
||||
for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
|
||||
if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
|
||||
itr += xmlEntityLength[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (itr == p) break;
|
||||
p = itr;
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
|
||||
{
|
||||
auto p = itr;
|
||||
while (itr > itrStart && *(itr - 1) == ';') {
|
||||
for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
|
||||
if (itr - xmlEntityLength[i] > itrStart &&
|
||||
strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
|
||||
itr -= xmlEntityLength[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (itr == p) break;
|
||||
p = itr;
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
|
||||
{
|
||||
itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
|
||||
auto p = itr;
|
||||
while (true) {
|
||||
if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
|
||||
else break;
|
||||
if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
|
||||
else break;
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
|
||||
{
|
||||
itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
|
||||
auto p = itr;
|
||||
while (true) {
|
||||
if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
|
||||
else break;
|
||||
if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
|
||||
else break;
|
||||
}
|
||||
return itr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
|
||||
{
|
||||
return (const char*)memchr(itr, '<', itrEnd - itr);
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
|
||||
{
|
||||
bool insideQuote[2] = {false, false}; // 0: ", 1: '
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if (*itr == '"' && !insideQuote[1]) insideQuote[0] = !insideQuote[0];
|
||||
if (*itr == '\'' && !insideQuote[0]) insideQuote[1] = !insideQuote[1];
|
||||
if (!insideQuote[0] && !insideQuote[1]) {
|
||||
if ((*itr == '>') || (*itr == '<'))
|
||||
return itr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
|
||||
{
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
|
||||
{
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
|
||||
{
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if (*itr == '>') return itr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
|
||||
{
|
||||
toff = 0;
|
||||
if (itr[1] == '/') {
|
||||
toff = 1;
|
||||
return SimpleXMLType::Close;
|
||||
} else if (itr[1] == '?') {
|
||||
toff = 1;
|
||||
return SimpleXMLType::Processing;
|
||||
} else if (itr[1] == '!') {
|
||||
if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) {
|
||||
toff = sizeof("!DOCTYPE") - 1;
|
||||
return SimpleXMLType::Doctype;
|
||||
} else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
|
||||
toff = sizeof("![CDATA[") - 1;
|
||||
return SimpleXMLType::CData;
|
||||
} else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
|
||||
toff = sizeof("!--") - 1;
|
||||
return SimpleXMLType::Comment;
|
||||
} else if (itr + sizeof("<!>") - 1 < itrEnd) {
|
||||
toff = sizeof("!") - 1;
|
||||
return SimpleXMLType::DoctypeChild;
|
||||
}
|
||||
return SimpleXMLType::Open;
|
||||
}
|
||||
return SimpleXMLType::Open;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
|
||||
{
|
||||
#ifdef THORVG_LOG_ENABLED
|
||||
static const char* TYPE_NAMES[] = {
|
||||
"Svg",
|
||||
"G",
|
||||
"Defs",
|
||||
"Animation",
|
||||
"Arc",
|
||||
"Circle",
|
||||
"Ellipse",
|
||||
"Image",
|
||||
"Line",
|
||||
"Path",
|
||||
"Polygon",
|
||||
"Polyline",
|
||||
"Rect",
|
||||
"Text",
|
||||
"TextArea",
|
||||
"Tspan",
|
||||
"Use",
|
||||
"Video",
|
||||
"ClipPath",
|
||||
"Mask",
|
||||
"Symbol",
|
||||
"Unknown",
|
||||
};
|
||||
return TYPE_NAMES[(int) type];
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
|
||||
{
|
||||
#ifdef THORVG_LOG_ENABLED
|
||||
const auto elementsNum = 1;
|
||||
const char* const elements[] = { "title" };
|
||||
|
||||
for (unsigned int i = 0; i < elementsNum; ++i) {
|
||||
if (!strncmp(tagName, elements[i], strlen(tagName))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
|
||||
{
|
||||
const char *itr = buf, *itrEnd = buf + bufLength;
|
||||
char* tmpBuf = (char*)malloc(bufLength + 1);
|
||||
|
||||
if (!buf || !func || !tmpBuf) goto error;
|
||||
|
||||
while (itr < itrEnd) {
|
||||
const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
|
||||
const char *key, *keyEnd, *value, *valueEnd;
|
||||
char* tval;
|
||||
|
||||
if (p == itrEnd) goto success;
|
||||
|
||||
key = p;
|
||||
for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
|
||||
if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
|
||||
}
|
||||
if (keyEnd == itrEnd) goto error;
|
||||
if (keyEnd == key) { // There is no key. This case is invalid, but explores the following syntax.
|
||||
itr = keyEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*keyEnd == '=') value = keyEnd + 1;
|
||||
else {
|
||||
value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
|
||||
if (!value) goto error;
|
||||
value++;
|
||||
}
|
||||
keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
|
||||
|
||||
value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
|
||||
if (value == itrEnd) goto error;
|
||||
|
||||
if ((*value == '"') || (*value == '\'')) {
|
||||
valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
|
||||
if (!valueEnd) goto error;
|
||||
value++;
|
||||
} else {
|
||||
valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
|
||||
}
|
||||
|
||||
itr = valueEnd + 1;
|
||||
|
||||
value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
|
||||
valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
|
||||
|
||||
memcpy(tmpBuf, key, keyEnd - key);
|
||||
tmpBuf[keyEnd - key] = '\0';
|
||||
|
||||
tval = tmpBuf + (keyEnd - key) + 1;
|
||||
int i = 0;
|
||||
while (value < valueEnd) {
|
||||
value = _simpleXmlSkipXmlEntities(value, valueEnd);
|
||||
tval[i++] = *value;
|
||||
value++;
|
||||
}
|
||||
tval[i] = '\0';
|
||||
|
||||
if (!func((void*)data, tmpBuf, tval)) {
|
||||
if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
|
||||
TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", tmpBuf, tval ? tval : "NONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success:
|
||||
free(tmpBuf);
|
||||
return true;
|
||||
|
||||
error:
|
||||
free(tmpBuf);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
|
||||
{
|
||||
const char *itr = buf, *itrEnd = buf + bufLength;
|
||||
|
||||
if (!buf || !func) return false;
|
||||
|
||||
while (itr < itrEnd) {
|
||||
if (itr[0] == '<') {
|
||||
//Invalid case
|
||||
if (itr + 1 >= itrEnd) return false;
|
||||
|
||||
size_t toff = 0;
|
||||
SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
|
||||
|
||||
const char* p;
|
||||
if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
|
||||
else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
|
||||
else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
|
||||
else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
|
||||
|
||||
if (p) {
|
||||
//Invalid case: '<' nested
|
||||
if (*p == '<' && type != SimpleXMLType::Doctype) return false;
|
||||
const char *start, *end;
|
||||
|
||||
start = itr + 1 + toff;
|
||||
end = p;
|
||||
|
||||
switch (type) {
|
||||
case SimpleXMLType::Open: {
|
||||
if (p[-1] == '/') {
|
||||
type = SimpleXMLType::OpenEmpty;
|
||||
end--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SimpleXMLType::CData: {
|
||||
if (!memcmp(p - 2, "]]", 2)) end -= 2;
|
||||
break;
|
||||
}
|
||||
case SimpleXMLType::Processing: {
|
||||
if (p[-1] == '?') end--;
|
||||
break;
|
||||
}
|
||||
case SimpleXMLType::Comment: {
|
||||
if (!memcmp(p - 2, "--", 2)) end -= 2;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strip && (type != SimpleXMLType::CData)) {
|
||||
start = _skipWhiteSpacesAndXmlEntities(start, end);
|
||||
end = _unskipWhiteSpacesAndXmlEntities(end, start);
|
||||
}
|
||||
|
||||
if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
|
||||
|
||||
itr = p + 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const char *p, *end;
|
||||
|
||||
if (strip) {
|
||||
p = itr;
|
||||
p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
|
||||
if (p) {
|
||||
if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
|
||||
itr = p;
|
||||
}
|
||||
}
|
||||
|
||||
p = _simpleXmlFindStartTag(itr, itrEnd);
|
||||
if (!p) p = itrEnd;
|
||||
|
||||
end = p;
|
||||
if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
|
||||
|
||||
if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
|
||||
|
||||
if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
|
||||
|
||||
itr = p;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
|
||||
{
|
||||
const char* end;
|
||||
char* key;
|
||||
char* val;
|
||||
char* next;
|
||||
|
||||
if (!buf) return false;
|
||||
|
||||
end = buf + bufLength;
|
||||
|
||||
if (buf == end) return true;
|
||||
|
||||
char* key_buf = (char*)malloc(end - buf + 1);
|
||||
char* val_buf = (char*)malloc(end - buf + 1);
|
||||
|
||||
key = key_buf;
|
||||
val = val_buf;
|
||||
do {
|
||||
char* sep = (char*)strchr(buf, ':');
|
||||
next = (char*)strchr(buf, ';');
|
||||
|
||||
if (auto src = strstr(buf, "src")) {//src tag from css font-face contains extra semicolon
|
||||
if (src < sep) {
|
||||
if (next + 1 < end) next = (char*)strchr(next + 1, ';');
|
||||
else {
|
||||
free(key_buf);
|
||||
free(val_buf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sep >= end) {
|
||||
next = nullptr;
|
||||
sep = nullptr;
|
||||
}
|
||||
if (next >= end) next = nullptr;
|
||||
|
||||
key[0] = '\0';
|
||||
val[0] = '\0';
|
||||
|
||||
if (sep != nullptr && next == nullptr) {
|
||||
memcpy(key, buf, sep - buf);
|
||||
key[sep - buf] = '\0';
|
||||
|
||||
memcpy(val, sep + 1, end - sep - 1);
|
||||
val[end - sep - 1] = '\0';
|
||||
} else if (sep != nullptr && sep < next) {
|
||||
memcpy(key, buf, sep - buf);
|
||||
key[sep - buf] = '\0';
|
||||
|
||||
memcpy(val, sep + 1, next - sep - 1);
|
||||
val[next - sep - 1] = '\0';
|
||||
} else if (next) {
|
||||
memcpy(key, buf, next - buf);
|
||||
key[next - buf] = '\0';
|
||||
}
|
||||
|
||||
if (key[0]) {
|
||||
key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
|
||||
key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
|
||||
val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
|
||||
val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
|
||||
|
||||
if (!func((void*)data, key, val)) {
|
||||
if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
|
||||
TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", key, val ? val : "NONE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!next) break;
|
||||
buf = next + 1;
|
||||
} while (true);
|
||||
|
||||
free(key_buf);
|
||||
free(val_buf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Supported formats:
|
||||
* tag {}, .name {}, tag.name{}
|
||||
*/
|
||||
const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength)
|
||||
{
|
||||
if (!buf) return nullptr;
|
||||
|
||||
*tag = *name = nullptr;
|
||||
*attrsLength = 0;
|
||||
|
||||
auto itr = _simpleXmlSkipWhiteSpace(buf, buf + bufLength);
|
||||
auto itrEnd = (const char*)memchr(buf, '{', bufLength);
|
||||
|
||||
if (!itrEnd || itr == itrEnd) return nullptr;
|
||||
|
||||
auto nextElement = (const char*)memchr(itrEnd, '}', bufLength - (itrEnd - buf));
|
||||
if (!nextElement) return nullptr;
|
||||
|
||||
*attrs = itrEnd + 1;
|
||||
*attrsLength = nextElement - *attrs;
|
||||
|
||||
const char *p;
|
||||
|
||||
itrEnd = _simpleXmlUnskipWhiteSpace(itrEnd, itr);
|
||||
if (*(itrEnd - 1) == '.') return nullptr;
|
||||
|
||||
for (p = itr; p < itrEnd; p++) {
|
||||
if (*p == '.') break;
|
||||
}
|
||||
|
||||
if (p == itr) *tag = strdup("all");
|
||||
else *tag = strDuplicate(itr, p - itr);
|
||||
|
||||
if (p == itrEnd) *name = nullptr;
|
||||
else *name = strDuplicate(p + 1, itrEnd - p - 1);
|
||||
|
||||
return (nextElement ? nextElement + 1 : nullptr);
|
||||
}
|
||||
|
||||
|
||||
const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
|
||||
{
|
||||
const char *itr = buf, *itrEnd = buf + bufLength;
|
||||
|
||||
for (; itr < itrEnd; itr++) {
|
||||
if (!isspace((unsigned char)*itr)) {
|
||||
//User skip tagname and already gave it the attributes.
|
||||
if (*itr == '=') return buf;
|
||||
} else {
|
||||
itr = _simpleXmlUnskipXmlEntities(itr, buf);
|
||||
if (itr == itrEnd) return nullptr;
|
||||
return itr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
58
thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h
vendored
Normal file
58
thirdparty/thorvg/src/loaders/svg/tvgXmlParser.h
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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_SIMPLE_XML_PARSER_H_
|
||||
#define _TVG_SIMPLE_XML_PARSER_H_
|
||||
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
|
||||
#define NUMBER_OF_XML_ENTITIES 9
|
||||
const char* const xmlEntity[] = {" ", """, " ", "'", "&", "<", ">", "#", "'"};
|
||||
const int xmlEntityLength[] = {5, 6, 6, 6, 5, 4, 4, 6, 6};
|
||||
|
||||
enum class SimpleXMLType
|
||||
{
|
||||
Open = 0, //!< \<tag attribute="value"\>
|
||||
OpenEmpty, //!< \<tag attribute="value" /\>
|
||||
Close, //!< \</tag\>
|
||||
Data, //!< tag text data
|
||||
CData, //!< \<![cdata[something]]\>
|
||||
Error, //!< error contents
|
||||
Processing, //!< \<?xml ... ?\> \<?php .. ?\>
|
||||
Doctype, //!< \<!doctype html
|
||||
Comment, //!< \<!-- something --\>
|
||||
Ignored, //!< whatever is ignored by parser, like whitespace
|
||||
DoctypeChild //!< \<!doctype_child
|
||||
};
|
||||
|
||||
typedef bool (*simpleXMLCb)(void* data, SimpleXMLType type, const char* content, unsigned int length);
|
||||
typedef bool (*simpleXMLAttributeCb)(void* data, const char* key, const char* value);
|
||||
|
||||
bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data);
|
||||
bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data);
|
||||
bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data);
|
||||
const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength);
|
||||
const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength);
|
||||
bool isIgnoreUnsupportedLogElements(const char* tagName);
|
||||
const char* simpleXmlNodeTypeToString(SvgNodeType type);
|
||||
|
||||
#endif //_TVG_SIMPLE_XML_PARSER_H_
|
||||
599
thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
vendored
Normal file
599
thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
vendored
Normal file
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
* 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 "tvgMath.h"
|
||||
#include "tvgRender.h"
|
||||
|
||||
#define SW_CURVE_TYPE_POINT 0
|
||||
#define SW_CURVE_TYPE_CUBIC 1
|
||||
#define SW_ANGLE_PI (180L << 16)
|
||||
#define SW_ANGLE_2PI (SW_ANGLE_PI << 1)
|
||||
#define SW_ANGLE_PI2 (SW_ANGLE_PI >> 1)
|
||||
|
||||
using SwCoord = signed long;
|
||||
using SwFixed = signed long long;
|
||||
|
||||
|
||||
static inline float TO_FLOAT(SwCoord val)
|
||||
{
|
||||
return static_cast<float>(val) / 64.0f;
|
||||
}
|
||||
|
||||
struct SwPoint
|
||||
{
|
||||
SwCoord x, y;
|
||||
|
||||
SwPoint& operator+=(const SwPoint& rhs)
|
||||
{
|
||||
x += rhs.x;
|
||||
y += rhs.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SwPoint operator+(const SwPoint& rhs) const
|
||||
{
|
||||
return {x + rhs.x, y + rhs.y};
|
||||
}
|
||||
|
||||
SwPoint operator-(const SwPoint& rhs) const
|
||||
{
|
||||
return {x - rhs.x, y - rhs.y};
|
||||
}
|
||||
|
||||
bool operator==(const SwPoint& rhs) const
|
||||
{
|
||||
return (x == rhs.x && y == rhs.y);
|
||||
}
|
||||
|
||||
bool operator!=(const SwPoint& rhs) const
|
||||
{
|
||||
return (x != rhs.x || y != rhs.y);
|
||||
}
|
||||
|
||||
bool zero() const
|
||||
{
|
||||
if (x == 0 && y == 0) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool small() const
|
||||
{
|
||||
//2 is epsilon...
|
||||
if (abs(x) < 2 && abs(y) < 2) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
Point toPoint() const
|
||||
{
|
||||
return {TO_FLOAT(x), TO_FLOAT(y)};
|
||||
}
|
||||
};
|
||||
|
||||
struct SwSize
|
||||
{
|
||||
SwCoord w, h;
|
||||
};
|
||||
|
||||
struct SwOutline
|
||||
{
|
||||
Array<SwPoint> pts; //the outline's points
|
||||
Array<uint32_t> cntrs; //the contour end points
|
||||
Array<uint8_t> types; //curve type
|
||||
Array<bool> closed; //opened or closed path?
|
||||
FillRule fillRule;
|
||||
};
|
||||
|
||||
struct SwSpan
|
||||
{
|
||||
uint16_t x, y;
|
||||
uint16_t len;
|
||||
uint8_t coverage;
|
||||
};
|
||||
|
||||
struct SwRle
|
||||
{
|
||||
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 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 solid = false; //solid color fill with the last color from colorStops
|
||||
bool translucent;
|
||||
};
|
||||
|
||||
struct SwStrokeBorder
|
||||
{
|
||||
uint32_t ptsCnt;
|
||||
uint32_t maxPts;
|
||||
SwPoint* pts;
|
||||
uint8_t* tags;
|
||||
int32_t start; //index of current sub-path start point
|
||||
bool movable; //true: for ends of lineto borders
|
||||
};
|
||||
|
||||
struct SwStroke
|
||||
{
|
||||
SwFixed angleIn;
|
||||
SwFixed angleOut;
|
||||
SwPoint center;
|
||||
SwFixed lineLength;
|
||||
SwFixed subPathAngle;
|
||||
SwPoint ptStartSubPath;
|
||||
SwFixed subPathLineLength;
|
||||
SwFixed width;
|
||||
SwFixed miterlimit;
|
||||
|
||||
StrokeCap cap;
|
||||
StrokeJoin join;
|
||||
StrokeJoin joinSaved;
|
||||
SwFill* fill = nullptr;
|
||||
|
||||
SwStrokeBorder borders[2];
|
||||
|
||||
float sx, sy;
|
||||
|
||||
bool firstPt;
|
||||
bool closedSubPath;
|
||||
bool handleWideStrokes;
|
||||
};
|
||||
|
||||
struct SwDashStroke
|
||||
{
|
||||
SwOutline* outline = nullptr;
|
||||
float curLen = 0;
|
||||
int32_t curIdx = 0;
|
||||
Point ptStart = {0, 0};
|
||||
Point ptCur = {0, 0};
|
||||
float* pattern = nullptr;
|
||||
uint32_t cnt = 0;
|
||||
bool curOpGap = false;
|
||||
bool move = true;
|
||||
};
|
||||
|
||||
struct SwShape
|
||||
{
|
||||
SwOutline* outline = nullptr;
|
||||
SwStroke* stroke = nullptr;
|
||||
SwFill* fill = nullptr;
|
||||
SwRle* rle = nullptr;
|
||||
SwRle* 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;
|
||||
SwRle* 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 : RenderSurface
|
||||
{
|
||||
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 = BlendMethod::Normal;
|
||||
|
||||
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) : RenderSurface(rhs)
|
||||
{
|
||||
join = rhs->join;
|
||||
memcpy(alphas, rhs->alphas, sizeof(alphas));
|
||||
blender = rhs->blender;
|
||||
compositor = rhs->compositor;
|
||||
blendMethod = rhs->blendMethod;
|
||||
}
|
||||
};
|
||||
|
||||
struct SwCompositor : RenderCompositor
|
||||
{
|
||||
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)
|
||||
{
|
||||
++a;
|
||||
return (((((c >> 8) & 0x00ff00ff) * a) & 0xff00ff00) + ((((c & 0x00ff00ff) * a) >> 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)
|
||||
{
|
||||
// (s + d) - (2 * s * d)
|
||||
auto c1 = C1(s) + C1(d) - 2 * MULTIPLY(C1(s), C1(d));
|
||||
tvg::clamp(c1, 0, 255);
|
||||
auto c2 = C2(s) + C2(d) - 2 * MULTIPLY(C2(s), C2(d));
|
||||
tvg::clamp(c2, 0, 255);
|
||||
auto c3 = C3(s) + C3(d) - 2 * MULTIPLY(C3(s), C3(d));
|
||||
tvg::clamp(c3, 0, 255);
|
||||
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)
|
||||
s = 0xffffffff - s;
|
||||
auto c1 = C1(d) == 0 ? 0 : (C1(s) == 0 ? 255 : std::min(C1(d) * 255 / C1(s), 255));
|
||||
auto c2 = C2(d) == 0 ? 0 : (C2(s) == 0 ? 255 : std::min(C2(d) * 255 / C2(s), 255));
|
||||
auto c3 = C3(d) == 0 ? 0 : (C3(s) == 0 ? 255 : std::min(C3(d) * 255 / C3(s), 255));
|
||||
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 = C1(d) == 255 ? 255 : (C1(s) == 0 ? 0 : 255 - std::min(C1(id) * 255 / C1(s), 255));
|
||||
auto c2 = C2(d) == 255 ? 255 : (C2(s) == 0 ? 0 : 255 - std::min(C2(id) * 255 / C2(s), 255));
|
||||
auto c3 = C3(d) == 255 ? 255 : (C3(s) == 0 ? 0 : 255 - std::min(C3(id) * 255 / C3(s), 255));
|
||||
|
||||
return JOIN(255, c1, c2, c3);
|
||||
}
|
||||
|
||||
static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
|
||||
{
|
||||
// if (s < sa), (2 * s * d)
|
||||
// else (sa * da) - 2 * (da - s) * (sa - d)
|
||||
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 = MULTIPLY(255 - std::min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + MULTIPLY(std::min(255, 2 * C1(s)), C1(d));
|
||||
auto c2 = MULTIPLY(255 - std::min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + MULTIPLY(std::min(255, 2 * C2(s)), C2(d));
|
||||
auto c3 = MULTIPLY(255 - std::min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + MULTIPLY(std::min(255, 2 * 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);
|
||||
void mathSplitLine(SwPoint* base);
|
||||
SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
|
||||
SwFixed mathLength(const SwPoint& pt);
|
||||
int mathCubicAngle(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& clippee);
|
||||
|
||||
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 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);
|
||||
const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata);
|
||||
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.
|
||||
|
||||
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
|
||||
SwRle* rleRender(const SwBBox* bbox);
|
||||
void rleFree(SwRle* rle);
|
||||
void rleReset(SwRle* rle);
|
||||
void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2);
|
||||
bool rleClip(SwRle* rle, const SwRle* clip);
|
||||
bool rleClip(SwRle* 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, const Fill* fdata, uint8_t opacity);
|
||||
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 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, const Fill* fdata, uint8_t opacity);
|
||||
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0);
|
||||
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
|
||||
void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity);
|
||||
void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity);
|
||||
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
|
||||
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped);
|
||||
void rasterUnpremultiply(RenderSurface* surface);
|
||||
void rasterPremultiply(RenderSurface* surface);
|
||||
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
|
||||
uint32_t rasterUnpremultiply(uint32_t data);
|
||||
|
||||
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params);
|
||||
bool effectGaussianBlurRegion(RenderEffectGaussianBlur* effect);
|
||||
void effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform);
|
||||
bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, bool direct);
|
||||
bool effectDropShadowRegion(RenderEffectDropShadow* effect);
|
||||
void effectDropShadowUpdate(RenderEffectDropShadow* effect, const Matrix& transform);
|
||||
void effectFillUpdate(RenderEffectFill* effect);
|
||||
bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct);
|
||||
void effectTintUpdate(RenderEffectTint* effect);
|
||||
bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct);
|
||||
void effectTritoneUpdate(RenderEffectTritone* effect);
|
||||
bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct);
|
||||
|
||||
#endif /* _TVG_SW_COMMON_H_ */
|
||||
873
thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
vendored
Normal file
873
thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
vendored
Normal file
@@ -0,0 +1,873 @@
|
||||
/*
|
||||
* 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 * 0.5f;
|
||||
deltaDeltaDet = 2.0f * deltaB * deltaB + deltaDeltaRr;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t _estimateAAMargin(const Fill* fdata)
|
||||
{
|
||||
constexpr float marginScalingFactor = 800.0f;
|
||||
if (fdata->type() == Type::RadialGradient) {
|
||||
auto radius = P(static_cast<const RadialGradient*>(fdata))->r;
|
||||
return tvg::zero(radius) ? 0 : static_cast<uint32_t>(marginScalingFactor / radius);
|
||||
}
|
||||
auto grad = P(static_cast<const LinearGradient*>(fdata));
|
||||
Point p1 {grad->x1, grad->y1};
|
||||
Point p2 {grad->x2, grad->y2};
|
||||
auto len = length(&p1, &p2);
|
||||
return tvg::zero(len) ? 0 : static_cast<uint32_t>(marginScalingFactor / len);
|
||||
}
|
||||
|
||||
|
||||
static void _adjustAAMargin(uint32_t& iMargin, uint32_t index)
|
||||
{
|
||||
constexpr float threshold = 0.1f;
|
||||
constexpr uint32_t iMarginMax = 40;
|
||||
|
||||
auto iThreshold = static_cast<uint32_t>(index * threshold);
|
||||
if (iMargin > iThreshold) iMargin = iThreshold;
|
||||
if (iMargin > iMarginMax) iMargin = iMarginMax;
|
||||
}
|
||||
|
||||
|
||||
static inline uint32_t _alphaUnblend(uint32_t c)
|
||||
{
|
||||
auto a = (c >> 24);
|
||||
if (a == 255 || a == 0) return c;
|
||||
auto invA = 255.0f / static_cast<float>(a);
|
||||
auto c0 = static_cast<uint8_t>(static_cast<float>((c >> 16) & 0xFF) * invA);
|
||||
auto c1 = static_cast<uint8_t>(static_cast<float>((c >> 8) & 0xFF) * invA);
|
||||
auto c2 = static_cast<uint8_t>(static_cast<float>(c & 0xFF) * invA);
|
||||
|
||||
return (a << 24) | (c0 << 16) | (c1 << 8) | c2;
|
||||
}
|
||||
|
||||
|
||||
static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end)
|
||||
{
|
||||
if (begin == 0 || end == 0) return;
|
||||
|
||||
auto i = GRADIENT_STOP_SIZE - end;
|
||||
auto rgbaEnd = _alphaUnblend(fill->ctable[i]);
|
||||
auto rgbaBegin = _alphaUnblend(fill->ctable[begin]);
|
||||
|
||||
auto dt = 1.0f / (begin + end + 1.0f);
|
||||
float t = dt;
|
||||
while (i != begin) {
|
||||
auto dist = 255 - static_cast<int32_t>(255 * t);
|
||||
auto color = INTERPOLATE(rgbaEnd, rgbaBegin, dist);
|
||||
fill->ctable[i++] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
|
||||
|
||||
if (i == GRADIENT_STOP_SIZE) i = 0;
|
||||
t += dt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
|
||||
{
|
||||
if (fill->solid) return true;
|
||||
|
||||
if (!fill->ctable) {
|
||||
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
|
||||
if (!fill->ctable) return false;
|
||||
}
|
||||
|
||||
const Fill::ColorStop* colors;
|
||||
auto cnt = fdata->colorStops(&colors);
|
||||
if (cnt == 0 || !colors) return false;
|
||||
|
||||
auto pColors = colors;
|
||||
|
||||
auto a = MULTIPLY(pColors->a, opacity);
|
||||
if (a < 255) fill->translucent = true;
|
||||
|
||||
auto r = pColors->r;
|
||||
auto g = pColors->g;
|
||||
auto b = pColors->b;
|
||||
auto rgba = surface->join(r, g, b, a);
|
||||
|
||||
auto inc = 1.0f / static_cast<float>(GRADIENT_STOP_SIZE);
|
||||
auto pos = 1.5f * inc;
|
||||
uint32_t i = 0;
|
||||
|
||||
//If repeat is true, anti-aliasing must be applied between the last and the first colors.
|
||||
auto repeat = fill->spread == FillSpread::Repeat;
|
||||
uint32_t iAABegin = repeat ? _estimateAAMargin(fdata) : 0;
|
||||
uint32_t iAAEnd = 0;
|
||||
|
||||
fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a);
|
||||
|
||||
while (pos <= pColors->offset) {
|
||||
fill->ctable[i] = fill->ctable[i - 1];
|
||||
++i;
|
||||
pos += inc;
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < cnt - 1; ++j) {
|
||||
if (repeat && j == cnt - 2 && iAAEnd == 0) {
|
||||
iAAEnd = iAABegin;
|
||||
_adjustAAMargin(iAAEnd, GRADIENT_STOP_SIZE - i);
|
||||
}
|
||||
|
||||
auto curr = colors + j;
|
||||
auto next = curr + 1;
|
||||
auto delta = 1.0f / (next->offset - curr->offset);
|
||||
auto a2 = MULTIPLY(next->a, opacity);
|
||||
if (!fill->translucent && a2 < 255) fill->translucent = true;
|
||||
|
||||
auto rgba2 = surface->join(next->r, next->g, next->b, a2);
|
||||
|
||||
while (pos < next->offset && i < GRADIENT_STOP_SIZE) {
|
||||
auto t = (pos - curr->offset) * delta;
|
||||
auto dist = static_cast<int32_t>(255 * t);
|
||||
auto dist2 = 255 - dist;
|
||||
|
||||
auto color = INTERPOLATE(rgba, rgba2, dist2);
|
||||
fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
|
||||
|
||||
++i;
|
||||
pos += inc;
|
||||
}
|
||||
rgba = rgba2;
|
||||
a = a2;
|
||||
|
||||
if (repeat && j == 0) _adjustAAMargin(iAABegin, i - 1);
|
||||
}
|
||||
rgba = ALPHA_BLEND((rgba | 0xff000000), a);
|
||||
|
||||
for (; i < GRADIENT_STOP_SIZE; ++i)
|
||||
fill->ctable[i] = rgba;
|
||||
|
||||
//For repeat fill spread apply anti-aliasing between the last and first colors,
|
||||
//othewise make sure the last color stop is represented at the end of the table.
|
||||
if (repeat) _applyAA(fill, iAABegin, iAAEnd);
|
||||
else fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& transform)
|
||||
{
|
||||
float x1, x2, y1, y2;
|
||||
if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false;
|
||||
|
||||
fill->linear.dx = x2 - x1;
|
||||
fill->linear.dy = y2 - y1;
|
||||
auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
|
||||
|
||||
if (len < FLOAT_EPSILON) {
|
||||
if (tvg::zero(fill->linear.dx) && tvg::zero(fill->linear.dy)) {
|
||||
fill->solid = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fill->linear.dx /= len;
|
||||
fill->linear.dy /= len;
|
||||
fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;
|
||||
|
||||
auto gradTransform = linear->transform();
|
||||
bool isTransformation = !identity((const Matrix*)(&gradTransform));
|
||||
|
||||
if (isTransformation) {
|
||||
gradTransform = transform * gradTransform;
|
||||
} else {
|
||||
gradTransform = transform;
|
||||
isTransformation = true;
|
||||
}
|
||||
|
||||
if (isTransformation) {
|
||||
Matrix invTransform;
|
||||
if (!inverse(&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;
|
||||
}
|
||||
|
||||
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 (tvg::zero(r)) {
|
||||
fill->solid = true;
|
||||
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 = !identity((const Matrix*)(&gradTransform));
|
||||
|
||||
if (isTransformation) gradTransform = transform * gradTransform;
|
||||
else {
|
||||
gradTransform = transform;
|
||||
isTransformation = true;
|
||||
}
|
||||
|
||||
if (isTransformation) {
|
||||
Matrix invTransform;
|
||||
if (!inverse(&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);
|
||||
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, 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 (tvg::zero(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 (tvg::zero(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 (tvg::zero(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(A(_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(A(_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 (tvg::zero(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 (tvg::zero(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 (tvg::zero(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 (fdata->type() == Type::LinearGradient) {
|
||||
if (!_prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform)) return false;
|
||||
} else if (fdata->type() == Type::RadialGradient) {
|
||||
if (!_prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform)) return false;
|
||||
}
|
||||
|
||||
if (ctable) return _updateColorTable(fill, fdata, surface, opacity);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata)
|
||||
{
|
||||
if (!fill->solid) return nullptr;
|
||||
|
||||
const Fill::ColorStop* colors;
|
||||
auto cnt = fdata->colorStops(&colors);
|
||||
if (cnt == 0 || !colors) return nullptr;
|
||||
|
||||
return colors + cnt - 1;
|
||||
}
|
||||
|
||||
|
||||
void fillReset(SwFill* fill)
|
||||
{
|
||||
if (fill->ctable) {
|
||||
free(fill->ctable);
|
||||
fill->ctable = nullptr;
|
||||
}
|
||||
fill->translucent = false;
|
||||
fill->solid = false;
|
||||
}
|
||||
|
||||
|
||||
void fillFree(SwFill* fill)
|
||||
{
|
||||
if (!fill) return;
|
||||
|
||||
if (fill->ctable) free(fill->ctable);
|
||||
|
||||
free(fill);
|
||||
}
|
||||
122
thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp
vendored
Normal file
122
thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 (tvg::equal(m.e11, 1.0f) && tvg::equal(m.e22, 1.0f) && tvg::zero(m.e12) && tvg::zero(m.e21)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool _genOutline(SwImage* image, 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];
|
||||
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 Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
|
||||
{
|
||||
image->direct = _onlyShifted(transform);
|
||||
|
||||
//Fast track: Non-transformed image but just shifted.
|
||||
if (image->direct) {
|
||||
image->ox = -static_cast<int32_t>(nearbyint(transform.e13));
|
||||
image->oy = -static_cast<int32_t>(nearbyint(transform.e23));
|
||||
//Figure out the scale factor by transform matrix
|
||||
} else {
|
||||
auto scaleX = sqrtf((transform.e11 * transform.e11) + (transform.e21 * transform.e21));
|
||||
auto scaleY = sqrtf((transform.e22 * transform.e22) + (transform.e12 * transform.e12));
|
||||
image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX;
|
||||
|
||||
if (tvg::zero(transform.e12) && tvg::zero(transform.e21)) image->scaled = true;
|
||||
else image->scaled = false;
|
||||
}
|
||||
|
||||
if (!_genOutline(image, 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);
|
||||
}
|
||||
328
thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
vendored
Normal file
328
thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
int mathCubicAngle(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 -1; //ignoreable
|
||||
} 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 0; //small size
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int64_t mathMultiply(int64_t a, int64_t b)
|
||||
{
|
||||
int32_t s = 1;
|
||||
|
||||
//move sign
|
||||
if (a < 0) {
|
||||
a = -a;
|
||||
s = -s;
|
||||
}
|
||||
if (b < 0) {
|
||||
b = -b;
|
||||
s = -s;
|
||||
}
|
||||
int64_t c = (a * b + 0x8000L) >> 16;
|
||||
return (s > 0) ? c : -c;
|
||||
}
|
||||
|
||||
|
||||
int64_t mathDivide(int64_t a, int64_t b)
|
||||
{
|
||||
int32_t s = 1;
|
||||
|
||||
//move sign
|
||||
if (a < 0) {
|
||||
a = -a;
|
||||
s = -s;
|
||||
}
|
||||
if (b < 0) {
|
||||
b = -b;
|
||||
s = -s;
|
||||
}
|
||||
int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL;
|
||||
return (s < 0 ? -q : q);
|
||||
}
|
||||
|
||||
|
||||
int64_t mathMulDiv(int64_t a, int64_t b, int64_t c)
|
||||
{
|
||||
int32_t s = 1;
|
||||
|
||||
//move sign
|
||||
if (a < 0) {
|
||||
a = -a;
|
||||
s = -s;
|
||||
}
|
||||
if (b < 0) {
|
||||
b = -b;
|
||||
s = -s;
|
||||
}
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
s = -s;
|
||||
}
|
||||
int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL;
|
||||
|
||||
return (s > 0 ? d : -d);
|
||||
}
|
||||
|
||||
|
||||
void mathRotate(SwPoint& pt, SwFixed angle)
|
||||
{
|
||||
if (angle == 0 || pt.zero()) return;
|
||||
|
||||
Point v = pt.toPoint();
|
||||
|
||||
auto radian = TO_RADIAN(angle);
|
||||
auto cosv = cosf(radian);
|
||||
auto sinv = sinf(radian);
|
||||
|
||||
pt.x = SwCoord(nearbyint((v.x * cosv - v.y * sinv) * 64.0f));
|
||||
pt.y = SwCoord(nearbyint((v.x * sinv + v.y * cosv) * 64.0f));
|
||||
}
|
||||
|
||||
|
||||
SwFixed mathTan(SwFixed angle)
|
||||
{
|
||||
if (angle == 0) return 0;
|
||||
return SwFixed(tanf(TO_RADIAN(angle)) * 65536.0f);
|
||||
}
|
||||
|
||||
|
||||
SwFixed mathAtan(const SwPoint& pt)
|
||||
{
|
||||
if (pt.zero()) return 0;
|
||||
return SwFixed(tvg::atan2(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;
|
||||
}
|
||||
|
||||
|
||||
void mathSplitLine(SwPoint* base)
|
||||
{
|
||||
base[2] = base[1];
|
||||
|
||||
base[1].x = (base[0].x + base[1].x) >> 1;
|
||||
base[1].y = (base[0].y + base[1].y) >> 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)
|
||||
{
|
||||
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& clippee)
|
||||
{
|
||||
clippee.max.x = (clippee.max.x < clipper.max.x) ? clippee.max.x : clipper.max.x;
|
||||
clippee.max.y = (clippee.max.y < clipper.max.y) ? clippee.max.y : clipper.max.y;
|
||||
clippee.min.x = (clippee.min.x > clipper.min.x) ? clippee.min.x : clipper.min.x;
|
||||
clippee.min.y = (clippee.min.y > clipper.min.y) ? clippee.min.y : clipper.min.y;
|
||||
|
||||
//Check valid region
|
||||
if (clippee.max.x - clippee.min.x < 1 && clippee.max.y - clippee.min.y < 1) return false;
|
||||
|
||||
//Check boundary
|
||||
if (clippee.min.x >= clipper.max.x || clippee.min.y >= clipper.max.y ||
|
||||
clippee.max.x <= clipper.min.x || clippee.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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
129
thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp
vendored
Normal file
129
thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "tvgSwCommon.h"
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx)
|
||||
{
|
||||
return &mpool->outline[idx];
|
||||
}
|
||||
|
||||
|
||||
void mpoolRetOutline(SwMpool* mpool, unsigned idx)
|
||||
{
|
||||
mpool->outline[idx].pts.clear();
|
||||
mpool->outline[idx].cntrs.clear();
|
||||
mpool->outline[idx].types.clear();
|
||||
mpool->outline[idx].closed.clear();
|
||||
}
|
||||
|
||||
|
||||
SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx)
|
||||
{
|
||||
return &mpool->strokeOutline[idx];
|
||||
}
|
||||
|
||||
|
||||
void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx)
|
||||
{
|
||||
mpool->strokeOutline[idx].pts.clear();
|
||||
mpool->strokeOutline[idx].cntrs.clear();
|
||||
mpool->strokeOutline[idx].types.clear();
|
||||
mpool->strokeOutline[idx].closed.clear();
|
||||
}
|
||||
|
||||
|
||||
SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx)
|
||||
{
|
||||
return &mpool->dashOutline[idx];
|
||||
}
|
||||
|
||||
|
||||
void mpoolRetDashOutline(SwMpool* mpool, unsigned idx)
|
||||
{
|
||||
mpool->dashOutline[idx].pts.clear();
|
||||
mpool->dashOutline[idx].cntrs.clear();
|
||||
mpool->dashOutline[idx].types.clear();
|
||||
mpool->dashOutline[idx].closed.clear();
|
||||
}
|
||||
|
||||
|
||||
SwMpool* mpoolInit(uint32_t threads)
|
||||
{
|
||||
auto allocSize = threads + 1;
|
||||
|
||||
auto mpool = static_cast<SwMpool*>(calloc(1, sizeof(SwMpool)));
|
||||
mpool->outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
|
||||
mpool->strokeOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
|
||||
mpool->dashOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize));
|
||||
mpool->allocSize = allocSize;
|
||||
|
||||
return mpool;
|
||||
}
|
||||
|
||||
|
||||
bool mpoolClear(SwMpool* mpool)
|
||||
{
|
||||
for (unsigned i = 0; i < mpool->allocSize; ++i) {
|
||||
mpool->outline[i].pts.reset();
|
||||
mpool->outline[i].cntrs.reset();
|
||||
mpool->outline[i].types.reset();
|
||||
mpool->outline[i].closed.reset();
|
||||
|
||||
mpool->strokeOutline[i].pts.reset();
|
||||
mpool->strokeOutline[i].cntrs.reset();
|
||||
mpool->strokeOutline[i].types.reset();
|
||||
mpool->strokeOutline[i].closed.reset();
|
||||
|
||||
mpool->dashOutline[i].pts.reset();
|
||||
mpool->dashOutline[i].cntrs.reset();
|
||||
mpool->dashOutline[i].types.reset();
|
||||
mpool->dashOutline[i].closed.reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool mpoolTerm(SwMpool* mpool)
|
||||
{
|
||||
if (!mpool) return false;
|
||||
|
||||
mpoolClear(mpool);
|
||||
|
||||
free(mpool->outline);
|
||||
free(mpool->strokeOutline);
|
||||
free(mpool->dashOutline);
|
||||
free(mpool);
|
||||
|
||||
return true;
|
||||
}
|
||||
589
thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp
vendored
Normal file
589
thirdparty/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp
vendored
Normal file
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "tvgMath.h"
|
||||
#include "tvgSwCommon.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Gaussian Blur Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
struct SwGaussianBlur
|
||||
{
|
||||
static constexpr int MAX_LEVEL = 3;
|
||||
int level;
|
||||
int kernel[MAX_LEVEL];
|
||||
int extends;
|
||||
};
|
||||
|
||||
|
||||
static inline int _gaussianEdgeWrap(int end, int idx)
|
||||
{
|
||||
auto r = idx % (end + 1);
|
||||
return (r < 0) ? (end + 1) + r : r;
|
||||
}
|
||||
|
||||
|
||||
static inline int _gaussianEdgeExtend(int end, int idx)
|
||||
{
|
||||
if (idx < 0) return 0;
|
||||
else if (idx > end) return end;
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
template<int border>
|
||||
static inline int _gaussianRemap(int end, int idx)
|
||||
{
|
||||
if (border == 1) return _gaussianEdgeWrap(end, idx);
|
||||
return _gaussianEdgeExtend(end, idx);
|
||||
}
|
||||
|
||||
|
||||
//TODO: SIMD OPTIMIZATION?
|
||||
template<int border = 0>
|
||||
static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, bool flipped)
|
||||
{
|
||||
if (flipped) {
|
||||
src += (bbox.min.x * stride + bbox.min.y) << 2;
|
||||
dst += (bbox.min.x * stride + bbox.min.y) << 2;
|
||||
} else {
|
||||
src += (bbox.min.y * stride + bbox.min.x) << 2;
|
||||
dst += (bbox.min.y * stride + bbox.min.x) << 2;
|
||||
}
|
||||
|
||||
auto iarr = 1.0f / (dimension + dimension + 1);
|
||||
auto end = w - 1;
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int y = 0; y < h; ++y) {
|
||||
auto p = y * stride;
|
||||
auto i = p * 4; //current index
|
||||
auto l = -(dimension + 1); //left index
|
||||
auto r = dimension; //right index
|
||||
int acc[4] = {0, 0, 0, 0}; //sliding accumulator
|
||||
|
||||
//initial accumulation
|
||||
for (int x = l; x < r; ++x) {
|
||||
auto id = (_gaussianRemap<border>(end, x) + p) * 4;
|
||||
acc[0] += src[id++];
|
||||
acc[1] += src[id++];
|
||||
acc[2] += src[id++];
|
||||
acc[3] += src[id];
|
||||
}
|
||||
//perform filtering
|
||||
for (int x = 0; x < w; ++x, ++r, ++l) {
|
||||
auto rid = (_gaussianRemap<border>(end, r) + p) * 4;
|
||||
auto lid = (_gaussianRemap<border>(end, l) + p) * 4;
|
||||
acc[0] += src[rid++] - src[lid++];
|
||||
acc[1] += src[rid++] - src[lid++];
|
||||
acc[2] += src[rid++] - src[lid++];
|
||||
acc[3] += src[rid] - src[lid];
|
||||
//ignored rounding for the performance. It should be originally: acc[idx] * iarr + 0.5f
|
||||
dst[i++] = static_cast<uint8_t>(acc[0] * iarr);
|
||||
dst[i++] = static_cast<uint8_t>(acc[1] * iarr);
|
||||
dst[i++] = static_cast<uint8_t>(acc[2] * iarr);
|
||||
dst[i++] = static_cast<uint8_t>(acc[3] * iarr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int _gaussianInit(SwGaussianBlur* data, float sigma, int quality)
|
||||
{
|
||||
const auto MAX_LEVEL = SwGaussianBlur::MAX_LEVEL;
|
||||
|
||||
if (tvg::zero(sigma)) return 0;
|
||||
|
||||
data->level = int(SwGaussianBlur::MAX_LEVEL * ((quality - 1) * 0.01f)) + 1;
|
||||
|
||||
//compute box kernel sizes
|
||||
auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1);
|
||||
if (wl % 2 == 0) --wl;
|
||||
auto wu = wl + 2;
|
||||
auto mi = (12 * sigma - MAX_LEVEL * wl * wl - 4 * MAX_LEVEL * wl - 3 * MAX_LEVEL) / (-4 * wl - 4);
|
||||
auto m = int(mi + 0.5f);
|
||||
auto extends = 0;
|
||||
|
||||
for (int i = 0; i < data->level; i++) {
|
||||
data->kernel[i] = ((i < m ? wl : wu) - 1) / 2;
|
||||
extends += data->kernel[i];
|
||||
}
|
||||
|
||||
return extends;
|
||||
}
|
||||
|
||||
|
||||
bool effectGaussianBlurRegion(RenderEffectGaussianBlur* params)
|
||||
{
|
||||
//bbox region expansion for feathering
|
||||
auto& region = params->extend;
|
||||
auto extra = static_cast<SwGaussianBlur*>(params->rd)->extends;
|
||||
|
||||
if (params->direction != 2) {
|
||||
region.x = -extra;
|
||||
region.w = extra * 2;
|
||||
}
|
||||
if (params->direction != 1) {
|
||||
region.y = -extra;
|
||||
region.h = extra * 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void effectGaussianBlurUpdate(RenderEffectGaussianBlur* params, const Matrix& transform)
|
||||
{
|
||||
if (!params->rd) params->rd = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur));
|
||||
auto rd = static_cast<SwGaussianBlur*>(params->rd);
|
||||
|
||||
//compute box kernel sizes
|
||||
auto scale = sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12);
|
||||
rd->extends = _gaussianInit(rd, std::pow(params->sigma * scale, 2), params->quality);
|
||||
|
||||
//invalid
|
||||
if (rd->extends == 0) {
|
||||
params->valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
params->valid = true;
|
||||
}
|
||||
|
||||
|
||||
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params)
|
||||
{
|
||||
auto& buffer = surface->compositor->image;
|
||||
auto data = static_cast<SwGaussianBlur*>(params->rd);
|
||||
auto& bbox = cmp->bbox;
|
||||
auto w = (bbox.max.x - bbox.min.x);
|
||||
auto h = (bbox.max.y - bbox.min.y);
|
||||
auto stride = cmp->image.stride;
|
||||
auto front = cmp->image.buf32;
|
||||
auto back = buffer.buf32;
|
||||
auto swapped = false;
|
||||
|
||||
TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level);
|
||||
|
||||
/* It is best to take advantage of the Gaussian blur’s separable property
|
||||
by dividing the process into two passes. horizontal and vertical.
|
||||
We can expect fewer calculations. */
|
||||
|
||||
//horizontal
|
||||
if (params->direction != 2) {
|
||||
for (int i = 0; i < data->level; ++i) {
|
||||
_gaussianFilter(reinterpret_cast<uint8_t*>(back), reinterpret_cast<uint8_t*>(front), stride, w, h, bbox, data->kernel[i], false);
|
||||
std::swap(front, back);
|
||||
swapped = !swapped;
|
||||
}
|
||||
}
|
||||
|
||||
//vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
|
||||
if (params->direction != 1) {
|
||||
rasterXYFlip(front, back, stride, w, h, bbox, false);
|
||||
std::swap(front, back);
|
||||
|
||||
for (int i = 0; i < data->level; ++i) {
|
||||
_gaussianFilter(reinterpret_cast<uint8_t*>(back), reinterpret_cast<uint8_t*>(front), stride, h, w, bbox, data->kernel[i], true);
|
||||
std::swap(front, back);
|
||||
swapped = !swapped;
|
||||
}
|
||||
|
||||
rasterXYFlip(front, back, stride, h, w, bbox, true);
|
||||
std::swap(front, back);
|
||||
}
|
||||
|
||||
if (swapped) std::swap(cmp->image.buf8, buffer.buf8);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* Drop Shadow Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
struct SwDropShadow : SwGaussianBlur
|
||||
{
|
||||
SwPoint offset;
|
||||
};
|
||||
|
||||
|
||||
//TODO: SIMD OPTIMIZATION?
|
||||
static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, int h, const SwBBox& bbox, int32_t dimension, uint32_t color, bool flipped)
|
||||
{
|
||||
if (flipped) {
|
||||
src += (bbox.min.x * stride + bbox.min.y);
|
||||
dst += (bbox.min.x * stride + bbox.min.y);
|
||||
} else {
|
||||
src += (bbox.min.y * stride + bbox.min.x);
|
||||
dst += (bbox.min.y * stride + bbox.min.x);
|
||||
}
|
||||
auto iarr = 1.0f / (dimension + dimension + 1);
|
||||
auto end = w - 1;
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int y = 0; y < h; ++y) {
|
||||
auto p = y * stride;
|
||||
auto i = p; //current index
|
||||
auto l = -(dimension + 1); //left index
|
||||
auto r = dimension; //right index
|
||||
int acc = 0; //sliding accumulator
|
||||
|
||||
//initial accumulation
|
||||
for (int x = l; x < r; ++x) {
|
||||
auto id = _gaussianEdgeExtend(end, x) + p;
|
||||
acc += A(src[id]);
|
||||
}
|
||||
//perform filtering
|
||||
for (int x = 0; x < w; ++x, ++r, ++l) {
|
||||
auto rid = _gaussianEdgeExtend(end, r) + p;
|
||||
auto lid = _gaussianEdgeExtend(end, l) + p;
|
||||
acc += A(src[rid]) - A(src[lid]);
|
||||
//ignored rounding for the performance. It should be originally: acc * iarr
|
||||
dst[i++] = ALPHA_BLEND(color, static_cast<uint8_t>(acc * iarr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _dropShadowShift(uint32_t* dst, uint32_t* src, int dstride, int sstride, SwBBox& region, SwPoint& offset, uint8_t opacity, bool direct)
|
||||
{
|
||||
src += (region.min.y * sstride + region.min.x);
|
||||
dst += (region.min.y * dstride + region.min.x);
|
||||
|
||||
auto w = region.max.x - region.min.x;
|
||||
auto h = region.max.y - region.min.y;
|
||||
auto translucent = (direct || opacity < 255);
|
||||
|
||||
//shift offset
|
||||
if (region.min.x + offset.x < 0) src -= offset.x;
|
||||
else dst += offset.x;
|
||||
|
||||
if (region.min.y + offset.y < 0) src -= (offset.y * sstride);
|
||||
else dst += (offset.y * dstride);
|
||||
|
||||
for (auto y = 0; y < h; ++y) {
|
||||
if (translucent) rasterTranslucentPixel32(dst, src, w, opacity);
|
||||
else rasterPixel32(dst, src, w, opacity);
|
||||
src += sstride;
|
||||
dst += dstride;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool effectDropShadowRegion(RenderEffectDropShadow* params)
|
||||
{
|
||||
//bbox region expansion for feathering
|
||||
auto& region = params->extend;
|
||||
auto& offset = static_cast<SwDropShadow*>(params->rd)->offset;
|
||||
auto extra = static_cast<SwDropShadow*>(params->rd)->extends;
|
||||
|
||||
region.x = -extra;
|
||||
region.w = extra * 2;
|
||||
region.y = -extra;
|
||||
region.h = extra * 2;
|
||||
|
||||
region.x = std::min(region.x + (int32_t)offset.x, region.x);
|
||||
region.y = std::min(region.y + (int32_t)offset.y, region.y);
|
||||
region.w += abs(offset.x);
|
||||
region.h += abs(offset.y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void effectDropShadowUpdate(RenderEffectDropShadow* params, const Matrix& transform)
|
||||
{
|
||||
if (!params->rd) params->rd = (SwDropShadow*)malloc(sizeof(SwDropShadow));
|
||||
auto rd = static_cast<SwDropShadow*>(params->rd);
|
||||
|
||||
//compute box kernel sizes
|
||||
auto scale = sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12);
|
||||
rd->extends = _gaussianInit(rd, std::pow(params->sigma * scale, 2), params->quality);
|
||||
|
||||
//invalid
|
||||
if (rd->extends == 0 || params->color[3] == 0) {
|
||||
params->valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//offset
|
||||
if (params->distance > 0.0f) {
|
||||
auto radian = tvg::deg2rad(90.0f - params->angle);
|
||||
rd->offset = {(SwCoord)(params->distance * cosf(radian)), (SwCoord)(-1.0f * params->distance * sinf(radian))};
|
||||
} else {
|
||||
rd->offset = {0, 0};
|
||||
}
|
||||
|
||||
params->valid = true;
|
||||
}
|
||||
|
||||
|
||||
//A quite same integration with effectGaussianBlur(). See it for detailed comments.
|
||||
//surface[0]: the original image, to overlay it into the filtered image.
|
||||
//surface[1]: temporary buffer for generating the filtered image.
|
||||
bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffectDropShadow* params, bool direct)
|
||||
{
|
||||
//FIXME: if the body is partially visible due to clipping, the shadow also becomes partially visible.
|
||||
|
||||
auto data = static_cast<SwDropShadow*>(params->rd);
|
||||
auto& bbox = cmp->bbox;
|
||||
auto w = (bbox.max.x - bbox.min.x);
|
||||
auto h = (bbox.max.y - bbox.min.y);
|
||||
|
||||
//outside the screen
|
||||
if (abs(data->offset.x) >= w || abs(data->offset.y) >= h) return true;
|
||||
|
||||
SwImage* buffer[] = {&surface[0]->compositor->image, &surface[1]->compositor->image};
|
||||
auto color = cmp->recoverSfc->join(params->color[0], params->color[1], params->color[2], 255);
|
||||
auto stride = cmp->image.stride;
|
||||
auto front = cmp->image.buf32;
|
||||
auto back = buffer[1]->buf32;
|
||||
|
||||
auto opacity = direct ? MULTIPLY(params->color[3], cmp->opacity) : params->color[3];
|
||||
|
||||
TVGLOG("SW_ENGINE", "DropShadow region(%ld, %ld, %ld, %ld) params(%f %f %f), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->angle, params->distance, params->sigma, data->level);
|
||||
|
||||
//saving the original image in order to overlay it into the filtered image.
|
||||
_dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[0], color, false);
|
||||
std::swap(front, buffer[0]->buf32);
|
||||
std::swap(front, back);
|
||||
|
||||
//horizontal
|
||||
for (int i = 1; i < data->level; ++i) {
|
||||
_dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[i], color, false);
|
||||
std::swap(front, back);
|
||||
}
|
||||
|
||||
//vertical
|
||||
rasterXYFlip(front, back, stride, w, h, bbox, false);
|
||||
std::swap(front, back);
|
||||
|
||||
for (int i = 0; i < data->level; ++i) {
|
||||
_dropShadowFilter(back, front, stride, h, w, bbox, data->kernel[i], color, true);
|
||||
std::swap(front, back);
|
||||
}
|
||||
|
||||
rasterXYFlip(front, back, stride, h, w, bbox, true);
|
||||
std::swap(cmp->image.buf32, back);
|
||||
|
||||
//draw to the main surface directly
|
||||
if (direct) {
|
||||
_dropShadowShift(cmp->recoverSfc->buf32, cmp->image.buf32, cmp->recoverSfc->stride, stride, bbox, data->offset, opacity, direct);
|
||||
std::swap(cmp->image.buf32, buffer[0]->buf32);
|
||||
return true;
|
||||
}
|
||||
|
||||
//draw to the intermediate surface
|
||||
rasterClear(surface[1], bbox.min.x, bbox.min.y, w, h);
|
||||
_dropShadowShift(buffer[1]->buf32, cmp->image.buf32, stride, stride, bbox, data->offset, opacity, direct);
|
||||
std::swap(cmp->image.buf32, buffer[1]->buf32);
|
||||
|
||||
//compositing shadow and body
|
||||
auto s = buffer[0]->buf32 + (bbox.min.y * buffer[0]->stride + bbox.min.x);
|
||||
auto d = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
|
||||
for (auto y = 0; y < h; ++y) {
|
||||
rasterTranslucentPixel32(d, s, w, 255);
|
||||
s += buffer[0]->stride;
|
||||
d += cmp->image.stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Fill Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
void effectFillUpdate(RenderEffectFill* params)
|
||||
{
|
||||
params->valid = true;
|
||||
}
|
||||
|
||||
|
||||
bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct)
|
||||
{
|
||||
auto opacity = direct ? MULTIPLY(params->color[3], cmp->opacity) : params->color[3];
|
||||
|
||||
auto& bbox = cmp->bbox;
|
||||
auto w = size_t(bbox.max.x - bbox.min.x);
|
||||
auto h = size_t(bbox.max.y - bbox.min.y);
|
||||
auto color = cmp->recoverSfc->join(params->color[0], params->color[1], params->color[2], 255);
|
||||
|
||||
TVGLOG("SW_ENGINE", "Fill region(%ld, %ld, %ld, %ld), param(%d %d %d %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->color[0], params->color[1], params->color[2], params->color[3]);
|
||||
|
||||
if (direct) {
|
||||
auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x);
|
||||
auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
auto dst = dbuffer;
|
||||
auto src = sbuffer;
|
||||
for (size_t x = 0; x < w; ++x, ++dst, ++src) {
|
||||
auto a = MULTIPLY(opacity, A(*src));
|
||||
auto tmp = ALPHA_BLEND(color, a);
|
||||
*dst = tmp + ALPHA_BLEND(*dst, 255 - a);
|
||||
}
|
||||
dbuffer += cmp->image.stride;
|
||||
sbuffer += cmp->recoverSfc->stride;
|
||||
}
|
||||
cmp->valid = true; //no need the subsequent composition
|
||||
} else {
|
||||
auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
auto dst = dbuffer;
|
||||
for (size_t x = 0; x < w; ++x, ++dst) {
|
||||
*dst = ALPHA_BLEND(color, MULTIPLY(opacity, A(*dst)));
|
||||
}
|
||||
dbuffer += cmp->image.stride;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Tint Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
void effectTintUpdate(RenderEffectTint* params)
|
||||
{
|
||||
params->valid = true;
|
||||
}
|
||||
|
||||
|
||||
bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct)
|
||||
{
|
||||
auto& bbox = cmp->bbox;
|
||||
auto w = size_t(bbox.max.x - bbox.min.x);
|
||||
auto h = size_t(bbox.max.y - bbox.min.y);
|
||||
auto black = cmp->recoverSfc->join(params->black[0], params->black[1], params->black[2], 255);
|
||||
auto white = cmp->recoverSfc->join(params->white[0], params->white[1], params->white[2], 255);
|
||||
auto opacity = cmp->opacity;
|
||||
auto luma = cmp->recoverSfc->alphas[2]; //luma function
|
||||
|
||||
TVGLOG("SW_ENGINE", "Tint region(%ld, %ld, %ld, %ld), param(%d %d %d, %d %d %d, %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->black[0], params->black[1], params->black[2], params->white[0], params->white[1], params->white[2], params->intensity);
|
||||
|
||||
/* Tint Formula: (1 - L) * Black + L * White, where the L is Luminance. */
|
||||
|
||||
if (direct) {
|
||||
auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x);
|
||||
auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
auto dst = dbuffer;
|
||||
auto src = sbuffer;
|
||||
for (size_t x = 0; x < w; ++x, ++dst, ++src) {
|
||||
auto tmp = rasterUnpremultiply(*src);
|
||||
auto val = INTERPOLATE(INTERPOLATE(black, white, luma((uint8_t*)&tmp)), tmp, params->intensity);
|
||||
*dst = INTERPOLATE(val, *dst, MULTIPLY(opacity, A(tmp)));
|
||||
}
|
||||
dbuffer += cmp->image.stride;
|
||||
sbuffer += cmp->recoverSfc->stride;
|
||||
}
|
||||
cmp->valid = true; //no need the subsequent composition
|
||||
} else {
|
||||
auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
auto dst = dbuffer;
|
||||
for (size_t x = 0; x < w; ++x, ++dst) {
|
||||
auto tmp = rasterUnpremultiply(*dst);
|
||||
auto val = INTERPOLATE(INTERPOLATE(black, white, luma((uint8_t*)&tmp)), tmp, params->intensity);
|
||||
*dst = ALPHA_BLEND(val, A(tmp));
|
||||
}
|
||||
dbuffer += cmp->image.stride;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Tritone Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static uint32_t _trintone(uint32_t s, uint32_t m, uint32_t h, int l)
|
||||
{
|
||||
/* Tritone Formula:
|
||||
if (L < 0.5) { (1 - 2L) * Shadow + 2L * Midtone }
|
||||
else { (1 - 2(L - 0.5)) * Midtone + (2(L - 0.5)) * Highlight }
|
||||
Where the L is Luminance. */
|
||||
|
||||
if (l < 128) {
|
||||
auto a = std::min(l * 2, 255);
|
||||
return ALPHA_BLEND(s, 255 - a) + ALPHA_BLEND(m, a);
|
||||
} else {
|
||||
auto a = 2 * std::max(0, l - 128);
|
||||
return ALPHA_BLEND(m, 255 - a) + ALPHA_BLEND(h, a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void effectTritoneUpdate(RenderEffectTritone* params)
|
||||
{
|
||||
params->valid = true;
|
||||
}
|
||||
|
||||
|
||||
bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct)
|
||||
{
|
||||
auto& bbox = cmp->bbox;
|
||||
auto w = size_t(bbox.max.x - bbox.min.x);
|
||||
auto h = size_t(bbox.max.y - bbox.min.y);
|
||||
auto shadow = cmp->recoverSfc->join(params->shadow[0], params->shadow[1], params->shadow[2], 255);
|
||||
auto midtone = cmp->recoverSfc->join(params->midtone[0], params->midtone[1], params->midtone[2], 255);
|
||||
auto highlight = cmp->recoverSfc->join(params->highlight[0], params->highlight[1], params->highlight[2], 255);
|
||||
auto opacity = cmp->opacity;
|
||||
auto luma = cmp->recoverSfc->alphas[2]; //luma function
|
||||
|
||||
TVGLOG("SW_ENGINE", "Tritone region(%ld, %ld, %ld, %ld), param(%d %d %d, %d %d %d, %d %d %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->shadow[0], params->shadow[1], params->shadow[2], params->midtone[0], params->midtone[1], params->midtone[2], params->highlight[0], params->highlight[1], params->highlight[2]);
|
||||
|
||||
if (direct) {
|
||||
auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x);
|
||||
auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
auto dst = dbuffer;
|
||||
auto src = sbuffer;
|
||||
for (size_t x = 0; x < w; ++x, ++dst, ++src) {
|
||||
auto tmp = rasterUnpremultiply(*src);
|
||||
*dst = INTERPOLATE(_trintone(shadow, midtone, highlight, luma((uint8_t*)&tmp)), *dst, MULTIPLY(opacity, A(tmp)));
|
||||
}
|
||||
dbuffer += cmp->image.stride;
|
||||
sbuffer += cmp->recoverSfc->stride;
|
||||
}
|
||||
cmp->valid = true; //no need the subsequent composition
|
||||
} else {
|
||||
auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
auto dst = dbuffer;
|
||||
for (size_t x = 0; x < w; ++x, ++dst) {
|
||||
auto tmp = rasterUnpremultiply(*dst);
|
||||
*dst = ALPHA_BLEND(_trintone(shadow, midtone, highlight, luma((uint8_t*)&tmp)), A(tmp));
|
||||
}
|
||||
dbuffer += cmp->image.stride;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
1846
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
vendored
Normal file
1846
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
230
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h
vendored
Normal file
230
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef THORVG_AVX_VECTOR_SUPPORT
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
#define N_32BITS_IN_128REG 4
|
||||
#define N_32BITS_IN_256REG 8
|
||||
|
||||
static inline __m128i ALPHA_BLEND(__m128i c, __m128i a)
|
||||
{
|
||||
//1. set the masks for the A/G and R/B channels
|
||||
auto AG = _mm_set1_epi32(0xff00ff00);
|
||||
auto RB = _mm_set1_epi32(0x00ff00ff);
|
||||
|
||||
//2. mask the alpha vector - originally quartet [a, a, a, a]
|
||||
auto aAG = _mm_and_si128(a, AG);
|
||||
auto aRB = _mm_and_si128(a, RB);
|
||||
|
||||
//3. calculate the alpha blending of the 2nd and 4th channel
|
||||
//- mask the color vector
|
||||
//- multiply it by the masked alpha vector
|
||||
//- add the correction to compensate bit shifting used instead of dividing by 255
|
||||
//- shift bits - corresponding to division by 256
|
||||
auto even = _mm_and_si128(c, RB);
|
||||
even = _mm_mullo_epi16(even, aRB);
|
||||
even =_mm_add_epi16(even, RB);
|
||||
even = _mm_srli_epi16(even, 8);
|
||||
|
||||
//4. calculate the alpha blending of the 1st and 3rd channel:
|
||||
//- mask the color vector
|
||||
//- multiply it by the corresponding masked alpha vector and store the high bits of the result
|
||||
//- add the correction to compensate division by 256 instead of by 255 (next step)
|
||||
//- remove the low 8 bits to mimic the division by 256
|
||||
auto odd = _mm_and_si128(c, AG);
|
||||
odd = _mm_mulhi_epu16(odd, aAG);
|
||||
odd = _mm_add_epi16(odd, RB);
|
||||
odd = _mm_and_si128(odd, AG);
|
||||
|
||||
//5. the final result
|
||||
return _mm_or_si128(odd, even);
|
||||
}
|
||||
|
||||
|
||||
static void avxRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
dst += offset;
|
||||
|
||||
__m256i vecVal = _mm256_set1_epi8(val);
|
||||
|
||||
int32_t i = 0;
|
||||
for (; i <= len - 32; i += 32) {
|
||||
_mm256_storeu_si256((__m256i*)(dst + i), vecVal);
|
||||
}
|
||||
|
||||
for (; i < len; ++i) {
|
||||
dst[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
//1. calculate how many iterations we need to cover the length
|
||||
uint32_t iterations = len / N_32BITS_IN_256REG;
|
||||
uint32_t avxFilled = iterations * N_32BITS_IN_256REG;
|
||||
|
||||
//2. set the beginning of the array
|
||||
dst += offset;
|
||||
|
||||
//3. fill the octets
|
||||
for (uint32_t i = 0; i < iterations; ++i, dst += N_32BITS_IN_256REG) {
|
||||
_mm256_storeu_si256((__m256i*)dst, _mm256_set1_epi32(val));
|
||||
}
|
||||
|
||||
//4. fill leftovers (in the first step we have to set the pointer to the place where the avx job is done)
|
||||
int32_t leftovers = len - avxFilled;
|
||||
while (leftovers--) *dst++ = val;
|
||||
}
|
||||
|
||||
|
||||
static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
|
||||
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
|
||||
|
||||
//32bits channels
|
||||
if (surface->channelSize == sizeof(uint32_t)) {
|
||||
auto color = surface->join(r, g, b, a);
|
||||
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
|
||||
|
||||
uint32_t ialpha = 255 - a;
|
||||
|
||||
auto avxColor = _mm_set1_epi32(color);
|
||||
auto avxIalpha = _mm_set1_epi8(ialpha);
|
||||
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
auto dst = &buffer[y * surface->stride];
|
||||
|
||||
//1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
|
||||
auto notAligned = ((uintptr_t)dst & 0xf) / 4;
|
||||
if (notAligned) {
|
||||
notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned);
|
||||
for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
|
||||
*dst = color + ALPHA_BLEND(*dst, ialpha);
|
||||
}
|
||||
}
|
||||
|
||||
//2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once
|
||||
uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG;
|
||||
uint32_t avxFilled = iterations * N_32BITS_IN_128REG;
|
||||
auto avxDst = (__m128i*)dst;
|
||||
for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
|
||||
*avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha));
|
||||
}
|
||||
|
||||
//3. fill the remaining pixels
|
||||
int32_t leftovers = w - notAligned - avxFilled;
|
||||
dst += avxFilled;
|
||||
while (leftovers--) {
|
||||
*dst = color + ALPHA_BLEND(*dst, ialpha);
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
//8bit grayscale
|
||||
} else if (surface->channelSize == sizeof(uint8_t)) {
|
||||
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
|
||||
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
|
||||
auto ialpha = ~a;
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
auto dst = &buffer[y * surface->stride];
|
||||
for (uint32_t x = 0; x < w; ++x, ++dst) {
|
||||
*dst = a + MULTIPLY(*dst, ialpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
auto span = rle->spans;
|
||||
|
||||
//32bit channels
|
||||
if (surface->channelSize == sizeof(uint32_t)) {
|
||||
auto color = surface->join(r, g, b, a);
|
||||
uint32_t src;
|
||||
|
||||
for (uint32_t i = 0; i < rle->size; ++i) {
|
||||
auto dst = &surface->buf32[span->y * surface->stride + span->x];
|
||||
|
||||
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
|
||||
else src = color;
|
||||
|
||||
auto ialpha = IA(src);
|
||||
|
||||
//1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
|
||||
auto notAligned = ((uintptr_t)dst & 0xf) / 4;
|
||||
if (notAligned) {
|
||||
notAligned = (N_32BITS_IN_128REG - notAligned > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
|
||||
for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
|
||||
*dst = src + ALPHA_BLEND(*dst, ialpha);
|
||||
}
|
||||
}
|
||||
|
||||
//2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
|
||||
//In order to avoid unnecessary avx variables declarations a check is made whether there are any iterations at all
|
||||
uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
|
||||
uint32_t avxFilled = 0;
|
||||
if (iterations > 0) {
|
||||
auto avxSrc = _mm_set1_epi32(src);
|
||||
auto avxIalpha = _mm_set1_epi8(ialpha);
|
||||
|
||||
avxFilled = iterations * N_32BITS_IN_128REG;
|
||||
auto avxDst = (__m128i*)dst;
|
||||
for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
|
||||
*avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
|
||||
}
|
||||
}
|
||||
|
||||
//3. fill the remaining pixels
|
||||
int32_t leftovers = span->len - notAligned - avxFilled;
|
||||
dst += avxFilled;
|
||||
while (leftovers--) {
|
||||
*dst = src + ALPHA_BLEND(*dst, ialpha);
|
||||
dst++;
|
||||
}
|
||||
|
||||
++span;
|
||||
}
|
||||
//8bit grayscale
|
||||
} else if (surface->channelSize == sizeof(uint8_t)) {
|
||||
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
|
||||
uint8_t src;
|
||||
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
|
||||
auto dst = &surface->buf8[span->y * surface->stride + span->x];
|
||||
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
|
||||
else src = a;
|
||||
auto ialpha = ~a;
|
||||
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
|
||||
*dst = src + MULTIPLY(*dst, ialpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
195
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterC.h
vendored
Normal file
195
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterC.h
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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 cRasterTranslucentPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity)
|
||||
{
|
||||
//TODO: 64bits faster?
|
||||
if (opacity == 255) {
|
||||
for (uint32_t x = 0; x < len; ++x, ++dst, ++src) {
|
||||
*dst = *src + ALPHA_BLEND(*dst, IA(*src));
|
||||
}
|
||||
} else {
|
||||
for (uint32_t x = 0; x < len; ++x, ++dst, ++src) {
|
||||
auto tmp = ALPHA_BLEND(*src, opacity);
|
||||
*dst = tmp + ALPHA_BLEND(*dst, IA(tmp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename PIXEL_T>
|
||||
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity)
|
||||
{
|
||||
//TODO: 64bits faster?
|
||||
if (opacity == 255) {
|
||||
for (uint32_t x = 0; x < len; ++x, ++dst, ++src) {
|
||||
*dst = *src;
|
||||
}
|
||||
} else {
|
||||
cRasterTranslucentPixels(dst, src, len, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 SwRle* 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(RenderSurface* 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(RenderSurface* surface)
|
||||
{
|
||||
//exactly same with ABGRtoARGB
|
||||
return cRasterABGRtoARGB(surface);
|
||||
}
|
||||
200
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h
vendored
Normal file
200
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef THORVG_NEON_VECTOR_SUPPORT
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
//TODO : need to support windows ARM
|
||||
|
||||
#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64)
|
||||
#define TVG_AARCH64 1
|
||||
#else
|
||||
#define TVG_AARCH64 0
|
||||
#endif
|
||||
|
||||
|
||||
static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a)
|
||||
{
|
||||
uint16x8_t t = vmull_u8(c, a);
|
||||
return vshrn_n_u16(t, 8);
|
||||
}
|
||||
|
||||
|
||||
static void neonRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
dst += offset;
|
||||
|
||||
int32_t i = 0;
|
||||
const uint8x16_t valVec = vdupq_n_u8(val);
|
||||
#if TVG_AARCH64
|
||||
uint8x16x4_t valQuad = {valVec, valVec, valVec, valVec};
|
||||
for (; i <= len - 16 * 4; i += 16 * 4) {
|
||||
vst1q_u8_x4(dst + i, valQuad);
|
||||
}
|
||||
#else
|
||||
for (; i <= len - 16; i += 16) {
|
||||
vst1q_u8(dst + i, valVec);
|
||||
}
|
||||
#endif
|
||||
for (; i < len; i++) {
|
||||
dst[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
dst += offset;
|
||||
|
||||
uint32x4_t vectorVal = vdupq_n_u32(val);
|
||||
|
||||
#if TVG_AARCH64
|
||||
uint32_t iterations = len / 16;
|
||||
uint32_t neonFilled = iterations * 16;
|
||||
uint32x4x4_t valQuad = {vectorVal, vectorVal, vectorVal, vectorVal};
|
||||
for (uint32_t i = 0; i < iterations; ++i) {
|
||||
vst4q_u32(dst, valQuad);
|
||||
dst += 16;
|
||||
}
|
||||
#else
|
||||
uint32_t iterations = len / 4;
|
||||
uint32_t neonFilled = iterations * 4;
|
||||
for (uint32_t i = 0; i < iterations; ++i) {
|
||||
vst1q_u32(dst, vectorVal);
|
||||
dst += 4;
|
||||
}
|
||||
#endif
|
||||
int32_t leftovers = len - neonFilled;
|
||||
while (leftovers--) *dst++ = val;
|
||||
}
|
||||
|
||||
|
||||
static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
auto span = rle->spans;
|
||||
|
||||
//32bit channels
|
||||
if (surface->channelSize == sizeof(uint32_t)) {
|
||||
auto color = surface->join(r, g, b, a);
|
||||
uint32_t src;
|
||||
uint8x8_t *vDst = nullptr;
|
||||
uint16_t align;
|
||||
|
||||
for (uint32_t i = 0; i < rle->size; ++i) {
|
||||
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
|
||||
else src = color;
|
||||
|
||||
auto dst = &surface->buf32[span->y * surface->stride + span->x];
|
||||
auto ialpha = IA(src);
|
||||
|
||||
if ((((uintptr_t) dst) & 0x7) != 0) {
|
||||
//fill not aligned byte
|
||||
*dst = src + ALPHA_BLEND(*dst, ialpha);
|
||||
vDst = (uint8x8_t*)(dst + 1);
|
||||
align = 1;
|
||||
} else {
|
||||
vDst = (uint8x8_t*) dst;
|
||||
align = 0;
|
||||
}
|
||||
|
||||
uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src);
|
||||
uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha);
|
||||
|
||||
for (uint32_t x = 0; x < (span->len - align) / 2; ++x)
|
||||
vDst[x] = vadd_u8(vSrc, ALPHA_BLEND(vDst[x], vIalpha));
|
||||
|
||||
auto leftovers = (span->len - align) % 2;
|
||||
if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha);
|
||||
|
||||
++span;
|
||||
}
|
||||
//8bit grayscale
|
||||
} else if (surface->channelSize == sizeof(uint8_t)) {
|
||||
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
|
||||
uint8_t src;
|
||||
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
|
||||
auto dst = &surface->buf8[span->y * surface->stride + span->x];
|
||||
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
|
||||
else src = a;
|
||||
auto ialpha = ~a;
|
||||
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
|
||||
*dst = src + MULTIPLY(*dst, ialpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
|
||||
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
|
||||
|
||||
//32bits channels
|
||||
if (surface->channelSize == sizeof(uint32_t)) {
|
||||
auto color = surface->join(r, g, b, a);
|
||||
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
|
||||
auto ialpha = 255 - a;
|
||||
|
||||
auto vColor = vdup_n_u32(color);
|
||||
auto vIalpha = vdup_n_u8((uint8_t) ialpha);
|
||||
|
||||
uint8x8_t* vDst = nullptr;
|
||||
uint32_t align;
|
||||
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
auto dst = &buffer[y * surface->stride];
|
||||
|
||||
if ((((uintptr_t) dst) & 0x7) != 0) {
|
||||
//fill not aligned byte
|
||||
*dst = color + ALPHA_BLEND(*dst, ialpha);
|
||||
vDst = (uint8x8_t*) (dst + 1);
|
||||
align = 1;
|
||||
} else {
|
||||
vDst = (uint8x8_t*) dst;
|
||||
align = 0;
|
||||
}
|
||||
|
||||
for (uint32_t x = 0; x < (w - align) / 2; ++x)
|
||||
vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha));
|
||||
|
||||
auto leftovers = (w - align) % 2;
|
||||
if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
|
||||
}
|
||||
//8bit grayscale
|
||||
} else if (surface->channelSize == sizeof(uint8_t)) {
|
||||
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
|
||||
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
|
||||
auto ialpha = ~a;
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
auto dst = &buffer[y * surface->stride];
|
||||
for (uint32_t x = 0; x < w; ++x, ++dst) {
|
||||
*dst = a + MULTIPLY(*dst, ialpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
962
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
vendored
Normal file
962
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
vendored
Normal file
@@ -0,0 +1,962 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
Point pt;
|
||||
Point uv;
|
||||
};
|
||||
|
||||
struct Polygon
|
||||
{
|
||||
Vertex vertex[3];
|
||||
};
|
||||
|
||||
struct AALine
|
||||
{
|
||||
int32_t x[2];
|
||||
int32_t coverage[2];
|
||||
int32_t length[2];
|
||||
};
|
||||
|
||||
struct AASpans
|
||||
{
|
||||
AALine *lines;
|
||||
int32_t yStart;
|
||||
int32_t yEnd;
|
||||
};
|
||||
|
||||
//Careful! Shared resource, No support threading
|
||||
static float dudx, dvdx;
|
||||
static float dxdya, dxdyb, dudya, dvdya;
|
||||
static float xa, xb, ua, va;
|
||||
|
||||
|
||||
//Y Range exception handling
|
||||
static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, int& yEnd)
|
||||
{
|
||||
int32_t regionTop, regionBottom;
|
||||
|
||||
if (region) {
|
||||
regionTop = region->min.y;
|
||||
regionBottom = region->max.y;
|
||||
} else {
|
||||
regionTop = image->rle->spans->y;
|
||||
regionBottom = image->rle->spans[image->rle->size - 1].y;
|
||||
}
|
||||
|
||||
if (yStart >= regionBottom) return false;
|
||||
|
||||
if (yStart < regionTop) yStart = regionTop;
|
||||
if (yEnd > regionBottom) yEnd = regionBottom;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0)
|
||||
{
|
||||
TVGERR("SW_ENGINE", "TODO: _rasterMaskedPolygonImageSegment()");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity)
|
||||
{
|
||||
float _dudx = dudx, _dvdx = dvdx;
|
||||
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
|
||||
float _xa = xa, _xb = xb, _ua = ua, _va = va;
|
||||
auto sbuf = image->buf32;
|
||||
auto dbuf = surface->buf32;
|
||||
int32_t sw = static_cast<int32_t>(image->w);
|
||||
int32_t sh = static_cast<int32_t>(image->h);
|
||||
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
|
||||
int32_t vv = 0, uu = 0;
|
||||
int32_t minx = INT32_MAX, maxx = 0;
|
||||
float dx, u, v, iptr;
|
||||
uint32_t* buf;
|
||||
SwSpan* span = nullptr; //used only when rle based.
|
||||
|
||||
if (!_arrange(image, region, yStart, yEnd)) return;
|
||||
|
||||
//Loop through all lines in the segment
|
||||
uint32_t spanIdx = 0;
|
||||
|
||||
if (region) {
|
||||
minx = region->min.x;
|
||||
maxx = region->max.x;
|
||||
} else {
|
||||
span = image->rle->spans;
|
||||
while (span->y < yStart) {
|
||||
++span;
|
||||
++spanIdx;
|
||||
}
|
||||
}
|
||||
|
||||
y = yStart;
|
||||
|
||||
while (y < yEnd) {
|
||||
x1 = (int32_t)_xa;
|
||||
x2 = (int32_t)_xb;
|
||||
|
||||
if (!region) {
|
||||
minx = INT32_MAX;
|
||||
maxx = 0;
|
||||
//one single row, could be consisted of multiple spans.
|
||||
while (span->y == y && spanIdx < image->rle->size) {
|
||||
if (minx > span->x) minx = span->x;
|
||||
if (maxx < span->x + span->len) maxx = span->x + span->len;
|
||||
++span;
|
||||
++spanIdx;
|
||||
}
|
||||
}
|
||||
if (x1 < minx) x1 = minx;
|
||||
if (x2 > maxx) x2 = maxx;
|
||||
|
||||
//Anti-Aliasing frames
|
||||
ay = y - aaSpans->yStart;
|
||||
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
|
||||
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
|
||||
|
||||
//Range allowed
|
||||
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
|
||||
|
||||
//Perform subtexel pre-stepping on UV
|
||||
dx = 1 - (_xa - x1);
|
||||
u = _ua + dx * _dudx;
|
||||
v = _va + dx * _dvdx;
|
||||
|
||||
buf = dbuf + ((y * surface->stride) + x1);
|
||||
|
||||
x = x1;
|
||||
|
||||
if (opacity == 255) {
|
||||
//Draw horizontal line
|
||||
while (x++ < x2) {
|
||||
uu = (int) u;
|
||||
vv = (int) v;
|
||||
|
||||
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
|
||||
|
||||
ar = (int)(255 * (1 - modff(u, &iptr)));
|
||||
ab = (int)(255 * (1 - modff(v, &iptr)));
|
||||
iru = uu + 1;
|
||||
irv = vv + 1;
|
||||
|
||||
px = *(sbuf + (vv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* right pixel */
|
||||
int px2 = *(sbuf + (vv * image->stride) + iru);
|
||||
px = INTERPOLATE(px, px2, ar);
|
||||
}
|
||||
/* vertical interpolate */
|
||||
if (irv < sh) {
|
||||
/* bottom pixel */
|
||||
int px2 = *(sbuf + (irv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* bottom right pixel */
|
||||
int px3 = *(sbuf + (irv * image->stride) + iru);
|
||||
px2 = INTERPOLATE(px2, px3, ar);
|
||||
}
|
||||
px = INTERPOLATE(px, px2, ab);
|
||||
}
|
||||
*buf = surface->blender(px, *buf, IA(px));
|
||||
++buf;
|
||||
|
||||
//Step UV horizontally
|
||||
u += _dudx;
|
||||
v += _dvdx;
|
||||
}
|
||||
} else {
|
||||
//Draw horizontal line
|
||||
while (x++ < x2) {
|
||||
uu = (int) u;
|
||||
vv = (int) v;
|
||||
|
||||
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
|
||||
|
||||
ar = (int)(255 * (1 - modff(u, &iptr)));
|
||||
ab = (int)(255 * (1 - modff(v, &iptr)));
|
||||
iru = uu + 1;
|
||||
irv = vv + 1;
|
||||
|
||||
px = *(sbuf + (vv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* right pixel */
|
||||
int px2 = *(sbuf + (vv * image->stride) + iru);
|
||||
px = INTERPOLATE(px, px2, ar);
|
||||
}
|
||||
/* vertical interpolate */
|
||||
if (irv < sh) {
|
||||
/* bottom pixel */
|
||||
int px2 = *(sbuf + (irv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* bottom right pixel */
|
||||
int px3 = *(sbuf + (irv * image->stride) + iru);
|
||||
px2 = INTERPOLATE(px2, px3, ar);
|
||||
}
|
||||
px = INTERPOLATE(px, px2, ab);
|
||||
}
|
||||
auto src = ALPHA_BLEND(px, opacity);
|
||||
*buf = surface->blender(src, *buf, IA(src));
|
||||
++buf;
|
||||
|
||||
//Step UV horizontally
|
||||
u += _dudx;
|
||||
v += _dvdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Step along both edges
|
||||
_xa += _dxdya;
|
||||
_xb += _dxdyb;
|
||||
_ua += _dudya;
|
||||
_va += _dvdya;
|
||||
|
||||
if (!region && spanIdx >= image->rle->size) break;
|
||||
|
||||
++y;
|
||||
}
|
||||
xa = _xa;
|
||||
xb = _xb;
|
||||
ua = _ua;
|
||||
va = _va;
|
||||
}
|
||||
|
||||
|
||||
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, bool matting)
|
||||
{
|
||||
float _dudx = dudx, _dvdx = dvdx;
|
||||
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
|
||||
float _xa = xa, _xb = xb, _ua = ua, _va = va;
|
||||
auto sbuf = image->buf32;
|
||||
auto dbuf = surface->buf32;
|
||||
int32_t sw = static_cast<int32_t>(image->w);
|
||||
int32_t sh = static_cast<int32_t>(image->h);
|
||||
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
|
||||
int32_t vv = 0, uu = 0;
|
||||
int32_t minx = INT32_MAX, maxx = 0;
|
||||
float dx, u, v, iptr;
|
||||
uint32_t* buf;
|
||||
SwSpan* span = nullptr; //used only when rle based.
|
||||
|
||||
//for matting(composition)
|
||||
auto csize = matting ? surface->compositor->image.channelSize: 0;
|
||||
auto alpha = matting ? surface->alpha(surface->compositor->method) : nullptr;
|
||||
uint8_t* cmp = nullptr;
|
||||
|
||||
if (!_arrange(image, region, yStart, yEnd)) return;
|
||||
|
||||
//Loop through all lines in the segment
|
||||
uint32_t spanIdx = 0;
|
||||
|
||||
if (region) {
|
||||
minx = region->min.x;
|
||||
maxx = region->max.x;
|
||||
} else {
|
||||
span = image->rle->spans;
|
||||
while (span->y < yStart) {
|
||||
++span;
|
||||
++spanIdx;
|
||||
}
|
||||
}
|
||||
|
||||
y = yStart;
|
||||
|
||||
while (y < yEnd) {
|
||||
x1 = (int32_t)_xa;
|
||||
x2 = (int32_t)_xb;
|
||||
|
||||
if (!region) {
|
||||
minx = INT32_MAX;
|
||||
maxx = 0;
|
||||
//one single row, could be consisted of multiple spans.
|
||||
while (span->y == y && spanIdx < image->rle->size) {
|
||||
if (minx > span->x) minx = span->x;
|
||||
if (maxx < span->x + span->len) maxx = span->x + span->len;
|
||||
++span;
|
||||
++spanIdx;
|
||||
}
|
||||
}
|
||||
if (x1 < minx) x1 = minx;
|
||||
if (x2 > maxx) x2 = maxx;
|
||||
|
||||
//Anti-Aliasing frames
|
||||
ay = y - aaSpans->yStart;
|
||||
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
|
||||
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
|
||||
|
||||
//Range allowed
|
||||
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
|
||||
|
||||
//Perform subtexel pre-stepping on UV
|
||||
dx = 1 - (_xa - x1);
|
||||
u = _ua + dx * _dudx;
|
||||
v = _va + dx * _dvdx;
|
||||
|
||||
buf = dbuf + ((y * surface->stride) + x1);
|
||||
|
||||
x = x1;
|
||||
|
||||
if (matting) cmp = &surface->compositor->image.buf8[(y * surface->compositor->image.stride + x1) * csize];
|
||||
|
||||
if (opacity == 255) {
|
||||
//Draw horizontal line
|
||||
while (x++ < x2) {
|
||||
uu = (int) u;
|
||||
vv = (int) v;
|
||||
|
||||
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
|
||||
|
||||
ar = (int)(255.0f * (1.0f - modff(u, &iptr)));
|
||||
ab = (int)(255.0f * (1.0f - modff(v, &iptr)));
|
||||
iru = uu + 1;
|
||||
irv = vv + 1;
|
||||
|
||||
px = *(sbuf + (vv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* right pixel */
|
||||
int px2 = *(sbuf + (vv * image->stride) + iru);
|
||||
px = INTERPOLATE(px, px2, ar);
|
||||
}
|
||||
/* vertical interpolate */
|
||||
if (irv < sh) {
|
||||
/* bottom pixel */
|
||||
int px2 = *(sbuf + (irv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* bottom right pixel */
|
||||
int px3 = *(sbuf + (irv * image->stride) + iru);
|
||||
px2 = INTERPOLATE(px2, px3, ar);
|
||||
}
|
||||
px = INTERPOLATE(px, px2, ab);
|
||||
}
|
||||
uint32_t src;
|
||||
if (matting) {
|
||||
src = ALPHA_BLEND(px, alpha(cmp));
|
||||
cmp += csize;
|
||||
} else {
|
||||
src = px;
|
||||
}
|
||||
*buf = src + ALPHA_BLEND(*buf, IA(src));
|
||||
++buf;
|
||||
|
||||
//Step UV horizontally
|
||||
u += _dudx;
|
||||
v += _dvdx;
|
||||
}
|
||||
} else {
|
||||
//Draw horizontal line
|
||||
while (x++ < x2) {
|
||||
uu = (int) u;
|
||||
vv = (int) v;
|
||||
|
||||
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
|
||||
|
||||
ar = (int)(255.0f * (1.0f - modff(u, &iptr)));
|
||||
ab = (int)(255.0f * (1.0f - modff(v, &iptr)));
|
||||
iru = uu + 1;
|
||||
irv = vv + 1;
|
||||
|
||||
px = *(sbuf + (vv * sw) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* right pixel */
|
||||
int px2 = *(sbuf + (vv * image->stride) + iru);
|
||||
px = INTERPOLATE(px, px2, ar);
|
||||
}
|
||||
/* vertical interpolate */
|
||||
if (irv < sh) {
|
||||
/* bottom pixel */
|
||||
int px2 = *(sbuf + (irv * image->stride) + uu);
|
||||
|
||||
/* horizontal interpolate */
|
||||
if (iru < sw) {
|
||||
/* bottom right pixel */
|
||||
int px3 = *(sbuf + (irv * image->stride) + iru);
|
||||
px2 = INTERPOLATE(px2, px3, ar);
|
||||
}
|
||||
px = INTERPOLATE(px, px2, ab);
|
||||
}
|
||||
uint32_t src;
|
||||
if (matting) {
|
||||
src = ALPHA_BLEND(px, MULTIPLY(opacity, alpha(cmp)));
|
||||
cmp += csize;
|
||||
} else {
|
||||
src = ALPHA_BLEND(px, opacity);
|
||||
}
|
||||
*buf = src + ALPHA_BLEND(*buf, IA(src));
|
||||
++buf;
|
||||
|
||||
//Step UV horizontally
|
||||
u += _dudx;
|
||||
v += _dvdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Step along both edges
|
||||
_xa += _dxdya;
|
||||
_xb += _dxdyb;
|
||||
_ua += _dudya;
|
||||
_va += _dvdya;
|
||||
|
||||
if (!region && spanIdx >= image->rle->size) break;
|
||||
|
||||
++y;
|
||||
}
|
||||
xa = _xa;
|
||||
xb = _xb;
|
||||
ua = _ua;
|
||||
va = _va;
|
||||
}
|
||||
|
||||
|
||||
/* This mapping algorithm is based on Mikael Kalms's. */
|
||||
static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const SwBBox* region, Polygon& polygon, AASpans* aaSpans, uint8_t opacity)
|
||||
{
|
||||
float x[3] = {polygon.vertex[0].pt.x, polygon.vertex[1].pt.x, polygon.vertex[2].pt.x};
|
||||
float y[3] = {polygon.vertex[0].pt.y, polygon.vertex[1].pt.y, polygon.vertex[2].pt.y};
|
||||
float u[3] = {polygon.vertex[0].uv.x, polygon.vertex[1].uv.x, polygon.vertex[2].uv.x};
|
||||
float v[3] = {polygon.vertex[0].uv.y, polygon.vertex[1].uv.y, polygon.vertex[2].uv.y};
|
||||
|
||||
float off_y;
|
||||
float dxdy[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
auto upper = false;
|
||||
|
||||
//Sort the vertices in ascending Y order
|
||||
if (y[0] > y[1]) {
|
||||
std::swap(x[0], x[1]);
|
||||
std::swap(y[0], y[1]);
|
||||
std::swap(u[0], u[1]);
|
||||
std::swap(v[0], v[1]);
|
||||
}
|
||||
if (y[0] > y[2]) {
|
||||
std::swap(x[0], x[2]);
|
||||
std::swap(y[0], y[2]);
|
||||
std::swap(u[0], u[2]);
|
||||
std::swap(v[0], v[2]);
|
||||
}
|
||||
if (y[1] > y[2]) {
|
||||
std::swap(x[1], x[2]);
|
||||
std::swap(y[1], y[2]);
|
||||
std::swap(u[1], u[2]);
|
||||
std::swap(v[1], v[2]);
|
||||
}
|
||||
|
||||
//Y indexes
|
||||
int yi[3] = {(int)y[0], (int)y[1], (int)y[2]};
|
||||
|
||||
//Skip drawing if it's too thin to cover any pixels at all.
|
||||
if ((yi[0] == yi[1] && yi[0] == yi[2]) || ((int) x[0] == (int) x[1] && (int) x[0] == (int) x[2])) return;
|
||||
|
||||
//Calculate horizontal and vertical increments for UV axes (these calcs are certainly not optimal, although they're stable (handles any dy being 0)
|
||||
auto denom = ((x[2] - x[0]) * (y[1] - y[0]) - (x[1] - x[0]) * (y[2] - y[0]));
|
||||
|
||||
//Skip poly if it's an infinitely thin line
|
||||
if (tvg::zero(denom)) return;
|
||||
|
||||
denom = 1 / denom; //Reciprocal for speeding up
|
||||
dudx = ((u[2] - u[0]) * (y[1] - y[0]) - (u[1] - u[0]) * (y[2] - y[0])) * denom;
|
||||
dvdx = ((v[2] - v[0]) * (y[1] - y[0]) - (v[1] - v[0]) * (y[2] - y[0])) * denom;
|
||||
auto dudy = ((u[1] - u[0]) * (x[2] - x[0]) - (u[2] - u[0]) * (x[1] - x[0])) * denom;
|
||||
auto dvdy = ((v[1] - v[0]) * (x[2] - x[0]) - (v[2] - v[0]) * (x[1] - x[0])) * denom;
|
||||
|
||||
//Calculate X-slopes along the edges
|
||||
if (y[1] > y[0]) dxdy[0] = (x[1] - x[0]) / (y[1] - y[0]);
|
||||
if (y[2] > y[0]) dxdy[1] = (x[2] - x[0]) / (y[2] - y[0]);
|
||||
if (y[2] > y[1]) dxdy[2] = (x[2] - x[1]) / (y[2] - y[1]);
|
||||
|
||||
//Determine which side of the polygon the longer edge is on
|
||||
auto side = (dxdy[1] > dxdy[0]) ? true : false;
|
||||
|
||||
if (tvg::equal(y[0], y[1])) side = x[0] > x[1];
|
||||
if (tvg::equal(y[1], y[2])) side = x[2] > x[1];
|
||||
|
||||
auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image?
|
||||
auto compositing = _compositing(surface); //Composition required
|
||||
auto blending = _blending(surface); //Blending required
|
||||
|
||||
//Longer edge is on the left side
|
||||
if (!side) {
|
||||
//Calculate slopes along left edge
|
||||
dxdya = dxdy[1];
|
||||
dudya = dxdya * dudx + dudy;
|
||||
dvdya = dxdya * dvdx + dvdy;
|
||||
|
||||
//Perform subpixel pre-stepping along left edge
|
||||
auto dy = 1.0f - (y[0] - yi[0]);
|
||||
xa = x[0] + dy * dxdya;
|
||||
ua = u[0] + dy * dudya;
|
||||
va = v[0] + dy * dvdya;
|
||||
|
||||
//Draw upper segment if possibly visible
|
||||
if (yi[0] < yi[1]) {
|
||||
off_y = y[0] < regionTop ? (regionTop - y[0]) : 0;
|
||||
xa += (off_y * dxdya);
|
||||
ua += (off_y * dudya);
|
||||
va += (off_y * dvdya);
|
||||
|
||||
// Set right edge X-slope and perform subpixel pre-stepping
|
||||
dxdyb = dxdy[0];
|
||||
xb = x[0] + dy * dxdyb + (off_y * dxdyb);
|
||||
|
||||
if (compositing) {
|
||||
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
|
||||
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 1);
|
||||
} else if (blending) {
|
||||
_rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity);
|
||||
} else {
|
||||
_rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
|
||||
}
|
||||
upper = true;
|
||||
}
|
||||
//Draw lower segment if possibly visible
|
||||
if (yi[1] < yi[2]) {
|
||||
off_y = y[1] < regionTop ? (regionTop - y[1]) : 0;
|
||||
if (!upper) {
|
||||
xa += (off_y * dxdya);
|
||||
ua += (off_y * dudya);
|
||||
va += (off_y * dvdya);
|
||||
}
|
||||
// Set right edge X-slope and perform subpixel pre-stepping
|
||||
dxdyb = dxdy[2];
|
||||
xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb);
|
||||
if (compositing) {
|
||||
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
|
||||
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 2);
|
||||
} else if (blending) {
|
||||
_rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity);
|
||||
} else {
|
||||
_rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
|
||||
}
|
||||
}
|
||||
//Longer edge is on the right side
|
||||
} else {
|
||||
//Set right edge X-slope and perform subpixel pre-stepping
|
||||
dxdyb = dxdy[1];
|
||||
auto dy = 1.0f - (y[0] - yi[0]);
|
||||
xb = x[0] + dy * dxdyb;
|
||||
|
||||
//Draw upper segment if possibly visible
|
||||
if (yi[0] < yi[1]) {
|
||||
off_y = y[0] < regionTop ? (regionTop - y[0]) : 0;
|
||||
xb += (off_y *dxdyb);
|
||||
|
||||
// Set slopes along left edge and perform subpixel pre-stepping
|
||||
dxdya = dxdy[0];
|
||||
dudya = dxdya * dudx + dudy;
|
||||
dvdya = dxdya * dvdx + dvdy;
|
||||
|
||||
xa = x[0] + dy * dxdya + (off_y * dxdya);
|
||||
ua = u[0] + dy * dudya + (off_y * dudya);
|
||||
va = v[0] + dy * dvdya + (off_y * dvdya);
|
||||
|
||||
if (compositing) {
|
||||
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
|
||||
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 3);
|
||||
} else if (blending) {
|
||||
_rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity);
|
||||
} else {
|
||||
_rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
|
||||
}
|
||||
upper = true;
|
||||
}
|
||||
//Draw lower segment if possibly visible
|
||||
if (yi[1] < yi[2]) {
|
||||
off_y = y[1] < regionTop ? (regionTop - y[1]) : 0;
|
||||
if (!upper) xb += (off_y *dxdyb);
|
||||
|
||||
// Set slopes along left edge and perform subpixel pre-stepping
|
||||
dxdya = dxdy[2];
|
||||
dudya = dxdya * dudx + dudy;
|
||||
dvdya = dxdya * dvdx + dvdy;
|
||||
dy = 1 - (y[1] - yi[1]);
|
||||
xa = x[1] + dy * dxdya + (off_y * dxdya);
|
||||
ua = u[1] + dy * dudya + (off_y * dudya);
|
||||
va = v[1] + dy * dvdya + (off_y * dvdya);
|
||||
|
||||
if (compositing) {
|
||||
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
|
||||
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 4);
|
||||
} else if (blending) {
|
||||
_rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity);
|
||||
} else {
|
||||
_rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static AASpans* _AASpans(float ymin, float ymax, const SwImage* image, const SwBBox* region)
|
||||
{
|
||||
auto yStart = static_cast<int>(ymin);
|
||||
auto yEnd = static_cast<int>(ymax);
|
||||
|
||||
if (!_arrange(image, region, yStart, yEnd)) return nullptr;
|
||||
|
||||
auto aaSpans = static_cast<AASpans*>(malloc(sizeof(AASpans)));
|
||||
aaSpans->yStart = yStart;
|
||||
aaSpans->yEnd = yEnd;
|
||||
|
||||
//Initialize X range
|
||||
auto height = yEnd - yStart;
|
||||
|
||||
aaSpans->lines = static_cast<AALine*>(malloc(height * sizeof(AALine)));
|
||||
|
||||
for (int32_t i = 0; i < height; i++) {
|
||||
aaSpans->lines[i].x[0] = INT32_MAX;
|
||||
aaSpans->lines[i].x[1] = 0;
|
||||
aaSpans->lines[i].length[0] = 0;
|
||||
aaSpans->lines[i].length[1] = 0;
|
||||
}
|
||||
return aaSpans;
|
||||
}
|
||||
|
||||
|
||||
static void _calcIrregularCoverage(AALine* lines, int32_t eidx, int32_t y, int32_t diagonal, int32_t edgeDist, bool reverse)
|
||||
{
|
||||
if (eidx == 1) reverse = !reverse;
|
||||
int32_t coverage = (255 / (diagonal + 2));
|
||||
int32_t tmp;
|
||||
for (int32_t ry = 0; ry < (diagonal + 2); ry++) {
|
||||
tmp = y - ry - edgeDist;
|
||||
if (tmp < 0) return;
|
||||
lines[tmp].length[eidx] = 1;
|
||||
if (reverse) lines[tmp].coverage[eidx] = 255 - (coverage * ry);
|
||||
else lines[tmp].coverage[eidx] = (coverage * ry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _calcVertCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t rewind, bool reverse)
|
||||
{
|
||||
if (eidx == 1) reverse = !reverse;
|
||||
int32_t coverage = (255 / (rewind + 1));
|
||||
int32_t tmp;
|
||||
for (int ry = 1; ry < (rewind + 1); ry++) {
|
||||
tmp = y - ry;
|
||||
if (tmp < 0) return;
|
||||
lines[tmp].length[eidx] = 1;
|
||||
if (reverse) lines[tmp].coverage[eidx] = (255 - (coverage * ry));
|
||||
else lines[tmp].coverage[eidx] = (coverage * ry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x, int32_t x2)
|
||||
{
|
||||
lines[y].length[eidx] = abs(x - x2);
|
||||
lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This Anti-Aliasing mechanism is originated from Hermet Park's idea.
|
||||
* To understand this AA logic, you can refer this page:
|
||||
* https://uigraphics.tistory.com/1
|
||||
*/
|
||||
static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
|
||||
{
|
||||
//Previous edge direction:
|
||||
#define DirOutHor 0x0011
|
||||
#define DirOutVer 0x0001
|
||||
#define DirInHor 0x0010
|
||||
#define DirInVer 0x0000
|
||||
#define DirNone 0x1000
|
||||
|
||||
#define PUSH_VERTEX() \
|
||||
do { \
|
||||
pEdge.x = lines[y].x[eidx]; \
|
||||
pEdge.y = y; \
|
||||
ptx[0] = tx[0]; \
|
||||
ptx[1] = tx[1]; \
|
||||
} while (0)
|
||||
|
||||
struct Point
|
||||
{
|
||||
int32_t x, y;
|
||||
};
|
||||
|
||||
int32_t y = 0;
|
||||
Point pEdge = {-1, -1}; //previous edge point
|
||||
Point edgeDiff = {0, 0}; //temporary used for point distance
|
||||
|
||||
/* store bigger to tx[0] between prev and current edge's x positions. */
|
||||
int32_t tx[2] = {0, 0};
|
||||
/* back up prev tx values */
|
||||
int32_t ptx[2] = {0, 0};
|
||||
int32_t diagonal = 0; //straight diagonal pixels count
|
||||
|
||||
auto yStart = aaSpans->yStart;
|
||||
auto yEnd = aaSpans->yEnd;
|
||||
auto lines = aaSpans->lines;
|
||||
|
||||
int32_t prevDir = DirNone;
|
||||
int32_t curDir = DirNone;
|
||||
|
||||
yEnd -= yStart;
|
||||
|
||||
//Start Edge
|
||||
if (y < yEnd) {
|
||||
pEdge.x = lines[y].x[eidx];
|
||||
pEdge.y = y;
|
||||
}
|
||||
|
||||
//Calculates AA Edges
|
||||
for (y++; y < yEnd; y++) {
|
||||
|
||||
if (lines[y].x[0] == INT32_MAX) continue;
|
||||
|
||||
//Ready tx
|
||||
if (eidx == 0) {
|
||||
tx[0] = pEdge.x;
|
||||
tx[1] = lines[y].x[0];
|
||||
} else {
|
||||
tx[0] = lines[y].x[1];
|
||||
tx[1] = pEdge.x;
|
||||
}
|
||||
edgeDiff.x = (tx[0] - tx[1]);
|
||||
edgeDiff.y = (y - pEdge.y);
|
||||
|
||||
//Confirm current edge direction
|
||||
if (edgeDiff.x > 0) {
|
||||
if (edgeDiff.y == 1) curDir = DirOutHor;
|
||||
else curDir = DirOutVer;
|
||||
} else if (edgeDiff.x < 0) {
|
||||
if (edgeDiff.y == 1) curDir = DirInHor;
|
||||
else curDir = DirInVer;
|
||||
} else curDir = DirNone;
|
||||
|
||||
//straight diagonal increase
|
||||
if ((curDir == prevDir) && (y < yEnd)) {
|
||||
if ((abs(edgeDiff.x) == 1) && (edgeDiff.y == 1)) {
|
||||
++diagonal;
|
||||
PUSH_VERTEX();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (curDir) {
|
||||
case DirOutHor: {
|
||||
_calcHorizCoverage(lines, eidx, y, tx[0], tx[1]);
|
||||
if (diagonal > 0) {
|
||||
_calcIrregularCoverage(lines, eidx, y, diagonal, 0, true);
|
||||
diagonal = 0;
|
||||
}
|
||||
/* Increment direction is changed: Outside Vertical -> Outside Horizontal */
|
||||
if (prevDir == DirOutVer) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
|
||||
|
||||
//Trick, but fine-tunning!
|
||||
if (y == 1) _calcHorizCoverage(lines, eidx, pEdge.y, tx[0], tx[1]);
|
||||
PUSH_VERTEX();
|
||||
}
|
||||
break;
|
||||
case DirOutVer: {
|
||||
_calcVertCoverage(lines, eidx, y, edgeDiff.y, true);
|
||||
if (diagonal > 0) {
|
||||
_calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, false);
|
||||
diagonal = 0;
|
||||
}
|
||||
/* Increment direction is changed: Outside Horizontal -> Outside Vertical */
|
||||
if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
|
||||
PUSH_VERTEX();
|
||||
}
|
||||
break;
|
||||
case DirInHor: {
|
||||
_calcHorizCoverage(lines, eidx, (y - 1), tx[0], tx[1]);
|
||||
if (diagonal > 0) {
|
||||
_calcIrregularCoverage(lines, eidx, y, diagonal, 0, false);
|
||||
diagonal = 0;
|
||||
}
|
||||
/* Increment direction is changed: Outside Horizontal -> Inside Horizontal */
|
||||
if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
|
||||
PUSH_VERTEX();
|
||||
}
|
||||
break;
|
||||
case DirInVer: {
|
||||
_calcVertCoverage(lines, eidx, y, edgeDiff.y, false);
|
||||
if (prevDir == DirOutHor) edgeDiff.y -= 1; //Weird, fine tuning?????????????????????
|
||||
if (diagonal > 0) {
|
||||
_calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, true);
|
||||
diagonal = 0;
|
||||
}
|
||||
/* Increment direction is changed: Outside Horizontal -> Inside Vertical */
|
||||
if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
|
||||
PUSH_VERTEX();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (curDir != DirNone) prevDir = curDir;
|
||||
}
|
||||
|
||||
//leftovers...?
|
||||
if ((edgeDiff.y == 1) && (edgeDiff.x != 0)) {
|
||||
if (y >= yEnd) y = (yEnd - 1);
|
||||
_calcHorizCoverage(lines, eidx, y - 1, ptx[0], ptx[1]);
|
||||
_calcHorizCoverage(lines, eidx, y, tx[0], tx[1]);
|
||||
} else {
|
||||
++y;
|
||||
if (y > yEnd) y = yEnd;
|
||||
_calcVertCoverage(lines, eidx, y, (edgeDiff.y + 1), (prevDir & 0x00000001));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _apply(SwSurface* surface, AASpans* aaSpans)
|
||||
{
|
||||
auto end = surface->buf32 + surface->h * surface->stride;
|
||||
auto y = aaSpans->yStart;
|
||||
uint32_t pixel;
|
||||
uint32_t* dst;
|
||||
int32_t pos;
|
||||
|
||||
//left side
|
||||
_calcAAEdge(aaSpans, 0);
|
||||
//right side
|
||||
_calcAAEdge(aaSpans, 1);
|
||||
|
||||
while (y < aaSpans->yEnd) {
|
||||
auto line = &aaSpans->lines[y - aaSpans->yStart];
|
||||
auto width = line->x[1] - line->x[0];
|
||||
if (width > 0) {
|
||||
auto offset = y * surface->stride;
|
||||
|
||||
//Left edge
|
||||
dst = surface->buf32 + (offset + line->x[0]);
|
||||
if (line->x[0] > 1) pixel = *(dst - 1);
|
||||
else pixel = *dst;
|
||||
pos = 1;
|
||||
|
||||
//exceptional handling. out of memory bound.
|
||||
if (dst + line->length[0] >= end) {
|
||||
pos += (dst + line->length[0] - end);
|
||||
}
|
||||
|
||||
while (pos <= line->length[0]) {
|
||||
*dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos);
|
||||
++dst;
|
||||
++pos;
|
||||
}
|
||||
|
||||
//Right edge
|
||||
dst = surface->buf32 + offset + line->x[1] - 1;
|
||||
|
||||
if (line->x[1] < (int32_t)(surface->w - 1)) pixel = *(dst + 1);
|
||||
else pixel = *dst;
|
||||
pos = line->length[1];
|
||||
|
||||
//exceptional handling. out of memory bound.
|
||||
if (dst - pos < surface->buf32) --pos;
|
||||
|
||||
while (pos > 0) {
|
||||
*dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * pos));
|
||||
--dst;
|
||||
--pos;
|
||||
}
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
free(aaSpans->lines);
|
||||
free(aaSpans);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
2 triangles constructs 1 mesh.
|
||||
below figure illustrates vert[4] index info.
|
||||
If you need better quality, please divide a mesh by more number of triangles.
|
||||
|
||||
0 -- 1
|
||||
| / |
|
||||
| / |
|
||||
3 -- 2
|
||||
*/
|
||||
static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix& transform, const SwBBox* region, uint8_t opacity)
|
||||
{
|
||||
if (surface->channelSize == sizeof(uint8_t)) {
|
||||
TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon!");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Exceptions: No dedicated drawing area?
|
||||
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return true;
|
||||
|
||||
/* Prepare vertices.
|
||||
shift XY coordinates to match the sub-pixeling technique. */
|
||||
Vertex vertices[4];
|
||||
vertices[0] = {{0.0f, 0.0f}, {0.0f, 0.0f}};
|
||||
vertices[1] = {{float(image->w), 0.0f}, {float(image->w), 0.0f}};
|
||||
vertices[2] = {{float(image->w), float(image->h)}, {float(image->w), float(image->h)}};
|
||||
vertices[3] = {{0.0f, float(image->h)}, {0.0f, float(image->h)}};
|
||||
|
||||
float ys = FLT_MAX, ye = -1.0f;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vertices[i].pt *= transform;
|
||||
if (vertices[i].pt.y < ys) ys = vertices[i].pt.y;
|
||||
if (vertices[i].pt.y > ye) ye = vertices[i].pt.y;
|
||||
}
|
||||
|
||||
auto aaSpans = _AASpans(ys, ye, image, region);
|
||||
if (!aaSpans) return true;
|
||||
|
||||
Polygon polygon;
|
||||
|
||||
//Draw the first polygon
|
||||
polygon.vertex[0] = vertices[0];
|
||||
polygon.vertex[1] = vertices[1];
|
||||
polygon.vertex[2] = vertices[3];
|
||||
|
||||
_rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
|
||||
|
||||
//Draw the second polygon
|
||||
polygon.vertex[0] = vertices[1];
|
||||
polygon.vertex[1] = vertices[2];
|
||||
polygon.vertex[2] = vertices[3];
|
||||
|
||||
_rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
|
||||
|
||||
#if 0
|
||||
if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) {
|
||||
_compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox);
|
||||
}
|
||||
#endif
|
||||
return _apply(surface, aaSpans);
|
||||
}
|
||||
840
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
vendored
Normal file
840
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
vendored
Normal file
@@ -0,0 +1,840 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef THORVG_SW_OPENMP_SUPPORT
|
||||
#include <omp.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgSwCommon.h"
|
||||
#include "tvgTaskScheduler.h"
|
||||
#include "tvgSwRenderer.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
static int32_t initEngineCnt = false;
|
||||
static int32_t rendererCnt = 0;
|
||||
static SwMpool* globalMpool = nullptr;
|
||||
static uint32_t threadsCnt = 0;
|
||||
|
||||
struct SwTask : Task
|
||||
{
|
||||
SwSurface* surface = nullptr;
|
||||
SwMpool* mpool = nullptr;
|
||||
SwBBox bbox; //Rendering Region
|
||||
Matrix transform;
|
||||
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(SwRle* target) = 0;
|
||||
virtual ~SwTask() {}
|
||||
};
|
||||
|
||||
|
||||
struct SwShapeTask : SwTask
|
||||
{
|
||||
SwShape shape;
|
||||
const RenderShape* rshape = nullptr;
|
||||
bool clipper = false;
|
||||
|
||||
/* We assume that if the stroke width is greater than 2,
|
||||
the shape's outline beneath the stroke could be adequately covered by the stroke drawing.
|
||||
Therefore, antialiasing is disabled under this condition.
|
||||
Additionally, the stroke style should not be dashed. */
|
||||
bool antialiasing(float strokeWidth)
|
||||
{
|
||||
return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;;
|
||||
}
|
||||
|
||||
float validStrokeWidth()
|
||||
{
|
||||
if (!rshape->stroke) return 0.0f;
|
||||
|
||||
auto width = rshape->stroke->width;
|
||||
if (tvg::zero(width)) return 0.0f;
|
||||
|
||||
if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f;
|
||||
if (tvg::zero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f;
|
||||
|
||||
return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12));
|
||||
}
|
||||
|
||||
bool clip(SwRle* target) override
|
||||
{
|
||||
if (shape.fastTrack) return rleClip(target, &bbox);
|
||||
else if (shape.rle) return rleClip(target, shape.rle);
|
||||
return false;
|
||||
}
|
||||
|
||||
void run(unsigned tid) override
|
||||
{
|
||||
//Invisible
|
||||
if (opacity == 0 && !clipper) {
|
||||
bbox.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
auto strokeWidth = validStrokeWidth();
|
||||
SwBBox renderRegion{};
|
||||
auto updateShape = flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform | RenderUpdateFlag::Clip);
|
||||
auto updateFill = false;
|
||||
|
||||
//Shape
|
||||
if (updateShape || flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient)) {
|
||||
uint8_t alpha = 0;
|
||||
rshape->fillColor(nullptr, nullptr, nullptr, &alpha);
|
||||
updateFill = (MULTIPLY(alpha, opacity) || rshape->fill);
|
||||
if (updateShape) shapeReset(&shape);
|
||||
if (updateFill || clipper) {
|
||||
if (shapePrepare(&shape, rshape, transform, bbox, renderRegion, mpool, tid, clips.count > 0 ? true : false)) {
|
||||
if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
|
||||
} else {
|
||||
updateFill = false;
|
||||
renderRegion.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
//Fill
|
||||
if (updateFill) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
//Stroke
|
||||
if (updateShape || flags & RenderUpdateFlag::Stroke) {
|
||||
if (strokeWidth > 0.0f) {
|
||||
shapeResetStroke(&shape, rshape, transform);
|
||||
if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderRegion, 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 {
|
||||
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);
|
||||
if (shape.rle && !clipper->clip(shape.rle)) goto err; //Clip shape rle
|
||||
if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; //Clip stroke rle
|
||||
}
|
||||
|
||||
bbox = renderRegion; //sync
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
bbox.reset();
|
||||
shapeReset(&shape);
|
||||
rleReset(shape.strokeRle);
|
||||
shapeDelOutline(&shape, mpool, tid);
|
||||
}
|
||||
|
||||
void dispose() override
|
||||
{
|
||||
shapeFree(&shape);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct SwImageTask : SwTask
|
||||
{
|
||||
SwImage image;
|
||||
RenderSurface* source; //Image source
|
||||
|
||||
bool clip(SwRle* target) override
|
||||
{
|
||||
TVGERR("SW_ENGINE", "Image is used as ClipPath?");
|
||||
return true;
|
||||
}
|
||||
|
||||
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, transform, clipRegion, bbox, mpool, tid)) goto end;
|
||||
|
||||
if (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, opacity);
|
||||
} 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, opacity);
|
||||
} else {
|
||||
if (task->rshape->strokeColor(&r, &g, &b, &a)) {
|
||||
a = MULTIPLY(opacity, a);
|
||||
if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
SwRenderer::~SwRenderer()
|
||||
{
|
||||
clearCompositors();
|
||||
|
||||
delete(surface);
|
||||
|
||||
if (!sharedMpool) mpoolTerm(mpool);
|
||||
|
||||
--rendererCnt;
|
||||
|
||||
if (rendererCnt == 0 && initEngineCnt == 0) _termEngine();
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::clear()
|
||||
{
|
||||
for (auto task = tasks.begin(); task < tasks.end(); ++task) {
|
||||
if ((*task)->disposed) {
|
||||
delete(*task);
|
||||
} else {
|
||||
(*task)->done();
|
||||
(*task)->pushed = false;
|
||||
}
|
||||
}
|
||||
tasks.clear();
|
||||
|
||||
if (!sharedMpool) mpoolClear(mpool);
|
||||
|
||||
if (surface) {
|
||||
vport.x = vport.y = 0;
|
||||
vport.w = surface->w;
|
||||
vport.h = surface->h;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::sync()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
RenderRegion SwRenderer::viewport()
|
||||
{
|
||||
return vport;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::viewport(const RenderRegion& vp)
|
||||
{
|
||||
vport = vp;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs)
|
||||
{
|
||||
if (!data || stride == 0 || w == 0 || h == 0 || w > stride) return false;
|
||||
|
||||
clearCompositors();
|
||||
|
||||
if (!surface) surface = new SwSurface;
|
||||
|
||||
surface->data = data;
|
||||
surface->stride = stride;
|
||||
surface->w = w;
|
||||
surface->h = h;
|
||||
surface->cs = cs;
|
||||
surface->channelSize = CHANNEL_SIZE(cs);
|
||||
surface->premultiplied = true;
|
||||
|
||||
return rasterCompositor(surface);
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::preRender()
|
||||
{
|
||||
return rasterClear(surface, 0, 0, surface->w, surface->h);
|
||||
}
|
||||
|
||||
|
||||
void SwRenderer::clearCompositors()
|
||||
{
|
||||
//Free Composite Caches
|
||||
for (auto comp = compositors.begin(); comp < compositors.end(); ++comp) {
|
||||
free((*comp)->compositor->image.data);
|
||||
delete((*comp)->compositor);
|
||||
delete(*comp);
|
||||
}
|
||||
compositors.reset();
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::postRender()
|
||||
{
|
||||
//Unmultiply alpha if needed
|
||||
if (surface->cs == ColorSpace::ABGR8888S || surface->cs == ColorSpace::ARGB8888S) {
|
||||
rasterUnpremultiply(surface);
|
||||
}
|
||||
|
||||
for (auto task = tasks.begin(); task < tasks.end(); ++task) {
|
||||
if ((*task)->disposed) delete(*task);
|
||||
else (*task)->pushed = false;
|
||||
}
|
||||
tasks.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::renderImage(RenderData data)
|
||||
{
|
||||
auto task = static_cast<SwImageTask*>(data);
|
||||
task->done();
|
||||
|
||||
if (task->opacity == 0) return true;
|
||||
|
||||
return rasterImage(surface, &task->image, task->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::Normal:
|
||||
surface->blender = nullptr;
|
||||
break;
|
||||
case BlendMethod::Multiply:
|
||||
surface->blender = opBlendMultiply;
|
||||
break;
|
||||
case BlendMethod::Screen:
|
||||
surface->blender = opBlendScreen;
|
||||
break;
|
||||
case BlendMethod::Overlay:
|
||||
surface->blender = opBlendOverlay;
|
||||
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;
|
||||
case BlendMethod::Difference:
|
||||
surface->blender = opBlendDifference;
|
||||
break;
|
||||
case BlendMethod::Exclusion:
|
||||
surface->blender = opBlendExclusion;
|
||||
break;
|
||||
case BlendMethod::Add:
|
||||
surface->blender = opBlendAdd;
|
||||
break;
|
||||
default:
|
||||
TVGLOG("SW_ENGINE", "Non supported blending option = %d", (int) method);
|
||||
surface->blender = nullptr;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
RenderRegion SwRenderer::region(RenderData data)
|
||||
{
|
||||
return static_cast<SwTask*>(data)->bounds();
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity)
|
||||
{
|
||||
if (!cmp) return false;
|
||||
auto p = static_cast<SwCompositor*>(cmp);
|
||||
|
||||
p->method = method;
|
||||
p->opacity = opacity;
|
||||
|
||||
//Current Context?
|
||||
if (p->method != CompositeMethod::None) {
|
||||
surface = p->recoverSfc;
|
||||
surface->compositor = p;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::mempool(bool shared)
|
||||
{
|
||||
if (shared == sharedMpool) return true;
|
||||
|
||||
if (shared) {
|
||||
if (!sharedMpool) {
|
||||
if (!mpoolTerm(mpool)) return false;
|
||||
mpool = globalMpool;
|
||||
}
|
||||
} else {
|
||||
if (sharedMpool) mpool = mpoolInit(threadsCnt);
|
||||
}
|
||||
|
||||
sharedMpool = shared;
|
||||
|
||||
if (mpool) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const RenderSurface* SwRenderer::mainSurface()
|
||||
{
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
SwSurface* SwRenderer::request(int channelSize, bool square)
|
||||
{
|
||||
SwSurface* cmp = nullptr;
|
||||
uint32_t w, h;
|
||||
|
||||
if (square) {
|
||||
//Same Dimensional Size is demanded for the Post Processing Fast Flipping
|
||||
w = h = std::max(surface->w, surface->h);
|
||||
} else {
|
||||
w = surface->w;
|
||||
h = surface->h;
|
||||
}
|
||||
|
||||
//Use cached data
|
||||
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
|
||||
auto cur = *p;
|
||||
if (cur->compositor->valid && cur->compositor->image.channelSize == channelSize) {
|
||||
if (w == cur->w && h == cur->h) {
|
||||
cmp = *p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//New Composition
|
||||
if (!cmp) {
|
||||
//Inherits attributes from main surface
|
||||
cmp = new SwSurface(surface);
|
||||
cmp->compositor = new SwCompositor;
|
||||
cmp->compositor->image.data = (pixel_t*)malloc(channelSize * w * h);
|
||||
cmp->w = cmp->compositor->image.w = w;
|
||||
cmp->h = cmp->compositor->image.h = h;
|
||||
cmp->stride = cmp->compositor->image.stride = w;
|
||||
cmp->compositor->image.direct = true;
|
||||
cmp->compositor->valid = true;
|
||||
cmp->channelSize = cmp->compositor->image.channelSize = channelSize;
|
||||
|
||||
compositors.push(cmp);
|
||||
}
|
||||
|
||||
//Sync. This may have been modified by post-processing.
|
||||
cmp->data = cmp->compositor->image.data;
|
||||
|
||||
return cmp;
|
||||
}
|
||||
|
||||
|
||||
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags)
|
||||
{
|
||||
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;
|
||||
|
||||
auto cmp = request(CHANNEL_SIZE(cs), (flags & CompositionFlag::PostProcessing));
|
||||
|
||||
//Boundary Check
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x + w > sw) w = (sw - x);
|
||||
if (y + h > sh) h = (sh - y);
|
||||
|
||||
if (w == 0 || h == 0) return nullptr;
|
||||
|
||||
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;
|
||||
|
||||
/* TODO: Currently, only blending might work.
|
||||
Blending and composition must be handled together. */
|
||||
auto color = (surface->blender && !surface->compositor) ? 0x00ffffff : 0x00000000;
|
||||
rasterClear(cmp, x, y, w, h, color);
|
||||
|
||||
//Switch render target
|
||||
surface = cmp;
|
||||
|
||||
return cmp->compositor;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::endComposite(RenderCompositor* cmp)
|
||||
{
|
||||
if (!cmp) return false;
|
||||
|
||||
auto p = static_cast<SwCompositor*>(cmp);
|
||||
|
||||
//Recover Context
|
||||
surface = p->recoverSfc;
|
||||
surface->compositor = p->recoverCmp;
|
||||
|
||||
//only invalid (currently used) surface can be composited
|
||||
if (p->valid) return true;
|
||||
p->valid = true;
|
||||
|
||||
//Default is alpha blending
|
||||
if (p->method == CompositeMethod::None) {
|
||||
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
return rasterImage(surface, &p->image, m, p->bbox, p->opacity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void SwRenderer::prepare(RenderEffect* effect, const Matrix& transform)
|
||||
{
|
||||
switch (effect->type) {
|
||||
case SceneEffect::GaussianBlur: effectGaussianBlurUpdate(static_cast<RenderEffectGaussianBlur*>(effect), transform); break;
|
||||
case SceneEffect::DropShadow: effectDropShadowUpdate(static_cast<RenderEffectDropShadow*>(effect), transform); break;
|
||||
case SceneEffect::Fill: effectFillUpdate(static_cast<RenderEffectFill*>(effect)); break;
|
||||
case SceneEffect::Tint: effectTintUpdate(static_cast<RenderEffectTint*>(effect)); break;
|
||||
case SceneEffect::Tritone: effectTritoneUpdate(static_cast<RenderEffectTritone*>(effect)); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::region(RenderEffect* effect)
|
||||
{
|
||||
switch (effect->type) {
|
||||
case SceneEffect::GaussianBlur: return effectGaussianBlurRegion(static_cast<RenderEffectGaussianBlur*>(effect));
|
||||
case SceneEffect::DropShadow: return effectDropShadowRegion(static_cast<RenderEffectDropShadow*>(effect));
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::render(RenderCompositor* cmp, const RenderEffect* effect, bool direct)
|
||||
{
|
||||
auto p = static_cast<SwCompositor*>(cmp);
|
||||
|
||||
if (p->image.channelSize != sizeof(uint32_t)) {
|
||||
TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (effect->type) {
|
||||
case SceneEffect::GaussianBlur: {
|
||||
return effectGaussianBlur(p, request(surface->channelSize, true), static_cast<const RenderEffectGaussianBlur*>(effect));
|
||||
}
|
||||
case SceneEffect::DropShadow: {
|
||||
auto cmp1 = request(surface->channelSize, true);
|
||||
cmp1->compositor->valid = false;
|
||||
auto cmp2 = request(surface->channelSize, true);
|
||||
SwSurface* surfaces[] = {cmp1, cmp2};
|
||||
auto ret = effectDropShadow(p, surfaces, static_cast<const RenderEffectDropShadow*>(effect), direct);
|
||||
cmp1->compositor->valid = true;
|
||||
return ret;
|
||||
}
|
||||
case SceneEffect::Fill: {
|
||||
return effectFill(p, static_cast<const RenderEffectFill*>(effect), direct);
|
||||
}
|
||||
case SceneEffect::Tint: {
|
||||
return effectTint(p, static_cast<const RenderEffectTint*>(effect), direct);
|
||||
}
|
||||
case SceneEffect::Tritone: {
|
||||
return effectTritone(p, static_cast<const RenderEffectTritone*>(effect), direct);
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
|
||||
{
|
||||
if (!surface) return task;
|
||||
if (flags == RenderUpdateFlag::None) return task;
|
||||
|
||||
//TODO: Failed threading them. It would be better if it's possible.
|
||||
//See: https://github.com/thorvg/thorvg/issues/1409
|
||||
//Guarantee composition targets get ready.
|
||||
for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
|
||||
static_cast<SwTask*>(*clip)->done();
|
||||
}
|
||||
|
||||
task->clips = clips;
|
||||
task->transform = transform;
|
||||
|
||||
//zero size?
|
||||
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 = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
|
||||
task->bbox.min.y = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
|
||||
task->bbox.max.x = std::min(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
|
||||
task->bbox.max.y = std::min(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(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
|
||||
{
|
||||
//prepare task
|
||||
auto task = static_cast<SwImageTask*>(data);
|
||||
if (!task) task = new SwImageTask;
|
||||
else task->done();
|
||||
|
||||
task->source = surface;
|
||||
|
||||
return prepareCommon(task, transform, clips, opacity, flags);
|
||||
}
|
||||
|
||||
|
||||
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
|
||||
{
|
||||
//prepare task
|
||||
auto task = static_cast<SwShapeTask*>(data);
|
||||
if (!task) task = new SwShapeTask;
|
||||
else task->done();
|
||||
|
||||
task->rshape = &rshape;
|
||||
task->clipper = clipper;
|
||||
|
||||
return prepareCommon(task, transform, clips, opacity, flags);
|
||||
}
|
||||
|
||||
|
||||
SwRenderer::SwRenderer():mpool(globalMpool)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::init(uint32_t threads)
|
||||
{
|
||||
if ((initEngineCnt++) > 0) return true;
|
||||
|
||||
threadsCnt = threads;
|
||||
|
||||
//Share the memory pool among the renderer
|
||||
globalMpool = mpoolInit(threads);
|
||||
if (!globalMpool) {
|
||||
--initEngineCnt;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int32_t SwRenderer::init()
|
||||
{
|
||||
#ifdef THORVG_SW_OPENMP_SUPPORT
|
||||
omp_set_num_threads(TaskScheduler::threads());
|
||||
#endif
|
||||
|
||||
return initEngineCnt;
|
||||
}
|
||||
|
||||
|
||||
bool SwRenderer::term()
|
||||
{
|
||||
if ((--initEngineCnt) > 0) return true;
|
||||
|
||||
initEngineCnt = 0;
|
||||
|
||||
_termEngine();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SwRenderer* SwRenderer::gen()
|
||||
{
|
||||
++rendererCnt;
|
||||
return new SwRenderer();
|
||||
}
|
||||
89
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
vendored
Normal file
89
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
|
||||
RenderData prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
|
||||
bool preRender() override;
|
||||
bool renderShape(RenderData data) override;
|
||||
bool renderImage(RenderData data) override;
|
||||
bool postRender() override;
|
||||
void dispose(RenderData data) override;
|
||||
RenderRegion region(RenderData data) override;
|
||||
RenderRegion viewport() override;
|
||||
bool viewport(const RenderRegion& vp) override;
|
||||
bool blend(BlendMethod method) override;
|
||||
ColorSpace colorSpace() override;
|
||||
const RenderSurface* mainSurface() override;
|
||||
|
||||
bool clear() override;
|
||||
bool sync() override;
|
||||
bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs);
|
||||
bool mempool(bool shared);
|
||||
|
||||
RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) override;
|
||||
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
|
||||
bool endComposite(RenderCompositor* cmp) override;
|
||||
void clearCompositors();
|
||||
|
||||
void prepare(RenderEffect* effect, const Matrix& transform) override;
|
||||
bool region(RenderEffect* effect) override;
|
||||
bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override;
|
||||
|
||||
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();
|
||||
|
||||
SwSurface* request(int channelSize, bool square);
|
||||
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* _TVG_SW_RENDERER_H_ */
|
||||
1052
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp
vendored
Normal file
1052
thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
675
thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
vendored
Normal file
675
thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
vendored
Normal file
@@ -0,0 +1,675 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
/************************************************************************/
|
||||
/* 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 = cur.length();
|
||||
if (tvg::zero(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 > DASH_PATTERN_THRESHOLD) {
|
||||
Line left, right;
|
||||
if (dash.curLen > 0) {
|
||||
len -= dash.curLen;
|
||||
cur.split(dash.curLen, left, right);
|
||||
if (!dash.curOpGap) {
|
||||
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
|
||||
_outlineMoveTo(*dash.outline, &left.pt1, transform);
|
||||
dash.move = false;
|
||||
}
|
||||
_outlineLineTo(*dash.outline, &left.pt2, transform);
|
||||
}
|
||||
} else {
|
||||
right = cur;
|
||||
}
|
||||
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
|
||||
dash.curLen = dash.pattern[dash.curIdx];
|
||||
dash.curOpGap = !dash.curOpGap;
|
||||
cur = right;
|
||||
dash.ptCur = cur.pt1;
|
||||
dash.move = true;
|
||||
}
|
||||
//leftovers
|
||||
dash.curLen -= len;
|
||||
if (!dash.curOpGap) {
|
||||
if (dash.move) {
|
||||
_outlineMoveTo(*dash.outline, &cur.pt1, transform);
|
||||
dash.move = false;
|
||||
}
|
||||
_outlineLineTo(*dash.outline, &cur.pt2, transform);
|
||||
}
|
||||
if (dash.curLen < 1 && TO_SWCOORD(len) > 1) {
|
||||
//move to next dash
|
||||
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
|
||||
dash.curLen = dash.pattern[dash.curIdx];
|
||||
dash.curOpGap = !dash.curOpGap;
|
||||
}
|
||||
}
|
||||
dash.ptCur = *to;
|
||||
}
|
||||
|
||||
|
||||
static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix& transform)
|
||||
{
|
||||
Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
|
||||
auto len = cur.length();
|
||||
|
||||
//draw the current line fully
|
||||
if (tvg::zero(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) > DASH_PATTERN_THRESHOLD) {
|
||||
Bezier left, right;
|
||||
if (dash.curLen > 0) {
|
||||
len -= dash.curLen;
|
||||
cur.split(dash.curLen, left, right);
|
||||
if (!dash.curOpGap) {
|
||||
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
|
||||
_outlineMoveTo(*dash.outline, &left.start, transform);
|
||||
dash.move = false;
|
||||
}
|
||||
_outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform);
|
||||
}
|
||||
} else {
|
||||
right = cur;
|
||||
}
|
||||
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
|
||||
dash.curLen = dash.pattern[dash.curIdx];
|
||||
dash.curOpGap = !dash.curOpGap;
|
||||
cur = right;
|
||||
dash.ptCur = right.start;
|
||||
dash.move = true;
|
||||
}
|
||||
//leftovers
|
||||
dash.curLen -= len;
|
||||
if (!dash.curOpGap) {
|
||||
if (dash.move) {
|
||||
_outlineMoveTo(*dash.outline, &cur.start, transform);
|
||||
dash.move = false;
|
||||
}
|
||||
_outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform);
|
||||
}
|
||||
if (dash.curLen < 0.1f && TO_SWCOORD(len) > 1) {
|
||||
//move to next dash
|
||||
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
|
||||
dash.curLen = dash.pattern[dash.curIdx];
|
||||
dash.curOpGap = !dash.curOpGap;
|
||||
}
|
||||
}
|
||||
dash.ptCur = *to;
|
||||
}
|
||||
|
||||
|
||||
static void _dashClose(SwDashStroke& dash, const Matrix& transform)
|
||||
{
|
||||
_dashLineTo(dash, &dash.ptStart, transform);
|
||||
}
|
||||
|
||||
|
||||
static void _dashMoveTo(SwDashStroke& dash, const Point* pts)
|
||||
{
|
||||
dash.ptCur = *pts;
|
||||
dash.ptStart = *pts;
|
||||
dash.move = true;
|
||||
}
|
||||
|
||||
|
||||
static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const Point* pts)
|
||||
{
|
||||
dash.curIdx = offIdx % dash.cnt;
|
||||
dash.curLen = dash.pattern[dash.curIdx] - offset;
|
||||
dash.curOpGap = offIdx % 2;
|
||||
dash.ptStart = dash.ptCur = *pts;
|
||||
dash.move = true;
|
||||
}
|
||||
|
||||
|
||||
static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length, float trimBegin, float trimEnd)
|
||||
{
|
||||
auto begin = length * trimBegin;
|
||||
auto end = length * trimEnd;
|
||||
|
||||
//default
|
||||
if (end > begin) {
|
||||
if (begin > 0.0f) dash->cnt = 4;
|
||||
else dash->cnt = 2;
|
||||
//looping
|
||||
} else dash->cnt = 3;
|
||||
|
||||
if (dash->cnt == 2) {
|
||||
dash->pattern[0] = end - begin;
|
||||
dash->pattern[1] = length - (end - begin);
|
||||
} else if (dash->cnt == 3) {
|
||||
dash->pattern[0] = end;
|
||||
dash->pattern[1] = (begin - end);
|
||||
dash->pattern[2] = length - begin;
|
||||
} else {
|
||||
dash->pattern[0] = 0; //zero dash to start with a space.
|
||||
dash->pattern[1] = begin;
|
||||
dash->pattern[2] = end - begin;
|
||||
dash->pattern[3] = length - end;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32_t shiftCmds, bool subpath)
|
||||
{
|
||||
const PathCommand* cmds = rshape->path.cmds.data + shiftCmds;
|
||||
auto cmdCnt = rshape->path.cmds.count - shiftCmds;
|
||||
const Point* pts = rshape->path.pts.data + shiftPts;
|
||||
auto ptsCnt = rshape->path.pts.count - shiftPts;
|
||||
|
||||
//No actual shape data
|
||||
if (cmdCnt <= 0 || ptsCnt <= 0) return 0.0f;
|
||||
|
||||
const Point* close = nullptr;
|
||||
auto len = 0.0f;
|
||||
|
||||
//must begin with moveTo
|
||||
if (cmds[0] == PathCommand::MoveTo) {
|
||||
close = pts;
|
||||
cmds++;
|
||||
pts++;
|
||||
cmdCnt--;
|
||||
}
|
||||
|
||||
while (cmdCnt-- > 0) {
|
||||
switch (*cmds) {
|
||||
case PathCommand::Close: {
|
||||
len += length(pts - 1, close);
|
||||
if (subpath) return len;
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
if (subpath) return len;
|
||||
close = pts;
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::LineTo: {
|
||||
len += length(pts - 1, pts);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
len += Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.length();
|
||||
pts += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
++cmds;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix& transform, bool trimmed, SwMpool* mpool, unsigned tid)
|
||||
{
|
||||
const PathCommand* cmds = rshape->path.cmds.data;
|
||||
auto cmdCnt = rshape->path.cmds.count;
|
||||
const Point* pts = rshape->path.pts.data;
|
||||
auto ptsCnt = rshape->path.pts.count;
|
||||
|
||||
//No actual shape data
|
||||
if (cmdCnt == 0 || ptsCnt == 0) return nullptr;
|
||||
|
||||
auto startPts = pts;
|
||||
auto startCmds = cmds;
|
||||
|
||||
SwDashStroke dash;
|
||||
auto offset = 0.0f;
|
||||
dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset);
|
||||
auto simultaneous = rshape->stroke->trim.simultaneous;
|
||||
float trimBegin = 0.0f, trimEnd = 1.0f;
|
||||
if (trimmed) rshape->stroke->strokeTrim(trimBegin, trimEnd);
|
||||
|
||||
if (dash.cnt == 0) {
|
||||
if (trimmed) dash.pattern = (float*)malloc(sizeof(float) * 4);
|
||||
else return nullptr;
|
||||
} else {
|
||||
//TODO: handle dash + trim - for now trimming ignoring is forced
|
||||
trimmed = false;
|
||||
}
|
||||
|
||||
//offset
|
||||
auto patternLength = 0.0f;
|
||||
uint32_t offIdx = 0;
|
||||
if (!tvg::zero(offset)) {
|
||||
for (size_t i = 0; i < dash.cnt; ++i) patternLength += dash.pattern[i];
|
||||
bool isOdd = dash.cnt % 2;
|
||||
if (isOdd) patternLength *= 2;
|
||||
|
||||
offset = fmodf(offset, patternLength);
|
||||
if (offset < 0) offset += patternLength;
|
||||
|
||||
for (size_t i = 0; i < dash.cnt * (1 + (size_t)isOdd); ++i, ++offIdx) {
|
||||
auto curPattern = dash.pattern[i % dash.cnt];
|
||||
if (offset < curPattern) break;
|
||||
offset -= curPattern;
|
||||
}
|
||||
}
|
||||
|
||||
dash.outline = mpoolReqDashOutline(mpool, tid);
|
||||
|
||||
//must begin with moveTo
|
||||
if (cmds[0] == PathCommand::MoveTo) {
|
||||
if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous), trimBegin, trimEnd);
|
||||
_dashMoveTo(dash, offIdx, offset, pts);
|
||||
cmds++;
|
||||
pts++;
|
||||
}
|
||||
|
||||
while (--cmdCnt > 0) {
|
||||
switch (*cmds) {
|
||||
case PathCommand::Close: {
|
||||
_dashClose(dash, transform);
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
if (trimmed) {
|
||||
if (simultaneous) {
|
||||
_trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true), trimBegin, trimEnd);
|
||||
_dashMoveTo(dash, offIdx, offset, pts);
|
||||
} else _dashMoveTo(dash, pts);
|
||||
} else _dashMoveTo(dash, offIdx, offset, pts);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::LineTo: {
|
||||
_dashLineTo(dash, pts, transform);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
_dashCubicTo(dash, pts, pts + 1, pts + 2, transform);
|
||||
pts += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
++cmds;
|
||||
}
|
||||
|
||||
_outlineEnd(*dash.outline);
|
||||
|
||||
if (trimmed) free(dash.pattern);
|
||||
|
||||
return dash.outline;
|
||||
}
|
||||
|
||||
|
||||
static bool _axisAlignedRect(const SwOutline* outline)
|
||||
{
|
||||
//Fast Track: axis-aligned rectangle?
|
||||
if (outline->pts.count != 5) return false;
|
||||
if (outline->types[2] == SW_CURVE_TYPE_CUBIC) return false;
|
||||
|
||||
auto pt1 = outline->pts.data + 0;
|
||||
auto pt2 = outline->pts.data + 1;
|
||||
auto pt3 = outline->pts.data + 2;
|
||||
auto pt4 = outline->pts.data + 3;
|
||||
|
||||
auto a = SwPoint{pt1->x, pt3->y};
|
||||
auto b = SwPoint{pt3->x, pt1->y};
|
||||
|
||||
if ((*pt2 == a && *pt4 == b) || (*pt2 == b && *pt4 == a)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix& transform, SwMpool* mpool, unsigned tid, bool hasComposite)
|
||||
{
|
||||
const PathCommand* cmds = rshape->path.cmds.data;
|
||||
auto cmdCnt = rshape->path.cmds.count;
|
||||
const Point* pts = rshape->path.pts.data;
|
||||
auto ptsCnt = rshape->path.pts.count;
|
||||
|
||||
//No actual shape data
|
||||
if (cmdCnt == 0 || ptsCnt == 0) return false;
|
||||
|
||||
shape->outline = mpoolReqOutline(mpool, tid);
|
||||
auto outline = shape->outline;
|
||||
auto closed = false;
|
||||
|
||||
//Generate Outlines
|
||||
while (cmdCnt-- > 0) {
|
||||
switch (*cmds) {
|
||||
case PathCommand::Close: {
|
||||
if (!closed) closed = _outlineClose(*outline);
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
closed = _outlineMoveTo(*outline, pts, transform, closed);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::LineTo: {
|
||||
if (closed) closed = _outlineBegin(*outline);
|
||||
_outlineLineTo(*outline, pts, transform);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
if (closed) closed = _outlineBegin(*outline);
|
||||
_outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform);
|
||||
pts += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
++cmds;
|
||||
}
|
||||
|
||||
if (!closed) _outlineEnd(*outline);
|
||||
|
||||
outline->fillRule = rshape->rule;
|
||||
shape->outline = outline;
|
||||
|
||||
shape->fastTrack = (!hasComposite && _axisAlignedRect(shape->outline));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
|
||||
{
|
||||
if (!_genOutline(shape, rshape, transform, mpool, tid, hasComposite)) return false;
|
||||
if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false;
|
||||
|
||||
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);
|
||||
shape->fastTrack = false;
|
||||
shape->bbox.reset();
|
||||
}
|
||||
|
||||
|
||||
void shapeFree(SwShape* shape)
|
||||
{
|
||||
rleFree(shape->rle);
|
||||
shape->rle = nullptr;
|
||||
|
||||
shapeDelFill(shape);
|
||||
|
||||
if (shape->stroke) {
|
||||
rleFree(shape->strokeRle);
|
||||
shape->strokeRle = nullptr;
|
||||
strokeFree(shape->stroke);
|
||||
shape->stroke = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void shapeDelStroke(SwShape* shape)
|
||||
{
|
||||
if (!shape->stroke) return;
|
||||
rleFree(shape->strokeRle);
|
||||
shape->strokeRle = nullptr;
|
||||
strokeFree(shape->stroke);
|
||||
shape->stroke = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& transform)
|
||||
{
|
||||
if (!shape->stroke) shape->stroke = static_cast<SwStroke*>(calloc(1, sizeof(SwStroke)));
|
||||
auto stroke = shape->stroke;
|
||||
if (!stroke) return;
|
||||
|
||||
strokeReset(stroke, rshape, transform);
|
||||
rleReset(shape->strokeRle);
|
||||
}
|
||||
|
||||
|
||||
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
|
||||
{
|
||||
SwOutline* shapeOutline = nullptr;
|
||||
SwOutline* strokeOutline = nullptr;
|
||||
auto dashStroking = false;
|
||||
auto ret = true;
|
||||
|
||||
//Dash style (+trimming)
|
||||
auto trimmed = rshape->strokeTrim();
|
||||
if (rshape->stroke->dashCnt > 0 || trimmed) {
|
||||
shapeOutline = _genDashOutline(rshape, transform, trimmed, mpool, tid);
|
||||
if (!shapeOutline) return false;
|
||||
dashStroking = true;
|
||||
//Normal style
|
||||
} else {
|
||||
if (!shape->outline) {
|
||||
if (!_genOutline(shape, rshape, transform, mpool, tid, false)) return false;
|
||||
}
|
||||
shapeOutline = shape->outline;
|
||||
}
|
||||
|
||||
if (!strokeParseOutline(shape->stroke, *shapeOutline)) {
|
||||
ret = false;
|
||||
goto clear;
|
||||
}
|
||||
|
||||
strokeOutline = strokeExportOutline(shape->stroke, mpool, tid);
|
||||
|
||||
if (!mathUpdateOutlineBBox(strokeOutline, clipRegion, renderRegion, false)) {
|
||||
ret = false;
|
||||
goto clear;
|
||||
}
|
||||
|
||||
shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderRegion, true);
|
||||
|
||||
clear:
|
||||
if (dashStroking) mpoolRetDashOutline(mpool, tid);
|
||||
mpoolRetStrokeOutline(mpool, tid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
|
||||
{
|
||||
return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable);
|
||||
}
|
||||
|
||||
|
||||
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
|
||||
{
|
||||
return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable);
|
||||
}
|
||||
|
||||
|
||||
void shapeResetFill(SwShape* shape)
|
||||
{
|
||||
if (!shape->fill) {
|
||||
shape->fill = static_cast<SwFill*>(calloc(1, sizeof(SwFill)));
|
||||
if (!shape->fill) return;
|
||||
}
|
||||
fillReset(shape->fill);
|
||||
}
|
||||
|
||||
|
||||
void shapeResetStrokeFill(SwShape* shape)
|
||||
{
|
||||
if (!shape->stroke->fill) {
|
||||
shape->stroke->fill = static_cast<SwFill*>(calloc(1, sizeof(SwFill)));
|
||||
if (!shape->stroke->fill) return;
|
||||
}
|
||||
fillReset(shape->stroke->fill);
|
||||
}
|
||||
|
||||
|
||||
void shapeDelFill(SwShape* shape)
|
||||
{
|
||||
if (!shape->fill) return;
|
||||
fillFree(shape->fill);
|
||||
shape->fill = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void shapeDelStrokeFill(SwShape* shape)
|
||||
{
|
||||
if (!shape->stroke->fill) return;
|
||||
fillFree(shape->stroke->fill);
|
||||
shape->stroke->fill = nullptr;
|
||||
}
|
||||
913
thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
vendored
Normal file
913
thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
vendored
Normal file
@@ -0,0 +1,913 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "tvgSwCommon.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static constexpr auto SW_STROKE_TAG_POINT = 1;
|
||||
static constexpr auto SW_STROKE_TAG_CUBIC = 2;
|
||||
static constexpr auto SW_STROKE_TAG_BEGIN = 4;
|
||||
static constexpr auto SW_STROKE_TAG_END = 8;
|
||||
|
||||
static inline SwFixed SIDE_TO_ROTATE(const int32_t s)
|
||||
{
|
||||
return (SW_ANGLE_PI2 - static_cast<SwFixed>(s) * SW_ANGLE_PI);
|
||||
}
|
||||
|
||||
|
||||
static inline void SCALE(const SwStroke& stroke, SwPoint& pt)
|
||||
{
|
||||
pt.x = static_cast<SwCoord>(pt.x * stroke.sx);
|
||||
pt.y = static_cast<SwCoord>(pt.y * stroke.sy);
|
||||
}
|
||||
|
||||
|
||||
static void _growBorder(SwStrokeBorder* border, uint32_t newPts)
|
||||
{
|
||||
auto maxOld = border->maxPts;
|
||||
auto maxNew = border->ptsCnt + newPts;
|
||||
|
||||
if (maxNew <= maxOld) return;
|
||||
|
||||
auto maxCur = maxOld;
|
||||
|
||||
while (maxCur < maxNew)
|
||||
maxCur += (maxCur >> 1) + 16;
|
||||
//OPTIMIZE: use mempool!
|
||||
border->pts = static_cast<SwPoint*>(realloc(border->pts, maxCur * sizeof(SwPoint)));
|
||||
border->tags = static_cast<uint8_t*>(realloc(border->tags, maxCur * sizeof(uint8_t)));
|
||||
border->maxPts = maxCur;
|
||||
}
|
||||
|
||||
|
||||
static void _borderClose(SwStrokeBorder* border, bool reverse)
|
||||
{
|
||||
auto start = border->start;
|
||||
auto count = border->ptsCnt;
|
||||
|
||||
//Don't record empty paths!
|
||||
if (count <= start + 1U) {
|
||||
border->ptsCnt = start;
|
||||
} else {
|
||||
/* Copy the last point to the start of this sub-path,
|
||||
since it contains the adjusted starting coordinates */
|
||||
border->ptsCnt = --count;
|
||||
border->pts[start] = border->pts[count];
|
||||
|
||||
if (reverse) {
|
||||
//reverse the points
|
||||
auto pt1 = border->pts + start + 1;
|
||||
auto pt2 = border->pts + count - 1;
|
||||
|
||||
while (pt1 < pt2) {
|
||||
auto tmp = *pt1;
|
||||
*pt1 = *pt2;
|
||||
*pt2 = tmp;
|
||||
++pt1;
|
||||
--pt2;
|
||||
}
|
||||
|
||||
//reverse the tags
|
||||
auto tag1 = border->tags + start + 1;
|
||||
auto tag2 = border->tags + count - 1;
|
||||
|
||||
while (tag1 < tag2) {
|
||||
auto tmp = *tag1;
|
||||
*tag1 = *tag2;
|
||||
*tag2 = tmp;
|
||||
++tag1;
|
||||
--tag2;
|
||||
}
|
||||
}
|
||||
|
||||
border->tags[start] |= SW_STROKE_TAG_BEGIN;
|
||||
border->tags[count - 1] |= SW_STROKE_TAG_END;
|
||||
}
|
||||
|
||||
border->start = -1;
|
||||
border->movable = false;
|
||||
}
|
||||
|
||||
|
||||
static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
|
||||
{
|
||||
_growBorder(border, 3);
|
||||
|
||||
auto pt = border->pts + border->ptsCnt;
|
||||
auto tag = border->tags + border->ptsCnt;
|
||||
|
||||
pt[0] = ctrl1;
|
||||
pt[1] = ctrl2;
|
||||
pt[2] = to;
|
||||
|
||||
tag[0] = SW_STROKE_TAG_CUBIC;
|
||||
tag[1] = SW_STROKE_TAG_CUBIC;
|
||||
tag[2] = SW_STROKE_TAG_POINT;
|
||||
|
||||
border->ptsCnt += 3;
|
||||
border->movable = false;
|
||||
}
|
||||
|
||||
|
||||
static void _borderArcTo(SwStrokeBorder* border, const SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke)
|
||||
{
|
||||
constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2;
|
||||
SwPoint a = {static_cast<SwCoord>(radius), 0};
|
||||
mathRotate(a, angleStart);
|
||||
SCALE(stroke, a);
|
||||
a += center;
|
||||
|
||||
auto total = angleDiff;
|
||||
auto angle = angleStart;
|
||||
auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2;
|
||||
|
||||
while (total != 0) {
|
||||
auto step = total;
|
||||
if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE;
|
||||
else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE;
|
||||
|
||||
auto next = angle + step;
|
||||
auto theta = step;
|
||||
if (theta < 0) theta = -theta;
|
||||
|
||||
theta >>= 1;
|
||||
|
||||
//compute end point
|
||||
SwPoint b = {static_cast<SwCoord>(radius), 0};
|
||||
mathRotate(b, next);
|
||||
SCALE(stroke, b);
|
||||
b += center;
|
||||
|
||||
//compute first and second control points
|
||||
auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3);
|
||||
|
||||
SwPoint a2 = {static_cast<SwCoord>(length), 0};
|
||||
mathRotate(a2, angle + rotate);
|
||||
SCALE(stroke, a2);
|
||||
a2 += a;
|
||||
|
||||
SwPoint b2 = {static_cast<SwCoord>(length), 0};
|
||||
mathRotate(b2, next - rotate);
|
||||
SCALE(stroke, b2);
|
||||
b2 += b;
|
||||
|
||||
//add cubic arc
|
||||
_borderCubicTo(border, a2, b2, b);
|
||||
|
||||
//process the rest of the arc?
|
||||
a = b;
|
||||
total -= step;
|
||||
angle = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movable)
|
||||
{
|
||||
if (border->movable) {
|
||||
//move last point
|
||||
border->pts[border->ptsCnt - 1] = to;
|
||||
} else {
|
||||
//don't add zero-length line_to
|
||||
if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
|
||||
|
||||
_growBorder(border, 1);
|
||||
border->pts[border->ptsCnt] = to;
|
||||
border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT;
|
||||
border->ptsCnt += 1;
|
||||
}
|
||||
|
||||
border->movable = movable;
|
||||
}
|
||||
|
||||
|
||||
static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to)
|
||||
{
|
||||
//close current open path if any?
|
||||
if (border->start >= 0) _borderClose(border, false);
|
||||
|
||||
border->start = border->ptsCnt;
|
||||
border->movable = false;
|
||||
|
||||
_borderLineTo(border, to, false);
|
||||
}
|
||||
|
||||
|
||||
static void _arcTo(SwStroke& stroke, int32_t side)
|
||||
{
|
||||
auto border = stroke.borders + side;
|
||||
auto rotate = SIDE_TO_ROTATE(side);
|
||||
auto total = mathDiff(stroke.angleIn, stroke.angleOut);
|
||||
if (total == SW_ANGLE_PI) total = -rotate * 2;
|
||||
|
||||
_borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke);
|
||||
border->movable = false;
|
||||
}
|
||||
|
||||
|
||||
static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
|
||||
{
|
||||
auto border = stroke.borders + side;
|
||||
|
||||
if (stroke.join == StrokeJoin::Round) {
|
||||
_arcTo(stroke, side);
|
||||
} else {
|
||||
//this is a mitered (pointed) or beveled (truncated) corner
|
||||
auto rotate = SIDE_TO_ROTATE(side);
|
||||
auto bevel = stroke.join == StrokeJoin::Bevel;
|
||||
SwFixed phi = 0;
|
||||
SwFixed thcos = 0;
|
||||
|
||||
if (!bevel) {
|
||||
auto theta = mathDiff(stroke.angleIn, stroke.angleOut);
|
||||
if (theta == SW_ANGLE_PI) {
|
||||
theta = rotate;
|
||||
phi = stroke.angleIn;
|
||||
} else {
|
||||
theta /= 2;
|
||||
phi = stroke.angleIn + theta + rotate;
|
||||
}
|
||||
|
||||
thcos = mathCos(theta);
|
||||
auto sigma = mathMultiply(stroke.miterlimit, thcos);
|
||||
|
||||
//is miter limit exceeded?
|
||||
if (sigma < 0x10000L) bevel = true;
|
||||
}
|
||||
|
||||
//this is a bevel (broken angle)
|
||||
if (bevel) {
|
||||
SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
|
||||
mathRotate(delta, stroke.angleOut + rotate);
|
||||
SCALE(stroke, delta);
|
||||
delta += stroke.center;
|
||||
border->movable = false;
|
||||
_borderLineTo(border, delta, false);
|
||||
//this is a miter (intersection)
|
||||
} else {
|
||||
auto length = mathDivide(stroke.width, thcos);
|
||||
SwPoint delta = {static_cast<SwCoord>(length), 0};
|
||||
mathRotate(delta, phi);
|
||||
SCALE(stroke, delta);
|
||||
delta += stroke.center;
|
||||
_borderLineTo(border, delta, false);
|
||||
|
||||
/* Now add and end point
|
||||
Only needed if not lineto (lineLength is zero for curves) */
|
||||
if (lineLength == 0) {
|
||||
delta = {static_cast<SwCoord>(stroke.width), 0};
|
||||
mathRotate(delta, stroke.angleOut + rotate);
|
||||
SCALE(stroke, delta);
|
||||
delta += stroke.center;
|
||||
_borderLineTo(border, delta, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength)
|
||||
{
|
||||
auto border = stroke.borders + side;
|
||||
auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2;
|
||||
SwPoint delta;
|
||||
bool intersect = false;
|
||||
|
||||
/* Only intersect borders if between two line_to's and both
|
||||
lines are long enough (line length is zero for curves). */
|
||||
if (border->movable && lineLength > 0) {
|
||||
//compute minimum required length of lines
|
||||
SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta)));
|
||||
if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true;
|
||||
}
|
||||
|
||||
auto rotate = SIDE_TO_ROTATE(side);
|
||||
|
||||
if (!intersect) {
|
||||
delta = {static_cast<SwCoord>(stroke.width), 0};
|
||||
mathRotate(delta, stroke.angleOut + rotate);
|
||||
SCALE(stroke, delta);
|
||||
delta += stroke.center;
|
||||
border->movable = false;
|
||||
} else {
|
||||
//compute median angle
|
||||
auto phi = stroke.angleIn + theta;
|
||||
auto thcos = mathCos(theta);
|
||||
delta = {static_cast<SwCoord>(mathDivide(stroke.width, thcos)), 0};
|
||||
mathRotate(delta, phi + rotate);
|
||||
SCALE(stroke, delta);
|
||||
delta += stroke.center;
|
||||
}
|
||||
|
||||
_borderLineTo(border, delta, false);
|
||||
}
|
||||
|
||||
|
||||
void _processCorner(SwStroke& stroke, SwFixed lineLength)
|
||||
{
|
||||
auto turn = mathDiff(stroke.angleIn, stroke.angleOut);
|
||||
|
||||
//no specific corner processing is required if the turn is 0
|
||||
if (turn == 0) return;
|
||||
|
||||
//when we turn to the right, the inside side is 0
|
||||
int32_t inside = 0;
|
||||
|
||||
//otherwise, the inside is 1
|
||||
if (turn < 0) inside = 1;
|
||||
|
||||
//process the inside
|
||||
_inside(stroke, inside, lineLength);
|
||||
|
||||
//process the outside
|
||||
_outside(stroke, 1 - inside, lineLength);
|
||||
}
|
||||
|
||||
|
||||
void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength)
|
||||
{
|
||||
SwPoint delta = {static_cast<SwCoord>(stroke.width), 0};
|
||||
mathRotate(delta, startAngle + SW_ANGLE_PI2);
|
||||
SCALE(stroke, delta);
|
||||
|
||||
auto pt = stroke.center + delta;
|
||||
auto border = stroke.borders;
|
||||
_borderMoveTo(border, pt);
|
||||
|
||||
pt = stroke.center - delta;
|
||||
++border;
|
||||
_borderMoveTo(border, pt);
|
||||
|
||||
/* Save angle, position and line length for last join
|
||||
lineLength is zero for curves */
|
||||
stroke.subPathAngle = startAngle;
|
||||
stroke.firstPt = false;
|
||||
stroke.subPathLineLength = lineLength;
|
||||
}
|
||||
|
||||
|
||||
static void _lineTo(SwStroke& stroke, const SwPoint& to)
|
||||
{
|
||||
auto delta = to - stroke.center;
|
||||
|
||||
//a zero-length lineto is a no-op
|
||||
if (delta.zero()) {
|
||||
//round and square caps are expected to be drawn as a dot even for zero-length lines
|
||||
if (stroke.firstPt && stroke.cap != StrokeCap::Butt) _firstSubPath(stroke, 0, 0);
|
||||
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;
|
||||
|
||||
auto valid = mathCubicAngle(arc, angleIn, angleMid, angleOut);
|
||||
|
||||
//valid size
|
||||
if (valid > 0 && arc < limit) {
|
||||
if (stroke.firstPt) stroke.angleIn = angleIn;
|
||||
mathSplitCubic(arc);
|
||||
arc += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
//ignoreable size
|
||||
if (valid < 0 && arc == bezStack) {
|
||||
stroke.center = to;
|
||||
|
||||
//round and square caps are expected to be drawn as a dot even for zero-length lines
|
||||
if (stroke.firstPt && stroke.cap != StrokeCap::Butt) _firstSubPath(stroke, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
//small size
|
||||
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 negative 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 doesn'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)
|
||||
{
|
||||
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));
|
||||
stroke->width = HALF_STROKE(rshape->strokeWidth());
|
||||
stroke->cap = rshape->strokeCap();
|
||||
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit() * 65536.0f);
|
||||
|
||||
//Save line join: it can be temporarily changed when stroking curves...
|
||||
stroke->joinSaved = stroke->join = rshape->strokeJoin();
|
||||
|
||||
stroke->borders[0].ptsCnt = 0;
|
||||
stroke->borders[0].start = -1;
|
||||
stroke->borders[1].ptsCnt = 0;
|
||||
stroke->borders[1].start = -1;
|
||||
}
|
||||
|
||||
|
||||
bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
|
||||
{
|
||||
uint32_t first = 0;
|
||||
uint32_t i = 0;
|
||||
|
||||
for (auto cntr = outline.cntrs.begin(); cntr < outline.cntrs.end(); ++cntr, ++i) {
|
||||
auto last = *cntr; //index of last point in contour
|
||||
auto limit = outline.pts.data + last;
|
||||
|
||||
//Skip empty points
|
||||
if (last <= first) {
|
||||
first = last + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto start = outline.pts[first];
|
||||
auto pt = outline.pts.data + first;
|
||||
auto types = outline.types.data + first;
|
||||
auto type = types[0];
|
||||
|
||||
//A contour cannot start with a cubic control point
|
||||
if (type == SW_CURVE_TYPE_CUBIC) return false;
|
||||
++types;
|
||||
|
||||
auto closed = outline.closed.data ? outline.closed.data[i]: false;
|
||||
|
||||
_beginSubPath(*stroke, start, closed);
|
||||
|
||||
while (pt < limit) {
|
||||
//emit a single line_to
|
||||
if (types[0] == SW_CURVE_TYPE_POINT) {
|
||||
++pt;
|
||||
++types;
|
||||
_lineTo(*stroke, *pt);
|
||||
//types cubic
|
||||
} else {
|
||||
pt += 3;
|
||||
types += 3;
|
||||
if (pt <= limit) _cubicTo(*stroke, pt[-2], pt[-1], pt[0]);
|
||||
else if (pt - 1 == limit) _cubicTo(*stroke, pt[-2], pt[-1], start);
|
||||
else 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;
|
||||
}
|
||||
106
thirdparty/thorvg/src/renderer/tvgAccessor.cpp
vendored
Normal file
106
thirdparty/thorvg/src/renderer/tvgAccessor.cpp
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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"
|
||||
#include "tvgCompressor.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static bool accessChildren(Iterator* it, function<bool(const Paint* paint, void* data)> func, void* data)
|
||||
{
|
||||
while (auto child = it->next()) {
|
||||
//Access the child
|
||||
if (!func(child, data)) return false;
|
||||
|
||||
//Access the children of the child
|
||||
if (auto it2 = IteratorAccessor::iterator(child)) {
|
||||
if (!accessChildren(it2, func, data)) {
|
||||
delete(it2);
|
||||
return false;
|
||||
}
|
||||
delete(it2);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
TVG_DEPRECATED unique_ptr<Picture> Accessor::set(unique_ptr<Picture> picture, function<bool(const Paint* paint)> func) noexcept
|
||||
{
|
||||
auto backward = [](const tvg::Paint* paint, void* data) -> bool
|
||||
{
|
||||
auto func = reinterpret_cast<function<bool(const Paint* paint)>*>(data);
|
||||
if (!(*func)(paint)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
set(picture.get(), backward, reinterpret_cast<void*>(&func));
|
||||
return picture;
|
||||
}
|
||||
|
||||
|
||||
Result Accessor::set(Paint* paint, function<bool(const Paint* paint, void* data)> func, void* data) noexcept
|
||||
{
|
||||
if (!paint || !func) return Result::InvalidArguments;
|
||||
|
||||
//Use the Preorder Tree-Searc
|
||||
|
||||
//Root
|
||||
if (!func(paint, data)) return Result::Success;
|
||||
|
||||
//Children
|
||||
if (auto it = IteratorAccessor::iterator(paint)) {
|
||||
accessChildren(it, func, data);
|
||||
delete(it);
|
||||
}
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
uint32_t Accessor::id(const char* name) noexcept
|
||||
{
|
||||
return djb2Encode(name);
|
||||
}
|
||||
|
||||
|
||||
Accessor::~Accessor()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
Accessor::Accessor() : pImpl(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<Accessor> Accessor::gen() noexcept
|
||||
{
|
||||
return unique_ptr<Accessor>(new Accessor);
|
||||
}
|
||||
126
thirdparty/thorvg/src/renderer/tvgAnimation.cpp
vendored
Normal file
126
thirdparty/thorvg/src/renderer/tvgAnimation.cpp
vendored
Normal 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.0f || end > 1.0f || 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);
|
||||
}
|
||||
48
thirdparty/thorvg/src/renderer/tvgAnimation.h
vendored
Normal file
48
thirdparty/thorvg/src/renderer/tvgAnimation.h
vendored
Normal 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_
|
||||
100
thirdparty/thorvg/src/renderer/tvgBinaryDesc.h
vendored
Normal file
100
thirdparty/thorvg/src/renderer/tvgBinaryDesc.h
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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_BINARY_DESC_H_
|
||||
#define _TVG_BINARY_DESC_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 "001500" //Major 00, Minor 15, 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
|
||||
|
||||
|
||||
//TODO: Keep this for the compatibility, Remove in TVG 1.0 release
|
||||
//Scene
|
||||
#define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30
|
||||
|
||||
|
||||
//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_BINARY_DESC_H_
|
||||
93
thirdparty/thorvg/src/renderer/tvgCanvas.cpp
vendored
Normal file
93
thirdparty/thorvg/src/renderer/tvgCanvas.cpp
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
Result Canvas::reserve(TVG_UNUSED uint32_t n) noexcept
|
||||
{
|
||||
return Result::NonSupport;
|
||||
}
|
||||
|
||||
|
||||
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 free) noexcept
|
||||
{
|
||||
return pImpl->clear(free);
|
||||
}
|
||||
|
||||
|
||||
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::viewport(int32_t x, int32_t y, int32_t w, int32_t h) noexcept
|
||||
{
|
||||
return pImpl->viewport(x, y, w, h);
|
||||
}
|
||||
|
||||
|
||||
Result Canvas::sync() noexcept
|
||||
{
|
||||
return pImpl->sync();
|
||||
}
|
||||
155
thirdparty/thorvg/src/renderer/tvgCanvas.h
vendored
Normal file
155
thirdparty/thorvg/src/renderer/tvgCanvas.h
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
|
||||
enum Status : uint8_t {Synced = 0, Updating, Drawing, Damaged};
|
||||
|
||||
struct Canvas::Impl
|
||||
{
|
||||
list<Paint*> paints;
|
||||
RenderMethod* renderer;
|
||||
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
|
||||
Status status = Status::Synced;
|
||||
|
||||
Impl(RenderMethod* pRenderer) : renderer(pRenderer)
|
||||
{
|
||||
renderer->ref();
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
//make it sure any deferred jobs
|
||||
renderer->sync();
|
||||
renderer->clear();
|
||||
|
||||
clearPaints();
|
||||
|
||||
if (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 cannot push paints during rendering.
|
||||
if (status == Status::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 free)
|
||||
{
|
||||
//Clear render target before drawing
|
||||
if (!renderer->clear()) return Result::InsufficientCondition;
|
||||
|
||||
//Free paints
|
||||
if (free) clearPaints();
|
||||
|
||||
status = Status::Synced;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Result update(Paint* paint, bool force)
|
||||
{
|
||||
if (paints.empty() || status == Status::Drawing) return Result::InsufficientCondition;
|
||||
|
||||
Array<RenderData> clips;
|
||||
auto flag = RenderUpdateFlag::None;
|
||||
if (status == Status::Damaged || force) flag = RenderUpdateFlag::All;
|
||||
|
||||
auto m = Matrix{1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
|
||||
if (paint) {
|
||||
paint->pImpl->update(renderer, m, clips, 255, flag);
|
||||
} else {
|
||||
for (auto paint : paints) {
|
||||
paint->pImpl->update(renderer, m, clips, 255, flag);
|
||||
}
|
||||
}
|
||||
status = Status::Updating;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Result draw()
|
||||
{
|
||||
if (status == Status::Damaged) update(nullptr, false);
|
||||
if (status == Status::Drawing || paints.empty() || !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;
|
||||
|
||||
status = Status::Drawing;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Result sync()
|
||||
{
|
||||
if (status == Status::Synced || status == Status::Damaged) return Result::InsufficientCondition;
|
||||
|
||||
if (renderer->sync()) {
|
||||
status = Status::Synced;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
return Result::Unknown;
|
||||
}
|
||||
|
||||
Result viewport(int32_t x, int32_t y, int32_t w, int32_t h)
|
||||
{
|
||||
if (status != Status::Damaged && status != Status::Synced) return Result::InsufficientCondition;
|
||||
|
||||
RenderRegion val = {x, y, w, h};
|
||||
//intersect if the target buffer is already set.
|
||||
auto surface = renderer->mainSurface();
|
||||
if (surface && surface->w > 0 && surface->h > 0) {
|
||||
val.intersect({0, 0, (int32_t)surface->w, (int32_t)surface->h});
|
||||
}
|
||||
if (vport == val) return Result::Success;
|
||||
renderer->viewport(val);
|
||||
vport = val;
|
||||
status = Status::Damaged;
|
||||
return Result::Success;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _TVG_CANVAS_H_ */
|
||||
93
thirdparty/thorvg/src/renderer/tvgCommon.h
vendored
Normal file
93
thirdparty/thorvg/src/renderer/tvgCommon.h
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
//for debugging
|
||||
#if 0
|
||||
#include <sys/time.h>
|
||||
static inline double THORVG_TIMESTAMP()
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (tv.tv_sec + tv.tv_usec / 1000000.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //_TVG_COMMON_H_
|
||||
259
thirdparty/thorvg/src/renderer/tvgFill.cpp
vendored
Normal file
259
thirdparty/thorvg/src/renderer/tvgFill.cpp
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED uint32_t Fill::identifier() const noexcept
|
||||
{
|
||||
return (uint32_t) type();
|
||||
}
|
||||
|
||||
|
||||
RadialGradient::RadialGradient():pImpl(new Impl())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED uint32_t RadialGradient::identifier() noexcept
|
||||
{
|
||||
return (uint32_t) Type::RadialGradient;
|
||||
}
|
||||
|
||||
|
||||
Type RadialGradient::type() const noexcept
|
||||
{
|
||||
return Type::RadialGradient;
|
||||
}
|
||||
|
||||
|
||||
LinearGradient::LinearGradient():pImpl(new Impl())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED uint32_t LinearGradient::identifier() noexcept
|
||||
{
|
||||
return (uint32_t) Type::LinearGradient;
|
||||
}
|
||||
|
||||
|
||||
Type LinearGradient::type() const noexcept
|
||||
{
|
||||
return Type::LinearGradient;
|
||||
}
|
||||
111
thirdparty/thorvg/src/renderer/tvgFill.h
vendored
Normal file
111
thirdparty/thorvg/src/renderer/tvgFill.h
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
~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));
|
||||
if (cnt > 0) 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_
|
||||
62
thirdparty/thorvg/src/renderer/tvgFrameModule.h
vendored
Normal file
62
thirdparty/thorvg/src/renderer/tvgFrameModule.h
vendored
Normal 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_
|
||||
93
thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp
vendored
Normal file
93
thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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(nullptr)
|
||||
#else
|
||||
GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(nullptr)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
GlCanvas::~GlCanvas()
|
||||
{
|
||||
delete(pImpl);
|
||||
}
|
||||
|
||||
|
||||
Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
|
||||
{
|
||||
#ifdef THORVG_GL_RASTER_SUPPORT
|
||||
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
//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;
|
||||
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
|
||||
renderer->viewport(Canvas::pImpl->vport);
|
||||
|
||||
//Paints must be updated again with this new target.
|
||||
Canvas::pImpl->status = Status::Damaged;
|
||||
|
||||
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;
|
||||
}
|
||||
179
thirdparty/thorvg/src/renderer/tvgInitializer.cpp
vendored
Normal file
179
thirdparty/thorvg/src/renderer/tvgInitializer.cpp
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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(uint32_t* major, uint32_t* minor, uint32_t* micro)
|
||||
{
|
||||
auto VER = THORVG_VERSION_STRING;
|
||||
auto p = VER;
|
||||
const char* x;
|
||||
|
||||
if (!(x = strchr(p, '.'))) return false;
|
||||
uint32_t majorVal = atoi(p);
|
||||
p = x + 1;
|
||||
|
||||
if (!(x = strchr(p, '.'))) return false;
|
||||
uint32_t minorVal = atoi(p);
|
||||
p = x + 1;
|
||||
|
||||
uint32_t microVal = atoi(p);
|
||||
|
||||
char sum[7];
|
||||
snprintf(sum, sizeof(sum), "%d%02d%02d", majorVal, minorVal, microVal);
|
||||
_version = atoi(sum);
|
||||
|
||||
if (major) *major = majorVal;
|
||||
if (minor) *minor = minorVal;
|
||||
if (micro) *micro = microVal;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept
|
||||
{
|
||||
auto nonSupport = true;
|
||||
if (static_cast<int>(engine) == 0) return Result::InvalidArguments;
|
||||
|
||||
if (engine & CanvasEngine::Sw) {
|
||||
#ifdef THORVG_SW_RASTER_SUPPORT
|
||||
if (!SwRenderer::init(threads)) return Result::FailedAllocation;
|
||||
nonSupport = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (engine & CanvasEngine::Gl) {
|
||||
#ifdef THORVG_GL_RASTER_SUPPORT
|
||||
if (!GlRenderer::init(threads)) return Result::FailedAllocation;
|
||||
nonSupport = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (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(nullptr, nullptr, nullptr)) 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 (static_cast<int>(engine) == 0) return Result::InvalidArguments;
|
||||
|
||||
if (engine & CanvasEngine::Sw) {
|
||||
#ifdef THORVG_SW_RASTER_SUPPORT
|
||||
if (!SwRenderer::term()) return Result::InsufficientCondition;
|
||||
nonSupport = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (engine & CanvasEngine::Gl) {
|
||||
#ifdef THORVG_GL_RASTER_SUPPORT
|
||||
if (!GlRenderer::term()) return Result::InsufficientCondition;
|
||||
nonSupport = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
|
||||
const char* Initializer::version(uint32_t* major, uint32_t* minor, uint32_t* micro) noexcept
|
||||
{
|
||||
if ((!major && ! minor && !micro) || _buildVersionInfo(major, minor, micro)) return THORVG_VERSION_STRING;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
uint16_t THORVG_VERSION_NUMBER()
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
43
thirdparty/thorvg/src/renderer/tvgIteratorAccessor.h
vendored
Normal file
43
thirdparty/thorvg/src/renderer/tvgIteratorAccessor.h
vendored
Normal 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_
|
||||
117
thirdparty/thorvg/src/renderer/tvgLoadModule.h
vendored
Normal file
117
thirdparty/thorvg/src/renderer/tvgLoadModule.h
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 <atomic>
|
||||
#include "tvgCommon.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
|
||||
atomic<uint16_t> sharing{}; //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, 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 atomic<ColorSpace> cs; //desired value
|
||||
|
||||
float w = 0, h = 0; //default image size
|
||||
RenderSurface surface;
|
||||
|
||||
ImageLoader(FileType type) : LoadModule(type) {}
|
||||
|
||||
virtual bool animatable() { return false; } //true if this loader supports animation.
|
||||
virtual Paint* paint() { return nullptr; }
|
||||
|
||||
virtual RenderSurface* bitmap()
|
||||
{
|
||||
if (surface.data) return &surface;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct FontMetrics
|
||||
{
|
||||
//TODO: add necessary metrics
|
||||
float minw;
|
||||
};
|
||||
|
||||
|
||||
struct FontLoader : LoadModule
|
||||
{
|
||||
FontLoader(FileType type) : LoadModule(type) {}
|
||||
|
||||
using LoadModule::read;
|
||||
|
||||
virtual bool read(Shape* shape, char* text, FontMetrics& out) = 0;
|
||||
virtual float transform(Paint* paint, FontMetrics& mertrics, float fontSize, bool italic) = 0;
|
||||
};
|
||||
|
||||
#endif //_TVG_LOAD_MODULE_H_
|
||||
468
thirdparty/thorvg/src/renderer/tvgLoader.cpp
vendored
Normal file
468
thirdparty/thorvg/src/renderer/tvgLoader.cpp
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
* 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 <atomic>
|
||||
#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 */
|
||||
/************************************************************************/
|
||||
|
||||
//TODO: remove it.
|
||||
atomic<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;
|
||||
}
|
||||
|
||||
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
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;
|
||||
|
||||
auto key = HASH_KEY(data);
|
||||
ScopedLock lock(_key);
|
||||
|
||||
auto loader = _activeLoaders.head;
|
||||
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) {
|
||||
if (loader->type != FileType::Ttf) {
|
||||
loader = loader->next;
|
||||
continue;
|
||||
}
|
||||
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()) {
|
||||
_activeLoaders.remove(loader);
|
||||
}
|
||||
delete(loader);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
LoadModule* LoaderMgr::loader(const string& path, bool* invalid)
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
*invalid = false;
|
||||
|
||||
//TODO: svg & lottie is not sharable.
|
||||
auto allowCache = true;
|
||||
auto ext = path.substr(path.find_last_of(".") + 1);
|
||||
if (!ext.compare("svg") || !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);
|
||||
}
|
||||
//Unknown 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;
|
||||
#endif
|
||||
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, 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, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//Unknown 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, 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 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, copy)) {
|
||||
if (!copy) {
|
||||
loader->hashkey = HASH_KEY((const char*)data);
|
||||
ScopedLock lock(_key);
|
||||
_activeLoaders.back(loader);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
delete(loader);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
//loads fonts from memory - loader is cached (regardless of copy value) in order to access it while setting font
|
||||
LoadModule* LoaderMgr::loader(const char* name, const char* data, uint32_t size, TVG_UNUSED const string& mimeType, bool copy)
|
||||
{
|
||||
#ifdef THORVG_TTF_LOADER_SUPPORT
|
||||
//TODO: add check for mimetype ?
|
||||
if (auto loader = _findFromCache(name)) return loader;
|
||||
|
||||
//function is dedicated for ttf loader (the only supported font loader)
|
||||
auto loader = new TtfLoader;
|
||||
if (loader->open(data, size, copy)) {
|
||||
loader->hashpath = strdup(name);
|
||||
loader->pathcache = true;
|
||||
ScopedLock lock(_key);
|
||||
_activeLoaders.back(loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
TVGLOG("LOADER", "The font data \"%s\" could not be loaded.", name);
|
||||
delete(loader);
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
41
thirdparty/thorvg/src/renderer/tvgLoader.h
vendored
Normal file
41
thirdparty/thorvg/src/renderer/tvgLoader.h
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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, bool copy);
|
||||
static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, bool copy);
|
||||
static LoadModule* loader(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy);
|
||||
static LoadModule* loader(const char* key);
|
||||
static bool retrieve(const string& path);
|
||||
static bool retrieve(LoadModule* loader);
|
||||
};
|
||||
|
||||
#endif //_TVG_LOADER_H_
|
||||
531
thirdparty/thorvg/src/renderer/tvgPaint.cpp
vendored
Normal file
531
thirdparty/thorvg/src/renderer/tvgPaint.cpp
vendored
Normal file
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
* 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 (paint->type()) { \
|
||||
case Type::Shape: ret = P((Shape*)paint)->METHOD; break; \
|
||||
case Type::Scene: ret = P((Scene*)paint)->METHOD; break; \
|
||||
case Type::Picture: ret = P((Picture*)paint)->METHOD; break; \
|
||||
case Type::Text: ret = P((Text*)paint)->METHOD; break; \
|
||||
default: ret = {}; \
|
||||
}
|
||||
|
||||
|
||||
static Result _clipRect(RenderMethod* renderer, const Point* pts, const Matrix& pm, const Matrix& rm, RenderRegion& before)
|
||||
{
|
||||
//sorting
|
||||
Point tmp[4];
|
||||
Point min = {FLT_MAX, FLT_MAX};
|
||||
Point max = {0.0f, 0.0f};
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tmp[i] = pts[i];
|
||||
tmp[i] *= rm;
|
||||
tmp[i] *= pm;
|
||||
if (tmp[i].x < min.x) min.x = tmp[i].x;
|
||||
if (tmp[i].x > max.x) max.x = tmp[i].x;
|
||||
if (tmp[i].y < min.y) min.y = tmp[i].y;
|
||||
if (tmp[i].y > max.y) max.y = tmp[i].y;
|
||||
}
|
||||
|
||||
float region[4] = {float(before.x), float(before.x + before.w), float(before.y), float(before.y + before.h)};
|
||||
|
||||
//figure out if the clipper is a superset of the current viewport(before) region
|
||||
if (min.x <= region[0] && max.x >= region[1] && min.y <= region[2] && max.y >= region[3]) {
|
||||
//viewport region is same, nothing to do.
|
||||
return Result::Success;
|
||||
//figure out if the clipper is totally outside of the viewport
|
||||
} else if (max.x <= region[0] || min.x >= region[1] || max.y <= region[2] || min.y >= region[3]) {
|
||||
renderer->viewport({0, 0, 0, 0});
|
||||
return Result::Success;
|
||||
}
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Matrix& pm, RenderRegion& before)
|
||||
{
|
||||
/* 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;
|
||||
|
||||
auto& rm = P(cmpTarget)->transform();
|
||||
|
||||
//No rotation and no skewing, still can try out clipping the rect region.
|
||||
auto tryClip = false;
|
||||
|
||||
if ((!rightAngle(pm) || skewed(pm))) tryClip = true;
|
||||
if ((!rightAngle(rm) || skewed(rm))) tryClip = true;
|
||||
|
||||
if (tryClip) return _clipRect(renderer, pts, pm, rm, before);
|
||||
|
||||
//Perpendicular Rectangle?
|
||||
auto pt1 = pts + 0;
|
||||
auto pt2 = pts + 1;
|
||||
auto pt3 = pts + 2;
|
||||
auto pt4 = pts + 3;
|
||||
|
||||
if ((tvg::equal(pt1->x, pt2->x) && tvg::equal(pt2->y, pt3->y) && tvg::equal(pt3->x, pt4->x) && tvg::equal(pt1->y, pt4->y)) ||
|
||||
(tvg::equal(pt2->x, pt3->x) && tvg::equal(pt1->y, pt2->y) && tvg::equal(pt1->x, pt4->x) && tvg::equal(pt3->y, pt4->y))) {
|
||||
|
||||
RenderRegion after;
|
||||
|
||||
auto v1 = *pt1;
|
||||
auto v2 = *pt3;
|
||||
v1 *= rm;
|
||||
v2 *= rm;
|
||||
v1 *= pm;
|
||||
v2 *= pm;
|
||||
|
||||
//sorting
|
||||
if (v1.x > v2.x) std::swap(v1.x, v2.x);
|
||||
if (v1.y > v2.y) std::swap(v1.y, v2.y);
|
||||
|
||||
after.x = static_cast<int32_t>(v1.x);
|
||||
after.y = static_cast<int32_t>(v1.y);
|
||||
after.w = static_cast<int32_t>(ceil(v2.x - after.x));
|
||||
after.h = static_cast<int32_t>(ceil(v2.y - after.y));
|
||||
|
||||
if (after.w < 0) after.w = 0;
|
||||
if (after.h < 0) after.h = 0;
|
||||
|
||||
after.intersect(before);
|
||||
renderer->viewport(after);
|
||||
|
||||
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)
|
||||
{
|
||||
if (ret) ret->composite(nullptr, CompositeMethod::None);
|
||||
|
||||
PAINT_METHOD(ret, duplicate(ret));
|
||||
|
||||
//duplicate Transform
|
||||
ret->pImpl->tr = tr;
|
||||
ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
|
||||
|
||||
ret->pImpl->opacity = opacity;
|
||||
|
||||
if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
|
||||
if (clipper) ret->pImpl->clip(clipper->duplicate());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool Paint::Impl::rotate(float degree)
|
||||
{
|
||||
if (tr.overriding) return false;
|
||||
if (tvg::equal(degree, tr.degree)) return true;
|
||||
tr.degree = degree;
|
||||
renderFlag |= RenderUpdateFlag::Transform;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Paint::Impl::scale(float factor)
|
||||
{
|
||||
if (tr.overriding) return false;
|
||||
if (tvg::equal(factor, tr.scale)) return true;
|
||||
tr.scale = factor;
|
||||
renderFlag |= RenderUpdateFlag::Transform;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Paint::Impl::translate(float x, float y)
|
||||
{
|
||||
if (tr.overriding) return false;
|
||||
if (tvg::equal(x, tr.m.e13) && tvg::equal(y, tr.m.e23)) return true;
|
||||
tr.m.e13 = x;
|
||||
tr.m.e23 = y;
|
||||
renderFlag |= RenderUpdateFlag::Transform;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Paint::Impl::render(RenderMethod* renderer)
|
||||
{
|
||||
if (opacity == 0) return true;
|
||||
|
||||
RenderCompositor* cmp = nullptr;
|
||||
|
||||
if (compData && !(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), CompositionFlag::Masking);
|
||||
if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) {
|
||||
compData->target->pImpl->render(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
|
||||
|
||||
bool ret;
|
||||
PAINT_METHOD(ret, render(renderer));
|
||||
|
||||
if (cmp) renderer->endComposite(cmp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, 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) tr.update();
|
||||
|
||||
/* 1. Composition Pre Processing */
|
||||
RenderData trd = nullptr; //composite target render data
|
||||
RenderRegion viewport;
|
||||
Result compFastTrack = Result::InsufficientCondition;
|
||||
|
||||
if (compData) {
|
||||
auto target = compData->target;
|
||||
auto method = compData->method;
|
||||
P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset
|
||||
|
||||
/* If the transformation has no rotational factors and the Alpha(InvAlpha)Masking involves a simple rectangle,
|
||||
we can optimize by using the viewport instead of the regular AlphaMasking sequence for improved performance. */
|
||||
if (target->type() == Type::Shape) {
|
||||
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) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) {
|
||||
viewport = renderer->viewport();
|
||||
if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
|
||||
P(target)->ctxFlag |= ContextFlag::FastTrack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compFastTrack == Result::InsufficientCondition) {
|
||||
trd = P(target)->update(renderer, pm, clips, 255, pFlag, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. Clipping */
|
||||
if (this->clipper) {
|
||||
auto pclip = P(this->clipper);
|
||||
if (pclip->renderFlag | static_cast<Shape*>(this->clipper)->pImpl->rFlag) renderFlag |= RenderUpdateFlag::Clip;
|
||||
pclip->ctxFlag &= ~ContextFlag::FastTrack; //reset
|
||||
viewport = renderer->viewport();
|
||||
/* TODO: Intersect the clipper's clipper, if both are FastTrack.
|
||||
Update the subsequent clipper first and check its ctxFlag. */
|
||||
if (!pclip->clipper && _compFastTrack(renderer, this->clipper, pm, viewport) == Result::Success) {
|
||||
pclip->ctxFlag |= ContextFlag::FastTrack;
|
||||
compFastTrack = Result::Success;
|
||||
} else {
|
||||
trd = pclip->update(renderer, pm, clips, 255, pFlag, true);
|
||||
clips.push(trd);
|
||||
compFastTrack = Result::InsufficientCondition;
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. Main Update */
|
||||
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
|
||||
renderFlag = RenderUpdateFlag::None;
|
||||
opacity = MULTIPLY(opacity, this->opacity);
|
||||
|
||||
RenderData rd = nullptr;
|
||||
|
||||
tr.cm = pm * tr.m;
|
||||
PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
|
||||
|
||||
/* 4. Composition Post Processing */
|
||||
if (compFastTrack == Result::Success) renderer->viewport(viewport);
|
||||
else if (this->clipper) clips.pop();
|
||||
|
||||
return rd;
|
||||
}
|
||||
|
||||
|
||||
bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin)
|
||||
{
|
||||
bool ret;
|
||||
const auto& m = this->transform(origin);
|
||||
|
||||
//Case: No transformed, quick return!
|
||||
if (!transformed || identity(&m)) {
|
||||
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++) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
void Paint::Impl::reset()
|
||||
{
|
||||
if (clipper) {
|
||||
delete(clipper);
|
||||
clipper = nullptr;
|
||||
}
|
||||
|
||||
if (compData) {
|
||||
if (P(compData->target)->unref() == 0) delete(compData->target);
|
||||
free(compData);
|
||||
compData = nullptr;
|
||||
}
|
||||
|
||||
tvg::identity(&tr.m);
|
||||
tr.degree = 0.0f;
|
||||
tr.scale = 1.0f;
|
||||
tr.overriding = false;
|
||||
|
||||
blendMethod = BlendMethod::Normal;
|
||||
renderFlag = RenderUpdateFlag::None;
|
||||
ctxFlag = ContextFlag::Default;
|
||||
opacity = 255;
|
||||
paint->id = 0;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* 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::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
Result Paint::scale(float factor) noexcept
|
||||
{
|
||||
if (pImpl->scale(factor)) return Result::Success;
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
Result Paint::translate(float x, float y) noexcept
|
||||
{
|
||||
if (pImpl->translate(x, y)) return Result::Success;
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
Result Paint::transform(const Matrix& m) noexcept
|
||||
{
|
||||
if (pImpl->transform(m)) return Result::Success;
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
Matrix Paint::transform() noexcept
|
||||
{
|
||||
return pImpl->transform();
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept
|
||||
{
|
||||
return this->bounds(x, y, w, h, false);
|
||||
}
|
||||
|
||||
|
||||
Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept
|
||||
{
|
||||
if (pImpl->bounds(x, y, w, h, transformed, true, transformed)) return Result::Success;
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
Paint* Paint::duplicate() const noexcept
|
||||
{
|
||||
return pImpl->duplicate();
|
||||
}
|
||||
|
||||
|
||||
Result Paint::clip(std::unique_ptr<Paint> clipper) noexcept
|
||||
{
|
||||
auto p = clipper.release();
|
||||
|
||||
if (p && p->type() != Type::Shape) {
|
||||
TVGERR("RENDERER", "Clipping only supports the Shape!");
|
||||
return Result::NonSupport;
|
||||
}
|
||||
pImpl->clip(p);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
|
||||
{
|
||||
//TODO: remove. Keep this for the backward compatibility
|
||||
if (target && method == CompositeMethod::ClipPath) return clip(std::move(target));
|
||||
|
||||
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 {
|
||||
//TODO: remove. Keep this for the backward compatibility
|
||||
if (pImpl->clipper) {
|
||||
if (target) *target = pImpl->clipper;
|
||||
return CompositeMethod::ClipPath;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED uint32_t Paint::identifier() const noexcept
|
||||
{
|
||||
return (uint32_t) type();
|
||||
}
|
||||
|
||||
|
||||
Result Paint::blend(BlendMethod method) noexcept
|
||||
{
|
||||
//TODO: Remove later
|
||||
if (method == BlendMethod::Hue || method == BlendMethod::Saturation || method == BlendMethod::Color || method == BlendMethod::Luminosity || method == BlendMethod::HardMix) return Result::NonSupport;
|
||||
|
||||
if (pImpl->blendMethod != method) {
|
||||
pImpl->blendMethod = method;
|
||||
pImpl->renderFlag |= RenderUpdateFlag::Blend;
|
||||
}
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
179
thirdparty/thorvg/src/renderer/tvgPaint.h
vendored
Normal file
179
thirdparty/thorvg/src/renderer/tvgPaint.h
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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 {Default = 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;
|
||||
Composite* compData = nullptr;
|
||||
Paint* clipper = nullptr;
|
||||
RenderMethod* renderer = nullptr;
|
||||
struct {
|
||||
Matrix m; //input matrix
|
||||
Matrix cm; //multipled parents matrix
|
||||
float degree; //rotation degree
|
||||
float scale; //scale factor
|
||||
bool overriding; //user transform?
|
||||
|
||||
void update()
|
||||
{
|
||||
if (overriding) return;
|
||||
m.e11 = 1.0f;
|
||||
m.e12 = 0.0f;
|
||||
m.e21 = 0.0f;
|
||||
m.e22 = 1.0f;
|
||||
m.e31 = 0.0f;
|
||||
m.e32 = 0.0f;
|
||||
m.e33 = 1.0f;
|
||||
tvg::scale(&m, scale, scale);
|
||||
tvg::rotate(&m, degree);
|
||||
}
|
||||
} tr;
|
||||
RenderUpdateFlag renderFlag = RenderUpdateFlag::None;
|
||||
BlendMethod blendMethod;
|
||||
uint8_t ctxFlag;
|
||||
uint8_t opacity;
|
||||
uint8_t refCnt = 0; //reference count
|
||||
|
||||
Impl(Paint* pnt) : paint(pnt)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
if (compData) {
|
||||
if (P(compData->target)->unref() == 0) delete(compData->target);
|
||||
free(compData);
|
||||
}
|
||||
if (clipper && P(clipper)->unref() == 0) delete(clipper);
|
||||
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 (&tr.m != &m) tr.m = m;
|
||||
tr.overriding = true;
|
||||
renderFlag |= RenderUpdateFlag::Transform;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Matrix& transform(bool origin = false)
|
||||
{
|
||||
//update transform
|
||||
if (renderFlag & RenderUpdateFlag::Transform) tr.update();
|
||||
if (origin) return tr.cm;
|
||||
return tr.m;
|
||||
}
|
||||
|
||||
void clip(Paint* clp)
|
||||
{
|
||||
if (this->clipper) {
|
||||
P(this->clipper)->unref();
|
||||
if (this->clipper != clp && P(this->clipper)->refCnt == 0) {
|
||||
delete(this->clipper);
|
||||
}
|
||||
}
|
||||
this->clipper = clp;
|
||||
if (!clp) return;
|
||||
|
||||
P(clipper)->ref();
|
||||
}
|
||||
|
||||
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, bool origin = false);
|
||||
RenderData update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
|
||||
bool render(RenderMethod* renderer);
|
||||
Paint* duplicate(Paint* ret = nullptr);
|
||||
void reset();
|
||||
};
|
||||
}
|
||||
|
||||
#endif //_TVG_PAINT_H_
|
||||
238
thirdparty/thorvg/src/renderer/tvgPicture.cpp
vendored
Normal file
238
thirdparty/thorvg/src/renderer/tvgPicture.cpp
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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 "tvgPaint.h"
|
||||
#include "tvgPicture.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
RenderUpdateFlag Picture::Impl::load()
|
||||
{
|
||||
if (loader) {
|
||||
if (paint) {
|
||||
loader->sync();
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!surface) {
|
||||
if ((surface = loader->bitmap())) {
|
||||
return RenderUpdateFlag::Image;
|
||||
}
|
||||
}
|
||||
}
|
||||
return RenderUpdateFlag::None;
|
||||
}
|
||||
|
||||
|
||||
void Picture::Impl::queryComposition(uint8_t opacity)
|
||||
{
|
||||
cFlag = CompositionFlag::Invalid;
|
||||
|
||||
//In this case, paint(scene) would try composition itself.
|
||||
if (opacity < 255) return;
|
||||
|
||||
//Composition test
|
||||
const Paint* target;
|
||||
auto method = picture->composite(&target);
|
||||
if (!target || method == tvg::CompositeMethod::ClipPath) return;
|
||||
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return;
|
||||
|
||||
cFlag = CompositionFlag::Opacity;
|
||||
}
|
||||
|
||||
|
||||
bool Picture::Impl::render(RenderMethod* renderer)
|
||||
{
|
||||
bool ret = false;
|
||||
renderer->blend(PP(picture)->blendMethod);
|
||||
|
||||
if (surface) return renderer->renderImage(rd);
|
||||
else if (paint) {
|
||||
RenderCompositor* cmp = nullptr;
|
||||
if (cFlag) {
|
||||
cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast<CompositionFlag>(cFlag));
|
||||
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};
|
||||
}
|
||||
|
||||
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Picture::~Picture()
|
||||
{
|
||||
delete(pImpl);
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<Picture> Picture::gen() noexcept
|
||||
{
|
||||
return unique_ptr<Picture>(new Picture);
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED uint32_t Picture::identifier() noexcept
|
||||
{
|
||||
return (uint32_t) Type::Picture;
|
||||
}
|
||||
|
||||
|
||||
Type Picture::type() const noexcept
|
||||
{
|
||||
return Type::Picture;
|
||||
}
|
||||
|
||||
|
||||
Result Picture::load(const std::string& path) noexcept
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
if (path.empty()) return Result::InvalidArguments;
|
||||
|
||||
return pImpl->load(path);
|
||||
#else
|
||||
TVGLOG("RENDERER", "FILE IO is disabled!");
|
||||
return Result::NonSupport;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Result Picture::load(const char* data, uint32_t size, const string& mimeType, bool copy) noexcept
|
||||
{
|
||||
if (!data || size <= 0) return Result::InvalidArguments;
|
||||
|
||||
return pImpl->load(data, size, mimeType, copy);
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED Result Picture::load(const char* data, uint32_t size, bool copy) noexcept
|
||||
{
|
||||
return load(data, size, "", copy);
|
||||
}
|
||||
|
||||
|
||||
Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept
|
||||
{
|
||||
if (!data || w <= 0 || h <= 0) return Result::InvalidArguments;
|
||||
|
||||
return pImpl->load(data, w, h, 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;
|
||||
}
|
||||
|
||||
|
||||
const Paint* Picture::paint(uint32_t id) noexcept
|
||||
{
|
||||
struct Value
|
||||
{
|
||||
uint32_t id;
|
||||
const Paint* ret;
|
||||
} value = {id, nullptr};
|
||||
|
||||
auto cb = [](const tvg::Paint* paint, void* data) -> bool
|
||||
{
|
||||
auto p = static_cast<Value*>(data);
|
||||
if (p->id == paint->id) {
|
||||
p->ret = paint;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
tvg::Accessor::gen()->set(this, cb, &value);
|
||||
return value.ret;
|
||||
}
|
||||
206
thirdparty/thorvg/src/renderer/tvgPicture.h
vendored
Normal file
206
thirdparty/thorvg/src/renderer/tvgPicture.h
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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
|
||||
RenderSurface* surface = nullptr; //bitmap picture uses
|
||||
RenderData rd = nullptr; //engine data
|
||||
float w = 0, h = 0;
|
||||
Picture* picture = nullptr;
|
||||
uint8_t cFlag = CompositionFlag::Invalid;
|
||||
bool resizing = false;
|
||||
|
||||
void queryComposition(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 Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
|
||||
{
|
||||
auto flag = static_cast<RenderUpdateFlag>(pFlag | load());
|
||||
|
||||
if (surface) {
|
||||
if (flag == RenderUpdateFlag::None) return rd;
|
||||
|
||||
//Overriding Transformation by the desired image size
|
||||
auto sx = w / loader->w;
|
||||
auto sy = h / loader->h;
|
||||
auto scale = sx < sy ? sx : sy;
|
||||
auto m = transform * Matrix{scale, 0, 0, 0, scale, 0, 0, 0, 1};
|
||||
|
||||
rd = renderer->prepare(surface, rd, m, clips, opacity, flag);
|
||||
} else if (paint) {
|
||||
if (resizing) {
|
||||
loader->resize(paint, w, h);
|
||||
resizing = false;
|
||||
}
|
||||
queryComposition(opacity);
|
||||
rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
|
||||
}
|
||||
return rd;
|
||||
}
|
||||
|
||||
bool bounds(float* x, float* y, float* w, float* h, bool stroking)
|
||||
{
|
||||
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, bool copy)
|
||||
{
|
||||
if (paint || surface) return Result::InsufficientCondition;
|
||||
auto loader = static_cast<ImageLoader*>(LoaderMgr::loader(data, size, mimeType, copy));
|
||||
if (!loader) return Result::NonSupport;
|
||||
return load(loader);
|
||||
}
|
||||
|
||||
Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy)
|
||||
{
|
||||
if (paint || surface) return Result::InsufficientCondition;
|
||||
|
||||
auto loader = static_cast<ImageLoader*>(LoaderMgr::loader(data, w, h, copy));
|
||||
if (!loader) return Result::FailedAllocation;
|
||||
|
||||
return load(loader);
|
||||
}
|
||||
|
||||
Paint* duplicate(Paint* ret)
|
||||
{
|
||||
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
|
||||
|
||||
load();
|
||||
|
||||
auto picture = Picture::gen().release();
|
||||
auto dup = picture->pImpl;
|
||||
|
||||
if (paint) dup->paint = paint->duplicate();
|
||||
|
||||
if (loader) {
|
||||
dup->loader = loader;
|
||||
++dup->loader->sharing;
|
||||
PP(picture)->renderFlag |= RenderUpdateFlag::Image;
|
||||
}
|
||||
|
||||
dup->surface = surface;
|
||||
dup->w = w;
|
||||
dup->h = h;
|
||||
dup->resizing = resizing;
|
||||
|
||||
return picture;
|
||||
}
|
||||
|
||||
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_
|
||||
78
thirdparty/thorvg/src/renderer/tvgRender.cpp
vendored
Normal file
78
thirdparty/thorvg/src/renderer/tvgRender.cpp
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 */
|
||||
/************************************************************************/
|
||||
|
||||
uint32_t RenderMethod::ref()
|
||||
{
|
||||
ScopedLock lock(key);
|
||||
return (++refCnt);
|
||||
}
|
||||
|
||||
|
||||
uint32_t RenderMethod::unref()
|
||||
{
|
||||
ScopedLock lock(key);
|
||||
return (--refCnt);
|
||||
}
|
||||
|
||||
|
||||
void RenderRegion::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 RenderRegion::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;
|
||||
}
|
||||
496
thirdparty/thorvg/src/renderer/tvgRender.h
vendored
Normal file
496
thirdparty/thorvg/src/renderer/tvgRender.h
vendored
Normal file
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
* 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 <math.h>
|
||||
#include <cstdarg>
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgArray.h"
|
||||
#include "tvgLock.h"
|
||||
|
||||
namespace tvg
|
||||
{
|
||||
|
||||
using RenderData = void*;
|
||||
using pixel_t = uint32_t;
|
||||
|
||||
#define DASH_PATTERN_THRESHOLD 0.001f
|
||||
|
||||
enum RenderUpdateFlag : uint16_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, Clip = 256, All = 0xffff};
|
||||
enum CompositionFlag : uint8_t {Invalid = 0, Opacity = 1, Blending = 2, Masking = 4, PostProcessing = 8}; //Composition Purpose
|
||||
|
||||
static inline void operator|=(RenderUpdateFlag& a, const RenderUpdateFlag b)
|
||||
{
|
||||
a = RenderUpdateFlag(uint16_t(a) | uint16_t(b));
|
||||
}
|
||||
|
||||
//TODO: Move this in public header unifying with SwCanvas::Colorspace
|
||||
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 RenderSurface
|
||||
{
|
||||
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
|
||||
|
||||
RenderSurface()
|
||||
{
|
||||
}
|
||||
|
||||
RenderSurface(const RenderSurface* rhs)
|
||||
{
|
||||
data = rhs->data;
|
||||
stride = rhs->stride;
|
||||
w = rhs->w;
|
||||
h = rhs->h;
|
||||
cs = rhs->cs;
|
||||
channelSize = rhs->channelSize;
|
||||
premultiplied = rhs->premultiplied;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
struct RenderCompositor
|
||||
{
|
||||
CompositeMethod method;
|
||||
uint8_t opacity;
|
||||
};
|
||||
|
||||
struct RenderRegion
|
||||
{
|
||||
int32_t x, y, w, h;
|
||||
|
||||
void intersect(const RenderRegion& rhs);
|
||||
void add(const RenderRegion& rhs);
|
||||
|
||||
bool operator==(const RenderRegion& rhs) const
|
||||
{
|
||||
if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
StrokeCap cap = StrokeCap::Square;
|
||||
StrokeJoin join = StrokeJoin::Bevel;
|
||||
float miterlimit = 4.0f;
|
||||
bool strokeFirst = false;
|
||||
|
||||
struct {
|
||||
float begin = 0.0f;
|
||||
float end = 1.0f;
|
||||
bool simultaneous = true;
|
||||
} trim;
|
||||
|
||||
void operator=(const RenderStroke& rhs)
|
||||
{
|
||||
width = rhs.width;
|
||||
|
||||
memcpy(color, rhs.color, sizeof(color));
|
||||
|
||||
delete(fill);
|
||||
if (rhs.fill) fill = rhs.fill->duplicate();
|
||||
else fill = nullptr;
|
||||
|
||||
free(dashPattern);
|
||||
if (rhs.dashCnt > 0) {
|
||||
dashPattern = static_cast<float*>(malloc(sizeof(float) * rhs.dashCnt));
|
||||
memcpy(dashPattern, rhs.dashPattern, sizeof(float) * rhs.dashCnt);
|
||||
} else {
|
||||
dashPattern = nullptr;
|
||||
}
|
||||
dashCnt = rhs.dashCnt;
|
||||
dashOffset = rhs.dashOffset;
|
||||
miterlimit = rhs.miterlimit;
|
||||
cap = rhs.cap;
|
||||
join = rhs.join;
|
||||
strokeFirst = rhs.strokeFirst;
|
||||
trim = rhs.trim;
|
||||
}
|
||||
|
||||
bool strokeTrim(float& begin, float& end) const
|
||||
{
|
||||
begin = trim.begin;
|
||||
end = trim.end;
|
||||
|
||||
if (fabsf(end - begin) >= 1.0f) {
|
||||
begin = 0.0f;
|
||||
end = 1.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto loop = true;
|
||||
|
||||
if (begin > 1.0f && end > 1.0f) loop = false;
|
||||
if (begin < 0.0f && end < 0.0f) loop = false;
|
||||
if (begin >= 0.0f && begin <= 1.0f && end >= 0.0f && end <= 1.0f) loop = false;
|
||||
|
||||
if (begin > 1.0f) begin -= 1.0f;
|
||||
if (begin < 0.0f) begin += 1.0f;
|
||||
if (end > 1.0f) end -= 1.0f;
|
||||
if (end < 0.0f) end += 1.0f;
|
||||
|
||||
if ((loop && begin < end) || (!loop && begin > end)) std::swap(begin, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
~RenderStroke()
|
||||
{
|
||||
free(dashPattern);
|
||||
delete(fill);
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderShape
|
||||
{
|
||||
struct
|
||||
{
|
||||
Array<PathCommand> cmds;
|
||||
Array<Point> pts;
|
||||
} path;
|
||||
|
||||
Fill *fill = nullptr;
|
||||
RenderStroke *stroke = nullptr;
|
||||
uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a
|
||||
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 (fabsf(stroke->trim.end - stroke->trim.begin) >= 1.0f) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool strokeColor(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;;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderEffect
|
||||
{
|
||||
RenderData rd = nullptr;
|
||||
RenderRegion extend = {0, 0, 0, 0};
|
||||
SceneEffect type;
|
||||
bool valid = false;
|
||||
|
||||
virtual ~RenderEffect()
|
||||
{
|
||||
free(rd);
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderEffectGaussianBlur : RenderEffect
|
||||
{
|
||||
float sigma;
|
||||
uint8_t direction; //0: both, 1: horizontal, 2: vertical
|
||||
uint8_t border; //0: duplicate, 1: wrap
|
||||
uint8_t quality; //0 ~ 100 (optional)
|
||||
|
||||
static RenderEffectGaussianBlur* gen(va_list& args)
|
||||
{
|
||||
auto inst = new RenderEffectGaussianBlur;
|
||||
inst->sigma = std::max((float) va_arg(args, double), 0.0f);
|
||||
inst->direction = std::min(va_arg(args, int), 2);
|
||||
inst->border = std::min(va_arg(args, int), 1);
|
||||
inst->quality = std::min(va_arg(args, int), 100);
|
||||
inst->type = SceneEffect::GaussianBlur;
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderEffectDropShadow : RenderEffect
|
||||
{
|
||||
uint8_t color[4]; //rgba
|
||||
float angle;
|
||||
float distance;
|
||||
float sigma;
|
||||
uint8_t quality; //0 ~ 100 (optional)
|
||||
|
||||
static RenderEffectDropShadow* gen(va_list& args)
|
||||
{
|
||||
auto inst = new RenderEffectDropShadow;
|
||||
inst->color[0] = va_arg(args, int);
|
||||
inst->color[1] = va_arg(args, int);
|
||||
inst->color[2] = va_arg(args, int);
|
||||
inst->color[3] = va_arg(args, int);
|
||||
inst->angle = (float) va_arg(args, double);
|
||||
inst->distance = (float) va_arg(args, double);
|
||||
inst->sigma = std::max((float) va_arg(args, double), 0.0f);
|
||||
inst->quality = std::min(va_arg(args, int), 100);
|
||||
inst->type = SceneEffect::DropShadow;
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderEffectFill : RenderEffect
|
||||
{
|
||||
uint8_t color[4]; //rgba
|
||||
|
||||
static RenderEffectFill* gen(va_list& args)
|
||||
{
|
||||
auto inst = new RenderEffectFill;
|
||||
inst->color[0] = va_arg(args, int);
|
||||
inst->color[1] = va_arg(args, int);
|
||||
inst->color[2] = va_arg(args, int);
|
||||
inst->color[3] = va_arg(args, int);
|
||||
inst->type = SceneEffect::Fill;
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderEffectTint : RenderEffect
|
||||
{
|
||||
uint8_t black[3]; //rgb
|
||||
uint8_t white[3]; //rgb
|
||||
uint8_t intensity; //0 - 255
|
||||
|
||||
static RenderEffectTint* gen(va_list& args)
|
||||
{
|
||||
auto inst = new RenderEffectTint;
|
||||
inst->black[0] = va_arg(args, int);
|
||||
inst->black[1] = va_arg(args, int);
|
||||
inst->black[2] = va_arg(args, int);
|
||||
inst->white[0] = va_arg(args, int);
|
||||
inst->white[1] = va_arg(args, int);
|
||||
inst->white[2] = va_arg(args, int);
|
||||
inst->intensity = (uint8_t)(va_arg(args, double) * 2.55);
|
||||
inst->type = SceneEffect::Tint;
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
struct RenderEffectTritone : RenderEffect
|
||||
{
|
||||
uint8_t shadow[3]; //rgb
|
||||
uint8_t midtone[3]; //rgb
|
||||
uint8_t highlight[3]; //rgb
|
||||
|
||||
static RenderEffectTritone* gen(va_list& args)
|
||||
{
|
||||
auto inst = new RenderEffectTritone;
|
||||
inst->shadow[0] = va_arg(args, int);
|
||||
inst->shadow[1] = va_arg(args, int);
|
||||
inst->shadow[2] = va_arg(args, int);
|
||||
inst->midtone[0] = va_arg(args, int);
|
||||
inst->midtone[1] = va_arg(args, int);
|
||||
inst->midtone[2] = va_arg(args, int);
|
||||
inst->highlight[0] = va_arg(args, int);
|
||||
inst->highlight[1] = va_arg(args, int);
|
||||
inst->highlight[2] = va_arg(args, int);
|
||||
inst->type = SceneEffect::Tritone;
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
class RenderMethod
|
||||
{
|
||||
private:
|
||||
uint32_t refCnt = 0; //reference count
|
||||
Key key;
|
||||
|
||||
public:
|
||||
uint32_t ref();
|
||||
uint32_t unref();
|
||||
|
||||
virtual ~RenderMethod() {}
|
||||
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
|
||||
virtual RenderData prepare(RenderSurface* surface, RenderData data, const Matrix& 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 const RenderSurface* mainSurface() = 0;
|
||||
|
||||
virtual bool clear() = 0;
|
||||
virtual bool sync() = 0;
|
||||
|
||||
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) = 0;
|
||||
virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
|
||||
virtual bool endComposite(RenderCompositor* cmp) = 0;
|
||||
|
||||
virtual void prepare(RenderEffect* effect, const Matrix& transform) = 0;
|
||||
virtual bool region(RenderEffect* effect) = 0;
|
||||
virtual bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 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:
|
||||
case CompositeMethod::LightenMask:
|
||||
case CompositeMethod::DarkenMask:
|
||||
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:
|
||||
case CompositeMethod::LightenMask:
|
||||
case CompositeMethod::DarkenMask:
|
||||
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_
|
||||
43
thirdparty/thorvg/src/renderer/tvgSaveModule.h
vendored
Normal file
43
thirdparty/thorvg/src/renderer/tvgSaveModule.h
vendored
Normal 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, const string& path, bool compress) = 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_
|
||||
204
thirdparty/thorvg/src/renderer/tvgSaver.cpp
vendored
Normal file
204
thirdparty/thorvg/src/renderer/tvgSaver.cpp
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#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(std::unique_ptr<Paint> paint, const string& path, bool compress) noexcept
|
||||
{
|
||||
auto p = paint.release();
|
||||
if (!p) return Result::MemoryCorruption;
|
||||
|
||||
//Already on saving another resource.
|
||||
if (pImpl->saveModule) {
|
||||
if (P(p)->refCnt == 0) delete(p);
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
if (auto saveModule = _find(path)) {
|
||||
if (saveModule->save(p, path, compress)) {
|
||||
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 (tvg::zero(a->totalFrame())) {
|
||||
if (remove) delete(a);
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
//Already on saving another 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);
|
||||
}
|
||||
151
thirdparty/thorvg/src/renderer/tvgScene.cpp
vendored
Normal file
151
thirdparty/thorvg/src/renderer/tvgScene.cpp
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 <cstdarg>
|
||||
#include "tvgScene.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
Result Scene::Impl::resetEffects()
|
||||
{
|
||||
if (effects) {
|
||||
for (auto e = effects->begin(); e < effects->end(); ++e) {
|
||||
delete(*e);
|
||||
}
|
||||
delete(effects);
|
||||
effects = nullptr;
|
||||
}
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
Scene::Scene() : pImpl(new Impl(this))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Scene::~Scene()
|
||||
{
|
||||
delete(pImpl);
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<Scene> Scene::gen() noexcept
|
||||
{
|
||||
return unique_ptr<Scene>(new Scene);
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED uint32_t Scene::identifier() noexcept
|
||||
{
|
||||
return (uint32_t) Type::Scene;
|
||||
}
|
||||
|
||||
|
||||
Type Scene::type() const noexcept
|
||||
{
|
||||
return Type::Scene;
|
||||
}
|
||||
|
||||
|
||||
Result Scene::push(unique_ptr<Paint> paint) noexcept
|
||||
{
|
||||
auto p = paint.release();
|
||||
if (!p) return Result::MemoryCorruption;
|
||||
P(p)->ref();
|
||||
|
||||
//Relocated the paint to the current scene space
|
||||
P(p)->renderFlag |= RenderUpdateFlag::Transform;
|
||||
|
||||
pImpl->paints.push_back(p);
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Scene::reserve(TVG_UNUSED uint32_t size) noexcept
|
||||
{
|
||||
return Result::NonSupport;
|
||||
}
|
||||
|
||||
|
||||
Result Scene::clear(bool free) noexcept
|
||||
{
|
||||
pImpl->clear(free);
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
list<Paint*>& Scene::paints() noexcept
|
||||
{
|
||||
return pImpl->paints;
|
||||
}
|
||||
|
||||
|
||||
Result Scene::push(SceneEffect effect, ...) noexcept
|
||||
{
|
||||
if (effect == SceneEffect::ClearAll) return pImpl->resetEffects();
|
||||
|
||||
if (!pImpl->effects) pImpl->effects = new Array<RenderEffect*>;
|
||||
|
||||
va_list args;
|
||||
va_start(args, effect);
|
||||
|
||||
RenderEffect* re = nullptr;
|
||||
|
||||
switch (effect) {
|
||||
case SceneEffect::GaussianBlur: {
|
||||
re = RenderEffectGaussianBlur::gen(args);
|
||||
break;
|
||||
}
|
||||
case SceneEffect::DropShadow: {
|
||||
re = RenderEffectDropShadow::gen(args);
|
||||
break;
|
||||
}
|
||||
case SceneEffect::Fill: {
|
||||
re = RenderEffectFill::gen(args);
|
||||
break;
|
||||
}
|
||||
case SceneEffect::Tint: {
|
||||
re = RenderEffectTint::gen(args);
|
||||
break;
|
||||
}
|
||||
case SceneEffect::Tritone: {
|
||||
re = RenderEffectTritone::gen(args);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!re) return Result::InvalidArguments;
|
||||
|
||||
pImpl->effects->push(re);
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
268
thirdparty/thorvg/src/renderer/tvgScene.h
vendored
Normal file
268
thirdparty/thorvg/src/renderer/tvgScene.h
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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 "tvgMath.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;
|
||||
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
|
||||
Array<RenderEffect*>* effects = nullptr;
|
||||
uint8_t compFlag = CompositionFlag::Invalid;
|
||||
uint8_t opacity; //for composition
|
||||
|
||||
Impl(Scene* s) : scene(s)
|
||||
{
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
resetEffects();
|
||||
|
||||
for (auto paint : paints) {
|
||||
if (P(paint)->unref() == 0) delete(paint);
|
||||
}
|
||||
|
||||
if (auto renderer = PP(scene)->renderer) {
|
||||
renderer->dispose(rd);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t needComposition(uint8_t opacity)
|
||||
{
|
||||
compFlag = CompositionFlag::Invalid;
|
||||
|
||||
if (opacity == 0 || paints.empty()) return 0;
|
||||
|
||||
//post effects, masking, blending may require composition
|
||||
if (effects) compFlag |= CompositionFlag::PostProcessing;
|
||||
auto compMethod = scene->composite(nullptr);
|
||||
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) compFlag |= CompositionFlag::Masking;
|
||||
if (PP(scene)->blendMethod != BlendMethod::Normal) compFlag |= CompositionFlag::Blending;
|
||||
|
||||
//Half translucent requires intermediate composition.
|
||||
if (opacity == 255) return compFlag;
|
||||
|
||||
//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()->type() == Type::Shape) return compFlag;
|
||||
|
||||
compFlag |= CompositionFlag::Opacity;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper)
|
||||
{
|
||||
this->vport = renderer->viewport();
|
||||
|
||||
if (needComposition(opacity)) {
|
||||
/* Overriding opacity value. If this scene is half-translucent,
|
||||
It must do intermediate composition with that opacity value. */
|
||||
this->opacity = opacity;
|
||||
opacity = 255;
|
||||
}
|
||||
for (auto paint : paints) {
|
||||
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
|
||||
}
|
||||
|
||||
if (effects) {
|
||||
for (auto e = effects->begin(); e < effects->end(); ++e) {
|
||||
renderer->prepare(*e, transform);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool render(RenderMethod* renderer)
|
||||
{
|
||||
RenderCompositor* cmp = nullptr;
|
||||
auto ret = true;
|
||||
|
||||
renderer->blend(PP(scene)->blendMethod);
|
||||
|
||||
if (compFlag) {
|
||||
cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast<CompositionFlag>(compFlag));
|
||||
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
|
||||
}
|
||||
|
||||
for (auto paint : paints) {
|
||||
ret &= paint->pImpl->render(renderer);
|
||||
}
|
||||
|
||||
if (cmp) {
|
||||
//Apply post effects if any.
|
||||
if (effects) {
|
||||
//Notify the possiblity of the direct composition of the effect result to the origin surface.
|
||||
auto direct = (effects->count == 1) & (compFlag == CompositionFlag::PostProcessing);
|
||||
for (auto e = effects->begin(); e < effects->end(); ++e) {
|
||||
if ((*e)->valid) renderer->render(cmp, *e, direct);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
//Extends the render region if post effects require
|
||||
int32_t ex = 0, ey = 0, ew = 0, eh = 0;
|
||||
if (effects) {
|
||||
for (auto e = effects->begin(); e < effects->end(); ++e) {
|
||||
auto effect = *e;
|
||||
if (effect->valid && renderer->region(effect)) {
|
||||
ex = std::min(ex, effect->extend.x);
|
||||
ey = std::min(ey, effect->extend.y);
|
||||
ew = std::max(ew, effect->extend.w);
|
||||
eh = std::max(eh, effect->extend.h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = RenderRegion{x1 + ex, y1 + ey, (x2 - x1) + ew, (y2 - y1) + eh};
|
||||
ret.intersect(this->vport);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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(Paint* ret)
|
||||
{
|
||||
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
|
||||
|
||||
auto scene = Scene::gen().release();
|
||||
auto dup = scene->pImpl;
|
||||
|
||||
for (auto paint : paints) {
|
||||
auto cdup = paint->duplicate();
|
||||
P(cdup)->ref();
|
||||
dup->paints.push_back(cdup);
|
||||
}
|
||||
|
||||
if (effects) TVGERR("RENDERER", "TODO: Duplicate Effects?");
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
void clear(bool free)
|
||||
{
|
||||
for (auto paint : paints) {
|
||||
if (P(paint)->unref() == 0 && free) delete(paint);
|
||||
}
|
||||
paints.clear();
|
||||
}
|
||||
|
||||
Iterator* iterator()
|
||||
{
|
||||
return new SceneIterator(&paints);
|
||||
}
|
||||
|
||||
Result resetEffects();
|
||||
};
|
||||
|
||||
#endif //_TVG_SCENE_H_
|
||||
428
thirdparty/thorvg/src/renderer/tvgShape.cpp
vendored
Normal file
428
thirdparty/thorvg/src/renderer/tvgShape.cpp
vendored
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* 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))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Shape :: ~Shape()
|
||||
{
|
||||
delete(pImpl);
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<Shape> Shape::gen() noexcept
|
||||
{
|
||||
return unique_ptr<Shape>(new Shape);
|
||||
}
|
||||
|
||||
|
||||
uint32_t Shape::identifier() noexcept
|
||||
{
|
||||
return (uint32_t) Type::Shape;
|
||||
}
|
||||
|
||||
|
||||
Type Shape::type() const noexcept
|
||||
{
|
||||
return Type::Shape;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::reset() noexcept
|
||||
{
|
||||
pImpl->rs.path.cmds.clear();
|
||||
pImpl->rs.path.pts.clear();
|
||||
|
||||
pImpl->rFlag |= 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);
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
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);
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
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);
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::close() noexcept
|
||||
{
|
||||
pImpl->close();
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
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();
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
TVG_DEPRECATED 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 = deg2rad(startAngle);
|
||||
sweep = deg2rad(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();
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
pImpl->rFlag |= RenderUpdateFlag::Path;
|
||||
|
||||
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->rFlag |= 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->rFlag |= 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->rFlag |= 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
|
||||
{
|
||||
pImpl->strokeFirst(strokeFirst);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::stroke(float width) noexcept
|
||||
{
|
||||
pImpl->strokeWidth(width);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
float Shape::strokeWidth() const noexcept
|
||||
{
|
||||
return pImpl->rs.strokeWidth();
|
||||
}
|
||||
|
||||
|
||||
Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
|
||||
{
|
||||
pImpl->strokeColor(r, g, b, a);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept
|
||||
{
|
||||
if (!pImpl->rs.strokeColor(r, g, b, a)) return Result::InsufficientCondition;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::stroke(unique_ptr<Fill> f) noexcept
|
||||
{
|
||||
return pImpl->strokeFill(std::move(f));
|
||||
}
|
||||
|
||||
|
||||
const Fill* Shape::strokeFill() const noexcept
|
||||
{
|
||||
return pImpl->rs.strokeFill();
|
||||
}
|
||||
|
||||
|
||||
Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept
|
||||
{
|
||||
return pImpl->strokeDash(dashPattern, cnt, 0);
|
||||
}
|
||||
|
||||
|
||||
uint32_t Shape::strokeDash(const float** dashPattern) const noexcept
|
||||
{
|
||||
return pImpl->rs.strokeDash(dashPattern, nullptr);
|
||||
}
|
||||
|
||||
|
||||
Result Shape::stroke(StrokeCap cap) noexcept
|
||||
{
|
||||
pImpl->strokeCap(cap);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::stroke(StrokeJoin join) noexcept
|
||||
{
|
||||
pImpl->strokeJoin(join);
|
||||
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::InvalidArguments;
|
||||
// TODO Find out a reasonable max value.
|
||||
pImpl->strokeMiterlimit(miterlimit);
|
||||
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::strokeTrim(float begin, float end, bool simultaneous) noexcept
|
||||
{
|
||||
pImpl->strokeTrim(begin, end, simultaneous);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Shape::fill(FillRule r) noexcept
|
||||
{
|
||||
pImpl->rs.rule = r;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
FillRule Shape::fillRule() const noexcept
|
||||
{
|
||||
return pImpl->rs.rule;
|
||||
}
|
||||
414
thirdparty/thorvg/src/renderer/tvgShape.h
vendored
Normal file
414
thirdparty/thorvg/src/renderer/tvgShape.h
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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;
|
||||
RenderUpdateFlag rFlag = RenderUpdateFlag::None;
|
||||
uint8_t cFlag = CompositionFlag::Invalid;
|
||||
uint8_t opacity; //for composition
|
||||
|
||||
Impl(Shape* s) : shape(s)
|
||||
{
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
if (auto renderer = PP(shape)->renderer) {
|
||||
renderer->dispose(rd);
|
||||
}
|
||||
}
|
||||
|
||||
bool render(RenderMethod* renderer)
|
||||
{
|
||||
if (!rd) return false;
|
||||
|
||||
RenderCompositor* cmp = nullptr;
|
||||
|
||||
renderer->blend(PP(shape)->blendMethod);
|
||||
|
||||
if (cFlag) {
|
||||
cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast<CompositionFlag>(cFlag));
|
||||
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
|
||||
}
|
||||
|
||||
auto ret = renderer->renderShape(rd);
|
||||
if (cmp) renderer->endComposite(cmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool needComposition(uint8_t opacity)
|
||||
{
|
||||
cFlag = CompositionFlag::Invalid;
|
||||
|
||||
if (opacity == 0) return false;
|
||||
|
||||
//Shape composition is only necessary when stroking & fill are valid.
|
||||
if (!rs.stroke || rs.stroke->width < FLOAT_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) {
|
||||
cFlag = CompositionFlag::Opacity;
|
||||
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->type() == Type::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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cFlag = CompositionFlag::Masking;
|
||||
return true;
|
||||
}
|
||||
|
||||
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
|
||||
{
|
||||
if (static_cast<RenderUpdateFlag>(pFlag | rFlag) == RenderUpdateFlag::None) return rd;
|
||||
|
||||
if (needComposition(opacity)) {
|
||||
/* Overriding opacity value. If this scene is half-translucent,
|
||||
It must do intermediate composition with that opacity value. */
|
||||
this->opacity = opacity;
|
||||
opacity = 255;
|
||||
}
|
||||
|
||||
rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | rFlag), clipper);
|
||||
rFlag = RenderUpdateFlag::None;
|
||||
return rd;
|
||||
}
|
||||
|
||||
RenderRegion bounds(RenderMethod* renderer)
|
||||
{
|
||||
if (!rd) return {0, 0, 0, 0};
|
||||
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;
|
||||
}
|
||||
|
||||
void moveTo(float x, float y)
|
||||
{
|
||||
rs.path.cmds.push(PathCommand::MoveTo);
|
||||
rs.path.pts.push({x, y});
|
||||
}
|
||||
|
||||
void lineTo(float x, float y)
|
||||
{
|
||||
rs.path.cmds.push(PathCommand::LineTo);
|
||||
rs.path.pts.push({x, y});
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void strokeWidth(float width)
|
||||
{
|
||||
if (!rs.stroke) rs.stroke = new RenderStroke();
|
||||
rs.stroke->width = width;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
void strokeTrim(float begin, float end, bool simultaneous)
|
||||
{
|
||||
if (!rs.stroke) {
|
||||
if (begin == 0.0f && end == 1.0f) return;
|
||||
rs.stroke = new RenderStroke();
|
||||
}
|
||||
|
||||
if (tvg::equal(rs.stroke->trim.begin, begin) && tvg::equal(rs.stroke->trim.end, end) &&
|
||||
rs.stroke->trim.simultaneous == simultaneous) return;
|
||||
|
||||
rs.stroke->trim.begin = begin;
|
||||
rs.stroke->trim.end = end;
|
||||
rs.stroke->trim.simultaneous = simultaneous;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
bool strokeTrim(float* begin, float* end)
|
||||
{
|
||||
if (rs.stroke) {
|
||||
if (begin) *begin = rs.stroke->trim.begin;
|
||||
if (end) *end = rs.stroke->trim.end;
|
||||
return rs.stroke->trim.simultaneous;
|
||||
} else {
|
||||
if (begin) *begin = 0.0f;
|
||||
if (end) *end = 1.0f;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void strokeCap(StrokeCap cap)
|
||||
{
|
||||
if (!rs.stroke) rs.stroke = new RenderStroke();
|
||||
rs.stroke->cap = cap;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
void strokeJoin(StrokeJoin join)
|
||||
{
|
||||
if (!rs.stroke) rs.stroke = new RenderStroke();
|
||||
rs.stroke->join = join;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
void strokeMiterlimit(float miterlimit)
|
||||
{
|
||||
if (!rs.stroke) rs.stroke = new RenderStroke();
|
||||
rs.stroke->miterlimit = miterlimit;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
void strokeColor(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;
|
||||
rFlag |= RenderUpdateFlag::GradientStroke;
|
||||
}
|
||||
|
||||
rs.stroke->color[0] = r;
|
||||
rs.stroke->color[1] = g;
|
||||
rs.stroke->color[2] = b;
|
||||
rs.stroke->color[3] = a;
|
||||
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
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;
|
||||
rs.stroke->color[3] = 0;
|
||||
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
rFlag |= 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] < FLOAT_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;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
bool strokeFirst()
|
||||
{
|
||||
if (!rs.stroke) return true;
|
||||
return rs.stroke->strokeFirst;
|
||||
}
|
||||
|
||||
void strokeFirst(bool strokeFirst)
|
||||
{
|
||||
if (!rs.stroke) rs.stroke = new RenderStroke();
|
||||
rs.stroke->strokeFirst = strokeFirst;
|
||||
rFlag |= RenderUpdateFlag::Stroke;
|
||||
}
|
||||
|
||||
void update(RenderUpdateFlag flag)
|
||||
{
|
||||
rFlag |= flag;
|
||||
}
|
||||
|
||||
Paint* duplicate(Paint* ret)
|
||||
{
|
||||
auto shape = static_cast<Shape*>(ret);
|
||||
if (shape) shape->reset();
|
||||
else shape = Shape::gen().release();
|
||||
|
||||
auto dup = shape->pImpl;
|
||||
delete(dup->rs.fill);
|
||||
|
||||
//Default Properties
|
||||
dup->rFlag = RenderUpdateFlag::All;
|
||||
dup->rs.rule = rs.rule;
|
||||
|
||||
//Color
|
||||
memcpy(dup->rs.color, rs.color, sizeof(rs.color));
|
||||
|
||||
//Path
|
||||
dup->rs.path.cmds.push(rs.path.cmds);
|
||||
dup->rs.path.pts.push(rs.path.pts);
|
||||
|
||||
//Stroke
|
||||
if (rs.stroke) {
|
||||
if (!dup->rs.stroke) dup->rs.stroke = new RenderStroke;
|
||||
*dup->rs.stroke = *rs.stroke;
|
||||
} else {
|
||||
delete(dup->rs.stroke);
|
||||
dup->rs.stroke = nullptr;
|
||||
}
|
||||
|
||||
//Fill
|
||||
if (rs.fill) dup->rs.fill = rs.fill->duplicate();
|
||||
else dup->rs.fill = nullptr;
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
PP(shape)->reset();
|
||||
rs.path.cmds.clear();
|
||||
rs.path.pts.clear();
|
||||
|
||||
rs.color[3] = 0;
|
||||
rs.rule = FillRule::Winding;
|
||||
|
||||
delete(rs.stroke);
|
||||
rs.stroke = nullptr;
|
||||
|
||||
delete(rs.fill);
|
||||
rs.fill = nullptr;
|
||||
}
|
||||
|
||||
Iterator* iterator()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //_TVG_SHAPE_H_
|
||||
116
thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp
vendored
Normal file
116
thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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(nullptr)
|
||||
#else
|
||||
SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(nullptr)
|
||||
#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
|
||||
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
//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;
|
||||
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
|
||||
renderer->viewport(Canvas::pImpl->vport);
|
||||
|
||||
//FIXME: The value must be associated with an individual canvas instance.
|
||||
ImageLoader::cs = static_cast<ColorSpace>(cs);
|
||||
|
||||
//Paints must be updated again with this new target.
|
||||
Canvas::pImpl->status = Status::Damaged;
|
||||
|
||||
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;
|
||||
}
|
||||
218
thirdparty/thorvg/src/renderer/tvgTaskScheduler.cpp
vendored
Normal file
218
thirdparty/thorvg/src/renderer/tvgTaskScheduler.cpp
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
111
thirdparty/thorvg/src/renderer/tvgTaskScheduler.h
vendored
Normal file
111
thirdparty/thorvg/src/renderer/tvgTaskScheduler.h
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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);
|
||||
};
|
||||
|
||||
} //namespace
|
||||
|
||||
#endif //_TVG_TASK_SCHEDULER_H_
|
||||
|
||||
129
thirdparty/thorvg/src/renderer/tvgText.cpp
vendored
Normal file
129
thirdparty/thorvg/src/renderer/tvgText.cpp
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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(this))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
bool invalid; //invalid path
|
||||
auto loader = LoaderMgr::loader(path, &invalid);
|
||||
if (loader) {
|
||||
if (loader->sharing > 0) --loader->sharing; //font loading doesn't mean sharing.
|
||||
return Result::Success;
|
||||
} else {
|
||||
if (invalid) return Result::InvalidArguments;
|
||||
else return Result::NonSupport;
|
||||
}
|
||||
return Result::Success;
|
||||
#else
|
||||
TVGLOG("RENDERER", "FILE IO is disabled!");
|
||||
return Result::NonSupport;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Result Text::load(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy) noexcept
|
||||
{
|
||||
if (!name || (size == 0 && data)) return Result::InvalidArguments;
|
||||
|
||||
//unload font
|
||||
if (!data) {
|
||||
if (LoaderMgr::retrieve(name)) return Result::Success;
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
if (!LoaderMgr::loader(name, data, size, mimeType, copy)) return Result::NonSupport;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
Result Text::unload(const std::string& path) noexcept
|
||||
{
|
||||
#ifdef THORVG_FILE_IO_SUPPORT
|
||||
if (LoaderMgr::retrieve(path)) return Result::Success;
|
||||
return Result::InsufficientCondition;
|
||||
#else
|
||||
TVGLOG("RENDERER", "FILE IO is disabled!");
|
||||
return Result::NonSupport;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Result Text::fill(uint8_t r, uint8_t g, uint8_t b) noexcept
|
||||
{
|
||||
return pImpl->shape->fill(r, g, b);
|
||||
}
|
||||
|
||||
|
||||
Result Text::fill(unique_ptr<Fill> f) noexcept
|
||||
{
|
||||
return pImpl->shape->fill(std::move(f));
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<Text> Text::gen() noexcept
|
||||
{
|
||||
return unique_ptr<Text>(new Text);
|
||||
}
|
||||
|
||||
|
||||
Type Text::type() const noexcept
|
||||
{
|
||||
return Type::Text;
|
||||
}
|
||||
174
thirdparty/thorvg/src/renderer/tvgText.h
vendored
Normal file
174
thirdparty/thorvg/src/renderer/tvgText.h
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 "tvgMath.h"
|
||||
#include "tvgShape.h"
|
||||
#include "tvgFill.h"
|
||||
#include "tvgLoader.h"
|
||||
|
||||
struct Text::Impl
|
||||
{
|
||||
FontLoader* loader = nullptr;
|
||||
Text* paint;
|
||||
Shape* shape;
|
||||
FontMetrics metrics;
|
||||
char* utf8 = nullptr;
|
||||
float fontSize;
|
||||
bool italic = false;
|
||||
bool changed = false;
|
||||
|
||||
Impl(Text* p) : paint(p), shape(Shape::gen().release())
|
||||
{
|
||||
shape->fill(FillRule::EvenOdd);
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
free(utf8);
|
||||
LoaderMgr::retrieve(loader);
|
||||
delete(shape);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (style && strstr(style, "italic")) italic = true;
|
||||
else italic = false;
|
||||
|
||||
fontSize = size;
|
||||
|
||||
//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);
|
||||
|
||||
changed = true;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
RenderRegion bounds(RenderMethod* renderer)
|
||||
{
|
||||
return P(shape)->bounds(renderer);
|
||||
}
|
||||
|
||||
bool render(RenderMethod* renderer)
|
||||
{
|
||||
if (!loader) return true;
|
||||
renderer->blend(PP(paint)->blendMethod);
|
||||
return PP(shape)->render(renderer);
|
||||
}
|
||||
|
||||
float load()
|
||||
{
|
||||
if (!loader) return 0.0f;
|
||||
|
||||
//reload
|
||||
if (changed) {
|
||||
loader->read(shape, utf8, metrics);
|
||||
changed = false;
|
||||
}
|
||||
return loader->transform(shape, metrics, fontSize, italic);
|
||||
}
|
||||
|
||||
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
|
||||
{
|
||||
auto scale = 1.0f / load();
|
||||
if (tvg::zero(scale)) return nullptr;
|
||||
|
||||
//transform the gradient coordinates based on the final scaled font.
|
||||
auto fill = P(shape)->rs.fill;
|
||||
if (fill && P(shape)->rFlag & RenderUpdateFlag::Gradient) {
|
||||
if (fill->type() == Type::LinearGradient) {
|
||||
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(shape)->update(renderer, transform, clips, opacity, pFlag, false);
|
||||
}
|
||||
|
||||
bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking)
|
||||
{
|
||||
if (load() == 0.0f) return false;
|
||||
PP(shape)->bounds(x, y, w, h, true, true, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
Paint* duplicate(Paint* ret)
|
||||
{
|
||||
if (ret) TVGERR("RENDERER", "TODO: duplicate()");
|
||||
|
||||
load();
|
||||
|
||||
auto text = Text::gen().release();
|
||||
auto dup = text->pImpl;
|
||||
P(shape)->duplicate(dup->shape);
|
||||
|
||||
if (loader) {
|
||||
dup->loader = loader;
|
||||
++dup->loader->sharing;
|
||||
}
|
||||
|
||||
dup->utf8 = strdup(utf8);
|
||||
dup->italic = italic;
|
||||
dup->fontSize = fontSize;
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
Iterator* iterator()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //_TVG_TEXT_H
|
||||
92
thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp
vendored
Normal file
92
thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 "tvgCanvas.h"
|
||||
|
||||
#ifdef THORVG_WG_RASTER_SUPPORT
|
||||
#include "tvgWgRenderer.h"
|
||||
#endif
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
struct WgCanvas::Impl
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
#ifdef THORVG_WG_RASTER_SUPPORT
|
||||
WgCanvas::WgCanvas() : Canvas(WgRenderer::gen()), pImpl(nullptr)
|
||||
#else
|
||||
WgCanvas::WgCanvas() : Canvas(nullptr), pImpl(nullptr)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
WgCanvas::~WgCanvas()
|
||||
{
|
||||
#ifdef THORVG_WG_RASTER_SUPPORT
|
||||
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
|
||||
renderer->target(nullptr, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h, void* device) noexcept
|
||||
{
|
||||
#ifdef THORVG_WG_RASTER_SUPPORT
|
||||
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
if (!instance || !surface || (w == 0) || (h == 0)) return Result::InvalidArguments;
|
||||
|
||||
//We know renderer type, avoid dynamic_cast for performance.
|
||||
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
|
||||
if (!renderer) return Result::MemoryCorruption;
|
||||
|
||||
if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h, (WGPUDevice)device)) return Result::Unknown;
|
||||
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
|
||||
renderer->viewport(Canvas::pImpl->vport);
|
||||
|
||||
//Paints must be updated again with this new target.
|
||||
Canvas::pImpl->status = Status::Damaged;
|
||||
|
||||
return Result::Success;
|
||||
#endif
|
||||
return Result::NonSupport;
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<WgCanvas> WgCanvas::gen() noexcept
|
||||
{
|
||||
#ifdef THORVG_WG_RASTER_SUPPORT
|
||||
return unique_ptr<WgCanvas>(new WgCanvas);
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
78
thirdparty/thorvg/update-thorvg.sh
vendored
Normal file
78
thirdparty/thorvg/update-thorvg.sh
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
VERSION=0.15.13
|
||||
# Uncomment and set a git hash to use specific commit instead of tag.
|
||||
#GIT_COMMIT=
|
||||
|
||||
pushd "$(dirname "$0")"
|
||||
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/
|
||||
|
||||
mkdir tmp/ && pushd tmp/
|
||||
|
||||
# Release
|
||||
if [ ! -z "$GIT_COMMIT" ]; then
|
||||
echo "Updating ThorVG to commit:" $GIT_COMMIT
|
||||
curl -L -O https://github.com/thorvg/thorvg/archive/$GIT_COMMIT.tar.gz
|
||||
else
|
||||
echo "Updating ThorVG to tagged release:" $VERSION
|
||||
curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
|
||||
fi
|
||||
|
||||
tar --strip-components=1 -xvf *.tar.gz
|
||||
rm *.tar.gz
|
||||
|
||||
# Install from local git checkout "thorvg-git" in the same directory
|
||||
# as godot git checkout.
|
||||
#d="../../../../thorvg-git"
|
||||
#cp -r ${d}/AUTHORS ${d}/inc ${d}/LICENSE ${d}/src .
|
||||
|
||||
find . -type f -name 'meson.build' -delete
|
||||
|
||||
# Fix newline at end of file.
|
||||
for source in $(find ./ -type f \( -iname \*.h -o -iname \*.cpp \)); do
|
||||
sed -i -e '$a\' $source
|
||||
done
|
||||
|
||||
cp -v AUTHORS LICENSE ..
|
||||
cp -rv inc ../
|
||||
|
||||
cat << EOF > ../inc/config.h
|
||||
#ifndef THORVG_CONFIG_H
|
||||
#define THORVG_CONFIG_H
|
||||
|
||||
#define THORVG_SW_RASTER_SUPPORT
|
||||
#define THORVG_SVG_LOADER_SUPPORT
|
||||
#define THORVG_PNG_LOADER_SUPPORT
|
||||
#ifndef WEB_ENABLED
|
||||
#define THORVG_THREAD_SUPPORT
|
||||
#endif
|
||||
|
||||
// Added conditionally if respective modules are enabled.
|
||||
//#define THORVG_WEBP_LOADER_SUPPORT
|
||||
//#define THORVG_JPG_LOADER_SUPPORT
|
||||
|
||||
// For internal debugging:
|
||||
//#define THORVG_LOG_ENABLED
|
||||
|
||||
#define THORVG_VERSION_STRING "$VERSION"
|
||||
#endif
|
||||
EOF
|
||||
|
||||
mkdir ../src
|
||||
cp -rv src/common ../src
|
||||
cp -rv src/renderer ../src/
|
||||
|
||||
# Only sw_engine is enabled.
|
||||
rm -rfv ../src/renderer/gl_engine
|
||||
rm -rfv ../src/renderer/wg_engine
|
||||
|
||||
# Enabled embedded loaders: raw, JPEG, PNG, WebP.
|
||||
mkdir ../src/loaders
|
||||
cp -rv src/loaders/svg src/loaders/raw ../src/loaders/
|
||||
cp -rv src/loaders/external_png ../src/loaders/
|
||||
cp -rv src/loaders/external_webp ../src/loaders/
|
||||
cp -rv src/loaders/external_jpg ../src/loaders/
|
||||
|
||||
popd
|
||||
rm -rf tmp
|
||||
popd
|
||||
Reference in New Issue
Block a user