diff --git a/include/flash/core.hpp b/include/flash/core.hpp index 1108ab3..ce0dbc1 100644 --- a/include/flash/core.hpp +++ b/include/flash/core.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -55,15 +56,6 @@ struct common_type, blaze::StaticVector> { namespace flash { -using signed_size = std::ptrdiff_t; -inline constexpr double pi = 3.14159265358979323846; - -template -using remove_cvref_t = std::remove_cv_t>; - -template -using image_matrix = blaze::CustomMatrix; - /** \brief Used to bypass scoped_channel_type of GIL Some channel types in GIL, like `gil::float32_t` and `gil::float64_t` have a different type @@ -90,6 +82,242 @@ struct true_channel_type { template using true_channel_type_t = typename true_channel_type::type; +template +struct pixel_vector { +}; + +template +struct pixel_vector, IsRowVector> + : boost::gil::pixel, Layout>, + blaze::DenseVector, IsRowVector>, + IsRowVector> { + using parent_t = boost::gil::pixel; + using boost::gil::pixel::pixel; + + using This = pixel_vector, IsRowVector>; + using Basetype = blaze::DenseVector; + using ResultType = This; + + using TransposeType = pixel_vector, !IsRowVector>; + + static constexpr bool simdEnabled = false; + + using ElementType = ChannelValue; + using TagType = blaze::Group0; + using ReturnType = const ChannelValue&; + using CompositeType = const This&; + + using Reference = ChannelValue&; + using ConstReference = const ChannelValue&; + using Pointer = ChannelValue*; + using ConstPointer = const ChannelValue*; + + // TODO: rule of 5 + template + pixel_vector(pixel_vector other) : parent_t(other) + { + } + + template + pixel_vector& operator=(const pixel_vector other) + { + parent_t::operator=(other); + return *this; + } + + template + pixel_vector& operator=(const blaze::DenseVector& v) + { + if ((~v).size() != size()) { + throw std::invalid_argument( + "incoming vector has incompatible size with this pixel vector"); + } + + for (std::size_t i = 0; i < size(); ++i) { + (*this)[i] = (~v)[i]; + } + + return *this; + } + + private: + template + void perform_op(pixel_vector p, Op op) + { + constexpr auto num_channels = boost::gil::num_channels{}; + static_assert(num_channels == boost::gil::num_channels{}); + auto& current = *this; + for (std::ptrdiff_t i = 0; i < num_channels; ++i) { + current[i] = op(current[i], p[i]); + } + } + + public: + std::size_t size() const { return boost::gil::num_channels{}; } + + constexpr bool canAlias() const { return true; } + + template + bool isAliased(const Other* alias) const noexcept + { + return static_cast(this) == static_cast(alias); + } + + template + bool canAlias(const Other* alias) const noexcept + { + return static_cast(this) == static_cast(alias); + } + + // TODO: Add SFINAE for cases when parent_t::is_mutable is false + template + pixel_vector& operator+=(pixel_vector other) + { + perform_op(other, std::plus<>{}); + return *this; + } + + template + pixel_vector& operator-=(pixel_vector other) + { + perform_op(other, std::minus<>{}); + return *this; + } + + template + pixel_vector& operator*=(pixel_vector other) + { + perform_op(other, std::multiplies<>{}); + return *this; + } + + template + pixel_vector& operator/=(pixel_vector other) + { + perform_op(other, std::divides<>{}); + return *this; + } +}; + +template +inline pixel_vector, Layout>, + IsRowVector> +operator+(pixel_vector, IsRowVector> lhs, + pixel_vector, IsRowVector> rhs) +{ + lhs += rhs; + return lhs; +} + +template +inline pixel_vector, Layout>, + IsRowVector> +operator-(pixel_vector, IsRowVector> lhs, + pixel_vector, IsRowVector> rhs) +{ + lhs -= rhs; + return lhs; +} + +template +inline pixel_vector, Layout>, + IsRowVector> +operator*(pixel_vector, IsRowVector> lhs, + pixel_vector, IsRowVector> rhs) +{ + lhs *= rhs; + return lhs; +} + +template +inline pixel_vector, Layout>, + IsRowVector> +operator/(pixel_vector, IsRowVector> lhs, + pixel_vector, IsRowVector> rhs) +{ + lhs /= rhs; + return lhs; +} + +template +inline bool operator==(pixel_vector, IsRowVector> lhs, + pixel_vector, IsRowVector> rhs) +{ + for (std::size_t i = 0; i < lhs.size(); ++i) + { + if (lhs[i] != rhs[i]) + { + return false; + } + } + return true; +} + +template +inline bool operator!=(pixel_vector, IsRowVector> lhs, + pixel_vector, IsRowVector> rhs) +{ + return !(lhs == rhs); +} + +template +inline bool operator==(const pixel_vector& v, Scalar s) +{ + for (std::size_t i = 0; i < v.size(); ++i) + { + if (v[i] != s) + { + return false; + } + } + + return true; +} + +template +inline bool operator!=(const pixel_vector& v, Scalar s) +{ + return !(v == s); +} + +template +struct pixel_vector_type; + +template +struct pixel_vector_type, IsRowVector> { + using type = pixel_vector, IsRowVector>; +}; + +using signed_size = std::ptrdiff_t; +inline constexpr double pi = 3.14159265358979323846; + +template +using remove_cvref_t = std::remove_cv_t>; + +template +using image_matrix = blaze::CustomMatrix; + +template +struct corresponding_vector_type { + using type = + blaze::StaticVector::type>, + boost::gil::num_channels{}, blaze::rowMajor, blaze::unaligned, + blaze::unpadded>; +}; + +template +struct is_layout_compatible { + static constexpr bool value = + sizeof(Pixel) == sizeof(typename corresponding_vector_type::type); +}; + +template +struct layout_compatible_vector { + using type = std::conditional_t::value, + typename corresponding_vector_type::type, + pixel_vector>; +}; + /** \brief Extract `channel`th value from each pixel in `view` and writes into `result` The function can be used with multi channel views, just the `channel` argument might need to be @@ -259,11 +487,12 @@ auto as_matrix_channeled(ImageView source) using pixel_t = typename ImageView::value_type; using channel_t = typename boost::gil::channel_type::type; constexpr auto num_channels = boost::gil::num_channels{}; - using element_type = blaze::StaticVector, - num_channels, - blaze::rowMajor, - IsPixelAligned, - IsPixelPadded>; + // using element_type = blaze::StaticVector, + // num_channels, + // blaze::rowMajor, + // IsPixelAligned, + // IsPixelPadded>; + using element_type = typename flash::layout_compatible_vector::type; static_assert(sizeof(pixel_t) == sizeof(element_type), "The function is made to believe that pixel and corresponding vector types are" "layout compatible, but they are not"); @@ -505,3 +734,15 @@ blaze::DynamicMatrix pad(const blaze::DynamicMatrix& source, std::size_t p return result; } } // namespace flash + +namespace blaze +{ +template +struct UnderlyingElement> { + using Type = typename boost::gil::channel_type::type; +}; + +template +struct IsDenseVector> : std::true_type { +}; +} // namespace blaze \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 554ff74..4ed3d94 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(test_target from_matrix_test.cpp as_matrix_test.cpp as_matrix_channeled_test.cpp - true_channel_type_test.cpp) + true_channel_type_test.cpp + pixel_vector_test.cpp) target_link_libraries(test_target PRIVATE Catch2::Catch2 blazing-gil) catch_discover_tests(test_target) diff --git a/test/as_matrix_channeled_test.cpp b/test/as_matrix_channeled_test.cpp index c57d059..7cb3e22 100644 --- a/test/as_matrix_channeled_test.cpp +++ b/test/as_matrix_channeled_test.cpp @@ -10,7 +10,41 @@ namespace gil = boost::gil; -gil::rgba8_pixel_t rgba8_zero_pixel(0, 0, 0, 0); +template +void pixel3_uint8_zeroes_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = flash::as_matrix_channeled(view); + REQUIRE(blaze::isZero(matrix_view)); +} + +template +void pixel3_uint8_different_values_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = flash::as_matrix_channeled(view); + std::uint8_t value(255); + for (std::size_t i = 0; i < matrix_view.rows(); ++i) { + for (std::size_t j = 0; j < matrix_view.columns(); ++j) { + matrix_view(i, j) = { + static_cast(i), static_cast(j), value}; + } + } + + for (flash::signed_size i = 0; i < matrix_view.rows(); ++i) { + for (flash::signed_size j = 0; j < matrix_view.columns(); ++j) { + PixelType expected_pixel( + static_cast(i), static_cast(j), value); + REQUIRE(view(j, i) == expected_pixel); + } + } +} template void pixel4_uint8_zeroes_test() @@ -41,7 +75,7 @@ void pixel4_uint8_different_values_test() for (flash::signed_size i = 0; i < matrix_view.rows(); ++i) { for (flash::signed_size j = 0; j < matrix_view.columns(); ++j) { - gil::rgba8_pixel_t expected_pixel( + PixelType expected_pixel( static_cast(i), static_cast(j), value, 0); REQUIRE(view(j, i) == expected_pixel); } @@ -124,6 +158,22 @@ TEST_CASE("check rgba8 as_matrix_channeled", "[as_matrix_channeled]") pixel4_uint8_different_values_test(); } +TEST_CASE("check rgb8 as_matrix_channeled", "[as_matrix_channeled]") +{ + using pixel_type = gil::rgb8_pixel_t; + using image_type = gil::rgb8_image_t; + pixel3_uint8_zeroes_test(); + pixel3_uint8_different_values_test(); +} + +TEST_CASE("check bgr8 as_matrix_channeled", "[as_matrix_channeled]") +{ + using pixel_type = gil::bgr8_pixel_t; + using image_type = gil::bgr8_image_t; + pixel3_uint8_zeroes_test(); + pixel3_uint8_different_values_test(); +} + TEST_CASE("check rgb32f as_matrix_channeled", "[as_matrix_channeled]") { using pixel_type = gil::rgb32f_pixel_t; diff --git a/test/pixel_vector_test.cpp b/test/pixel_vector_test.cpp new file mode 100644 index 0000000..426c551 --- /dev/null +++ b/test/pixel_vector_test.cpp @@ -0,0 +1,49 @@ +#include + +#include +#include + +namespace gil = boost::gil; + +template +void test_pixel_vector() +{ + using pixel_type = typename PixelVector::parent_t; + using channel_type = typename gil::channel_type::type; + channel_type value = 23; + constexpr auto num_channels = gil::num_channels{}; + { + PixelVector zero_vector = blaze::generate(num_channels, [](std::size_t){return 0;}); + REQUIRE(blaze::isZero(zero_vector)); + REQUIRE(zero_vector.size() == num_channels); + } + + { + PixelVector vector = blaze::generate(num_channels, [value](){return value;}); + for (std::size_t i = 0; i < vector.size(); ++i) + { + REQUIRE(vector[i] == value); + } + REQUIRE(vector == value); + } + + { + PixelVector vector; + for (std::size_t i = 0; i < vector.size(); ++i) + { + vector[i] = static_cast(i); + } + + vector = blaze::map(vector, [](auto x) {return x * 2;}); + for (std::size_t i = 0; i < vector.size(); ++i) + { + REQUIRE(vector[i] == static_cast(2 * i)); + } + } +} + +TEST_CASE("rgb8 check") +{ + using pixel_vector = flash::pixel_vector; + test_pixel_vector(); +} \ No newline at end of file