From f08a3261ab806fa97028fc121f7db13f50373b26 Mon Sep 17 00:00:00 2001 From: Michael Hofmann Date: Sun, 24 Feb 2019 15:12:32 +0100 Subject: [PATCH] Add delayed evaluation for image conversions. --- selene/CMakeLists.txt | 1 + selene/img/pixel/Pixel.hpp | 18 ++ selene/img_ops/ImageConversions.hpp | 237 ++++++++++++------ selene/img_ops/_impl/ImageConversionExpr.hpp | 130 ++++++++++ test/selene/img_ops/ImageConversions.cpp | 70 ++++++ test/selene/img_ops/Transformations.cpp | 250 +++++++++++++------ 6 files changed, 559 insertions(+), 147 deletions(-) create mode 100644 selene/img_ops/_impl/ImageConversionExpr.hpp diff --git a/selene/CMakeLists.txt b/selene/CMakeLists.txt index bc9df65..48f270d 100644 --- a/selene/CMakeLists.txt +++ b/selene/CMakeLists.txt @@ -271,6 +271,7 @@ target_sources(selene_img_ops PRIVATE ${CMAKE_CURRENT_LIST_DIR}/img_ops/View.hpp ${CMAKE_CURRENT_LIST_DIR}/img_ops/_impl/FlipExpr.hpp ${CMAKE_CURRENT_LIST_DIR}/img_ops/_impl/IdentityExpr.hpp + ${CMAKE_CURRENT_LIST_DIR}/img_ops/_impl/ImageConversionExpr.hpp ${CMAKE_CURRENT_LIST_DIR}/img_ops/_impl/TransposeExpr.hpp ) diff --git a/selene/img/pixel/Pixel.hpp b/selene/img/pixel/Pixel.hpp index f82601a..9337ec6 100644 --- a/selene/img/pixel/Pixel.hpp +++ b/selene/img/pixel/Pixel.hpp @@ -971,6 +971,24 @@ round(const Pixel& px) // ----- +/** \brief Converts a pixel value into a character stream representation, e.g. for printing. + * + * @param os An output stream. + * @param px The pixel to be converted. + * @return A reference to the provided output stream. + */ +template +std::ostream& operator<<(std::ostream& os, const Pixel& px) +{ + os << '('; + for (auto c = std::size_t{0}; c < nr_channels_ - 1; ++c) + { + os << static_cast>(px[c]) << ", "; + } + os << static_cast>(px[nr_channels_ - 1]) << ')'; + return os; +} + /** \brief Converts a pixel value into a character stream representation, e.g. for printing. * * @param os An output stream. diff --git a/selene/img_ops/ImageConversions.hpp b/selene/img_ops/ImageConversions.hpp index 512f727..ef7f820 100644 --- a/selene/img_ops/ImageConversions.hpp +++ b/selene/img_ops/ImageConversions.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace sln { @@ -29,12 +30,6 @@ template ::PixelType>::pixel_format, pixel_format_dst)>> void convert_image(const ImageBase& img_src, ImageBase& img_dst); -template ::PixelType>::pixel_format != PixelFormat::Unknown>, - typename = std::enable_if_t::PixelType>::pixel_format, pixel_format_dst)>> -auto convert_image(const ImageBase& img_src); - template ::PixelType>::pixel_format, pixel_format_dst)>> void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value); +template ::PixelType>::pixel_format != PixelFormat::Unknown>, + typename = std::enable_if_t::PixelType>::pixel_format, pixel_format_dst)>> +auto convert_image(const ImageBase& img_src); + template ::PixelType>::pixel_format, pixel_format_dst)>> auto convert_image(const ImageBase& img_src, ElementType alpha_value); +template ::PixelType>::pixel_format != PixelFormat::Unknown>, + typename = std::enable_if_t::PixelType>::pixel_format, pixel_format_dst)>> +auto convert_image_expr(const ImageExpr& img_src); + +template ::PixelType>::pixel_format != PixelFormat::Unknown>, + typename = std::enable_if_t::PixelType>::pixel_format, pixel_format_dst)>> +auto convert_image_expr(const ImageExpr& img_src, ElementType alpha_value); + // Overloads for unknown source pixel format template > void convert_image(const ImageBase& img_src, ImageBase& img_dst); +template ::PixelType>::pixel_format == PixelFormat::Unknown>, + typename = std::enable_if_t> +void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value); + template & img_src); template ::PixelType>::pixel_format == PixelFormat::Unknown>, typename = std::enable_if_t> -void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value); +auto convert_image(const ImageBase& img_src, ElementType alpha_value); + +template ::PixelType>::pixel_format == PixelFormat::Unknown>, + typename = std::enable_if_t> +auto convert_image_expr(const ImageExpr& img_src); template ::PixelType>::pixel_format == PixelFormat::Unknown>, + typename = std::enable_if_t::PixelType>::pixel_format == PixelFormat::Unknown>, typename = std::enable_if_t> -auto convert_image(const ImageBase& img_src, ElementType alpha_value); +auto convert_image_expr(const ImageExpr& img_src, ElementType alpha_value); /// @} @@ -224,7 +253,8 @@ inline void convert_image(const ImageBase& img_src, ImageBase RGBA). + * In this case, the additional alpha value has to be manually specified. * * This overload is valid if the source pixel has a known pixel format, i.e. pixel format is not PixelFormat::Unknown. * @@ -234,30 +264,41 @@ inline void convert_image(const ImageBase& img_src, ImageBase(img_rgb)` will perform an RGB -> grayscale conversion, - * returning the output image. + * Example: `convert_image(img_rgb, img_y, 255)` will perform an RGB -> + * grayscale+luminance conversion, writing the output to `img_y`. * * @tparam pixel_format_dst The target pixel format. * @tparam DerivedSrc The typed source image type (usually automatically deduced). + * @tparam DerivedDst The typed target image type (usually automatically deduced). + * @tparam ElementType The pixel element type (for the target format; usually automatically deduced). * @param img_src The source image. - * @return The target image. + * @param img_dst The target image. Its pixel type has to be compatible with the target pixel format. + * @param alpha_value The alpha value to assign to each pixel in the target image. */ template -inline auto convert_image(const ImageBase& img_src) +inline void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value) { using PixelSrc = typename ImageBase::PixelType; + using PixelDst = typename ImageBase::PixelType; + + static_assert(get_nr_channels(pixel_format_dst) == PixelTraits::nr_channels, + "Incorrect target number of channels for given pixel format."); + static_assert(PixelTraits::pixel_format == pixel_format_dst + || PixelTraits::pixel_format == PixelFormat::Unknown, + "Incorrect target pixel format."); constexpr auto pixel_format_src = PixelTraits::pixel_format; - return impl::ImageConversion::apply(img_src); + impl::ImageConversion::apply(img_src, img_dst, alpha_value); } /** \brief Converts an image (i.e. each pixel) from a source to a target pixel format. * - * This is an overload for performing conversions that add an alpha channel (e.g. RGB -> RGBA). - * In this case, the additional alpha value has to be manually specified. + * This overload returns the target image, for which the type is automatically determined. * * This overload is valid if the source pixel has a known pixel format, i.e. pixel format is not PixelFormat::Unknown. * @@ -267,36 +308,24 @@ inline auto convert_image(const ImageBase& img_src) * Currently, conversions from/to the following pixel formats are supported: Y, YA, RGB, BGR, RGBA, BGRA, * ARGB, ABGR. * - * Example: `convert_image(img_rgb, img_y, 255)` will perform an RGB -> - * grayscale+luminance conversion, writing the output to `img_y`. + * Example: `convert_image(img_rgb)` will perform an RGB -> grayscale conversion, + * returning the output image. * * @tparam pixel_format_dst The target pixel format. * @tparam DerivedSrc The typed source image type (usually automatically deduced). - * @tparam DerivedDst The typed target image type (usually automatically deduced). - * @tparam ElementType The pixel element type (for the target format; usually automatically deduced). * @param img_src The source image. - * @param img_dst The target image. Its pixel type has to be compatible with the target pixel format. - * @param alpha_value The alpha value to assign to each pixel in the target image. + * @return The target image. */ template -inline void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value) +inline auto convert_image(const ImageBase& img_src) { using PixelSrc = typename ImageBase::PixelType; - using PixelDst = typename ImageBase::PixelType; - - static_assert(get_nr_channels(pixel_format_dst) == PixelTraits::nr_channels, - "Incorrect target number of channels for given pixel format."); - static_assert(PixelTraits::pixel_format == pixel_format_dst - || PixelTraits::pixel_format == PixelFormat::Unknown, - "Incorrect target pixel format."); constexpr auto pixel_format_src = PixelTraits::pixel_format; - impl::ImageConversion::apply(img_src, img_dst, alpha_value); + return impl::ImageConversion::apply(img_src); } /** \brief Converts an image (i.e. each pixel) from a source to a target pixel format. @@ -337,6 +366,33 @@ inline auto convert_image(const ImageBase& img_src, ElementType alph return impl::ImageConversion::apply(img_src, alpha_value); } +template +auto convert_image_expr(const ImageExpr& img_src) +{ + using PixelSrc = typename ImageExpr::PixelType; + using PixelDst = typename impl::TargetPixelType::type; + + constexpr auto pixel_format_src = PixelTraits::pixel_format; + return impl::ImageConversionExpr>(img_src); +} + +template +auto convert_image_expr(const ImageExpr& img_src, ElementType alpha_value) +{ + using PixelSrc = typename ImageExpr::PixelType; + using PixelDst = typename impl::TargetPixelType::type; + + constexpr auto pixel_format_src = PixelTraits::pixel_format; + return impl::ImageConversionAlphaExpr>(img_src, alpha_value); +} + // Overloads for unknown source pixel format @@ -363,11 +419,11 @@ inline auto convert_image(const ImageBase& img_src, ElementType alph * @param img_dst The target image. Its pixel type has to be compatible with the target pixel format. */ template + PixelFormat pixel_format_dst, + typename DerivedSrc, + typename DerivedDst, + typename, + typename> inline void convert_image(const ImageBase& img_src, ImageBase& img_dst) { using PixelSrc = typename ImageBase::PixelType; @@ -383,7 +439,8 @@ inline void convert_image(const ImageBase& img_src, ImageBase RGBA). + * In this case, the additional alpha value has to be manually specified. * * This overload is valid if the source pixel has pixel format PixelFormat::Unknown. In this case, the source format * will have to be explicitly specified as first template parameter. @@ -394,34 +451,41 @@ inline void convert_image(const ImageBase& img_src, ImageBase(img_rgb)` will perform an RGB -> grayscale conversion, - * returning the output image. + * Example: `convert_image(img_rgb, img_y, 255)` will perform an RGB -> + * grayscale+luminance conversion, writing the output to `img_y`. * * @tparam pixel_format_src The source pixel format. * @tparam pixel_format_dst The target pixel format. * @tparam DerivedSrc The typed source image type (usually automatically deduced). + * @tparam DerivedDst The typed target image type (usually automatically deduced). + * @tparam ElementType The pixel element type (for the target format; usually automatically deduced). * @param img_src The source image. - * @return The target image. + * @param img_dst The target image. Its pixel type has to be compatible with the target pixel format. + * @param alpha_value The alpha value to assign to each pixel in the target image. */ template -inline auto convert_image(const ImageBase& img_src) + PixelFormat pixel_format_dst, + typename DerivedSrc, + typename DerivedDst, + typename ElementType, + typename, + typename> +inline void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value) { using PixelSrc = typename ImageBase::PixelType; + using PixelDst = typename ImageBase::PixelType; static_assert(get_nr_channels(pixel_format_src) == PixelTraits::nr_channels, "Incorrect source number of channels for given pixel format."); + static_assert(get_nr_channels(pixel_format_dst) == PixelTraits::nr_channels, + "Incorrect target number of channels for given pixel format."); - return impl::ImageConversion::apply(img_src); + impl::ImageConversion::apply(img_src, img_dst, alpha_value); } /** \brief Converts an image (i.e. each pixel) from a source to a target pixel format. * - * This is an overload for performing conversions that add an alpha channel (e.g. RGB -> RGBA). - * In this case, the additional alpha value has to be manually specified. + * This overload returns the target image, for which the type is automatically determined. * * This overload is valid if the source pixel has pixel format PixelFormat::Unknown. In this case, the source format * will have to be explicitly specified as first template parameter. @@ -432,36 +496,28 @@ inline auto convert_image(const ImageBase& img_src) * Currently, conversions from/to the following pixel formats are supported: Y, YA, RGB, BGR, RGBA, BGRA, * ARGB, ABGR. * - * Example: `convert_image(img_rgb, img_y, 255)` will perform an RGB -> - * grayscale+luminance conversion, writing the output to `img_y`. + * Example: `convert_image(img_rgb)` will perform an RGB -> grayscale conversion, + * returning the output image. * * @tparam pixel_format_src The source pixel format. * @tparam pixel_format_dst The target pixel format. * @tparam DerivedSrc The typed source image type (usually automatically deduced). - * @tparam DerivedDst The typed target image type (usually automatically deduced). - * @tparam ElementType The pixel element type (for the target format; usually automatically deduced). * @param img_src The source image. - * @param img_dst The target image. Its pixel type has to be compatible with the target pixel format. - * @param alpha_value The alpha value to assign to each pixel in the target image. + * @return The target image. */ template -inline void convert_image(const ImageBase& img_src, ImageBase& img_dst, ElementType alpha_value) + PixelFormat pixel_format_dst, + typename DerivedSrc, + typename, + typename> +inline auto convert_image(const ImageBase& img_src) { using PixelSrc = typename ImageBase::PixelType; - using PixelDst = typename ImageBase::PixelType; static_assert(get_nr_channels(pixel_format_src) == PixelTraits::nr_channels, "Incorrect source number of channels for given pixel format."); - static_assert(get_nr_channels(pixel_format_dst) == PixelTraits::nr_channels, - "Incorrect target number of channels for given pixel format."); - impl::ImageConversion::apply(img_src, img_dst, alpha_value); + return impl::ImageConversion::apply(img_src); } /** \brief Converts an image (i.e. each pixel) from a source to a target pixel format. @@ -492,11 +548,11 @@ inline void convert_image(const ImageBase& img_src, ImageBase + PixelFormat pixel_format_dst, + typename DerivedSrc, + typename ElementType, + typename, + typename> inline auto convert_image(const ImageBase& img_src, ElementType alpha_value) { using PixelSrc = typename ImageBase::PixelType; @@ -507,6 +563,39 @@ inline auto convert_image(const ImageBase& img_src, ElementType alph return impl::ImageConversion::apply(img_src, alpha_value); } +template +auto convert_image_expr(const ImageExpr& img_src) +{ + using PixelSrc = typename ImageExpr::PixelType; + using PixelDst = typename impl::TargetPixelType::type; + + static_assert(get_nr_channels(pixel_format_src) == PixelTraits::nr_channels, + "Incorrect source number of channels for given pixel format."); + + return impl::ImageConversionExpr>(img_src); +} + +template +auto convert_image_expr(const ImageExpr& img_src, ElementType alpha_value) +{ + using PixelSrc = typename ImageExpr::PixelType; + using PixelDst = typename impl::TargetPixelType::type; + + static_assert(get_nr_channels(pixel_format_src) == PixelTraits::nr_channels, + "Incorrect source number of channels for given pixel format."); + + return impl::ImageConversionAlphaExpr>(img_src, alpha_value); +} + } // namespace sln #endif // SELENE_IMG_OPS_IMAGE_CONVERSIONS_HPP diff --git a/selene/img_ops/_impl/ImageConversionExpr.hpp b/selene/img_ops/_impl/ImageConversionExpr.hpp new file mode 100644 index 0000000..1a695e4 --- /dev/null +++ b/selene/img_ops/_impl/ImageConversionExpr.hpp @@ -0,0 +1,130 @@ +// This file is part of the `Selene` library. +// Copyright 2017-2019 Michael Hofmann (https://github.com/kmhofmann). +// Distributed under MIT license. See accompanying LICENSE file in the top-level directory. + +#ifndef SELENE_IMG_IMPL_IMAGE_CONVERSION_EXPR_HPP +#define SELENE_IMG_IMPL_IMAGE_CONVERSION_EXPR_HPP + +/// @file + +#include + +#include + +#include + +namespace sln::impl { + +template class ImageConversionExpr; + +template +struct ImageExprTraits> +{ + using PixelType = PixelTypeDst; + constexpr static bool is_view = false; + constexpr static bool is_modifiable = true; +}; + +template +class ImageConversionExpr + : public ImageExpr> +{ +public: + using PixelType = typename ImageExprTraits>::PixelType; + + explicit ImageConversionExpr(const Expr& e) : e_(e) {} + + TypedLayout layout() const noexcept { return TypedLayout{this->width(), this->height(), this->stride_bytes()}; } + + PixelLength width() const noexcept { return e_.width(); } + PixelLength height() const noexcept { return e_.height(); } + Stride stride_bytes() const noexcept { return Stride{PixelTraits::nr_bytes * this->width()}; } + + decltype(auto) operator()(PixelIndex x, PixelIndex y) const noexcept + { + return PixelConversion::apply(e_(x, y)); + } + + template + decltype(auto) eval() const noexcept + { + return Image(*this); + } + +private: + const Expr& e_; +}; + +// --- + +template class ImageConversionAlphaExpr; + +template +struct ImageExprTraits> +{ + using PixelType = PixelTypeDst; + constexpr static bool is_view = false; + constexpr static bool is_modifiable = true; +}; + +template +class ImageConversionAlphaExpr + : public ImageExpr> +{ +public: + using PixelType = typename ImageExprTraits>::PixelType; + + explicit ImageConversionAlphaExpr(const Expr& e, ElementType alpha) : e_(e), alpha_(alpha) {} + + TypedLayout layout() const noexcept { return TypedLayout{this->width(), this->height(), this->stride_bytes()}; } + + PixelLength width() const noexcept { return e_.width(); } + PixelLength height() const noexcept { return e_.height(); } + Stride stride_bytes() const noexcept { return Stride{PixelTraits::nr_bytes * this->width()}; } + + decltype(auto) operator()(PixelIndex x, PixelIndex y) const noexcept + { + return PixelConversion::apply(e_(x, y), alpha_); + } + + template + decltype(auto) eval() const noexcept + { + return Image(*this); + } + +private: + const Expr& e_; + ElementType alpha_; +}; + +} // namespace sln::impl + +#endif // SELENE_IMG_IMPL_IMAGE_CONVERSION_EXPR_HPP diff --git a/test/selene/img_ops/ImageConversions.cpp b/test/selene/img_ops/ImageConversions.cpp index 634f59c..c53e8b3 100644 --- a/test/selene/img_ops/ImageConversions.cpp +++ b/test/selene/img_ops/ImageConversions.cpp @@ -164,3 +164,73 @@ TEST_CASE("Image conversions", "[img]") REQUIRE(img_rgba_1 == img_rgba); } } + +TEST_CASE("Image conversion expressions", "[img]") +{ + const auto img_x = sln_test::make_3x3_test_image_8u1(); + const auto img_xxx = sln_test::make_3x3_test_image_8u3(); + + const auto img_rgb = sln::view_with_pixel_type(img_xxx); + + // Just covering a few select conversions for now... + + SECTION("Convert RGB to Y (unknown source pixel format)") + { + auto img_y_0_expr = sln::convert_image_expr(img_xxx); + + REQUIRE(img_y_0_expr(0_idx, 0_idx) == 11); + REQUIRE(img_y_0_expr(1_idx, 0_idx) == 21); + REQUIRE(img_y_0_expr(2_idx, 0_idx) == 31); + REQUIRE(img_y_0_expr(0_idx, 1_idx) == 41); + REQUIRE(img_y_0_expr(1_idx, 1_idx) == 51); + REQUIRE(img_y_0_expr(2_idx, 1_idx) == 61); + REQUIRE(img_y_0_expr(0_idx, 2_idx) == 71); + REQUIRE(img_y_0_expr(1_idx, 2_idx) == 81); + REQUIRE(img_y_0_expr(2_idx, 2_idx) == 91); + } + + SECTION("Convert RGB to Y (known source pixel format)") + { + auto img_y_0_expr = sln::convert_image(img_rgb); + + REQUIRE(img_y_0_expr(0_idx, 0_idx) == 11); + REQUIRE(img_y_0_expr(1_idx, 0_idx) == 21); + REQUIRE(img_y_0_expr(2_idx, 0_idx) == 31); + REQUIRE(img_y_0_expr(0_idx, 1_idx) == 41); + REQUIRE(img_y_0_expr(1_idx, 1_idx) == 51); + REQUIRE(img_y_0_expr(2_idx, 1_idx) == 61); + REQUIRE(img_y_0_expr(0_idx, 2_idx) == 71); + REQUIRE(img_y_0_expr(1_idx, 2_idx) == 81); + REQUIRE(img_y_0_expr(2_idx, 2_idx) == 91); + } + + SECTION("Convert RGB to RGBA (unknown source pixel format)") + { + auto img_rgba_expr = sln::convert_image(img_xxx, std::uint8_t{255}); + + REQUIRE(img_rgba_expr(0_idx, 0_idx) == sln::PixelRGBA_8u(10, 11, 12, 255)); + REQUIRE(img_rgba_expr(1_idx, 0_idx) == sln::PixelRGBA_8u(20, 21, 22, 255)); + REQUIRE(img_rgba_expr(2_idx, 0_idx) == sln::PixelRGBA_8u(30, 31, 32, 255)); + REQUIRE(img_rgba_expr(0_idx, 1_idx) == sln::PixelRGBA_8u(40, 41, 42, 255)); + REQUIRE(img_rgba_expr(1_idx, 1_idx) == sln::PixelRGBA_8u(50, 51, 52, 255)); + REQUIRE(img_rgba_expr(2_idx, 1_idx) == sln::PixelRGBA_8u(60, 61, 62, 255)); + REQUIRE(img_rgba_expr(0_idx, 2_idx) == sln::PixelRGBA_8u(70, 71, 72, 255)); + REQUIRE(img_rgba_expr(1_idx, 2_idx) == sln::PixelRGBA_8u(80, 81, 82, 255)); + REQUIRE(img_rgba_expr(2_idx, 2_idx) == sln::PixelRGBA_8u(90, 91, 92, 255)); + } + + SECTION("Convert RGB to RGBA (known source pixel format)") + { + auto img_rgba_expr = sln::convert_image(img_rgb, std::uint8_t{255}); + + REQUIRE(img_rgba_expr(0_idx, 0_idx) == sln::PixelRGBA_8u(10, 11, 12, 255)); + REQUIRE(img_rgba_expr(1_idx, 0_idx) == sln::PixelRGBA_8u(20, 21, 22, 255)); + REQUIRE(img_rgba_expr(2_idx, 0_idx) == sln::PixelRGBA_8u(30, 31, 32, 255)); + REQUIRE(img_rgba_expr(0_idx, 1_idx) == sln::PixelRGBA_8u(40, 41, 42, 255)); + REQUIRE(img_rgba_expr(1_idx, 1_idx) == sln::PixelRGBA_8u(50, 51, 52, 255)); + REQUIRE(img_rgba_expr(2_idx, 1_idx) == sln::PixelRGBA_8u(60, 61, 62, 255)); + REQUIRE(img_rgba_expr(0_idx, 2_idx) == sln::PixelRGBA_8u(70, 71, 72, 255)); + REQUIRE(img_rgba_expr(1_idx, 2_idx) == sln::PixelRGBA_8u(80, 81, 82, 255)); + REQUIRE(img_rgba_expr(2_idx, 2_idx) == sln::PixelRGBA_8u(90, 91, 92, 255)); + } +} \ No newline at end of file diff --git a/test/selene/img_ops/Transformations.cpp b/test/selene/img_ops/Transformations.cpp index 9d9a3c7..d1f51f6 100644 --- a/test/selene/img_ops/Transformations.cpp +++ b/test/selene/img_ops/Transformations.cpp @@ -4,6 +4,8 @@ #include +#include + #include #include @@ -16,101 +18,203 @@ using namespace sln::literals; -TEST_CASE("Image transformations", "[img]") +template void test_flip(const Img& img) { - std::mt19937 rng(100); - // std::uniform_int_distribution dist_size(0, 64); - std::uniform_int_distribution dist_size(2, 4); - - for (std::size_t count = 0; count < 32; ++count) - { - const auto width = sln::PixelLength{dist_size(rng)}; - const auto height = sln::PixelLength{dist_size(rng)}; - const auto img = sln_test::construct_random_image(width, height, rng); - REQUIRE(img.width() == width); - REQUIRE(img.height() == height); - - // flip horizontal + // flip horizontal - const auto img_flip_h = sln::flip(img); - REQUIRE(sln::flip(img_flip_h) == img); - REQUIRE(img_flip_h.width() == img.width()); - REQUIRE(img_flip_h.height() == img.height()); + const auto img_flip_h = sln::flip(img); + REQUIRE(sln::flip(img_flip_h) == img); + REQUIRE(img_flip_h.width() == img.width()); + REQUIRE(img_flip_h.height() == img.height()); - for (auto y = 0_idx; y < img_flip_h.height(); ++y) + for (auto y = 0_idx; y < img_flip_h.height(); ++y) + { + for (auto x = 0_idx; x < img_flip_h.width(); ++x) { - for (auto x = 0_idx; x < img_flip_h.width(); ++x) - { - const auto px = sln::PixelIndex{img.width() - x - 1}; - REQUIRE(img_flip_h(x, y) == img(px, y)); - } + const auto px = sln::PixelIndex{img.width() - x - 1}; + REQUIRE(img_flip_h(x, y) == img(px, y)); } + } - auto img_flip_h_in_place = sln::clone(img); - sln::flip_horizontally_in_place(img_flip_h_in_place); - REQUIRE(img_flip_h_in_place == img_flip_h); + auto img_flip_h_in_place = sln::clone(img); + sln::flip_horizontally_in_place(img_flip_h_in_place); + REQUIRE(img_flip_h_in_place == img_flip_h); - // flip vertical + // flip vertical - const auto img_flip_v = sln::flip(img); - REQUIRE(sln::flip(img_flip_v) == img); - REQUIRE(img_flip_v.width() == img.width()); - REQUIRE(img_flip_v.height() == img.height()); + const auto img_flip_v = sln::flip(img); + REQUIRE(sln::flip(img_flip_v) == img); + REQUIRE(img_flip_v.width() == img.width()); + REQUIRE(img_flip_v.height() == img.height()); - for (auto y = 0_idx; y < img_flip_v.height(); ++y) + for (auto y = 0_idx; y < img_flip_v.height(); ++y) + { + for (auto x = 0_idx; x < img_flip_v.width(); ++x) { - for (auto x = 0_idx; x < img_flip_v.width(); ++x) - { - const auto py = sln::PixelIndex{img.height() - y - 1}; - REQUIRE(img_flip_v(x, y) == img(x, py)); - } + const auto py = sln::PixelIndex{img.height() - y - 1}; + REQUIRE(img_flip_v(x, y) == img(x, py)); } + } - auto img_flip_v_in_place = sln::clone(img); - sln::flip_vertically_in_place(img_flip_v_in_place); - REQUIRE(img_flip_v_in_place == img_flip_v); + auto img_flip_v_in_place = sln::clone(img); + sln::flip_vertically_in_place(img_flip_v_in_place); + REQUIRE(img_flip_v_in_place == img_flip_v); - // flip both + // flip both - const auto img_flip_b = sln::flip(img); - REQUIRE(sln::flip(img_flip_b) == img); - REQUIRE(img_flip_b.width() == img.width()); - REQUIRE(img_flip_b.height() == img.height()); + const auto img_flip_b = sln::flip(img); + REQUIRE(sln::flip(img_flip_b) == img); + REQUIRE(img_flip_b.width() == img.width()); + REQUIRE(img_flip_b.height() == img.height()); - for (auto y = 0_idx; y < img_flip_b.height(); ++y) + for (auto y = 0_idx; y < img_flip_b.height(); ++y) + { + for (auto x = 0_idx; x < img_flip_b.width(); ++x) { - for (auto x = 0_idx; x < img_flip_b.width(); ++x) - { - const auto px = sln::PixelIndex{img.width() - x - 1}; - const auto py = sln::PixelIndex{img.height() - y - 1}; - REQUIRE(img_flip_b(x, y) == img(px, py)); - } + const auto px = sln::PixelIndex{img.width() - x - 1}; + const auto py = sln::PixelIndex{img.height() - y - 1}; + REQUIRE(img_flip_b(x, y) == img(px, py)); } + } +} - // transpose - - const auto img_transp = sln::transpose(img); - REQUIRE(img_transp.width() == img.height()); - REQUIRE(img_transp.height() == img.width()); - REQUIRE(sln::transpose(img_transp) == img); +template void test_transpose(const Img& img) +{ + const auto img_transp = sln::transpose(img); + REQUIRE(img_transp.width() == img.height()); + REQUIRE(img_transp.height() == img.width()); + REQUIRE(sln::transpose(img_transp) == img); - for (auto y = 0_idx; y < img_transp.height(); ++y) + for (auto y = 0_idx; y < img_transp.height(); ++y) + { + for (auto x = 0_idx; x < img_transp.width(); ++x) { - for (auto x = 0_idx; x < img_transp.width(); ++x) - { - REQUIRE(img_transp(x, y) == img(y, x)); - } + REQUIRE(img_transp(x, y) == img(y, x)); } + } +} - // rotate +template void test_rotate(const Img& img) +{ + REQUIRE(sln::rotate(img) + == sln::rotate(img)); + REQUIRE(sln::rotate(img) + == sln::rotate(img)); + REQUIRE(sln::rotate(img) + == sln::rotate(img)); + REQUIRE(sln::rotate(img) + == sln::rotate(img)); +} + +TEST_CASE("Image transformations / random images", "[img]") +{ + std::mt19937 rng(100); + std::uniform_int_distribution dist_size(2, 4); + + for (std::size_t count = 0; count < 32; ++count) + { + const auto width = sln::PixelLength{dist_size(rng)}; + const auto height = sln::PixelLength{dist_size(rng)}; + const auto img = sln_test::construct_random_image(width, height, rng); + REQUIRE(img.width() == width); + REQUIRE(img.height() == height); - REQUIRE(sln::rotate(img) - == sln::rotate(img)); - REQUIRE(sln::rotate(img) - == sln::rotate(img)); - REQUIRE(sln::rotate(img) - == sln::rotate(img)); - REQUIRE(sln::rotate(img) - == sln::rotate(img)); + test_flip(img); + test_transpose(img); + test_rotate(img); } } + +TEST_CASE("Image transformation expressions", "[img]") +{ + sln::ImageY_8u img({3_px, 2_px}); + img(0_idx, 0_idx) = 10; + img(1_idx, 0_idx) = 20; + img(2_idx, 0_idx) = 30; + img(0_idx, 1_idx) = 40; + img(1_idx, 1_idx) = 50; + img(2_idx, 1_idx) = 60; + + SECTION("Flip") + { + auto flip_h_expr = sln::flip_expr(img); + REQUIRE(flip_h_expr(0_idx, 0_idx) == 30); + REQUIRE(flip_h_expr(1_idx, 0_idx) == 20); + REQUIRE(flip_h_expr(2_idx, 0_idx) == 10); + REQUIRE(flip_h_expr(0_idx, 1_idx) == 60); + REQUIRE(flip_h_expr(1_idx, 1_idx) == 50); + REQUIRE(flip_h_expr(2_idx, 1_idx) == 40); + + auto flip_v_expr = sln::flip_expr(img); + REQUIRE(flip_v_expr(0_idx, 0_idx) == 40); + REQUIRE(flip_v_expr(1_idx, 0_idx) == 50); + REQUIRE(flip_v_expr(2_idx, 0_idx) == 60); + REQUIRE(flip_v_expr(0_idx, 1_idx) == 10); + REQUIRE(flip_v_expr(1_idx, 1_idx) == 20); + REQUIRE(flip_v_expr(2_idx, 1_idx) == 30); + + auto flip_b_expr = sln::flip_expr(img); + REQUIRE(flip_b_expr(0_idx, 0_idx) == 60); + REQUIRE(flip_b_expr(1_idx, 0_idx) == 50); + REQUIRE(flip_b_expr(2_idx, 0_idx) == 40); + REQUIRE(flip_b_expr(0_idx, 1_idx) == 30); + REQUIRE(flip_b_expr(1_idx, 1_idx) == 20); + REQUIRE(flip_b_expr(2_idx, 1_idx) == 10); + } + + SECTION("Transpose") + { + auto transp_expr = sln::transpose_expr(img); + REQUIRE(transp_expr.width() == img.height()); + REQUIRE(transp_expr.height() == img.width()); + REQUIRE(transp_expr(0_idx, 0_idx) == 10); + REQUIRE(transp_expr(1_idx, 0_idx) == 40); + REQUIRE(transp_expr(0_idx, 1_idx) == 20); + REQUIRE(transp_expr(1_idx, 1_idx) == 50); + REQUIRE(transp_expr(0_idx, 2_idx) == 30); + REQUIRE(transp_expr(1_idx, 2_idx) == 60); + } + + SECTION("Rotate") + { + auto rot_0_expr = sln::rotate_expr(img); + REQUIRE(rot_0_expr.width() == img.width()); + REQUIRE(rot_0_expr.height() == img.height()); + REQUIRE(rot_0_expr(0_idx, 0_idx) == 10); + REQUIRE(rot_0_expr(1_idx, 0_idx) == 20); + REQUIRE(rot_0_expr(2_idx, 0_idx) == 30); + REQUIRE(rot_0_expr(0_idx, 1_idx) == 40); + REQUIRE(rot_0_expr(1_idx, 1_idx) == 50); + REQUIRE(rot_0_expr(2_idx, 1_idx) == 60); + + auto rot_90_expr = sln::rotate_expr(img); + REQUIRE(rot_90_expr.width() == img.height()); + REQUIRE(rot_90_expr.height() == img.width()); + REQUIRE(rot_90_expr(0_idx, 0_idx) == 40); + REQUIRE(rot_90_expr(1_idx, 0_idx) == 10); + REQUIRE(rot_90_expr(0_idx, 1_idx) == 50); + REQUIRE(rot_90_expr(1_idx, 1_idx) == 20); + REQUIRE(rot_90_expr(0_idx, 2_idx) == 60); + REQUIRE(rot_90_expr(1_idx, 2_idx) == 30); + + auto rot_180_expr = sln::rotate_expr(img); + REQUIRE(rot_180_expr.width() == img.width()); + REQUIRE(rot_180_expr.height() == img.height()); + REQUIRE(rot_180_expr(0_idx, 0_idx) == 60); + REQUIRE(rot_180_expr(1_idx, 0_idx) == 50); + REQUIRE(rot_180_expr(2_idx, 0_idx) == 40); + REQUIRE(rot_180_expr(0_idx, 1_idx) == 30); + REQUIRE(rot_180_expr(1_idx, 1_idx) == 20); + REQUIRE(rot_180_expr(2_idx, 1_idx) == 10); + + auto rot_270_expr = sln::rotate_expr(img); + REQUIRE(rot_270_expr.width() == img.height()); + REQUIRE(rot_270_expr.height() == img.width()); + REQUIRE(rot_270_expr(0_idx, 0_idx) == 30); + REQUIRE(rot_270_expr(1_idx, 0_idx) == 60); + REQUIRE(rot_270_expr(0_idx, 1_idx) == 20); + REQUIRE(rot_270_expr(1_idx, 1_idx) == 50); + REQUIRE(rot_270_expr(0_idx, 2_idx) == 10); + REQUIRE(rot_270_expr(1_idx, 2_idx) == 40); + } +} \ No newline at end of file