From 18c49f7740e4070d86522689b194a0c02175bcf6 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Tue, 6 Jun 2023 13:22:00 +0300 Subject: [PATCH] Add CPU vector graphics library --- libs/vecr/CMakeLists.txt | 6 ++ libs/vecr/include/psemek/vecr/add.hpp | 17 ++++ libs/vecr/include/psemek/vecr/any.hpp | 51 +++++++++++ libs/vecr/include/psemek/vecr/blend_mode.hpp | 22 +++++ libs/vecr/include/psemek/vecr/border.hpp | 27 ++++++ libs/vecr/include/psemek/vecr/circle.hpp | 17 ++++ libs/vecr/include/psemek/vecr/colorizer.hpp | 70 +++++++++++++++ libs/vecr/include/psemek/vecr/exact.hpp | 58 ++++++++++++ libs/vecr/include/psemek/vecr/fill.hpp | 15 ++++ libs/vecr/include/psemek/vecr/grow.hpp | 24 +++++ libs/vecr/include/psemek/vecr/halfspace.hpp | 20 +++++ libs/vecr/include/psemek/vecr/intersect.hpp | 21 +++++ libs/vecr/include/psemek/vecr/invert.hpp | 24 +++++ libs/vecr/include/psemek/vecr/mirror.hpp | 37 ++++++++ libs/vecr/include/psemek/vecr/path.hpp | 18 ++++ libs/vecr/include/psemek/vecr/renderer.hpp | 42 +++++++++ libs/vecr/include/psemek/vecr/sdf.hpp | 14 +++ libs/vecr/include/psemek/vecr/subtract.hpp | 23 +++++ libs/vecr/include/psemek/vecr/tile.hpp | 28 ++++++ libs/vecr/include/psemek/vecr/transform.hpp | 93 ++++++++++++++++++++ libs/vecr/source/add.cpp | 41 +++++++++ libs/vecr/source/circle.cpp | 15 ++++ libs/vecr/source/colorizer.cpp | 33 +++++++ libs/vecr/source/fill.cpp | 32 +++++++ libs/vecr/source/intersect.cpp | 42 +++++++++ libs/vecr/source/path.cpp | 30 +++++++ libs/vecr/source/renderer.cpp | 85 ++++++++++++++++++ 27 files changed, 905 insertions(+) create mode 100644 libs/vecr/CMakeLists.txt create mode 100644 libs/vecr/include/psemek/vecr/add.hpp create mode 100644 libs/vecr/include/psemek/vecr/any.hpp create mode 100644 libs/vecr/include/psemek/vecr/blend_mode.hpp create mode 100644 libs/vecr/include/psemek/vecr/border.hpp create mode 100644 libs/vecr/include/psemek/vecr/circle.hpp create mode 100644 libs/vecr/include/psemek/vecr/colorizer.hpp create mode 100644 libs/vecr/include/psemek/vecr/exact.hpp create mode 100644 libs/vecr/include/psemek/vecr/fill.hpp create mode 100644 libs/vecr/include/psemek/vecr/grow.hpp create mode 100644 libs/vecr/include/psemek/vecr/halfspace.hpp create mode 100644 libs/vecr/include/psemek/vecr/intersect.hpp create mode 100644 libs/vecr/include/psemek/vecr/invert.hpp create mode 100644 libs/vecr/include/psemek/vecr/mirror.hpp create mode 100644 libs/vecr/include/psemek/vecr/path.hpp create mode 100644 libs/vecr/include/psemek/vecr/renderer.hpp create mode 100644 libs/vecr/include/psemek/vecr/sdf.hpp create mode 100644 libs/vecr/include/psemek/vecr/subtract.hpp create mode 100644 libs/vecr/include/psemek/vecr/tile.hpp create mode 100644 libs/vecr/include/psemek/vecr/transform.hpp create mode 100644 libs/vecr/source/add.cpp create mode 100644 libs/vecr/source/circle.cpp create mode 100644 libs/vecr/source/colorizer.cpp create mode 100644 libs/vecr/source/fill.cpp create mode 100644 libs/vecr/source/intersect.cpp create mode 100644 libs/vecr/source/path.cpp create mode 100644 libs/vecr/source/renderer.cpp diff --git a/libs/vecr/CMakeLists.txt b/libs/vecr/CMakeLists.txt new file mode 100644 index 00000000..f96a8c5d --- /dev/null +++ b/libs/vecr/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE PSEMEK_VECR_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") +file(GLOB_RECURSE PSEMEK_VECR_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") + +psemek_add_library(psemek-vecr ${PSEMEK_VECR_HEADERS} ${PSEMEK_VECR_SOURCES}) +target_include_directories(psemek-vecr PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(psemek-vecr PUBLIC psemek-util psemek-geom psemek-io psemek-gfx) diff --git a/libs/vecr/include/psemek/vecr/add.hpp b/libs/vecr/include/psemek/vecr/add.hpp new file mode 100644 index 00000000..86bdc8cd --- /dev/null +++ b/libs/vecr/include/psemek/vecr/add.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + struct add + { + std::vector shapes; + float smooth = 0.f; + }; + + sdf_sample sdf(add const & s, geom::point const & p); + +} diff --git a/libs/vecr/include/psemek/vecr/any.hpp b/libs/vecr/include/psemek/vecr/any.hpp new file mode 100644 index 00000000..9d7a8d46 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/any.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::vecr +{ + + struct any + { + any() = default; + any(any const &) = default; + any(any &&) = default; + + template + any(Shape && shape) + { + *this = std::forward(shape); + } + + any & operator = (any &&) = default; + any & operator = (any const &) = default; + + template + any & operator = (Shape && shape) + { + sdf_ = [shape = std::forward(shape)](geom::point const & p){ + return sdf(shape, p); + }; + return *this; + } + + explicit operator bool() const + { + return static_cast(sdf_); + } + + friend sdf_sample sdf(any const & s, geom::point const & p); + + private: + std::function const &)> sdf_; + }; + + inline sdf_sample sdf(any const & s, geom::point const & p) + { + return s.sdf_ ? s.sdf_(p) : sdf_sample{}; + } + +} diff --git a/libs/vecr/include/psemek/vecr/blend_mode.hpp b/libs/vecr/include/psemek/vecr/blend_mode.hpp new file mode 100644 index 00000000..be2ee9d3 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/blend_mode.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace psemek::vecr +{ + + using blend_mode = std::function; + + constexpr auto blend = [](gfx::color_4f const & dst, gfx::color_4f const & src) + { + return gfx::blend(dst, src); + }; + + constexpr auto overlay = [](gfx::color_4f const & dst, gfx::color_4f const & src) + { + return dst + src; + }; + +} diff --git a/libs/vecr/include/psemek/vecr/border.hpp b/libs/vecr/include/psemek/vecr/border.hpp new file mode 100644 index 00000000..10d6ed4e --- /dev/null +++ b/libs/vecr/include/psemek/vecr/border.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + template + struct border + { + Shape shape; + }; + + template + sdf_sample sdf(border const & s, geom::point const & p) + { + auto result = sdf(s.shape, p); + if (result.value < 0.f) + { + result.value *= -1.f; + result.gradient *= -1.f; + } + return result; + } + +} diff --git a/libs/vecr/include/psemek/vecr/circle.hpp b/libs/vecr/include/psemek/vecr/circle.hpp new file mode 100644 index 00000000..a3578089 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/circle.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + struct circle + { + geom::point center; + float radius; + }; + + sdf_sample sdf(circle const & s, geom::point const & p); + +} diff --git a/libs/vecr/include/psemek/vecr/colorizer.hpp b/libs/vecr/include/psemek/vecr/colorizer.hpp new file mode 100644 index 00000000..b3ffb9be --- /dev/null +++ b/libs/vecr/include/psemek/vecr/colorizer.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::vecr +{ + + gfx::color_4f colorize(gfx::color_rgba const & color, geom::point const & p, sdf_sample const & sample); + + struct gradient + { + gfx::color_rgba color0 = {0, 0, 0, 0}; + gfx::color_rgba color1 = {0, 0, 0, 0}; + any shape = {}; + }; + + gfx::color_4f colorize(gradient const & gradient, geom::point const & p, sdf_sample const & sample); + + struct lighting + { + any shape = {}; + gfx::color_rgba color0 = {0, 0, 0, 0}; + gfx::color_rgba color1 = {0, 0, 0, 0}; + float slope = 1.f; + geom::vector direction = {0.f, 0.f, 1.f}; + }; + + gfx::color_4f colorize(lighting const & lighting, geom::point const & p, sdf_sample const & sample); + + struct any_colorizer + { + any_colorizer() = default; + any_colorizer(any_colorizer &&) = default; + any_colorizer(any_colorizer const &) = default; + any_colorizer & operator = (any_colorizer const &) = default; + any_colorizer & operator = (any_colorizer &&) = default; + + template + any_colorizer(Colorizer && colorizer) + { + (*this) = std::forward(colorizer); + } + + template + any_colorizer & operator = (Colorizer && colorizer) + { + colorizer_ = [colorizer = std::forward(colorizer)](geom::point const & p, sdf_sample const & sample){ + return colorize(colorizer, p, sample); + }; + return *this; + } + + explicit operator bool() const + { + return static_cast(colorizer_); + } + + friend gfx::color_4f colorize(any_colorizer const & colorizer, geom::point const & p, sdf_sample const & sample) + { + return colorizer.colorizer_ ? colorizer.colorizer_(p, sample) : gfx::color_4f::zero(); + } + + private: + std::function const &, sdf_sample const &)> colorizer_; + }; + +} diff --git a/libs/vecr/include/psemek/vecr/exact.hpp b/libs/vecr/include/psemek/vecr/exact.hpp new file mode 100644 index 00000000..b525431a --- /dev/null +++ b/libs/vecr/include/psemek/vecr/exact.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::vecr +{ + + template + struct exact + { + Shape shape; + int iterations = 8; + }; + + template + sdf_sample sdf(exact const & s, geom::point const & p) + { + // Newton-Raphson method + + std::optional first_sample; + + auto q = p; + for (int i = 0; i < s.iterations; ++i) + { + auto sample = sdf(s.shape, q); + if (first_sample) first_sample = sample; + + auto d = geom::length_sqr(sample.gradient); + if (d > 0.f) + q -= (sample.value / d) * sample.gradient; + else + break; + } + + auto r = p - q; + auto l = geom::length(r); + + sdf_sample result; + result.value = l; + + if (l > 0.f) + result.gradient = r / l; + else + result.gradient = {0.f, 0.f}; + + if (first_sample->value < 0.f) + { + result.value *= -1.f; + result.gradient *= -1.f; + } + + return result; + } + +} diff --git a/libs/vecr/include/psemek/vecr/fill.hpp b/libs/vecr/include/psemek/vecr/fill.hpp new file mode 100644 index 00000000..4862b59e --- /dev/null +++ b/libs/vecr/include/psemek/vecr/fill.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace psemek::vecr +{ + + struct fill + { + path border; + }; + + sdf_sample sdf(fill const & s, geom::point const & p); + +} diff --git a/libs/vecr/include/psemek/vecr/grow.hpp b/libs/vecr/include/psemek/vecr/grow.hpp new file mode 100644 index 00000000..5dd9f0eb --- /dev/null +++ b/libs/vecr/include/psemek/vecr/grow.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + template + struct grow + { + Shape shape; + float distance; + }; + + template + sdf_sample sdf(grow const & s, geom::point const & p) + { + auto result = sdf(s.shape, p); + result.value -= s.distance; + return result; + } + +} diff --git a/libs/vecr/include/psemek/vecr/halfspace.hpp b/libs/vecr/include/psemek/vecr/halfspace.hpp new file mode 100644 index 00000000..b7a6a3e1 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/halfspace.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + struct halfspace + { + geom::point origin; + geom::vector normal; + }; + + inline sdf_sample sdf(halfspace const & s, geom::point const & p) + { + return {geom::dot(p - s.origin, s.normal), s.normal}; + } + +} diff --git a/libs/vecr/include/psemek/vecr/intersect.hpp b/libs/vecr/include/psemek/vecr/intersect.hpp new file mode 100644 index 00000000..3ab40888 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/intersect.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace psemek::vecr +{ + + struct intersect + { + std::vector shapes; + float smooth = 0.f; + }; + + sdf_sample sdf(intersect const & s, geom::point const & p); + +} diff --git a/libs/vecr/include/psemek/vecr/invert.hpp b/libs/vecr/include/psemek/vecr/invert.hpp new file mode 100644 index 00000000..e2998f39 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/invert.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + template + struct invert + { + Shape shape; + }; + + template + sdf_sample sdf(invert const & s, geom::point const & p) + { + auto result = sdf(s.shape, p); + result.value *= -1.f; + result.gradient *= -1.f; + return result; + } + +} diff --git a/libs/vecr/include/psemek/vecr/mirror.hpp b/libs/vecr/include/psemek/vecr/mirror.hpp new file mode 100644 index 00000000..41f8cac0 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/mirror.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + template + struct mirror + { + Shape shape; + geom::point origin{0.f, 0.f}; + geom::vector axis{1.f, 0.f}; + }; + + template + sdf_sample sdf(mirror const & s, geom::point const & p) + { + sdf_sample result; + + auto r = p - s.origin; + auto d = geom::dot(r, s.axis); + if (d < 0.f) + { + result = sdf(s.shape, p); + } + else + { + result = sdf(s.shape, p - (2.f * d) * s.axis); + result.gradient -= (2.f * geom::dot(result.gradient, s.axis)) * s.axis; + } + + return result; + } + +} diff --git a/libs/vecr/include/psemek/vecr/path.hpp b/libs/vecr/include/psemek/vecr/path.hpp new file mode 100644 index 00000000..1b596dda --- /dev/null +++ b/libs/vecr/include/psemek/vecr/path.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::vecr +{ + + struct path + { + std::vector> points; + }; + + sdf_sample sdf(path const & s, geom::point const & p, bool closed = false); + +} diff --git a/libs/vecr/include/psemek/vecr/renderer.hpp b/libs/vecr/include/psemek/vecr/renderer.hpp new file mode 100644 index 00000000..cfb3fd31 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/renderer.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace psemek::vecr +{ + + struct primitive + { + any mask = {}; + float blur = 1.f; + any_colorizer colorizer = {}; + blend_mode blend = vecr::blend; + }; + + struct renderer + { + void reset(geom::vector const & size, std::size_t samples = 4, gfx::color_rgba const & color = {0, 0, 0, 0}); + + geom::vector size() const; + std::size_t samples() const; + gfx::pixmap_rgba const & result() const; + + void clear(gfx::color_rgba const & color = {0, 0, 0, 0}); + + void draw(primitive const & primitive); + + private: + std::size_t samples_ = 4; + gfx::pixmap_rgba canvas_; + mutable gfx::pixmap_rgba result_; + mutable bool need_resolve_ = false; + + void resolve() const; + }; + + +} diff --git a/libs/vecr/include/psemek/vecr/sdf.hpp b/libs/vecr/include/psemek/vecr/sdf.hpp new file mode 100644 index 00000000..41239ec4 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/sdf.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace psemek::vecr +{ + + struct sdf_sample + { + float value = std::numeric_limits::infinity(); + geom::vector gradient{0.f, 0.f}; + }; + +} diff --git a/libs/vecr/include/psemek/vecr/subtract.hpp b/libs/vecr/include/psemek/vecr/subtract.hpp new file mode 100644 index 00000000..28b6f937 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/subtract.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace psemek::vecr +{ + + template + struct subtract + { + Shape1 shape1; + Shape2 shape2; + float smooth = 0.f; + }; + + template + sdf_sample sdf(subtract const & s, geom::point const & p) + { + return sdf(intersect{{s.shape1, invert{s.shape2}}, s.smooth}, p); + } + +} diff --git a/libs/vecr/include/psemek/vecr/tile.hpp b/libs/vecr/include/psemek/vecr/tile.hpp new file mode 100644 index 00000000..47bfa058 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/tile.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include + +namespace psemek::vecr +{ + + template + struct tile + { + Shape shape; + geom::box cell; + }; + + template + sdf_sample sdf(tile const & s, geom::point const & p) + { + auto q = p; + for (std::size_t i : {0, 1}) + q[i] = s.cell[i].min + std::fmod(q[i] - s.cell[i].min, s.cell[i].length()); + return sdf(s.shape, q); + } + +} diff --git a/libs/vecr/include/psemek/vecr/transform.hpp b/libs/vecr/include/psemek/vecr/transform.hpp new file mode 100644 index 00000000..09cf5536 --- /dev/null +++ b/libs/vecr/include/psemek/vecr/transform.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace psemek::vecr +{ + + template + struct translate + { + Shape shape; + geom::vector delta{0.f, 0.f}; + }; + + template + struct rotate + { + Shape shape; + float angle = 0.f; + geom::point origin{0.f, 0.f}; + }; + + template + struct scale + { + Shape shape; + geom::vector factor{1.f, 1.f}; + geom::point origin{0.f, 0.f}; + }; + + template + struct transform + { + Shape shape; + geom::matrix matrix = geom::matrix::identity(); + }; + + template + sdf_sample sdf(translate const & s, geom::point const & p) + { + return sdf(s.shape, p - s.delta); + } + + template + sdf_sample sdf(rotate const & s, geom::point const & p) + { + auto result = sdf(s.shape, s.origin + geom::rotate(p - s.origin, -s.angle)); + result.gradient = geom::rotate(result.gradient, s.angle); + return result; + } + + template + sdf_sample sdf(scale const & s, geom::point const & p) + { + auto result = sdf(s.shape, s.origin + geom::pointwise_mult(p - s.origin, {1.f / s.factor[0], 1.f / s.factor[1]})); + auto old_grad = geom::length(result.gradient); + result.gradient[0] /= s.factor[0]; + result.gradient[1] /= s.factor[1]; + if (auto new_grad = geom::length(result.gradient); new_grad != 0.f) + result.value *= (old_grad / new_grad); + return result; + } + + template + sdf_sample sdf(transform const & s, geom::point const & p) + { + auto minv = *geom::inverse(s.matrix); + + auto q = minv * geom::homogeneous(p); + + auto result = sdf(s.shape, geom::as_point(q)); + + auto old_grad = geom::length(result.gradient); + + geom::matrix mgrad; + mgrad[0][0] = (minv[0][0] * q[2] - minv[2][0] * q[0]) / (q[2] * q[2]); + mgrad[0][1] = (minv[1][0] * q[2] - minv[2][0] * q[1]) / (q[2] * q[2]); + mgrad[1][0] = (minv[0][1] * q[2] - minv[2][1] * q[0]) / (q[2] * q[2]); + mgrad[1][1] = (minv[1][1] * q[2] - minv[2][1] * q[1]) / (q[2] * q[2]); + + result.gradient = mgrad * result.gradient; + if (auto new_grad = geom::length(result.gradient); new_grad != 0.f) + result.value *= (old_grad / new_grad); + + return result; + } + + +} diff --git a/libs/vecr/source/add.cpp b/libs/vecr/source/add.cpp new file mode 100644 index 00000000..23a508c5 --- /dev/null +++ b/libs/vecr/source/add.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include + +namespace psemek::vecr +{ + + sdf_sample sdf(add const & s, geom::point const & p) + { + sdf_sample result; + + if (s.smooth == 0.f) + { + for (auto const & ss : s.shapes) + { + auto r = sdf(ss, p); + if (geom::make_min(result.value, r.value)) + result.gradient = r.gradient; + } + } + else + { + float v = 0.f; + + for (auto const & ss : s.shapes) + { + auto r = sdf(ss, p); + float vs = std::exp(- r.value / s.smooth); + v += vs; + result.gradient += r.gradient * vs; + } + + result.value = - std::log(v) * s.smooth; + result.gradient /= v; + } + + return result; + } + +} diff --git a/libs/vecr/source/circle.cpp b/libs/vecr/source/circle.cpp new file mode 100644 index 00000000..7527c18d --- /dev/null +++ b/libs/vecr/source/circle.cpp @@ -0,0 +1,15 @@ +#include + +namespace psemek::vecr +{ + + sdf_sample sdf(circle const & s, geom::point const & p) + { + auto r = p - s.center; + auto l = geom::length(r); + if (l > 0.f) + return {l - s.radius, r / l}; + return {l - s.radius, {0.f, 0.f}}; + } + +} diff --git a/libs/vecr/source/colorizer.cpp b/libs/vecr/source/colorizer.cpp new file mode 100644 index 00000000..48746336 --- /dev/null +++ b/libs/vecr/source/colorizer.cpp @@ -0,0 +1,33 @@ +#include +#include + +namespace psemek::vecr +{ + + gfx::color_4f colorize(gfx::color_rgba const & color, geom::point const &, sdf_sample const &) + { + return gfx::to_colorf(color); + } + + gfx::color_4f colorize(gradient const & gradient, geom::point const & p, sdf_sample const &) + { + return gfx::lerp( + gfx::to_colorf(gradient.color0), + gfx::to_colorf(gradient.color1), + geom::clamp(sdf(gradient.shape, p).value + 0.5f, {0.f, 1.f}) + ); + } + + gfx::color_4f colorize(lighting const & lighting, geom::point const & p, sdf_sample const & sample) + { + auto const real_sample = lighting.shape ? sdf(lighting.shape, p) : sample; + + auto normal = geom::swizzle<0, 1, -1>(real_sample.gradient * lighting.slope); + normal[2] = 1.f; + + auto factor = 0.5f + 0.5f * geom::dot(geom::normalized(normal), lighting.direction); + + return gfx::lerp(gfx::to_colorf(lighting.color0), gfx::to_colorf(lighting.color1), factor); + } + +} diff --git a/libs/vecr/source/fill.cpp b/libs/vecr/source/fill.cpp new file mode 100644 index 00000000..b099d64c --- /dev/null +++ b/libs/vecr/source/fill.cpp @@ -0,0 +1,32 @@ +#include +#include + +namespace psemek::vecr +{ + + sdf_sample sdf(fill const & s, geom::point const & p) + { + auto result = sdf(s.border, p, true); + + int inside = 0; + for (std::size_t i = 0; i < s.border.points.size(); ++i) + { + auto const j = (i + 1) % s.border.points.size(); + bool const s0 = s.border.points[i][1] <= p[1]; + bool const s1 = p[1] < s.border.points[j][1]; + auto const sign = geom::orientation(s.border.points[i], s.border.points[j], p); + + if (s0 && s1 && sign == geom::sign_t::positive) inside += 1; + if (!s0 && !s1 && sign == geom::sign_t::negative) inside -= 1; + } + + if (inside != 0) + { + result.value *= -1.f; + result.gradient *= -1.f; + } + + return result; + } + +} diff --git a/libs/vecr/source/intersect.cpp b/libs/vecr/source/intersect.cpp new file mode 100644 index 00000000..63ae27a6 --- /dev/null +++ b/libs/vecr/source/intersect.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include + +namespace psemek::vecr +{ + + sdf_sample sdf(intersect const & s, geom::point const & p) + { + sdf_sample result; + + if (s.smooth == 0.f) + { + result.value = -std::numeric_limits::infinity(); + for (auto const & ss : s.shapes) + { + auto r = sdf(ss, p); + if (geom::make_max(result.value, r.value)) + result.gradient = r.gradient; + } + } + else + { + float v = 0.f; + + for (auto const & ss : s.shapes) + { + auto r = sdf(ss, p); + float vs = std::exp(r.value / s.smooth); + v += vs; + result.gradient += r.gradient * vs; + } + + result.value = std::log(v) * s.smooth; + result.gradient /= v; + } + + return result; + } + +} diff --git a/libs/vecr/source/path.cpp b/libs/vecr/source/path.cpp new file mode 100644 index 00000000..d0e23baa --- /dev/null +++ b/libs/vecr/source/path.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +namespace psemek::vecr +{ + + sdf_sample sdf(path const & s, geom::point const & p, bool closed) + { + sdf_sample result; + for (std::size_t i = 0; i + (closed ? 0 : 1) < s.points.size(); ++i) + { + auto const j = (i + 1) % s.points.size(); + auto const e = s.points[j] - s.points[i]; + auto const v = p - s.points[i]; + auto const t = geom::clamp(geom::dot(v, e) / geom::dot(e, e), {0.f, 1.f}); + auto const q = v - t * e; + auto const l = geom::length(q); + if (geom::make_min(result.value, l)) + { + if (l > 0.f) + result.gradient = q / l; + else + result.gradient = {0.f, 0.f}; + } + } + return result; + } + +} diff --git a/libs/vecr/source/renderer.cpp b/libs/vecr/source/renderer.cpp new file mode 100644 index 00000000..20acb9f7 --- /dev/null +++ b/libs/vecr/source/renderer.cpp @@ -0,0 +1,85 @@ +#include +#include + +namespace psemek::vecr +{ + + void renderer::reset(geom::vector const & size, std::size_t samples, gfx::color_rgba const & color) + { + samples_ = samples; + canvas_.assign({size[0] * samples, size[1] * samples}, color); + result_.assign({size[0], size[1]}, color); + need_resolve_ = false; + } + + geom::vector renderer::size() const + { + return {result_.width(), result_.height()}; + } + + std::size_t renderer::samples() const + { + return samples_; + } + + gfx::pixmap_rgba const & renderer::result() const + { + resolve(); + return result_; + } + + void renderer::clear(gfx::color_rgba const & color) + { + canvas_.fill(color); + result_.fill(color); + need_resolve_ = false; + } + + void renderer::draw(primitive const & primitive) + { + float const aa = primitive.blur / 2.f; + + for (auto const & idx : canvas_.indices()) + { + geom::point const center{(idx[0] + 0.5f) / samples_, (idx[1] + 0.5f) / samples_}; + + auto const sample = sdf(primitive.mask, center); + + if (sample.value > aa) continue; + + float blur = 1.f; + if (sample.value > - aa) + blur = (aa - sample.value) / (2.f * aa); + + auto color = colorize(primitive.colorizer, center, sample); + color[3] *= blur; + + canvas_(idx) = gfx::to_coloru8(primitive.blend(gfx::to_colorf(canvas_(idx)), color)); + } + + need_resolve_ = true; + } + + void renderer::resolve() const + { + if (!need_resolve_) return; + + for (auto const & idx : result_.indices()) + { + gfx::color_4f sum{0.f, 0.f, 0.f, 0.f}; + + for (std::size_t y = 0; y < samples_; ++y) + { + for (std::size_t x = 0; x < samples_; ++x) + { + sum += gfx::premult(gfx::to_colorf(canvas_({idx[0] * samples_ + x, idx[1] * samples_ + y}))); + } + } + + result_(idx) = gfx::to_coloru8(gfx::unpremult(sum / (1.f * samples_ * samples_))); + } + + need_resolve_ = false; + } + +}