diff options
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r-- | source/blender/blenlib/BLI_color.hh | 302 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_index_mask.hh | 2 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_math_base.h | 3 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_mpq3.hh | 16 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_vector.hh | 2 | ||||
-rw-r--r-- | source/blender/blenlib/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/blenlib/intern/BLI_color.cc | 55 | ||||
-rw-r--r-- | source/blender/blenlib/intern/fileops.c | 4 | ||||
-rw-r--r-- | source/blender/blenlib/intern/math_base_inline.c | 15 | ||||
-rw-r--r-- | source/blender/blenlib/intern/memory_utils.c | 2 | ||||
-rw-r--r-- | source/blender/blenlib/intern/mesh_boolean.cc | 223 | ||||
-rw-r--r-- | source/blender/blenlib/intern/mesh_intersect.cc | 91 | ||||
-rw-r--r-- | source/blender/blenlib/intern/polyfill_2d_beautify.c | 2 | ||||
-rw-r--r-- | source/blender/blenlib/intern/timecode.c | 4 | ||||
-rw-r--r-- | source/blender/blenlib/intern/winstuff.c | 4 | ||||
-rw-r--r-- | source/blender/blenlib/tests/BLI_color_test.cc | 133 |
16 files changed, 713 insertions, 147 deletions
diff --git a/source/blender/blenlib/BLI_color.hh b/source/blender/blenlib/BLI_color.hh index e57a5109a66..3b01bbfb86e 100644 --- a/source/blender/blenlib/BLI_color.hh +++ b/source/blender/blenlib/BLI_color.hh @@ -22,41 +22,122 @@ namespace blender { -struct Color4f { - float r, g, b, a; +/** + * CPP based color structures. + * + * Strongly typed color storage structures with space and alpha association. + * Will increase readability and visibility of typical mistakes when + * working with colors. + * + * The storage structs can hold 4 channels (r, g, b and a). + * + * Usage: + * + * Convert a theme byte color to a linearrgb premultiplied. + * ``` + * ColorTheme4b theme_color; + * ColorSceneLinear4f<eAlpha::Premultiplied> linearrgb_color = + * BLI_color_convert_to_scene_linear(theme_color).premultiply_alpha(); + * ``` + * + * The API is structured to make most use of inlining. Most notable are space + * conversions done via `BLI_color_convert_to*` functions. + * + * - Conversions between spaces (theme <=> scene linear) should always be done by + * invoking the `BLI_color_convert_to*` methods. + * - Encoding colors (compressing to store colors inside a less precision storage) + * should be done by invoking the `encode` and `decode` methods. + * - Changing alpha association should be done by invoking `premultiply_alpha` or + * `unpremultiply_alpha` methods. + * + * # Encoding. + * + * Color encoding is used to store colors with less precision as in using `uint8_t` in + * stead of `float`. This encoding is supported for `eSpace::SceneLinear`. + * To make this clear to the developer the `eSpace::SceneLinearByteEncoded` + * space is added. + * + * # Precision + * + * Colors can be stored using `uint8_t` or `float` colors. The conversion + * between the two precisions are available as methods. (`to_4b` and + * `to_4f`). + * + * # Alpha conversion + * + * Alpha conversion is only supported in SceneLinear space. + * + * Extending this file: + * - This file can be extended with `ColorHex/Hsl/Hsv` for different representations + * of rgb based colors. `ColorHsl4f<eSpace::SceneLinear, eAlpha::Premultiplied>` + * - Add non RGB spaces/storages ColorXyz. + */ + +/* Enumeration containing the different alpha modes. */ +enum class eAlpha { + /* Color and alpha are unassociated. */ + Straight, + /* Color and alpha are associated. */ + Premultiplied, +}; +std::ostream &operator<<(std::ostream &stream, const eAlpha &space); - Color4f() = default; +/* Enumeration containing internal spaces. */ +enum class eSpace { + /* Blender theme color space (sRGB). */ + Theme, + /* Blender internal scene linear color space (maps to SceneReference role in OCIO). */ + SceneLinear, + /* Blender internal scene linear color space compressed to be stored in 4 uint8_t. */ + SceneLinearByteEncoded, +}; +std::ostream &operator<<(std::ostream &stream, const eSpace &space); - Color4f(const float *rgba) : r(rgba[0]), g(rgba[1]), b(rgba[2]), a(rgba[3]) +/* Template class to store RGBA values with different precision, space and alpha association. */ +template<typename ChannelStorageType, eSpace Space, eAlpha Alpha> class ColorRGBA { + public: + ChannelStorageType r, g, b, a; + constexpr ColorRGBA() = default; + + constexpr ColorRGBA(const ChannelStorageType rgba[4]) + : r(rgba[0]), g(rgba[1]), b(rgba[2]), a(rgba[3]) { } - Color4f(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) + constexpr ColorRGBA(const ChannelStorageType r, + const ChannelStorageType g, + const ChannelStorageType b, + const ChannelStorageType a) + : r(r), g(g), b(b), a(a) { } - operator float *() + operator ChannelStorageType *() { return &r; } - operator const float *() const + operator const ChannelStorageType *() const { return &r; } - friend std::ostream &operator<<(std::ostream &stream, Color4f c) + friend std::ostream &operator<<(std::ostream &stream, + const ColorRGBA<ChannelStorageType, Space, Alpha> &c) { - stream << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; + + stream << Space << Alpha << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; return stream; } - friend bool operator==(const Color4f &a, const Color4f &b) + friend bool operator==(const ColorRGBA<ChannelStorageType, Space, Alpha> &a, + const ColorRGBA<ChannelStorageType, Space, Alpha> &b) { return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } - friend bool operator!=(const Color4f &a, const Color4f &b) + friend bool operator!=(const ColorRGBA<ChannelStorageType, Space, Alpha> &a, + const ColorRGBA<ChannelStorageType, Space, Alpha> &b) { return !(a == b); } @@ -71,58 +152,209 @@ struct Color4f { } }; -struct Color4b { - uint8_t r, g, b, a; +/* Forward declarations of concrete color classes. */ +template<eAlpha Alpha> class ColorSceneLinear4f; +template<eAlpha Alpha> class ColorSceneLinearByteEncoded4b; +template<typename ChannelStorageType> class ColorTheme4; - Color4b() = default; +/* Forward declaration of precision conversion methods. */ +BLI_INLINE ColorTheme4<float> BLI_color_convert_to_theme4f(const ColorTheme4<uint8_t> &srgb4b); +BLI_INLINE ColorTheme4<uint8_t> BLI_color_convert_to_theme4b(const ColorTheme4<float> &srgb4f); - Color4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a) +template<eAlpha Alpha> +class ColorSceneLinear4f final : public ColorRGBA<float, eSpace::SceneLinear, Alpha> { + public: + constexpr ColorSceneLinear4f<Alpha>() : ColorRGBA<float, eSpace::SceneLinear, Alpha>() { } - Color4b(Color4f other) + constexpr ColorSceneLinear4f<Alpha>(const float *rgba) + : ColorRGBA<float, eSpace::SceneLinear, Alpha>(rgba) { - rgba_float_to_uchar(*this, other); } - operator Color4f() const + constexpr ColorSceneLinear4f<Alpha>(float r, float g, float b, float a) + : ColorRGBA<float, eSpace::SceneLinear, Alpha>(r, g, b, a) { - Color4f result; - rgba_uchar_to_float(result, *this); - return result; } - operator uint8_t *() + /** + * Convert to its byte encoded counter space. + **/ + ColorSceneLinearByteEncoded4b<Alpha> encode() const { - return &r; + ColorSceneLinearByteEncoded4b<Alpha> encoded; + linearrgb_to_srgb_uchar4(encoded, *this); + return encoded; } - operator const uint8_t *() const + /** + * Convert color and alpha association to premultiplied alpha. + * + * Does nothing when color has already a premultiplied alpha. + */ + ColorSceneLinear4f<eAlpha::Premultiplied> premultiply_alpha() const { - return &r; + if constexpr (Alpha == eAlpha::Straight) { + ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied; + straight_to_premul_v4_v4(premultiplied, *this); + return premultiplied; + } + else { + return *this; + } } - friend std::ostream &operator<<(std::ostream &stream, Color4b c) + /** + * Convert color and alpha association to straight alpha. + * + * Does nothing when color has straighten alpha. + */ + ColorSceneLinear4f<eAlpha::Straight> unpremultiply_alpha() const { - stream << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; - return stream; + if constexpr (Alpha == eAlpha::Premultiplied) { + ColorSceneLinear4f<eAlpha::Straight> straighten; + premul_to_straight_v4_v4(straighten, *this); + return straighten; + } + else { + return *this; + } } +}; - friend bool operator==(const Color4b &a, const Color4b &b) +template<eAlpha Alpha> +class ColorSceneLinearByteEncoded4b final + : public ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha> { + public: + constexpr ColorSceneLinearByteEncoded4b() = default; + + constexpr ColorSceneLinearByteEncoded4b(const uint8_t *rgba) + : ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha>(rgba) { - return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } - friend bool operator!=(const Color4b &a, const Color4b &b) + constexpr ColorSceneLinearByteEncoded4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + : ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha>(r, g, b, a) { - return !(a == b); } - uint64_t hash() const + /** + * Convert to back to float color. + */ + ColorSceneLinear4f<Alpha> decode() const + { + ColorSceneLinear4f<Alpha> decoded; + srgb_to_linearrgb_uchar4(decoded, *this); + return decoded; + } +}; + +/** + * Theme color template class. + * + * Don't use directly, but use `ColorTheme4b/ColorTheme4b`. + * + * This has been implemented as a template to improve inlining. When implemented as concrete + * classes (ColorTheme4b/f) the functions would be hidden in a compile unit what wouldn't be + * inlined. + */ +template<typename ChannelStorageType> +class ColorTheme4 final : public ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight> { + public: + constexpr ColorTheme4() : ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(){}; + + constexpr ColorTheme4(const ChannelStorageType *rgba) + : ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(rgba) + { + } + + constexpr ColorTheme4(ChannelStorageType r, + ChannelStorageType g, + ChannelStorageType b, + ChannelStorageType a) + : ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(r, g, b, a) + { + } + + /** + * Change precision of color to float. + */ + ColorTheme4<float> to_4f() const { - return static_cast<uint64_t>(r * 1283591) ^ static_cast<uint64_t>(g * 850177) ^ - static_cast<uint64_t>(b * 735391) ^ static_cast<uint64_t>(a * 442319); + if constexpr ((std::is_same_v<ChannelStorageType, uint8_t>)) { + return BLI_color_convert_to_theme4f(*this); + } + else { + return *this; + } + } + + /** + * Change precision of color to uint8_t. + */ + ColorTheme4<uint8_t> to_4b() const + { + if constexpr ((std::is_same_v<ChannelStorageType, float>)) { + return BLI_color_convert_to_theme4b(*this); + } + else { + return *this; + } } }; +using ColorTheme4b = ColorTheme4<uint8_t>; +using ColorTheme4f = ColorTheme4<float>; + +BLI_INLINE ColorTheme4b BLI_color_convert_to_theme4b(const ColorTheme4f &theme4f) +{ + ColorTheme4b theme4b; + rgba_float_to_uchar(theme4b, theme4f); + return theme4b; +} + +BLI_INLINE ColorTheme4f BLI_color_convert_to_theme4f(const ColorTheme4b &theme4b) +{ + ColorTheme4f theme4f; + rgba_uchar_to_float(theme4f, theme4b); + return theme4f; +} + +BLI_INLINE ColorSceneLinear4f<eAlpha::Straight> BLI_color_convert_to_scene_linear( + const ColorTheme4f &theme4f) +{ + ColorSceneLinear4f<eAlpha::Straight> scene_linear; + srgb_to_linearrgb_v4(scene_linear, theme4f); + return scene_linear; +} + +BLI_INLINE ColorSceneLinear4f<eAlpha::Straight> BLI_color_convert_to_scene_linear( + const ColorTheme4b &theme4b) +{ + ColorSceneLinear4f<eAlpha::Straight> scene_linear; + srgb_to_linearrgb_uchar4(scene_linear, theme4b); + return scene_linear; +} + +BLI_INLINE ColorTheme4f +BLI_color_convert_to_theme4f(const ColorSceneLinear4f<eAlpha::Straight> &scene_linear) +{ + ColorTheme4f theme4f; + linearrgb_to_srgb_v4(theme4f, scene_linear); + return theme4f; +} + +BLI_INLINE ColorTheme4b +BLI_color_convert_to_theme4b(const ColorSceneLinear4f<eAlpha::Straight> &scene_linear) +{ + ColorTheme4b theme4b; + linearrgb_to_srgb_uchar4(theme4b, scene_linear); + return theme4b; +} + +/* Internal roles. For convenience to shorten the type names and hide complexity. */ +using ColorGeometry4f = ColorSceneLinear4f<eAlpha::Premultiplied>; +using ColorGeometry4b = ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied>; + } // namespace blender diff --git a/source/blender/blenlib/BLI_index_mask.hh b/source/blender/blenlib/BLI_index_mask.hh index 48b01edcd6f..f04c0e9c80a 100644 --- a/source/blender/blenlib/BLI_index_mask.hh +++ b/source/blender/blenlib/BLI_index_mask.hh @@ -110,7 +110,7 @@ class IndexMask { } /** - * Returns the n-th index referenced by this IndexMask. The `index_mask` method returns an + * Returns the n-th index referenced by this IndexMask. The `index_range` method returns an * IndexRange containing all indices that can be used as parameter here. */ int64_t operator[](int64_t n) const diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h index 028ca31a059..46219ad5493 100644 --- a/source/blender/blenlib/BLI_math_base.h +++ b/source/blender/blenlib/BLI_math_base.h @@ -154,6 +154,9 @@ MINLINE int max_iii(int a, int b, int c); MINLINE int min_iiii(int a, int b, int c, int d); MINLINE int max_iiii(int a, int b, int c, int d); +MINLINE uint min_uu(uint a, uint b); +MINLINE uint max_uu(uint a, uint b); + MINLINE size_t min_zz(size_t a, size_t b); MINLINE size_t max_zz(size_t a, size_t b); diff --git a/source/blender/blenlib/BLI_mpq3.hh b/source/blender/blenlib/BLI_mpq3.hh index fb5e2b61cdb..b9eda2ad7e1 100644 --- a/source/blender/blenlib/BLI_mpq3.hh +++ b/source/blender/blenlib/BLI_mpq3.hh @@ -218,6 +218,15 @@ struct mpq3 { return a.x * b.x + a.y * b.y + a.z * b.z; } + static mpq_class dot_with_buffer(const mpq3 &a, const mpq3 &b, mpq3 &buffer) + { + buffer = a; + buffer *= b; + buffer.x += buffer.y; + buffer.x += buffer.z; + return buffer.x; + } + static mpq3 cross(const mpq3 &a, const mpq3 &b) { return mpq3(a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]); @@ -246,6 +255,13 @@ struct mpq3 { return mpq3::dot(diff, diff); } + static mpq_class distance_squared_with_buffer(const mpq3 &a, const mpq3 &b, mpq3 &buffer) + { + buffer = a; + buffer -= b; + return mpq3::dot(buffer, buffer); + } + static mpq3 interpolate(const mpq3 &a, const mpq3 &b, mpq_class t) { mpq_class s = 1 - t; diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh index 8bea2584ca7..f6f546c90b9 100644 --- a/source/blender/blenlib/BLI_vector.hh +++ b/source/blender/blenlib/BLI_vector.hh @@ -161,7 +161,7 @@ class Vector { } /** - * Create a vector from an array ref. The values in the vector are copy constructed. + * Create a vector from a span. The values in the vector are copy constructed. */ template<typename U, typename std::enable_if_t<std::is_convertible_v<U, T>> * = nullptr> Vector(Span<U> values, Allocator allocator = {}) : Vector(NoExceptConstructor(), allocator) diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index ce3515ac153..f3dc343ee20 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -39,6 +39,7 @@ set(SRC intern/BLI_args.c intern/BLI_array.c intern/BLI_assert.c + intern/BLI_color.cc intern/BLI_dial_2d.c intern/BLI_dynstr.c intern/BLI_filelist.c @@ -389,6 +390,7 @@ if(WITH_GTESTS) tests/BLI_array_store_test.cc tests/BLI_array_test.cc tests/BLI_array_utils_test.cc + tests/BLI_color_test.cc tests/BLI_delaunay_2d_test.cc tests/BLI_disjoint_set_test.cc tests/BLI_edgehash_test.cc diff --git a/source/blender/blenlib/intern/BLI_color.cc b/source/blender/blenlib/intern/BLI_color.cc new file mode 100644 index 00000000000..6dcef4f4688 --- /dev/null +++ b/source/blender/blenlib/intern/BLI_color.cc @@ -0,0 +1,55 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_color.hh" + +namespace blender { + +std::ostream &operator<<(std::ostream &stream, const eAlpha &space) +{ + switch (space) { + case eAlpha::Straight: { + stream << "Straight"; + break; + } + case eAlpha::Premultiplied: { + stream << "Premultiplied"; + break; + } + } + return stream; +} + +std::ostream &operator<<(std::ostream &stream, const eSpace &space) +{ + switch (space) { + case eSpace::Theme: { + stream << "Theme"; + break; + } + case eSpace::SceneLinear: { + stream << "SceneLinear"; + break; + } + case eSpace::SceneLinearByteEncoded: { + stream << "SceneLinearByteEncoded"; + break; + } + } + return stream; +} + +} // namespace blender diff --git a/source/blender/blenlib/intern/fileops.c b/source/blender/blenlib/intern/fileops.c index 106bd5bc793..107c27da6a2 100644 --- a/source/blender/blenlib/intern/fileops.c +++ b/source/blender/blenlib/intern/fileops.c @@ -171,7 +171,7 @@ size_t BLI_gzip_mem_to_file_at_pos( z_stream strm; unsigned char out[CHUNK]; - fseek(file, gz_stream_offset, 0); + BLI_fseek(file, gz_stream_offset, 0); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; @@ -217,7 +217,7 @@ size_t BLI_ungzip_file_to_mem_at_pos(void *buf, size_t len, FILE *file, size_t g size_t chunk = 256 * 1024; unsigned char in[CHUNK]; - fseek(file, gz_stream_offset, 0); + BLI_fseek(file, gz_stream_offset, 0); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c index 2a7c091d1b9..d73afff64c8 100644 --- a/source/blender/blenlib/intern/math_base_inline.c +++ b/source/blender/blenlib/intern/math_base_inline.c @@ -514,6 +514,15 @@ MINLINE int max_ii(int a, int b) return (b < a) ? a : b; } +MINLINE uint min_uu(uint a, uint b) +{ + return (a < b) ? a : b; +} +MINLINE uint max_uu(uint a, uint b) +{ + return (b < a) ? a : b; +} + MINLINE float min_fff(float a, float b, float c) { return min_ff(min_ff(a, b), c); @@ -798,9 +807,9 @@ MINLINE unsigned char unit_float_to_uchar_clamp(float val) MINLINE unsigned short unit_float_to_ushort_clamp(float val) { - return (unsigned short)((val >= 1.0f - 0.5f / 65535) ? - 65535 : - (val <= 0.0f) ? 0 : (val * 65535.0f + 0.5f)); + return (unsigned short)((val >= 1.0f - 0.5f / 65535) ? 65535 : + (val <= 0.0f) ? 0 : + (val * 65535.0f + 0.5f)); } #define unit_float_to_ushort_clamp(val) \ ((CHECK_TYPE_INLINE(val, float)), unit_float_to_ushort_clamp(val)) diff --git a/source/blender/blenlib/intern/memory_utils.c b/source/blender/blenlib/intern/memory_utils.c index d477c20a242..5ca7b96c136 100644 --- a/source/blender/blenlib/intern/memory_utils.c +++ b/source/blender/blenlib/intern/memory_utils.c @@ -31,7 +31,7 @@ #include "BLI_strict_flags.h" /** - * Check if memory is zero'd, as with memset(arr, 0, arr_size) + * Check if memory is zeroed, as with `memset(arr, 0, arr_size)`. */ bool BLI_memory_is_zero(const void *arr, const size_t arr_size) { diff --git a/source/blender/blenlib/intern/mesh_boolean.cc b/source/blender/blenlib/intern/mesh_boolean.cc index 25291b8c3b0..00d53a010b0 100644 --- a/source/blender/blenlib/intern/mesh_boolean.cc +++ b/source/blender/blenlib/intern/mesh_boolean.cc @@ -41,6 +41,7 @@ # include "BLI_set.hh" # include "BLI_span.hh" # include "BLI_stack.hh" +# include "BLI_task.hh" # include "BLI_vector.hh" # include "BLI_vector_set.hh" @@ -48,6 +49,10 @@ # include "BLI_mesh_boolean.hh" +# ifdef WITH_TBB +# include "tbb/spin_mutex.h" +# endif + // # define PERFDEBUG namespace blender::meshintersect { @@ -144,11 +149,9 @@ class TriMeshTopology : NonCopyable { * Else return NO_INDEX. */ int other_tri_if_manifold(Edge e, int t) const { - if (edge_tri_.contains(e)) { - auto *p = edge_tri_.lookup(e); - if (p->size() == 2) { - return ((*p)[0] == t) ? (*p)[1] : (*p)[0]; - } + auto p = edge_tri_.lookup_ptr(e); + if (p != nullptr && (*p)->size() == 2) { + return ((**p)[0] == t) ? (**p)[1] : (**p)[0]; } return NO_INDEX; } @@ -1690,9 +1693,24 @@ static int find_containing_cell(const Vert *v, * If the closest point is on an edge, return 0, 1, or 2 * for edges ab, bc, or ca in *r_edge; else -1. * (Adapted from #closest_on_tri_to_point_v3()). + * The arguments ab, ac, ..., r are used as temporaries + * in this routine. Passing them in from the caller can + * avoid many allocs and frees of temporary mpq3 values + * and the mpq_class values within them. */ -static mpq_class closest_on_tri_to_point( - const mpq3 &p, const mpq3 &a, const mpq3 &b, const mpq3 &c, int *r_edge, int *r_vert) +static mpq_class closest_on_tri_to_point(const mpq3 &p, + const mpq3 &a, + const mpq3 &b, + const mpq3 &c, + mpq3 &ab, + mpq3 &ac, + mpq3 &ap, + mpq3 &bp, + mpq3 &cp, + mpq3 &m, + mpq3 &r, + int *r_edge, + int *r_vert) { constexpr int dbg_level = 0; if (dbg_level > 0) { @@ -1700,11 +1718,15 @@ static mpq_class closest_on_tri_to_point( std::cout << " a = " << a << ", b = " << b << ", c = " << c << "\n"; } /* Check if p in vertex region outside a. */ - mpq3 ab = b - a; - mpq3 ac = c - a; - mpq3 ap = p - a; - mpq_class d1 = mpq3::dot(ab, ap); - mpq_class d2 = mpq3::dot(ac, ap); + ab = b; + ab -= a; + ac = c; + ac -= a; + ap = p; + ap -= a; + + mpq_class d1 = mpq3::dot_with_buffer(ab, ap, m); + mpq_class d2 = mpq3::dot_with_buffer(ac, ap, m); if (d1 <= 0 && d2 <= 0) { /* Barycentric coordinates (1,0,0). */ *r_edge = -1; @@ -1712,12 +1734,13 @@ static mpq_class closest_on_tri_to_point( if (dbg_level > 0) { std::cout << " answer = a\n"; } - return mpq3::distance_squared(p, a); + return mpq3::distance_squared_with_buffer(p, a, m); } /* Check if p in vertex region outside b. */ - mpq3 bp = p - b; - mpq_class d3 = mpq3::dot(ab, bp); - mpq_class d4 = mpq3::dot(ac, bp); + bp = p; + bp -= b; + mpq_class d3 = mpq3::dot_with_buffer(ab, bp, m); + mpq_class d4 = mpq3::dot_with_buffer(ac, bp, m); if (d3 >= 0 && d4 <= d3) { /* Barycentric coordinates (0,1,0). */ *r_edge = -1; @@ -1725,25 +1748,28 @@ static mpq_class closest_on_tri_to_point( if (dbg_level > 0) { std::cout << " answer = b\n"; } - return mpq3::distance_squared(p, b); + return mpq3::distance_squared_with_buffer(p, b, m); } /* Check if p in region of ab. */ mpq_class vc = d1 * d4 - d3 * d2; if (vc <= 0 && d1 >= 0 && d3 <= 0) { mpq_class v = d1 / (d1 - d3); /* Barycentric coordinates (1-v,v,0). */ - mpq3 r = a + v * ab; + r = ab; + r *= v; + r += a; *r_vert = -1; *r_edge = 0; if (dbg_level > 0) { std::cout << " answer = on ab at " << r << "\n"; } - return mpq3::distance_squared(p, r); + return mpq3::distance_squared_with_buffer(p, r, m); } /* Check if p in vertex region outside c. */ - mpq3 cp = p - c; - mpq_class d5 = mpq3::dot(ab, cp); - mpq_class d6 = mpq3::dot(ac, cp); + cp = p; + cp -= c; + mpq_class d5 = mpq3::dot_with_buffer(ab, cp, m); + mpq_class d6 = mpq3::dot_with_buffer(ac, cp, m); if (d6 >= 0 && d5 <= d6) { /* Barycentric coordinates (0,0,1). */ *r_edge = -1; @@ -1751,49 +1777,67 @@ static mpq_class closest_on_tri_to_point( if (dbg_level > 0) { std::cout << " answer = c\n"; } - return mpq3::distance_squared(p, c); + return mpq3::distance_squared_with_buffer(p, c, m); } /* Check if p in edge region of ac. */ mpq_class vb = d5 * d2 - d1 * d6; if (vb <= 0 && d2 >= 0 && d6 <= 0) { mpq_class w = d2 / (d2 - d6); /* Barycentric coordinates (1-w,0,w). */ - mpq3 r = a + w * ac; + r = ac; + r *= w; + r += a; *r_vert = -1; *r_edge = 2; if (dbg_level > 0) { std::cout << " answer = on ac at " << r << "\n"; } - return mpq3::distance_squared(p, r); + return mpq3::distance_squared_with_buffer(p, r, m); } /* Check if p in edge region of bc. */ mpq_class va = d3 * d6 - d5 * d4; if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) { mpq_class w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); /* Barycentric coordinates (0,1-w,w). */ - mpq3 r = c - b; - r = w * r; - r = r + b; + r = c; + r -= b; + r *= w; + r += b; *r_vert = -1; *r_edge = 1; if (dbg_level > 0) { std::cout << " answer = on bc at " << r << "\n"; } - return mpq3::distance_squared(p, r); + return mpq3::distance_squared_with_buffer(p, r, m); } /* p inside face region. Compute barycentric coordinates (u,v,w). */ mpq_class denom = 1 / (va + vb + vc); mpq_class v = vb * denom; mpq_class w = vc * denom; - ac = w * ac; - mpq3 r = a + v * ab; - r = r + ac; + ac *= w; + r = ab; + r *= v; + r += a; + r += ac; *r_vert = -1; *r_edge = -1; if (dbg_level > 0) { std::cout << " answer = inside at " << r << "\n"; } - return mpq3::distance_squared(p, r); + return mpq3::distance_squared_with_buffer(p, r, m); +} + +static float closest_on_tri_to_point_float_dist_squared(const float3 &p, + const double3 &a, + const double3 &b, + const double3 &c) +{ + float3 fa, fb, fc, closest; + copy_v3fl_v3db(fa, a); + copy_v3fl_v3db(fb, b); + copy_v3fl_v3db(fc, c); + closest_on_tri_to_point_v3(closest, p, fa, fb, fc); + return len_squared_v3v3(p, closest); } struct ComponentContainer { @@ -1832,6 +1876,11 @@ static Vector<ComponentContainer> find_component_containers(int comp, if (dbg_level > 0) { std::cout << "test vertex in comp: " << test_v << "\n"; } + const double3 &test_v_d = test_v->co; + float3 test_v_f(test_v_d[0], test_v_d[1], test_v_d[2]); + + mpq3 buf[7]; + for (int comp_other : components.index_range()) { if (comp == comp_other) { continue; @@ -1843,6 +1892,7 @@ static Vector<ComponentContainer> find_component_containers(int comp, int nearest_tri_close_vert = -1; int nearest_tri_close_edge = -1; mpq_class nearest_tri_dist_squared; + float nearest_tri_dist_squared_float = FLT_MAX; for (int p : components[comp_other]) { const Patch &patch = pinfo.patch(p); for (int t : patch.tris()) { @@ -1852,10 +1902,23 @@ static Vector<ComponentContainer> find_component_containers(int comp, } int close_vert; int close_edge; + /* Try a cheap float test first. */ + float d2_f = closest_on_tri_to_point_float_dist_squared( + test_v_f, tri[0]->co, tri[1]->co, tri[2]->co); + if (d2_f - FLT_EPSILON > nearest_tri_dist_squared_float) { + continue; + } mpq_class d2 = closest_on_tri_to_point(test_v->co_exact, tri[0]->co_exact, tri[1]->co_exact, tri[2]->co_exact, + buf[0], + buf[1], + buf[2], + buf[3], + buf[4], + buf[5], + buf[6], &close_edge, &close_vert); if (dbg_level > 1) { @@ -1867,6 +1930,7 @@ static Vector<ComponentContainer> find_component_containers(int comp, nearest_tri_close_edge = close_edge; nearest_tri_close_vert = close_vert; nearest_tri_dist_squared = d2; + nearest_tri_dist_squared_float = d2_f; } } } @@ -2567,47 +2631,58 @@ static IMesh raycast_tris_boolean(const IMesh &tm, BVHTree *tree = raycast_tree(tm); Vector<Face *> out_faces; out_faces.reserve(tm.face_size()); - Array<float> in_shape(nshapes, 0); - Array<int> winding(nshapes, 0); - for (int t : tm.face_index_range()) { - Face &tri = *tm.face(t); - int shape = shape_fn(tri.orig); - if (dbg_level > 0) { - std::cout << "process triangle " << t << " = " << &tri << "\n"; - std::cout << "shape = " << shape << "\n"; - } - test_tri_inside_shapes(tm, shape_fn, nshapes, t, tree, in_shape); - for (int other_shape = 0; other_shape < nshapes; ++other_shape) { - if (other_shape == shape) { - continue; - } - /* The in_shape array has a confidence value for "insideness". - * For most operations, even a hint of being inside - * gives good results, but when shape is a cutter in a Difference - * operation, we want to be pretty sure that the point is inside other_shape. - * E.g., T75827. - * Also, when the operation is intersection, we also want high confidence. - */ - bool need_high_confidence = (op == BoolOpType::Difference && shape != 0) || - op == BoolOpType::Intersect; - bool inside = in_shape[other_shape] >= (need_high_confidence ? 0.5f : 0.1f); +# ifdef WITH_TBB + tbb::spin_mutex mtx; +# endif + const int grainsize = 256; + parallel_for(IndexRange(tm.face_size()), grainsize, [&](IndexRange range) { + Array<float> in_shape(nshapes, 0); + Array<int> winding(nshapes, 0); + for (int t : range) { + Face &tri = *tm.face(t); + int shape = shape_fn(tri.orig); if (dbg_level > 0) { - std::cout << "test point is " << (inside ? "inside" : "outside") << " other_shape " - << other_shape << " val = " << in_shape[other_shape] << "\n"; + std::cout << "process triangle " << t << " = " << &tri << "\n"; + std::cout << "shape = " << shape << "\n"; } - winding[other_shape] = inside; - } - bool do_flip; - bool do_remove = raycast_test_remove(op, winding, shape, &do_flip); - if (!do_remove) { - if (!do_flip) { - out_faces.append(&tri); + test_tri_inside_shapes(tm, shape_fn, nshapes, t, tree, in_shape); + for (int other_shape = 0; other_shape < nshapes; ++other_shape) { + if (other_shape == shape) { + continue; + } + /* The in_shape array has a confidence value for "insideness". + * For most operations, even a hint of being inside + * gives good results, but when shape is a cutter in a Difference + * operation, we want to be pretty sure that the point is inside other_shape. + * E.g., T75827. + * Also, when the operation is intersection, we also want high confidence. + */ + bool need_high_confidence = (op == BoolOpType::Difference && shape != 0) || + op == BoolOpType::Intersect; + bool inside = in_shape[other_shape] >= (need_high_confidence ? 0.5f : 0.1f); + if (dbg_level > 0) { + std::cout << "test point is " << (inside ? "inside" : "outside") << " other_shape " + << other_shape << " val = " << in_shape[other_shape] << "\n"; + } + winding[other_shape] = inside; } - else { - raycast_add_flipped(out_faces, tri, arena); + bool do_flip; + bool do_remove = raycast_test_remove(op, winding, shape, &do_flip); + { +# ifdef WITH_TBB + tbb::spin_mutex::scoped_lock lock(mtx); +# endif + if (!do_remove) { + if (!do_flip) { + out_faces.append(&tri); + } + else { + raycast_add_flipped(out_faces, tri, arena); + } + } } } - } + }); BLI_bvhtree_free(tree); ans.set_faces(out_faces); return ans; @@ -3259,9 +3334,13 @@ static IMesh polymesh_from_trimesh_with_dissolve(const IMesh &tm_out, std::cout << "\nPOLYMESH_FROM_TRIMESH_WITH_DISSOLVE\n"; } /* For now: need plane normals for all triangles. */ - for (Face *tri : tm_out.faces()) { - tri->populate_plane(false); - } + const int grainsize = 1024; + parallel_for(tm_out.face_index_range(), grainsize, [&](IndexRange range) { + for (int i : range) { + Face *tri = tm_out.face(i); + tri->populate_plane(false); + } + }); /* Gather all output triangles that are part of each input face. * face_output_tris[f] will be indices of triangles in tm_out * that have f as their original face. */ diff --git a/source/blender/blenlib/intern/mesh_intersect.cc b/source/blender/blenlib/intern/mesh_intersect.cc index 636209883c3..97f856476c5 100644 --- a/source/blender/blenlib/intern/mesh_intersect.cc +++ b/source/blender/blenlib/intern/mesh_intersect.cc @@ -1165,13 +1165,19 @@ static int filter_plane_side(const double3 &p, * Assumes ab is not perpendicular to n. * This works because the ratio of the projections of ab and ac onto n is the same as * the ratio along the line ab of the intersection point to the whole of ab. + * The ab, ac, and dotbuf arguments are used as a temporaries; declaring them + * in the caller can avoid many allocs and frees of mpq3 and mpq_class structures. */ -static inline mpq3 tti_interp(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &n) -{ - mpq3 ab = a - b; - mpq_class den = mpq3::dot(ab, n); +static inline mpq3 tti_interp( + const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &n, mpq3 &ab, mpq3 &ac, mpq3 &dotbuf) +{ + ab = a; + ab -= b; + ac = a; + ac -= c; + mpq_class den = mpq3::dot_with_buffer(ab, n, dotbuf); BLI_assert(den != 0); - mpq_class alpha = mpq3::dot(a - c, n) / den; + mpq_class alpha = mpq3::dot_with_buffer(ac, n, dotbuf) / den; return a - alpha * ab; } @@ -1179,11 +1185,28 @@ static inline mpq3 tti_interp(const mpq3 &a, const mpq3 &b, const mpq3 &c, const * Return +1, 0, -1 as a + ad is above, on, or below the oriented plane containing a, b, c in CCW * order. This is the same as -oriented(a, b, c, a + ad), but uses fewer arithmetic operations. * TODO: change arguments to `const Vert *` and use floating filters. + * The ba, ca, n, and dotbuf arguments are used as temporaries; declaring them + * in the caller can avoid many allocs and frees of mpq3 and mpq_class structures. */ -static inline int tti_above(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &ad) +static inline int tti_above(const mpq3 &a, + const mpq3 &b, + const mpq3 &c, + const mpq3 &ad, + mpq3 &ba, + mpq3 &ca, + mpq3 &n, + mpq3 &dotbuf) { - mpq3 n = mpq3::cross(b - a, c - a); - return sgn(mpq3::dot(ad, n)); + ba = b; + ba -= a; + ca = c; + ca -= a; + + n.x = ba.y * ca.z - ba.z * ca.y; + n.y = ba.z * ca.x - ba.x * ca.z; + n.z = ba.x * ca.y - ba.y * ca.x; + + return sgn(mpq3::dot_with_buffer(ad, n, dotbuf)); } /** @@ -1227,20 +1250,21 @@ static ITT_value itt_canon2(const mpq3 &p1, mpq3 p1p2 = p2 - p1; mpq3 intersect_1; mpq3 intersect_2; + mpq3 buf[4]; bool no_overlap = false; /* Top test in classification tree. */ - if (tti_above(p1, q1, r2, p1p2) > 0) { + if (tti_above(p1, q1, r2, p1p2, buf[0], buf[1], buf[2], buf[3]) > 0) { /* Middle right test in classification tree. */ - if (tti_above(p1, r1, r2, p1p2) <= 0) { + if (tti_above(p1, r1, r2, p1p2, buf[0], buf[1], buf[2], buf[3]) <= 0) { /* Bottom right test in classification tree. */ - if (tti_above(p1, r1, q2, p1p2) > 0) { + if (tti_above(p1, r1, q2, p1p2, buf[0], buf[1], buf[2], buf[3]) > 0) { /* Overlap is [k [i l] j]. */ if (dbg_level > 0) { std::cout << "overlap [k [i l] j]\n"; } /* i is intersect with p1r1. l is intersect with p2r2. */ - intersect_1 = tti_interp(p1, r1, p2, n2); - intersect_2 = tti_interp(p2, r2, p1, n1); + intersect_1 = tti_interp(p1, r1, p2, n2, buf[0], buf[1], buf[2]); + intersect_2 = tti_interp(p2, r2, p1, n1, buf[0], buf[1], buf[2]); } else { /* Overlap is [i [k l] j]. */ @@ -1248,8 +1272,8 @@ static ITT_value itt_canon2(const mpq3 &p1, std::cout << "overlap [i [k l] j]\n"; } /* k is intersect with p2q2. l is intersect is p2r2. */ - intersect_1 = tti_interp(p2, q2, p1, n1); - intersect_2 = tti_interp(p2, r2, p1, n1); + intersect_1 = tti_interp(p2, q2, p1, n1, buf[0], buf[1], buf[2]); + intersect_2 = tti_interp(p2, r2, p1, n1, buf[0], buf[1], buf[2]); } } else { @@ -1262,7 +1286,7 @@ static ITT_value itt_canon2(const mpq3 &p1, } else { /* Middle left test in classification tree. */ - if (tti_above(p1, q1, q2, p1p2) < 0) { + if (tti_above(p1, q1, q2, p1p2, buf[0], buf[1], buf[2], buf[3]) < 0) { /* No overlap: [i j] [k l]. */ if (dbg_level > 0) { std::cout << "no overlap: [i j] [k l]\n"; @@ -1271,14 +1295,14 @@ static ITT_value itt_canon2(const mpq3 &p1, } else { /* Bottom left test in classification tree. */ - if (tti_above(p1, r1, q2, p1p2) >= 0) { + if (tti_above(p1, r1, q2, p1p2, buf[0], buf[1], buf[2], buf[3]) >= 0) { /* Overlap is [k [i j] l]. */ if (dbg_level > 0) { std::cout << "overlap [k [i j] l]\n"; } /* i is intersect with p1r1. j is intersect with p1q1. */ - intersect_1 = tti_interp(p1, r1, p2, n2); - intersect_2 = tti_interp(p1, q1, p2, n2); + intersect_1 = tti_interp(p1, r1, p2, n2, buf[0], buf[1], buf[2]); + intersect_2 = tti_interp(p1, q1, p2, n2, buf[0], buf[1], buf[2]); } else { /* Overlap is [i [k j] l]. */ @@ -1286,8 +1310,8 @@ static ITT_value itt_canon2(const mpq3 &p1, std::cout << "overlap [i [k j] l]\n"; } /* k is intersect with p2q2. j is intersect with p1q1. */ - intersect_1 = tti_interp(p2, q2, p1, n1); - intersect_2 = tti_interp(p1, q1, p2, n2); + intersect_1 = tti_interp(p2, q2, p1, n1, buf[0], buf[1], buf[2]); + intersect_2 = tti_interp(p1, q1, p2, n2, buf[0], buf[1], buf[2]); } } } @@ -1438,6 +1462,7 @@ static ITT_value intersect_tri_tri(const IMesh &tm, int t1, int t2) return ITT_value(INONE); } + mpq3 buf[2]; const mpq3 &p1 = vp1->co_exact; const mpq3 &q1 = vq1->co_exact; const mpq3 &r1 = vr1->co_exact; @@ -1447,13 +1472,19 @@ static ITT_value intersect_tri_tri(const IMesh &tm, int t1, int t2) const mpq3 &n2 = tri2.plane->norm_exact; if (sp1 == 0) { - sp1 = sgn(mpq3::dot(p1 - r2, n2)); + buf[0] = p1; + buf[0] -= r2; + sp1 = sgn(mpq3::dot_with_buffer(buf[0], n2, buf[1])); } if (sq1 == 0) { - sq1 = sgn(mpq3::dot(q1 - r2, n2)); + buf[0] = q1; + buf[0] -= r2; + sq1 = sgn(mpq3::dot_with_buffer(buf[0], n2, buf[1])); } if (sr1 == 0) { - sr1 = sgn(mpq3::dot(r1 - r2, n2)); + buf[0] = r1; + buf[0] -= r2; + sr1 = sgn(mpq3::dot_with_buffer(buf[0], n2, buf[1])); } if (dbg_level > 1) { @@ -1473,13 +1504,19 @@ static ITT_value intersect_tri_tri(const IMesh &tm, int t1, int t2) /* Repeat for signs of t2's vertices with respect to plane of t1. */ const mpq3 &n1 = tri1.plane->norm_exact; if (sp2 == 0) { - sp2 = sgn(mpq3::dot(p2 - r1, n1)); + buf[0] = p2; + buf[0] -= r1; + sp2 = sgn(mpq3::dot_with_buffer(buf[0], n1, buf[1])); } if (sq2 == 0) { - sq2 = sgn(mpq3::dot(q2 - r1, n1)); + buf[0] = q2; + buf[0] -= r1; + sq2 = sgn(mpq3::dot_with_buffer(buf[0], n1, buf[1])); } if (sr2 == 0) { - sr2 = sgn(mpq3::dot(r2 - r1, n1)); + buf[0] = r2; + buf[0] -= r1; + sr2 = sgn(mpq3::dot_with_buffer(buf[0], n1, buf[1])); } if (dbg_level > 1) { diff --git a/source/blender/blenlib/intern/polyfill_2d_beautify.c b/source/blender/blenlib/intern/polyfill_2d_beautify.c index 7bfca149ffb..98fa5c872b0 100644 --- a/source/blender/blenlib/intern/polyfill_2d_beautify.c +++ b/source/blender/blenlib/intern/polyfill_2d_beautify.c @@ -375,7 +375,7 @@ void BLI_polyfill_beautify(const float (*coords)[2], for (uint i = 0, base_index = 0; i < order_edges_len; base_index++) { const struct OrderEdge *oe_a = &order_edges[i++]; const struct OrderEdge *oe_b = &order_edges[i++]; - BLI_assert(oe_a->verts[0] == oe_a->verts[0] && oe_a->verts[1] == oe_a->verts[1]); + BLI_assert(oe_a->verts[0] == oe_b->verts[0] && oe_a->verts[1] == oe_b->verts[1]); half_edges[oe_a->e_half].e_radial = oe_b->e_half; half_edges[oe_b->e_half].e_radial = oe_a->e_half; half_edges[oe_a->e_half].base_index = base_index; diff --git a/source/blender/blenlib/intern/timecode.c b/source/blender/blenlib/intern/timecode.c index 9586da941a4..7d7436411ac 100644 --- a/source/blender/blenlib/intern/timecode.c +++ b/source/blender/blenlib/intern/timecode.c @@ -216,10 +216,10 @@ size_t BLI_timecode_string_from_time_simple(char *str, const int hun = ((int)(fmod(time_seconds, 1.0) * 100)); if (hr) { - rlen = BLI_snprintf(str, maxncpy, "%.2d:%.2d:%.2d.%.2d", hr, min, sec, hun); + rlen = BLI_snprintf_rlen(str, maxncpy, "%.2d:%.2d:%.2d.%.2d", hr, min, sec, hun); } else { - rlen = BLI_snprintf(str, maxncpy, "%.2d:%.2d.%.2d", min, sec, hun); + rlen = BLI_snprintf_rlen(str, maxncpy, "%.2d:%.2d.%.2d", min, sec, hun); } return rlen; diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c index 333b6783087..3aa61d1fec5 100644 --- a/source/blender/blenlib/intern/winstuff.c +++ b/source/blender/blenlib/intern/winstuff.c @@ -94,9 +94,9 @@ void BLI_windows_register_blend_extension(const bool background) GetModuleFileName(0, BlPath, MAX_PATH); /* Replace the actual app name with the wrapper. */ - blender_app = strstr(BlPath, "blender-app.exe"); + blender_app = strstr(BlPath, "blender.exe"); if (blender_app != NULL) { - strcpy(blender_app, "blender.exe"); + strcpy(blender_app, "blender-launcher.exe"); } /* root is HKLM by default */ diff --git a/source/blender/blenlib/tests/BLI_color_test.cc b/source/blender/blenlib/tests/BLI_color_test.cc new file mode 100644 index 00000000000..14796e6bf71 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_color_test.cc @@ -0,0 +1,133 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "BLI_color.hh" + +namespace blender::tests { + +/** + * \name Conversions + * \{ */ + +TEST(color, ThemeByteToFloat) +{ + ColorTheme4b theme_byte(192, 128, 64, 128); + ColorTheme4f theme_float = theme_byte.to_4f(); + EXPECT_NEAR(0.75f, theme_float.r, 0.01f); + EXPECT_NEAR(0.5f, theme_float.g, 0.01f); + EXPECT_NEAR(0.25f, theme_float.b, 0.01f); + EXPECT_NEAR(0.5f, theme_float.a, 0.01f); +} + +TEST(color, SrgbStraightFloatToByte) +{ + ColorTheme4f theme_float(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4b theme_byte = theme_float.to_4b(); + EXPECT_EQ(191, theme_byte.r); + EXPECT_EQ(128, theme_byte.g); + EXPECT_EQ(64, theme_byte.b); + EXPECT_EQ(128, theme_byte.a); +} + +TEST(color, SrgbStraightToSceneLinearPremultiplied) +{ + BLI_init_srgb_conversion(); + + ColorTheme4b theme(192, 128, 64, 128); + ColorSceneLinear4f<eAlpha::Premultiplied> linear = + BLI_color_convert_to_scene_linear(theme).premultiply_alpha(); + EXPECT_NEAR(0.26f, linear.r, 0.01f); + EXPECT_NEAR(0.11f, linear.g, 0.01f); + EXPECT_NEAR(0.02f, linear.b, 0.01f); + EXPECT_NEAR(0.5f, linear.a, 0.01f); +} + +TEST(color, SceneLinearStraightToPremultiplied) +{ + ColorSceneLinear4f<eAlpha::Straight> straight(0.75f, 0.5f, 0.25f, 0.5f); + ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied = straight.premultiply_alpha(); + EXPECT_NEAR(0.37f, premultiplied.r, 0.01f); + EXPECT_NEAR(0.25f, premultiplied.g, 0.01f); + EXPECT_NEAR(0.12f, premultiplied.b, 0.01f); + EXPECT_NEAR(0.5f, premultiplied.a, 0.01f); +} + +TEST(color, SceneLinearPremultipliedToStraight) +{ + ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied(0.75f, 0.5f, 0.25f, 0.5f); + ColorSceneLinear4f<eAlpha::Straight> straight = premultiplied.unpremultiply_alpha(); + EXPECT_NEAR(1.5f, straight.r, 0.01f); + EXPECT_NEAR(1.0f, straight.g, 0.01f); + EXPECT_NEAR(0.5f, straight.b, 0.01f); + EXPECT_NEAR(0.5f, straight.a, 0.01f); +} + +TEST(color, SceneLinearStraightSrgbFloat) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Straight> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4f theme = BLI_color_convert_to_theme4f(linear); + EXPECT_NEAR(0.88f, theme.r, 0.01); + EXPECT_NEAR(0.73f, theme.g, 0.01); + EXPECT_NEAR(0.53f, theme.b, 0.01); + EXPECT_NEAR(0.5f, theme.a, 0.01); +} + +TEST(color, SceneLinearPremultipliedToSrgbFloat) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Premultiplied> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4f theme = BLI_color_convert_to_theme4f(linear.unpremultiply_alpha()); + + EXPECT_NEAR(1.19f, theme.r, 0.01); + EXPECT_NEAR(1.0f, theme.g, 0.01); + EXPECT_NEAR(0.74f, theme.b, 0.01); + EXPECT_NEAR(0.5f, theme.a, 0.01); +} + +TEST(color, SceneLinearStraightSrgbByte) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Straight> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4b theme = BLI_color_convert_to_theme4b(linear); + EXPECT_EQ(225, theme.r); + EXPECT_EQ(188, theme.g); + EXPECT_EQ(137, theme.b); + EXPECT_EQ(128, theme.a); +} + +TEST(color, SceneLinearPremultipliedToSrgbByte) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Premultiplied> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4b theme = BLI_color_convert_to_theme4b(linear.unpremultiply_alpha()); + EXPECT_EQ(255, theme.r); + EXPECT_EQ(255, theme.g); + EXPECT_EQ(188, theme.b); + EXPECT_EQ(128, theme.a); +} + +TEST(color, SceneLinearByteEncoding) +{ + ColorSceneLinear4f<eAlpha::Premultiplied> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied> encoded = linear.encode(); + EXPECT_EQ(225, encoded.r); + EXPECT_EQ(188, encoded.g); + EXPECT_EQ(137, encoded.b); + EXPECT_EQ(128, encoded.a); +} + +TEST(color, SceneLinearByteDecoding) +{ + ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied> encoded(225, 188, 137, 128); + ColorSceneLinear4f<eAlpha::Premultiplied> decoded = encoded.decode(); + EXPECT_NEAR(0.75f, decoded.r, 0.01f); + EXPECT_NEAR(0.5f, decoded.g, 0.01f); + EXPECT_NEAR(0.25f, decoded.b, 0.01f); + EXPECT_NEAR(0.5f, decoded.a, 0.01f); +} + +/* \} */ + +} // namespace blender::tests |