kfr

Fast, modern C++ DSP framework, FFT, Sample Rate Conversion, FIR/IIR/Biquad Filters (SSE, AVX, AVX-512, ARM NEON)
Log | Files | Refs | README

commit 50b19a74b2d3bc8d32a8efc1dc455cc8251593ae
parent 199751ba124cd3b96e58167e083011aef43ffca5
Author: d.levin256@gmail.com <d.levin256@gmail.com>
Date:   Fri,  4 Aug 2023 02:44:46 +0100

color.hpp/geometry.hpp refactoring

Diffstat:
Minclude/kfr/graphics/color.hpp | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Minclude/kfr/graphics/geometry.hpp | 437+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Minclude/kfr/graphics/impl/scaled.hpp | 21++++++++++++++++++---
Minclude/kfr/simd/horizontal.hpp | 17+++++++++++++++++
Mtests/unit/graphics/color.cpp | 30++++++++++++++++++++++++++++--
Mtests/unit/graphics/geometry.cpp | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 530 insertions(+), 229 deletions(-)

diff --git a/include/kfr/graphics/color.hpp b/include/kfr/graphics/color.hpp @@ -29,14 +29,48 @@ namespace kfr { -inline namespace CMT_ARCH_NAME + +// 8-bit bgra (written as argb), gamma-corrected +// u8argb(0xFF4400DD) +// AARRGGBB +enum class u8argb : uint32_t; +// 16-bit bgra (written as argb), linear +enum class u16argb : uint64_t; + +namespace colors { +constexpr static u8argb white = static_cast<u8argb>(0xFF'FFFFFF); +constexpr static u8argb black = static_cast<u8argb>(0xFF'000000); +constexpr static u8argb red = static_cast<u8argb>(0xFF'FF0000); +constexpr static u8argb green = static_cast<u8argb>(0xFF'00FF00); +constexpr static u8argb blue = static_cast<u8argb>(0xFF'0000FF); +constexpr static u8argb yellow = static_cast<u8argb>(0xFF'FFFF00); +constexpr static u8argb cyan = static_cast<u8argb>(0xFF'00FFFF); +constexpr static u8argb magenta = static_cast<u8argb>(0xFF'FF00FF); +constexpr static u8argb transparent = static_cast<u8argb>(0x00'000000); +constexpr static u8argb grey = static_cast<u8argb>(0xFF'808080); +}; // namespace colors + +template <typename T> +struct color; + +using f32color = color<f32>; +using u8color = color<u8>; +using u16color = color<u16>; + +CMT_INTRINSIC u8color to_color(u8argb x); +CMT_INTRINSIC u16color to_color(u16argb x); -template <typename T, int maximum = 1> +template <typename T, size_t N> +CMT_INTRINSIC vec<T, N> from_srgb(vec<T, N> v); + +template <typename T> struct color { - constexpr static T min = std::numeric_limits<T>::min(); - constexpr static T max = std::numeric_limits<T>::max(); + using Tfloat = std::common_type_t<T, float>; + + constexpr static inline bool gamma_corrected = std::is_same_v<T, u8>; + constexpr static inline int maximum = std::is_floating_point_v<T> ? 1 : std::numeric_limits<T>::max(); using vec_type = vec<T, 4>; @@ -57,30 +91,55 @@ struct color #else constexpr color(const color&) = default; #endif - constexpr color(const vec<T, 4>& v) : v(v) {} - constexpr color(const vec<T, 3>& v, T a = maximum) : v(concat(v, vec<T, 1>(a))) {} + constexpr explicit color(const vec<T, 4>& v) : v(v) {} + constexpr explicit color(const vec<T, 3>& v, T a = maximum) : v(concat(v, vec<T, 1>(a))) {} constexpr color(const vec<T, 4>& v, T a) : v(concat(slice<0, 3>(v), vec<T, 1>(a))) {} - // @brief Convert from 0xAARRGGBB representation (BGRA in little endian) - static constexpr color from_argb(uint32_t c) + static constexpr color from_argb(uint32_t c) { return color(static_cast<u8argb>(c)); } + + constexpr color(u8argb c) : color(to_color(c)) {} + constexpr color(u16argb c) : color(to_color(c)) {} + template <typename U> + operator color<U>() const { - return color(convert_scaled<T, maximum, 255>(bitcast<u8>(u32x1(static_cast<uint32_t>(c)))) - .shuffle(csizes_t<2, 1, 0, 3>())); + if constexpr (gamma_corrected != color<U>::gamma_corrected) + { + vec<Tfloat, 4> tmp = convert_scaled<Tfloat, 1, maximum>(v); + if constexpr (gamma_corrected) + tmp = concat(from_srgb(slice<0, 3>(tmp)), slice<3, 1>(tmp)); + else + tmp = concat(to_srgb(slice<0, 3>(tmp)), slice<3, 1>(tmp)); + return color<U>(convert_scaled<U, color<U>::maximum, 1>(tmp)); + } + else + { + return color<U>(convert_scaled<U, color<U>::maximum, maximum>(v)); + } } - template <typename U, int Umax> - operator color<U, Umax>() const + constexpr color() : v() {} + + constexpr color lighter(Tfloat n = 1.2f) const noexcept { - return convert_scaled<U, Umax, maximum>(v); + return color(clamp(v * vec<T, 4>(n), T(0), T(maximum)), a); + } + constexpr color darker(Tfloat n = 1.2f) const noexcept + { + return color(clamp(v / vec<T, 4>(n), T(0), T(maximum)), a); } - constexpr color() = default; - - constexpr color lighter(double n = 1.2) const noexcept { return color(v * vec<T, 4>(n), a); } - constexpr color darker(double n = 1.2) const noexcept { return color(v / vec<T, 4>(n), a); } constexpr T lightness() const { - using Tcommon = std::conditional_t<std::is_floating_point_v<T>, T, findinttype<min * 3, max * 3>>; - return (Tcommon(r) + g + b) / 3; + if constexpr (std::is_floating_point<T>::value) + { + return (r + g + b) * static_cast<T>(0.33333); + } + else + { + constexpr T min = std::numeric_limits<T>::min(); + constexpr T max = std::numeric_limits<T>::max(); + using Tcommon = findinttype<min * 3, max * 3>; + return (Tcommon(r) + g + b) / 3; + } } constexpr T lightness_perc() const @@ -88,7 +147,13 @@ struct color return static_cast<T>(sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b)); } - constexpr color normalize() const { return v / lightness_perc(); } + constexpr color desaturate(Tfloat t = 1.0) const + { + const T l = lightness_perc(); + return color(kfr::mix(t, v, concat(vec<T, 3>(l), vec<T, 1>(a)))); + } + + constexpr color normalize() const { return color(v / lightness_perc()); } constexpr void apply_red(T r) noexcept { this->r = r; } constexpr void apply_green(T g) noexcept { this->g = g; } @@ -97,21 +162,25 @@ struct color constexpr color with_red(T r) const noexcept { - return blend(v, broadcast<4>(r), elements_t<1, 0, 0, 0>()); + return color(blend(v, broadcast<4>(r), elements_t<1, 0, 0, 0>())); } constexpr color with_green(T g) const noexcept { - return blend(v, broadcast<4>(g), elements_t<0, 1, 0, 0>()); + return color(blend(v, broadcast<4>(g), elements_t<0, 1, 0, 0>())); } constexpr color with_blue(T b) const noexcept { - return blend(v, broadcast<4>(b), elements_t<0, 0, 1, 0>()); + return color(blend(v, broadcast<4>(b), elements_t<0, 0, 1, 0>())); } constexpr color with_alpha(T a) const noexcept { - return blend(v, broadcast<4>(a), elements_t<0, 0, 0, 1>()); + return color(blend(v, broadcast<4>(a), elements_t<0, 0, 0, 1>())); + } + constexpr color with_alpha_premul(T a) const noexcept { return color(v * a); } + constexpr color premultiply() const noexcept + { + return color(blend(color(color<Tfloat>(*this).v * Tfloat(a)).v, v, elements_t<0, 0, 0, 1>())); } - constexpr color with_alpha_premul(T a) const noexcept { return v * a; } constexpr bool operator==(const color& c) const { return all(v == c.v); } constexpr bool operator!=(const color& c) const { return !(*this == c); } @@ -139,35 +208,67 @@ struct color }; }; -using f32color = color<f32>; -using u8color = color<u8, 255>; +CMT_INTRINSIC u8color to_color(u8argb x) +{ + return u8color( + vec<u8, 4>{ bitcast<u8>(u32x1(static_cast<uint32_t>(x))) }.shuffle(csizes_t<2, 1, 0, 3>())); +} +CMT_INTRINSIC u16color to_color(u16argb x) +{ + return u16color( + vec<u16, 4>{ bitcast<u16>(u64x1(static_cast<uint64_t>(x))) }.shuffle(csizes_t<2, 1, 0, 3>())); +} -CMT_INTRINSIC f32color mix(float t, const f32color& a, const f32color& b) { return kfr::mix(t, a.v, b.v); } +template <typename T> +CMT_INTRINSIC color<T> mix(float t, const color<T>& a, const color<T>& b) +{ + return color<T>(kfr::mix(t, a.v, b.v)); +} -CMT_INTRINSIC f32color from_srgb_approx(f32color color) +template <typename T, size_t N> +CMT_INTRINSIC vec<T, N> from_srgb(vec<T, N> v) { - float a = color.a; - color.v = color.v * (color.v * (color.v * 0.305306011f + 0.682171111f) + 0.012522878f); - color.a = a; - return color; + static_assert(std::is_floating_point_v<T>); +#ifndef KFR_SRGB_APPROX + mask<T, N> m = v <= T(0.04045); + return select(m, v * T(0.07739938080495356), kfr::pow((v + T(0.055)) * T(0.947867298578199), T(2.4))); +#else + return v * (v * (v * 0.305306011f + 0.682171111f) + 0.012522878f); +#endif +} + +template <typename T> +CMT_INTRINSIC color<T> from_srgb(color<T> c) +{ + return color<T>(from_srgb(slice<0, 3>(c.v)), c.a); +} + +template <typename T, size_t N> +CMT_INTRINSIC vec<T, N> to_srgb(vec<T, N> v) +{ + static_assert(std::is_floating_point_v<T>); +#ifndef KFR_SRGB_APPROX + mask<T, N> m = v <= T(0.0031308); + return select(m, v * T(12.92), T(1.055) * kfr::pow(v, T(0.4166666666666666667)) - T(0.055)); +#else + vec<T, N> S1 = kfr::sqrt(v); + vec<T, N> S2 = kfr::sqrt(S1); + vec<T, N> S3 = kfr::sqrt(S2); + return T(0.585122381) * S1 + T(0.783140355) * S2 - T(0.368262736) * S3; +#endif } -CMT_INTRINSIC f32color to_srgb_approx(f32color color) +template <typename T> +CMT_INTRINSIC color<T> to_srgb(color<T> c) { - float a = color.a; - f32x4 S1 = kfr::sqrt(color.v); - f32x4 S2 = kfr::sqrt(S1); - f32x4 S3 = kfr::sqrt(S2); - color = 0.585122381f * S1 + 0.783140355f * S2 - 0.368262736f * S3; - color.a = a; - return color; + return color<T>(to_srgb(slice<0, 3>(c.v)), c.a); } -constexpr inline f32color operator""_argb(unsigned long long argb) { return f32color::from_argb(argb); } +constexpr inline u8color operator""_argb(unsigned long long argb) { return u8color::from_argb(argb); } -constexpr inline f32color operator""_rgb(unsigned long long rgb) +constexpr inline u8color operator""_rgb(unsigned long long rgb) { - return f32color::from_argb(0xFF000000u | rgb); + return u8color::from_argb(0xFF000000u | rgb); } CMT_INTRINSIC f32x3 lab_to_xyz(const f32x3& lab) @@ -218,13 +319,13 @@ CMT_INTRINSIC f32x3 xyz_to_lab(const f32x3& xyz) CMT_INTRINSIC f32color lab_to_srgb(const f32x3& lab, float alpha = 1.f) { const f32x3 srgb = clamp(rgb_to_srgb(xyz_to_rgb(lab_to_xyz(lab))), 0.f, 1.f); - return f32x4(srgb, f32x1{ alpha }); + return f32color(f32x4(srgb, f32x1{ alpha })); } CMT_INTRINSIC f32color lab_to_linear_rgb(const f32x3& lab, float alpha = 1.f) { const f32x3 rgb = clamp(xyz_to_rgb(lab_to_xyz(lab)), 0.f, 1.f); - return f32x4(rgb, f32x1{ alpha }); + return f32color(f32x4(rgb, f32x1{ alpha })); } CMT_INTRINSIC f32x3 srgb_to_lab(const f32color& srgb) @@ -290,16 +391,15 @@ CMT_INTRINSIC f32color hsv_to_srgb(const f32x3& hsv, float alpha = 1.f) } } -} // namespace CMT_ARCH_NAME } // namespace kfr namespace cometa { -template <typename T, int maximum> -struct representation<kfr::color<T, maximum>> +template <typename T> +struct representation<kfr::color<T>> { using type = std::string; - static std::string get(const kfr::color<T, maximum>& value) noexcept + static std::string get(const kfr::color<T>& value) noexcept { return as_string("color(", value.r, ", ", value.g, ", ", value.b, ", ", value.a, ")"); } diff --git a/include/kfr/graphics/geometry.hpp b/include/kfr/graphics/geometry.hpp @@ -29,40 +29,84 @@ namespace kfr { -inline namespace CMT_ARCH_NAME + +template <typename T> +struct point; +template <typename T> +struct size; +template <typename T> +struct border; +template <typename T> +struct rectangle; +template <typename T> +struct matrix2d; + +struct percents { + double value; + template <typename T> + inline constexpr T calc(T total) const + { + return static_cast<T>(total * value / 100.0); + } +}; -template <typename T, int maximum = 1> +constexpr inline percents operator""_perc(long double v) { return { static_cast<double>(v) }; } +constexpr inline percents operator""_perc(unsigned long long v) { return { static_cast<double>(v) }; } + +template <typename T> struct point { - constexpr point() noexcept = default; - constexpr point(const vec<T, 2>& v) noexcept : v(v) {} + using Tfloat = std::common_type_t<T, float>; + + constexpr point() noexcept : v() {} + constexpr explicit point(const vec<T, 2>& v) noexcept : v(v) {} constexpr point(T x, T y) noexcept : v(x, y) {} + constexpr point(const size<T>& sz) noexcept : v(sz.v) {} - template <typename U, int Umax> - operator point<U, Umax>() const + template <typename U> + operator point<U>() const { - return convert_scaled<U, Umax, maximum>(v); + return point<U>(convert_scaled<U, 1, 1>(v)); } constexpr bool operator==(const point& c) const { return all(v == c.v); } constexpr bool operator!=(const point& c) const { return !operator==(c); } - constexpr friend point operator+(const point& p1, const point& p2) { return p1.v + p2.v; } - constexpr friend point operator-(const point& p1, const point& p2) { return p1.v - p2.v; } - constexpr friend point operator*(const point& p1, const point& p2) { return p1.v * p2.v; } - constexpr friend point operator-(const point& p1) { return -p1.v; } + constexpr friend point operator+(const point& p1, const point& p2) { return point(p1.v + p2.v); } + constexpr friend point operator-(const point& p1, const point& p2) { return point(p1.v - p2.v); } + constexpr friend point operator*(const point& p1, const point& p2) { return point(p1.v * p2.v); } + constexpr friend point operator/(const point& p1, const point& p2) { return point(p1.v / p2.v); } + constexpr friend point operator-(const point& p1) { return point(-p1.v); } + constexpr friend point operator*(const point& p1, const T& v2) { return point(p1.v * v2); } + constexpr friend point operator*(const T& v1, const point& p2) { return point(v1 * p2.v); } + constexpr friend point operator/(const point& p1, const T& v2) { return point(p1.v / v2); } + constexpr friend point operator/(const T& v1, const point& p2) { return point(v1 / p2.v); } - constexpr point flipped() const { return kfr::swap(v); } + constexpr point flipped() const { return point(kfr::swap(v)); } T distance(const point& pt) const { return std::sqrt(sqr(pt.x - x) + sqr(pt.y - y)); } + T manhattan_distance(const point& pt) const { return std::max(std::abs(pt.x - x), std::abs(pt.y - y)); } + T operator[](size_t i) const { return v[i]; } T& operator[](size_t i) { return a[i]; } - constexpr point round() const { return kfr::round(v); } - constexpr point floor() const { return kfr::floor(v); } - constexpr point ceil() const { return kfr::ceil(v); } - constexpr point trunc() const { return kfr::trunc(v); } + rectangle<T> aligned_rect(const kfr::size<T>& inner_size, const point<Tfloat>& alignment) const + { + const kfr::size<T> sz = inner_size; + const kfr::size<T> gap = -sz; + const vec<T, 2> p = v + cast<T>(gap.v * alignment.v); + return rectangle(concat(p, p + sz.v)); + } + rectangle<T> aligned_rect(T width, T height, Tfloat align_x, Tfloat align_y) const + { + return aligned_rect({ width, height }, { align_x, align_y }); + } + + constexpr point round() const { return point(kfr::round(v)); } + constexpr point floor() const { return point(kfr::floor(v)); } + constexpr point ceil() const { return point(kfr::ceil(v)); } + constexpr point trunc() const { return point(kfr::trunc(v)); } union { @@ -82,32 +126,47 @@ struct point }; }; -template <typename T, int maximum = 1> +template <typename T> +CMT_INLINE point<T> min(const point<T>& a, const point<T>& b) +{ + return point<T>(min(a.v, b.v)); +} +template <typename T> +CMT_INLINE point<T> max(const point<T>& a, const point<T>& b) +{ + return point<T>(max(a.v, b.v)); +} + +template <typename T> struct size { - constexpr size() noexcept = default; + constexpr size() noexcept : v() {} constexpr size(T x, T y) noexcept : v(x, y) {} constexpr explicit size(T xy) noexcept : v(xy, xy) {} constexpr size(const vec<T, 2>& v) noexcept : v(v) {} - template <typename U, int Umax> - operator size<U, Umax>() const noexcept + template <typename U> + operator size<U>() const noexcept { - return convert_scaled<U, Umax, maximum>(v); + return size<U>(convert_scaled<U, 1, 1>(v)); } - constexpr size round() const { return kfr::round(v); } - constexpr size floor() const { return kfr::floor(v); } - constexpr size ceil() const { return kfr::ceil(v); } - constexpr size trunc() const { return kfr::trunc(v); } - - constexpr size flipped() const { return kfr::swap(v); } - - constexpr friend size operator+(const size& s1, const size& s2) { return s1.v + s2.v; } - constexpr friend size operator-(const size& s1, const size& s2) { return s1.v - s2.v; } - constexpr friend size operator*(const size& s1, const size& s2) { return s1.v * s2.v; } - constexpr friend size operator*(const size& s1, T s2) { return s1.v * s2; } - constexpr friend size operator*(T s1, const size& s2) { return s1 * s2.v; } + constexpr size round() const { return size(kfr::round(v)); } + constexpr size floor() const { return size(kfr::floor(v)); } + constexpr size ceil() const { return size(kfr::ceil(v)); } + constexpr size trunc() const { return size(kfr::trunc(v)); } + + constexpr size flipped() const { return size(kfr::swap(v)); } + + constexpr friend size operator+(const size& s1, const size& s2) { return size(s1.v + s2.v); } + constexpr friend size operator-(const size& s1, const size& s2) { return size(s1.v - s2.v); } + constexpr friend size operator*(const size& s1, const size& s2) { return size(s1.v * s2.v); } + constexpr friend size operator/(const size& s1, const size& s2) { return size(s1.v / s2.v); } + constexpr friend size operator*(const size& s1, T s2) { return size(s1.v * s2); } + constexpr friend size operator*(T s1, const size& s2) { return size(s1 * s2.v); } + constexpr friend size operator/(const size& s1, T s2) { return size(s1.v / s2); } + constexpr friend size operator/(T s1, const size& s2) { return size(s1 / s2.v); } + constexpr friend size operator-(const size& s1) { return size(-s1.v); } constexpr T area() const { return x * y; } T operator[](size_t i) const { return v[i]; } @@ -138,26 +197,49 @@ struct size }; }; -template <typename T, int maximum = 1> +template <typename T> +CMT_INLINE size<T> min(const size<T>& a, const size<T>& b) +{ + return size<T>(min(a.v, b.v)); +} +template <typename T> +CMT_INLINE size<T> max(const size<T>& a, const size<T>& b) +{ + return size<T>(max(a.v, b.v)); +} + +template <typename T> struct border { constexpr border() noexcept : v() {} constexpr explicit border(T value) noexcept : v(value) {} constexpr border(T h, T v) noexcept : v(h, v, h, v) {} constexpr border(T x1, T y1, T x2, T y2) noexcept : v(x1, y1, x2, y2) {} + constexpr explicit border(const vec<T, 4>& v) : v(v) {} - constexpr border round() const { return kfr::round(v); } - constexpr border floor() const { return kfr::floor(v); } - constexpr border ceil() const { return kfr::ceil(v); } - constexpr border trunc() const { return kfr::trunc(v); } + template <typename U> + operator border<U>() const + { + return border<U>(convert_scaled<U, 1, 1>(v)); + } - kfr::size<T> size() const { return { x1 + x2, y1 + y2 }; } + constexpr border round() const { return border(kfr::round(v)); } + constexpr border floor() const { return border(kfr::floor(v)); } + constexpr border ceil() const { return border(kfr::ceil(v)); } + constexpr border trunc() const { return border(kfr::trunc(v)); } - T horizontal() const { return x1 + x2; } - T vertical() const { return y1 + y2; } + kfr::size<T> size() const { return kfr::size<T>(low(v) + high(v)); } - point<T> starting() const { return { x1, y1 }; } - point<T> trailing() const { return { x2, y2 }; } + T horizontal() const { return size().x; } + T vertical() const { return size().y; } + + point<T> leading() const { return point<T>(slice<0, 2>(v)); } + point<T> trailing() const { return point<T>(slice<2, 2>(v)); } + + T operator[](size_t i) const { return v[i]; } + T& operator[](size_t i) { return a[i]; } + + constexpr friend border operator+(const border& b1, const border& b2) { return border(b1.v + b2.v); } constexpr bool operator==(const border& c) const { return all(v == c.v); } constexpr bool operator!=(const border& c) const { return !(operator==(c)); } @@ -174,89 +256,73 @@ struct border { vec<T, 4> v; }; - }; -}; - -using i32border = border<i32>; -using f32border = border<f32>; - -template <typename T, int maximum = 1> -struct vector4 -{ - constexpr vector4(T x, T y, T z, T w) : v(x, y, z, w) {} - constexpr vector4(const vec<T, 4>& v) : v(v) {} - constexpr vector4() = default; - - constexpr bool operator==(const vector4& c) const { return all(v == c.v); } - constexpr bool operator!=(const vector4& c) const { return !operator==(c); } - - union - { - struct - { - T x; - T y; - T z; - T w; - }; struct { - point<T> p1; - point<T> p2; - }; - struct - { - vec<T, 4> v; + T a[4]; }; }; }; -template <typename T, int maximum = 1> +template <typename T> +CMT_INLINE border<T> min(const border<T>& a, const border<T>& b) +{ + return border<T>(min(a.v, b.v)); +} +template <typename T> +CMT_INLINE border<T> max(const border<T>& a, const border<T>& b) +{ + return border<T>(max(a.v, b.v)); +} + +template <typename T> struct rectangle { + using Tfloat = std::common_type_t<T, float>; + constexpr rectangle(T x1, T y1, T x2, T y2) : v(x1, y1, x2, y2) {} - constexpr rectangle(const vec<T, 4>& v) : v(v) {} + constexpr explicit rectangle(const vec<T, 4>& v) : v(v) {} - template <typename U, int Umax> - operator rectangle<U, Umax>() const + template <typename U> + operator rectangle<U>() const { - return convert_scaled<U, Umax, maximum>(v); + return rectangle<U>(convert_scaled<U, 1, 1>(v)); } constexpr rectangle(const point<T>& point, const size<T>& size) : v(point.v, point.v + size.v) {} constexpr rectangle(const point<T>& point1, const point<T>& point2) : v(point1.v, point2.v) {} - constexpr rectangle(const point<T>& base, const kfr::size<T>& dim, const point<T>& alignment) - : v(base.v - dim.v * alignment.v, base.v + dim.v * (1 - alignment.v)) + constexpr rectangle(const point<T>& base, const kfr::size<T>& dim, const point<Tfloat>& alignment) + : rectangle(base.aligned_rect(dim, alignment)) { } - constexpr rectangle() = default; + constexpr rectangle() : v() {} - constexpr bool empty() const noexcept { return width() <= 0 || height() <= 0; } + constexpr bool empty() const noexcept { return any(size().v <= 0); } - constexpr kfr::size<T, maximum> size() const { return high(v) - low(v); } + constexpr kfr::size<T> size() const { return high(v) - low(v); } constexpr T area() const { return size().area(); } constexpr T width() const { return x2 - x1; } constexpr T height() const { return y2 - y1; } - constexpr T min_side() const { return std::min(width(), height()); } - constexpr T max_side() const { return std::max(width(), height()); } + constexpr T min_side() const { return hmin(size().v); } + constexpr T max_side() const { return hmax(size().v); } - point<T, maximum> center() const { return at(0.5f, 0.5f); } + point<T> center() const { return at(0.5f, 0.5f); } - point<T> to_norm_coord(const point<T>& pt) const { return (pt.v - p1.v) / (p1.v - p1.v); } - point<T> to_norm_coord(const point<T>& pt, const point<T>& ifoutside) const + point<Tfloat> to_norm_coord(const point<T>& pt) const { return point<T>((pt.v - p1.v) / (p1.v - p1.v)); } + point<Tfloat> to_norm_coord(const point<T>& pt, const point<T>& ifoutside) const { if (!contains(pt)) return ifoutside; - return (pt.v - p1.v) / (p2.v - p1.v); + return point<T>((pt.v - p1.v) / (p2.v - p1.v)); } - rectangle split(const point<float>& point1, const kfr::size<float>& size) const noexcept + rectangle split(const point<Tfloat>& point1, const kfr::size<Tfloat>& size) const noexcept { - const vec<float, 2> point2 = point1.v + size.v; - return concat(cast<T>(p1.v + this->size().v * point1.v), cast<T>(p1.v + this->size().v * point2)); + const vec<Tfloat, 2> point2 = point1.v + size.v; + return rectangle( + concat(cast<T>(p1.v + this->size().v * point1.v), cast<T>(p1.v + this->size().v * point2))); } - rectangle split(float x, float y, float w, float h) const noexcept { return split({ x, y }, { w, h }); } + rectangle split(Tfloat x, Tfloat y, Tfloat w, Tfloat h) const noexcept { return split({ x, y }, { w, h }); } rectangle cut_h_start(T width, bool partial = false) { @@ -297,15 +363,20 @@ struct rectangle return result; } - point<T> at(const point<float>& pt) const noexcept { return p1.v + point<T>(pt.v * size().v); } - point<T> at(float x, float y) const noexcept + rectangle cut_h_start(percents width) { return cut_h_start(width.calc(this->width())); } + rectangle cut_v_start(percents height) { return cut_v_start(height.calc(this->height())); } + rectangle cut_h_end(percents width) { return cut_h_end(width.calc(this->width())); } + rectangle cut_v_end(percents height) { return cut_v_end(height.calc(this->height())); } + + point<T> at(const point<Tfloat>& pt) const noexcept { return p1 + point<T>(pt.v * size().v); } + point<T> at(Tfloat x, Tfloat y) const noexcept { - return p1.v + point<T>(point<float>{ x, y }.v * size().v); + return p1 + point<T>(point<Tfloat>{ x, y }.v * size().v); } - void apply_start(const point<T, maximum>& p) { v = concat(p.v, p.v + size()); } + void apply_start(const point<T>& p) { v = concat(p.v, p.v + size()); } void apply_start(T x, T y) { v = concat(pack(x, y), pack(x, y) + size()); } - void apply_size(const kfr::size<T, maximum>& s) { v = concat(low(v), low(v) + s.v); } + void apply_size(const kfr::size<T>& s) { v = concat(low(v), low(v) + s.v); } void apply_size(T w, T h) { v = concat(low(v), low(v) + pack(w, h)); } void apply_width(T w) { apply_size(w, height()); } void apply_height(T h) { apply_size(width(), h); } @@ -320,67 +391,79 @@ struct rectangle void apply_padding(const border<T>& p) { v += vec<T, 4>(1, 1, -1, -1) * p.v; } void apply_padding(T x1, T y1, T x2, T y2) { v += vec<T, 4>(+x1, +y1, -x2, -y2); } - rectangle aligned_rect(const kfr::size<T>& inner_size, const point<float>& alignment) const + rectangle aligned_rect(const kfr::size<T>& inner_size, const point<Tfloat>& alignment) const { const kfr::size<T> sz = inner_size; // kfr::min(inner_size.v, this->size().v); const kfr::size<T> gap = size() - sz; const vec<T, 2> p = p1.v + cast<T>(gap.v * alignment.v); return rectangle(concat(p, p + sz.v)); } - rectangle aligned_rect(T width, T height, float align_x, float align_y) const + rectangle aligned_rect(T width, T height, Tfloat align_x, Tfloat align_y) const { return aligned_rect({ width, height }, { align_x, align_y }); } - constexpr rectangle with_start(const point<T, maximum>& p) const { return concat(p.v, p.v + size()); } - constexpr rectangle with_start(T x, T y) const { return concat(pack(x, y), pack(x, y) + size()); } - constexpr rectangle with_size(const kfr::size<T, maximum>& s) const + constexpr rectangle with_start(const point<T>& p) const { return rectangle(concat(p.v, p.v + size())); } + constexpr rectangle with_start(T x, T y) const + { + return rectangle(concat(pack(x, y), pack(x, y) + size())); + } + constexpr rectangle with_size(const kfr::size<T>& s) const { - return concat(low(v), low(v) + s.v); + return rectangle(concat(low(v), low(v) + s.v)); } - constexpr rectangle with_size(T w, T h) const { return concat(low(v), low(v) + pack(w, h)); } + constexpr rectangle with_size(T w, T h) const { return rectangle(concat(low(v), low(v) + pack(w, h))); } constexpr rectangle with_width(T w) const { return with_size(w, height()); } constexpr rectangle with_height(T h) const { return with_size(width(), h); } - constexpr rectangle with_offset(const point<T>& p) const { return v + vec<T, 4>(p.v, p.v); } - constexpr rectangle with_offset(T x, T y) const { return v + vec<T, 4>(x, y, x, y); } - constexpr rectangle with_scale(T x, T y) const { return v * vec<T, 4>(x, y, x, y); } - constexpr rectangle with_margin(T h, T v) const { return this->v + vec<T, 4>(-h, -v, +h, +v); } - constexpr rectangle with_padding(T h, T v) const { return this->v + vec<T, 4>(+h, +v, -h, -v); } + constexpr rectangle with_offset(const point<T>& p) const { return rectangle(v + vec<T, 4>(p.v, p.v)); } + constexpr rectangle with_offset(T x, T y) const { return rectangle(v + vec<T, 4>(x, y, x, y)); } + constexpr rectangle with_scale(T x, T y) const { return rectangle(v * vec<T, 4>(x, y, x, y)); } + constexpr rectangle with_margin(T h, T v) const { return rectangle(this->v + vec<T, 4>(-h, -v, +h, +v)); } + constexpr rectangle with_padding(T h, T v) const + { + return rectangle(this->v + vec<T, 4>(+h, +v, -h, -v)); + } constexpr rectangle with_padding(T x1, T y1, T x2, T y2) const { - return v + vec<T, 4>(+x1, +y1, -x2, -y2); + return rectangle(v + vec<T, 4>(+x1, +y1, -x2, -y2)); } - constexpr rectangle with_margin(T m) const { return v + vec<T, 4>(-m, -m, +m, +m); } - constexpr rectangle with_padding(T p) const { return v + vec<T, 4>(+p, +p, -p, -p); } + constexpr rectangle with_margin(T m) const { return rectangle(v + vec<T, 4>(-m, -m, +m, +m)); } + constexpr rectangle with_padding(T p) const { return rectangle(v + vec<T, 4>(+p, +p, -p, -p)); } - constexpr rectangle with_padding(const border<T>& p) const { return v + vec<T, 4>(1, 1, -1, -1) * p.v; } - constexpr rectangle with_margin(const border<T>& m) const { return v + vec<T, 4>(-1, -1, 1, 1) * m.v; } + constexpr rectangle with_padding(const border<T>& p) const + { + return rectangle(v + vec<T, 4>(1, 1, -1, -1) * p.v); + } + constexpr rectangle with_margin(const border<T>& m) const + { + return rectangle(v + vec<T, 4>(-1, -1, 1, 1) * m.v); + } - constexpr rectangle flipped() const { return kfr::swap(v); } + constexpr rectangle flipped() const { return rectangle(kfr::swap(v)); } - constexpr rectangle round() const { return kfr::round(v); } - constexpr rectangle floor() const { return kfr::floor(v); } - constexpr rectangle ceil() const { return kfr::ceil(v); } - constexpr rectangle trunc() const { return kfr::trunc(v); } + constexpr rectangle round() const { return rectangle(kfr::round(v)); } + constexpr rectangle floor() const { return rectangle(kfr::floor(v)); } + constexpr rectangle ceil() const { return rectangle(kfr::ceil(v)); } + constexpr rectangle trunc() const { return rectangle(kfr::trunc(v)); } - bool contains(const point<T, maximum>& pt) const { return all(pt.v >= p1.v && pt.v < p2.v); } + bool contains(const point<T>& pt) const { return all(pt.v >= p1.v && pt.v < p2.v); } - bool operator<<(const point<T, maximum>& pt) const { return contains(pt); } + bool operator<<(const point<T>& pt) const { return contains(pt); } constexpr bool operator==(const rectangle& c) const { return all(v == c.v); } constexpr bool operator!=(const rectangle& c) const { return !(*this == c); } - constexpr rectangle operator&(const rectangle& c) const + constexpr rectangle union_(const rectangle& c) const { - return blend<0, 0, 1, 1>(min(v, c.v), max(v, c.v)); + return rectangle(blend<0, 0, 1, 1>(min(v, c.v), max(v, c.v))); } - constexpr rectangle operator|(const rectangle& c) const + constexpr rectangle intersection(const rectangle& c) const { - return blend<1, 1, 0, 0>(min(v, c.v), max(v, c.v)); + return rectangle(blend<1, 1, 0, 0>(min(v, c.v), max(v, c.v))); } - rectangle& operator&=(const rectangle& c) { return *this = *this & c; } - rectangle& operator|=(const rectangle& c) { return *this = *this | c; } + T operator[](size_t i) const { return v[i]; } + T& operator[](size_t i) { return a[i]; } union { @@ -400,33 +483,18 @@ struct rectangle { vec<T, 4> v; }; + struct + { + T a[4]; + }; }; }; -using f32rectangle = rectangle<f32>; -using f32point = point<f32>; -using i32point = point<i32>; -using f32size = size<f32>; -using i32size = size<i32>; -using i32rectangle = rectangle<i32>; -using i32vector4 = vector4<i32>; -using f32vector4 = vector4<f32>; - -template <typename T> -CMT_INTRINSIC size<T> min(const size<T>& a, const size<T>& b) -{ - return { min(a.x, b.x), min(a.y, b.y) }; -} -template <typename T> -CMT_INTRINSIC size<T> max(const size<T>& a, const size<T>& b) -{ - return { max(a.x, b.x), max(a.y, b.y) }; -} - /// @brief 2D matrix template <typename T> -struct matrix +struct matrix2d { + static_assert(std::is_floating_point_v<T>); union { vec<T, 6> v; @@ -436,20 +504,25 @@ struct matrix }; }; - matrix() : v{ 1, 0, 0, 1, 0, 0 } {} + matrix2d() : v{ 1, 0, 0, 1, 0, 0 } {} - matrix(T a, T b, T c, T d, T e, T f) : v{ a, b, c, d, e, f } {} + matrix2d(T a, T b, T c, T d, T e, T f) : v{ a, b, c, d, e, f } {} - static matrix translate(T x, T y) { return matrix{ 1, 0, 0, 1, x, y }; } - static matrix scale(T x, T y) { return matrix{ x, 0, 0, y, 0, 0 }; } - static matrix rotate(T angle) { return matrix{ cos(angle), sin(angle), -sin(angle), cos(angle), 0, 0 }; } + explicit matrix2d(const vec<T, 6>& v) : v(v) {} - static matrix rotate90(int angle) + static matrix2d translate(T x, T y) { return matrix2d{ 1, 0, 0, 1, x, y }; } + static matrix2d scale(T x, T y) { return matrix2d{ x, 0, 0, y, 0, 0 }; } + static matrix2d rotate(T angle) { - static const matrix m[4] = { + return matrix2d{ cos(angle), sin(angle), -sin(angle), cos(angle), 0, 0 }; + } + + static matrix2d rotate90(int angle) + { + static constexpr portable_vec<T, 6> m[4] = { { 1, 0, 0, 1, 0, 0 }, { 0, 1, -1, 0, 0, 0 }, { -1, 0, 0, -1, 0, 0 }, { 0, -1, 1, 0, 0, 0 } }; - return m[angle % 4]; + return matrix2d(vec<T, 6>(m[angle % 4])); } std::array<std::array<T, 3>, 3> full() const @@ -457,7 +530,7 @@ struct matrix return { { { a, b, T() }, { c, d, T() }, { e, f, T(1) } } }; } - friend matrix<T> operator*(const matrix<T>& m, const matrix<T>& n) + friend matrix2d<T> operator*(const matrix2d<T>& m, const matrix2d<T>& n) { const std::array<std::array<T, 3>, 3> mm = m.full(); const std::array<std::array<T, 3>, 3> nn = n.full(); @@ -477,50 +550,66 @@ struct matrix return { a[0][0], a[0][1], a[1][0], a[1][1], a[2][0], a[2][1] }; } - friend point<T> operator*(const point<T>& pt, const matrix<T>& m) + friend point<T> operator*(const point<T>& pt, const matrix2d<T>& m) { return { pt.x * m.a + pt.y * m.c + m.e, pt.y * m.d + pt.x * m.b + m.f }; } }; -using f32matrix = matrix<f32>; -} // namespace CMT_ARCH_NAME +using i32point = point<i32>; +using f32point = point<f32>; +using f64point = point<f64>; + +using i32size = size<i32>; +using f32size = size<f32>; +using f64size = size<f64>; + +using i32border = border<i32>; +using f32border = border<f32>; +using f64border = border<f64>; + +using i32rectangle = rectangle<i32>; +using f32rectangle = rectangle<f32>; +using f64rectangle = rectangle<f64>; + +using f32matrix2d = matrix2d<f32>; +using f64matrix2d = matrix2d<f64>; } // namespace kfr namespace cometa { -template <typename T, int maximum> -struct representation<kfr::point<T, maximum>> +template <typename T> +struct representation<kfr::point<T>> { using type = std::string; - static std::string get(const kfr::point<T, maximum>& value) noexcept + static std::string get(const kfr::point<T>& value) noexcept { return as_string("point(", value.x, ", ", value.y, ")"); } }; -template <typename T, int maximum> -struct representation<kfr::size<T, maximum>> +template <typename T> +struct representation<kfr::size<T>> { using type = std::string; - static std::string get(const kfr::size<T, maximum>& value) noexcept + static std::string get(const kfr::size<T>& value) noexcept { return as_string("size(", value.x, ", ", value.y, ")"); } }; -template <typename T, int maximum> -struct representation<kfr::border<T, maximum>> +template <typename T> +struct representation<kfr::border<T>> { using type = std::string; - static std::string get(const kfr::border<T, maximum>& value) noexcept + static std::string get(const kfr::border<T>& value) noexcept { return as_string("border(", value.x1, ", ", value.y1, ", ", value.x2, ", ", value.y2, ")"); } }; -template <typename T, int maximum> -struct representation<kfr::rectangle<T, maximum>> +template <typename T> +struct representation<kfr::rectangle<T>> { using type = std::string; - static std::string get(const kfr::rectangle<T, maximum>& value) noexcept + static std::string get(const kfr::rectangle<T>& value) noexcept { return as_string("rectangle(", value.x1, ", ", value.y1, ", ", value.x2, ", ", value.y2, ")"); } diff --git a/include/kfr/graphics/impl/scaled.hpp b/include/kfr/graphics/impl/scaled.hpp @@ -31,13 +31,19 @@ namespace kfr { +inline namespace CMT_ARCH_NAME +{ + template <typename Tout, int Mout, int Min, typename Tin, size_t N, KFR_ENABLE_IF(Mout != Min && (std::is_floating_point<Tin>::value || std::is_floating_point<Tout>::value))> KFR_INTRINSIC vec<Tout, N> convert_scaled(const vec<Tin, N>& value) { - using Tcommon = std::common_type_t<Tin, Tout>; - return static_cast<vec<Tout, N>>(static_cast<vec<Tcommon, N>>(value) * Mout / Min); + using Tcommon = std::common_type_t<Tin, Tout>; + vec<Tcommon, N> x = static_cast<vec<Tcommon, N>>(value) * Mout / Min; + if constexpr (!std::is_floating_point_v<Tout>) + x += 0.5f / Mout; + return static_cast<vec<Tout, N>>(x); } template <typename Tout, int Mout, int Min, typename Tin, size_t N, @@ -53,6 +59,15 @@ KFR_INTRINSIC vec<Tout, N> convert_scaled(const vec<Tin, N>& value) template <typename Tout, int Mout, int Min, typename Tin, size_t N, KFR_ENABLE_IF(Mout == Min)> KFR_INTRINSIC vec<Tout, N> convert_scaled(const vec<Tin, N>& value) { - return static_cast<vec<Tout, N>>(value); + if constexpr (!std::is_floating_point_v<Tout>) + { + return static_cast<vec<Tout, N>>(round(value)); + } + else + { + return static_cast<vec<Tout, N>>(value); + } } +} // namespace CMT_ARCH_NAME + } // namespace kfr diff --git a/include/kfr/simd/horizontal.hpp b/include/kfr/simd/horizontal.hpp @@ -26,6 +26,7 @@ #pragma once #include "operators.hpp" +#include "min_max.hpp" namespace kfr { @@ -134,5 +135,21 @@ KFR_INTRINSIC T hrms(const vec<T, N>& value) return builtin_sqrt(hadd(value * value) / N); } KFR_FN(hrms) + +/// @brief Calculate the minimum of all elements in the vector +template <typename T, size_t N> +KFR_INTRINSIC T hmin(const vec<T, N>& value) +{ + return horizontal(value, fn::min()); +} +KFR_FN(hmin) + +/// @brief Calculate the maximum of all elements in the vector +template <typename T, size_t N> +KFR_INTRINSIC T hmax(const vec<T, N>& value) +{ + return horizontal(value, fn::max()); +} +KFR_FN(hmax) } // namespace CMT_ARCH_NAME } // namespace kfr diff --git a/tests/unit/graphics/color.cpp b/tests/unit/graphics/color.cpp @@ -32,17 +32,43 @@ TEST(color) CHECK(c3.b == 255); CHECK(c3.a == 127); - f32color c4 = from_srgb_approx(f32color(0.75f, 0.25f, 0.5f, 1.f)); + f32color c4 = from_srgb(f32color(0.75f, 0.25f, 0.5f, 1.f)); +#ifndef KFR_SRGB_APPROX + CHECK(c4.r == 0.522521f); + CHECK(c4.g == 0.0508761f); + CHECK(c4.b == 0.214041f); +#else CHECK(c4.r == 0.521914f); CHECK(c4.g == 0.0505368f); CHECK(c4.b == 0.214967f); +#endif CHECK(c4.a == 1.f); - f32color c5 = to_srgb_approx(f32color(0.75f, 0.25f, 0.5f, 1.f)); + f32color c5 = to_srgb(f32color(0.75f, 0.25f, 0.5f, 1.f)); +#ifndef KFR_SRGB_APPROX + CHECK(c5.r == 0.880825f); + CHECK(c5.g == 0.537099f); + CHECK(c5.b == 0.735357f); +#else CHECK(c5.r == 0.88027f); CHECK(c5.g == 0.536654f); CHECK(c5.b == 0.734586f); +#endif CHECK(c5.a == 1.f); + + f32color c6 = u8color(0, 127, 255); + CHECK(c6.r == 0.f); +#ifndef KFR_SRGB_APPROX + CHECK(c6.g == 0.212231f); +#else + CHECK(c6.g == 0.213161f); +#endif + CHECK(c6.b == 1.f); + + f32color c7 = u16color(0, 32767, 65535); + CHECK(c7.r == 0.f); + CHECK(c7.g == 0.499992370489f); + CHECK(c7.b == 1.f); } } // namespace CMT_ARCH_NAME } // namespace kfr diff --git a/tests/unit/graphics/geometry.cpp b/tests/unit/graphics/geometry.cpp @@ -10,6 +10,60 @@ namespace kfr { inline namespace CMT_ARCH_NAME { +TEST(point) +{ + testo::eplison_scope<void> e(10); + + f32point p{ 0.f, 0.5f }; + CHECK(p.distance(i32point{ 1, 2 }) == 1.80277563773f); + CHECK(i32point{ 1, 2 } + i32point{ 4, -4 } == i32point{ 5, -2 }); + CHECK(i32point{ 1, 2 } * 4 == i32point{ 4, 8 }); + + CHECK(i32point(f32point{ 0.25f, 0.75f }) == i32point{ 0, 1 }); + + CHECK(f32point{ 0, 0 }.aligned_rect({ 10, 10 }, { 0.5f, 0.5f }) == f32rectangle{ -5, -5, 5, 5 }); + CHECK(f32point{ 0, 0 }.aligned_rect({ 10, 10 }, { 0.f, 0.f }) == f32rectangle{ 0, 0, 10, 10 }); + CHECK(f32point{ 0, 0 }.aligned_rect({ 9, 9 }, { 0.5f, 0.5f }) == + f32rectangle{ -4.5f, -4.5f, 4.5f, 4.5f }); + CHECK(f32point{ 5, 2 }.aligned_rect({ 10, 10 }, { 0.5f, 0.5f }) == f32rectangle{ 0, -3, 10, 7 }); +} +TEST(size) +{ + CHECK(i32size(f32size{ 0.25f, 0.75f }) == i32size{ 0, 1 }); + CHECK(i32size{ 1, 2 } + i32size{ 4, -4 } == i32size{ 5, -2 }); + CHECK(i32size{ 1, 2 } * 4 == i32size{ 4, 8 }); + CHECK(i32size{ 1, 2 }.area() = 2); } + +TEST(border) +{ + CHECK(i32border{ 1, 2, 3, 4 }.size() == i32size{ 4, 6 }); + CHECK(i32border{ 1, 2, 3, 4 }.leading() == i32size{ 1, 2 }); + CHECK(i32border{ 1, 2, 3, 4 }.trailing() == i32size{ 3, 4 }); + CHECK(i32border{ 1, 2, 3, 4 }.horizontal() == 4); + CHECK(i32border{ 1, 2, 3, 4 }.vertical() == 6); +} + +TEST(rectangle) +{ + CHECK(f32rectangle{ f32point{ 1, 2 }, f32size{ 2, 2 } } == f32rectangle{ 1, 2, 3, 4 }); + CHECK(f32rectangle{ f32point{ 1, 2 }, f32point{ 3, 4 } } == f32rectangle{ 1, 2, 3, 4 }); + CHECK(f32rectangle{ f32point{ 1, 2 }, f32size{ 3, 4 }, f32point{ 0.5f, 0.5f } } == + f32rectangle{ -0.5f, 0, 2.5f, 4 }); + + CHECK(!i32rectangle{ i32point{ 0, 0 }, i32size{ 3, 1 } }.empty()); + CHECK(i32rectangle{ i32point{ 0, 0 }, i32size{ 3, 0 } }.empty()); + CHECK(i32rectangle{ i32point{ 0, 0 }, i32size{ 0, 3 } }.empty()); + CHECK(i32rectangle{ i32point{ 4, 0 }, i32point{ 3, 101 } }.empty()); + CHECK(!i32rectangle{ i32point{ 3, 0 }, i32point{ 4, 101 } }.empty()); + CHECK(f32rectangle{ 1, 2, 7, 6 }.size() == f32size{ 6, 4 }); + CHECK(f32rectangle{ 1, 2, 7, 6 }.area() == 24); + + CHECK(f32rectangle{ 1, 2, 7, 6 }.min_side() == 4); + CHECK(f32rectangle{ 1, 2, 7, 6 }.max_side() == 6); + CHECK(f32rectangle{ 1, 2, 8, 6 }.center() == f32point{ 4.5f, 4.f }); +} + +} // namespace CMT_ARCH_NAME } // namespace kfr