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:
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