matterbridge/vendor/github.com/Benau/go_rlottie/vector_vraster.cpp

564 lines
14 KiB
C++

/*
* Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "vector_vraster.h"
#include <climits>
#include <cstring>
#include <memory>
#include "config.h"
#include "vector_freetype_v_ft_raster.h"
#include "vector_freetype_v_ft_stroker.h"
#include "vector_vdebug.h"
#include "vector_vmatrix.h"
#include "vector_vpath.h"
#include "vector_vrle.h"
V_BEGIN_NAMESPACE
template <typename T>
class dyn_array {
public:
explicit dyn_array(size_t size)
: mCapacity(size), mData(std::make_unique<T[]>(mCapacity))
{
}
void reserve(size_t size)
{
if (mCapacity > size) return;
mCapacity = size;
mData = std::make_unique<T[]>(mCapacity);
}
T * data() const { return mData.get(); }
dyn_array &operator=(dyn_array &&) noexcept = delete;
private:
size_t mCapacity{0};
std::unique_ptr<T[]> mData{nullptr};
};
struct FTOutline {
public:
void reset();
void grow(size_t, size_t);
void convert(const VPath &path);
void convert(CapStyle, JoinStyle, float, float);
void moveTo(const VPointF &pt);
void lineTo(const VPointF &pt);
void cubicTo(const VPointF &ctr1, const VPointF &ctr2, const VPointF end);
void close();
void end();
void transform(const VMatrix &m);
SW_FT_Pos TO_FT_COORD(float x)
{
return SW_FT_Pos(x * 64);
} // to freetype 26.6 coordinate.
SW_FT_Outline ft;
bool closed{false};
SW_FT_Stroker_LineCap ftCap;
SW_FT_Stroker_LineJoin ftJoin;
SW_FT_Fixed ftWidth;
SW_FT_Fixed ftMiterLimit;
dyn_array<SW_FT_Vector> mPointMemory{100};
dyn_array<char> mTagMemory{100};
dyn_array<short> mContourMemory{10};
dyn_array<char> mContourFlagMemory{10};
};
void FTOutline::reset()
{
ft.n_points = ft.n_contours = 0;
ft.flags = 0x0;
}
void FTOutline::grow(size_t points, size_t segments)
{
reset();
mPointMemory.reserve(points + segments);
mTagMemory.reserve(points + segments);
mContourMemory.reserve(segments);
mContourFlagMemory.reserve(segments);
ft.points = mPointMemory.data();
ft.tags = mTagMemory.data();
ft.contours = mContourMemory.data();
ft.contours_flag = mContourFlagMemory.data();
}
void FTOutline::convert(const VPath &path)
{
const std::vector<VPath::Element> &elements = path.elements();
const std::vector<VPointF> & points = path.points();
grow(points.size(), path.segments());
size_t index = 0;
for (auto element : elements) {
switch (element) {
case VPath::Element::MoveTo:
moveTo(points[index]);
index++;
break;
case VPath::Element::LineTo:
lineTo(points[index]);
index++;
break;
case VPath::Element::CubicTo:
cubicTo(points[index], points[index + 1], points[index + 2]);
index = index + 3;
break;
case VPath::Element::Close:
close();
break;
}
}
end();
}
void FTOutline::convert(CapStyle cap, JoinStyle join, float width,
float miterLimit)
{
// map strokeWidth to freetype. It uses as the radius of the pen not the
// diameter
width = width / 2.0f;
// convert to freetype co-ordinate
// IMP: stroker takes radius in 26.6 co-ordinate
ftWidth = SW_FT_Fixed(width * (1 << 6));
// IMP: stroker takes meterlimit in 16.16 co-ordinate
ftMiterLimit = SW_FT_Fixed(miterLimit * (1 << 16));
// map to freetype capstyle
switch (cap) {
case CapStyle::Square:
ftCap = SW_FT_STROKER_LINECAP_SQUARE;
break;
case CapStyle::Round:
ftCap = SW_FT_STROKER_LINECAP_ROUND;
break;
default:
ftCap = SW_FT_STROKER_LINECAP_BUTT;
break;
}
switch (join) {
case JoinStyle::Bevel:
ftJoin = SW_FT_STROKER_LINEJOIN_BEVEL;
break;
case JoinStyle::Round:
ftJoin = SW_FT_STROKER_LINEJOIN_ROUND;
break;
default:
ftJoin = SW_FT_STROKER_LINEJOIN_MITER_FIXED;
break;
}
}
void FTOutline::moveTo(const VPointF &pt)
{
assert(ft.n_points <= SHRT_MAX - 1);
ft.points[ft.n_points].x = TO_FT_COORD(pt.x());
ft.points[ft.n_points].y = TO_FT_COORD(pt.y());
ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON;
if (ft.n_points) {
ft.contours[ft.n_contours] = ft.n_points - 1;
ft.n_contours++;
}
// mark the current contour as open
// will be updated if ther is a close tag at the end.
ft.contours_flag[ft.n_contours] = 1;
ft.n_points++;
}
void FTOutline::lineTo(const VPointF &pt)
{
assert(ft.n_points <= SHRT_MAX - 1);
ft.points[ft.n_points].x = TO_FT_COORD(pt.x());
ft.points[ft.n_points].y = TO_FT_COORD(pt.y());
ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON;
ft.n_points++;
}
void FTOutline::cubicTo(const VPointF &cp1, const VPointF &cp2,
const VPointF ep)
{
assert(ft.n_points <= SHRT_MAX - 3);
ft.points[ft.n_points].x = TO_FT_COORD(cp1.x());
ft.points[ft.n_points].y = TO_FT_COORD(cp1.y());
ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC;
ft.n_points++;
ft.points[ft.n_points].x = TO_FT_COORD(cp2.x());
ft.points[ft.n_points].y = TO_FT_COORD(cp2.y());
ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC;
ft.n_points++;
ft.points[ft.n_points].x = TO_FT_COORD(ep.x());
ft.points[ft.n_points].y = TO_FT_COORD(ep.y());
ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON;
ft.n_points++;
}
void FTOutline::close()
{
assert(ft.n_points <= SHRT_MAX - 1);
// mark the contour as a close path.
ft.contours_flag[ft.n_contours] = 0;
int index;
if (ft.n_contours) {
index = ft.contours[ft.n_contours - 1] + 1;
} else {
index = 0;
}
// make sure atleast 1 point exists in the segment.
if (ft.n_points == index) {
closed = false;
return;
}
ft.points[ft.n_points].x = ft.points[index].x;
ft.points[ft.n_points].y = ft.points[index].y;
ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON;
ft.n_points++;
}
void FTOutline::end()
{
assert(ft.n_contours <= SHRT_MAX - 1);
if (ft.n_points) {
ft.contours[ft.n_contours] = ft.n_points - 1;
ft.n_contours++;
}
}
static void rleGenerationCb(int count, const SW_FT_Span *spans, void *user)
{
VRle *rle = static_cast<VRle *>(user);
auto *rleSpan = reinterpret_cast<const VRle::Span *>(spans);
rle->addSpan(rleSpan, count);
}
static void bboxCb(int x, int y, int w, int h, void *user)
{
VRle *rle = static_cast<VRle *>(user);
rle->setBoundingRect({x, y, w, h});
}
class SharedRle {
public:
SharedRle() = default;
VRle &unsafe() { return _rle; }
void notify()
{
{
std::lock_guard<std::mutex> lock(_mutex);
_ready = true;
}
_cv.notify_one();
}
void wait()
{
if (!_pending) return;
{
std::unique_lock<std::mutex> lock(_mutex);
while (!_ready) _cv.wait(lock);
}
_pending = false;
}
VRle &get()
{
wait();
return _rle;
}
void reset()
{
wait();
_ready = false;
_pending = true;
}
private:
VRle _rle;
std::mutex _mutex;
std::condition_variable _cv;
bool _ready{true};
bool _pending{false};
};
struct VRleTask {
SharedRle mRle;
VPath mPath;
float mStrokeWidth;
float mMiterLimit;
VRect mClip;
FillRule mFillRule;
CapStyle mCap;
JoinStyle mJoin;
bool mGenerateStroke;
VRle &rle() { return mRle.get(); }
void update(VPath path, FillRule fillRule, const VRect &clip)
{
mRle.reset();
mPath = std::move(path);
mFillRule = fillRule;
mClip = clip;
mGenerateStroke = false;
}
void update(VPath path, CapStyle cap, JoinStyle join, float width,
float miterLimit, const VRect &clip)
{
mRle.reset();
mPath = std::move(path);
mCap = cap;
mJoin = join;
mStrokeWidth = width;
mMiterLimit = miterLimit;
mClip = clip;
mGenerateStroke = true;
}
void render(FTOutline &outRef)
{
SW_FT_Raster_Params params;
mRle.unsafe().reset();
params.flags = SW_FT_RASTER_FLAG_DIRECT | SW_FT_RASTER_FLAG_AA;
params.gray_spans = &rleGenerationCb;
params.bbox_cb = &bboxCb;
params.user = &mRle.unsafe();
params.source = &outRef.ft;
if (!mClip.empty()) {
params.flags |= SW_FT_RASTER_FLAG_CLIP;
params.clip_box.xMin = mClip.left();
params.clip_box.yMin = mClip.top();
params.clip_box.xMax = mClip.right();
params.clip_box.yMax = mClip.bottom();
}
// compute rle
sw_ft_grays_raster.raster_render(nullptr, &params);
}
void operator()(FTOutline &outRef, SW_FT_Stroker &stroker)
{
if (mPath.points().size() > SHRT_MAX ||
mPath.points().size() + mPath.segments() > SHRT_MAX) {
return;
}
if (mGenerateStroke) { // Stroke Task
outRef.convert(mPath);
outRef.convert(mCap, mJoin, mStrokeWidth, mMiterLimit);
uint points, contors;
SW_FT_Stroker_Set(stroker, outRef.ftWidth, outRef.ftCap,
outRef.ftJoin, outRef.ftMiterLimit);
SW_FT_Stroker_ParseOutline(stroker, &outRef.ft);
SW_FT_Stroker_GetCounts(stroker, &points, &contors);
outRef.grow(points, contors);
SW_FT_Stroker_Export(stroker, &outRef.ft);
} else { // Fill Task
outRef.convert(mPath);
int fillRuleFlag = SW_FT_OUTLINE_NONE;
switch (mFillRule) {
case FillRule::EvenOdd:
fillRuleFlag = SW_FT_OUTLINE_EVEN_ODD_FILL;
break;
default:
fillRuleFlag = SW_FT_OUTLINE_NONE;
break;
}
outRef.ft.flags = fillRuleFlag;
}
render(outRef);
mPath = VPath();
mRle.notify();
}
};
using VTask = std::shared_ptr<VRleTask>;
#ifdef LOTTIE_THREAD_SUPPORT
#include <thread>
#include "vector_vtaskqueue.h"
class RleTaskScheduler {
const unsigned _count{std::thread::hardware_concurrency()};
std::vector<std::thread> _threads;
std::vector<TaskQueue<VTask>> _q{_count};
std::atomic<unsigned> _index{0};
void run(unsigned i)
{
/*
* initalize per thread objects.
*/
FTOutline outlineRef;
SW_FT_Stroker stroker;
SW_FT_Stroker_New(&stroker);
// Task Loop
VTask task;
while (true) {
bool success = false;
for (unsigned n = 0; n != _count * 2; ++n) {
if (_q[(i + n) % _count].try_pop(task)) {
success = true;
break;
}
}
if (!success && !_q[i].pop(task)) break;
(*task)(outlineRef, stroker);
}
// cleanup
SW_FT_Stroker_Done(stroker);
}
RleTaskScheduler()
{
for (unsigned n = 0; n != _count; ++n) {
_threads.emplace_back([&, n] { run(n); });
}
}
public:
static RleTaskScheduler &instance()
{
static RleTaskScheduler singleton;
return singleton;
}
~RleTaskScheduler()
{
for (auto &e : _q) e.done();
for (auto &e : _threads) e.join();
}
void process(VTask task)
{
auto i = _index++;
for (unsigned n = 0; n != _count; ++n) {
if (_q[(i + n) % _count].try_push(std::move(task))) return;
}
if (_count > 0) {
_q[i % _count].push(std::move(task));
}
}
};
#else
class RleTaskScheduler {
public:
FTOutline outlineRef{};
SW_FT_Stroker stroker;
public:
static RleTaskScheduler &instance()
{
static RleTaskScheduler singleton;
return singleton;
}
RleTaskScheduler() { SW_FT_Stroker_New(&stroker); }
~RleTaskScheduler() { SW_FT_Stroker_Done(stroker); }
void process(VTask task) { (*task)(outlineRef, stroker); }
};
#endif
struct VRasterizer::VRasterizerImpl {
VRleTask mTask;
VRle & rle() { return mTask.rle(); }
VRleTask &task() { return mTask; }
};
VRle VRasterizer::rle()
{
if (!d) return VRle();
return d->rle();
}
void VRasterizer::init()
{
if (!d) d = std::make_shared<VRasterizerImpl>();
}
void VRasterizer::updateRequest()
{
VTask taskObj = VTask(d, &d->task());
RleTaskScheduler::instance().process(std::move(taskObj));
}
void VRasterizer::rasterize(VPath path, FillRule fillRule, const VRect &clip)
{
init();
if (path.empty()) {
d->rle().reset();
return;
}
d->task().update(std::move(path), fillRule, clip);
updateRequest();
}
void VRasterizer::rasterize(VPath path, CapStyle cap, JoinStyle join,
float width, float miterLimit, const VRect &clip)
{
init();
if (path.empty() || vIsZero(width)) {
d->rle().reset();
return;
}
d->task().update(std::move(path), cap, join, width, miterLimit, clip);
updateRequest();
}
V_END_NAMESPACE