diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 524365e51f..b0bc095fed 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -26,6 +26,7 @@ jobs: sudo -E apt-get update sudo -E apt-get -yq --no-install-suggests --no-install-recommends install libpng-dev libjpeg-dev libtiff5-dev libraw-dev displayName: 'Install dependencies' + - template: .ci/azure-pipelines/steps-install-blaze.yml - template: .ci/azure-pipelines/steps-install-boost.yml - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml @@ -41,6 +42,7 @@ jobs: sudo -E apt-get update sudo -E apt-get -yq --no-install-suggests --no-install-recommends install libpng-dev libjpeg-dev libtiff5-dev libraw-dev displayName: 'Install dependencies' + - template: .ci/azure-pipelines/steps-install-blaze.yml - template: .ci/azure-pipelines/steps-install-boost.yml - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml parameters: @@ -58,6 +60,7 @@ jobs: architecture: 'x64' - template: .ci/azure-pipelines/steps-check-cmake.yml - template: .ci/azure-pipelines/steps-install-conan.yml + - template: .ci/azure-pipelines/steps-install-blaze.yml - template: .ci/azure-pipelines/steps-install-boost.yml - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml parameters: @@ -75,6 +78,7 @@ jobs: architecture: 'x64' - template: .ci/azure-pipelines/steps-check-cmake.yml - template: .ci/azure-pipelines/steps-install-conan.yml + - template: .ci/azure-pipelines/steps-install-blaze.yml - template: .ci/azure-pipelines/steps-install-boost.yml - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml parameters: @@ -94,6 +98,7 @@ jobs: - template: .ci/azure-pipelines/steps-install-boost.yml parameters: toolset: darwin + - template: .ci/azure-pipelines/steps-install-blaze.yml - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml parameters: use_conan: 'ON' diff --git a/.ci/azure-pipelines/steps-cmake-build-and-test.yml b/.ci/azure-pipelines/steps-cmake-build-and-test.yml index c60d6b2b26..e4daca2d07 100644 --- a/.ci/azure-pipelines/steps-cmake-build-and-test.yml +++ b/.ci/azure-pipelines/steps-cmake-build-and-test.yml @@ -18,7 +18,7 @@ steps: export BOOST_ROOT=$(Build.SourcesDirectory)/boost-root export BOOST_INCLUDEDIR=$BOOST_ROOT export BOOST_LIBRARYDIR=$BOOST_ROOT/lib - cmake -H. -B_build -DCMAKE_BUILD_TYPE=${{ parameters.configuration }} -DCMAKE_CXX_STANDARD=${{ parameters.cxxver }} -DCMAKE_VERBOSE_MAKEFILE=ON -DBoost_DEBUG=ON -DBoost_NO_SYSTEM_PATHS=ON -DBOOST_GIL_USE_CONAN=${{ parameters.use_conan }} -DBOOST_GIL_ENABLE_EXT_IO=${{ parameters.enable_ext_io }} -DBOOST_GIL_ENABLE_EXT_NUMERIC=${{ parameters.enable_ext_numeric }} -DBOOST_GIL_ENABLE_EXT_TOOLBOX=${{ parameters.enable_ext_toolbox }} + cmake -H. -B_build -DCMAKE_BUILD_TYPE=${{ parameters.configuration }} -DCMAKE_CXX_STANDARD=${{ parameters.cxxver }} -DCMAKE_VERBOSE_MAKEFILE=ON -DBoost_DEBUG=ON -DBoost_NO_SYSTEM_PATHS=ON -DBOOST_GIL_USE_CONAN=${{ parameters.use_conan }} -DBOOST_GIL_ENABLE_EXT_IO=${{ parameters.enable_ext_io }} -DBOOST_GIL_ENABLE_EXT_NUMERIC=${{ parameters.enable_ext_numeric }} -DBOOST_GIL_ENABLE_EXT_TOOLBOX=${{ parameters.enable_ext_toolbox }} -DBOOST_GIL_ENABLE_EXT_BLAZE=ON workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil displayName: 'Run CMake to configure build on Unix' condition: ne(variables['Agent.OS'], 'Windows_NT') @@ -27,7 +27,7 @@ steps: set BOOST_ROOT=$(Build.SourcesDirectory)\boost-root set BOOST_INCLUDEDIR=%BOOST_ROOT% set BOOST_LIBRARYDIR=%BOOST_ROOT%\lib - cmake -H. -B_build -DCMAKE_BUILD_TYPE=${{ parameters.configuration }} -DCMAKE_CXX_STANDARD=${{ parameters.cxxver }} -DCMAKE_VERBOSE_MAKEFILE=ON -DBoost_DEBUG=ON -DBoost_ADDITIONAL_VERSIONS="1.70;1.71" -DBoost_ARCHITECTURE=-x32 -DBoost_NO_SYSTEM_PATHS=ON -DBOOST_GIL_USE_CONAN=${{ parameters.use_conan }} -DBOOST_GIL_ENABLE_EXT_IO=${{ parameters.enable_ext_io }} -DBOOST_GIL_ENABLE_EXT_NUMERIC=${{ parameters.enable_ext_numeric }} -DBOOST_GIL_ENABLE_EXT_TOOLBOX=${{ parameters.enable_ext_toolbox }} + cmake -H. -B_build -DCMAKE_BUILD_TYPE=${{ parameters.configuration }} -DCMAKE_CXX_STANDARD=${{ parameters.cxxver }} -DCMAKE_VERBOSE_MAKEFILE=ON -DBoost_DEBUG=ON -DBoost_ADDITIONAL_VERSIONS="1.70;1.71" -DBoost_ARCHITECTURE=-x32 -DBoost_NO_SYSTEM_PATHS=ON -DBOOST_GIL_USE_CONAN=${{ parameters.use_conan }} -DBOOST_GIL_ENABLE_EXT_IO=${{ parameters.enable_ext_io }} -DBOOST_GIL_ENABLE_EXT_NUMERIC=${{ parameters.enable_ext_numeric }} -DBOOST_GIL_ENABLE_EXT_TOOLBOX=${{ parameters.enable_ext_toolbox }} -DBOOST_GIL_ENABLE_EXT_BLAZE=ON workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil displayName: 'Run CMake to configure build on Windows' condition: eq(variables['Agent.OS'], 'Windows_NT') diff --git a/.ci/azure-pipelines/steps-install-blaze.yml b/.ci/azure-pipelines/steps-install-blaze.yml new file mode 100644 index 0000000000..94cca2a474 --- /dev/null +++ b/.ci/azure-pipelines/steps-install-blaze.yml @@ -0,0 +1,14 @@ +# Azure pipelines for Boost.GIL +# +# Copyright 2019 Olzhas Zhumabek +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) + +steps: + - bash: | + git clone --depth=1 https://scarlet-roses@bitbucket.org/scarlet-roses/blaze.git + cd blaze + mkdir build && cd build + cmake -DUSE_LAPACK=OFF -DBLAZE_BLAS_MODE=OFF -DBLAZE_CACHE_SIZE_AUTO=OFF -DBLAZE_CACHE_SIZE_DEFAULT=131072 .. + sudo make install + displayName: "Install Blaze" diff --git a/CMakeLists.txt b/CMakeLists.txt index 52327b933a..ab7c6a9166 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,14 @@ option(BOOST_GIL_ENABLE_EXT_NUMERIC "Enable Numeric extension, tests and example option(BOOST_GIL_ENABLE_EXT_TOOLBOX "Enable Toolbox extension, tests and examples" ON) option(BOOST_GIL_USE_CONAN "Use Conan to install dependencies" OFF) option(BOOST_GIL_USE_CLANG_TIDY "Set CMAKE_CXX_CLANG_TIDY property on targets to enable clang-tidy linting" OFF) -set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard version to use (default is 11)") +option(BOOST_GIL_ENABLE_EXT_BLAZE "Enable Blaze library extensions, tests and examples" OFF) +set(BOOST_GIL_BLAS_LIBRARY "" CACHE STRING "BLAS library to use with Blaze, only queried if BOOST_GIL_ENABLE_EXT_BLAZE is on") + +if (BOOST_GIL_ENABLE_EXT_BLAZE) + set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard version to use (requires at least 17 for Blaze extension)") +else() + set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard version to use (default is 11)") +endif() #----------------------------------------------------------------------------- # Project @@ -196,6 +203,31 @@ if(BOOST_GIL_ENABLE_EXT_IO) endif() endif() +#----------------------------------------------------------------------------- +# Optional dependency: Blaze +#----------------------------------------------------------------------------- +if (BOOST_GIL_ENABLE_EXT_BLAZE) + # Just add to the dependencies target + find_package(blaze REQUIRED) + target_link_libraries(gil_dependencies INTERFACE blaze::blaze) + # do nothing if BOOST_GIL_BLAS_LIBRARY is empty, hope that Blaze installation used defaults + # Usage notes for Intel MKL: either pass MKLROOT or have env variable MKLROOT set + # It is a requirement of find_package + if (BOOST_GIL_BLAS_LIBRARY STREQUAL MKL) + if (DEFINED MKLROOT) + set(ENV{MKLROOT} ${MKLROOT}) + elseif(NOT DEFINED ENV{MKLROOT}) + message(FATAL_ERROR "Please either pass MKLROOT variable or set environment variable MKLROOT") + endif() + add_library(mkl INTERFACE) + find_package(BLAS) + target_include_directories(mkl INTERFACE $ENV{MKLROOT}/include) + target_link_libraries(mkl INTERFACE ${BLAS_LIBRARIES}) + target_link_libraries(gil_dependencies INTERFACE mkl) + endif() + # TODO: support for OpenBLAS and other BLAS libraries for Blaze +endif() + #----------------------------------------------------------------------------- # clang-tidy # - default checks specified in .clang-tidy configuration file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index c16faf6628..d8339cdbbd 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -43,3 +43,12 @@ endforeach() unset(_example) unset(_examples) + +# Blaze targets +if (BOOST_GIL_ENABLE_EXT_BLAZE) + foreach(blaze_example blaze) + add_executable(${blaze_example} ${blaze_example}.cpp) + target_link_libraries(${blaze_example} PRIVATE gil_include_directories gil_dependencies gil_compile_options) + + endforeach() +endif() diff --git a/example/blaze.cpp b/example/blaze.cpp new file mode 100644 index 0000000000..1c24d07058 --- /dev/null +++ b/example/blaze.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include +#include +#include + +namespace gil = boost::gil; + +int main() +{ + using element_type = blaze::StaticVector; + element_type v({0, 0, 255}); + blaze::DynamicMatrix matrix(16, 16, v); + + auto image = gil::from_matrix(matrix); + gil::write_view("output.png", gil::view(image), gil::png_tag{}); +} diff --git a/include/boost/gil/extension/blaze/blaze.hpp b/include/boost/gil/extension/blaze/blaze.hpp new file mode 100644 index 0000000000..b23bba4175 --- /dev/null +++ b/include/boost/gil/extension/blaze/blaze.hpp @@ -0,0 +1,540 @@ +#include + +#include +#include +#include + +#include +#include + +namespace boost { namespace gil { +/** \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 + compared to it's underlying float type (because they constrain the range to be [0...1]), + this struct will strip that and provide typedef equal to the underlying channel type. +*/ +template +struct true_channel_type { + using type = ChannelType; +}; + +/// \cond specializations +template <> +struct true_channel_type { + using type = float; +}; + +template <> +struct true_channel_type { + using type = double; +}; +/// \endcond + +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 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 + adjusted to select appropriate channel. Do note that it will strip wrapper type around floating + point types like gil::float32_t, etc. + + \arg channel The channel to extract from pixels, defaults to 0 + \tparam View The source view type to convert into matrix + \arg view The source view to convert into matrix + \tparam MT The concrete type of matrix + \arg result The out argument to write result into + \tparam SO Storage order flag +*/ +template +void to_matrix(View view, blaze::DenseMatrix& result, std::size_t channel = 0) +{ + constexpr auto num_channels = boost::gil::num_channels{}; + if (channel >= num_channels) { + throw std::invalid_argument("channel index exceeds available channels in the view"); + } + (~result) = blaze::generate( + view.height(), view.width(), [&view, channel](std::size_t i, std::size_t j) { + using element_type = blaze::UnderlyingElement_t; + return static_cast(view(j, i)[channel]); + }); +} + +/** \brief Converts an image view into `DynamicMatrix`, where each entry corresponds to + selected channel value of the pixel entry + + The function can be used with multi channel views, just the `channel` argument might need to be + adjusted to select appropriate channel. Do note that it will strip wrapper type around floating + point types like gil::float32_t, etc. + + \arg channel The channel to extract from pixels, defaults to 0 + \tparam View The source view type to convert into matrix + \arg view The source view to convert into matrix +*/ +template +auto to_matrix(View view, std::size_t channel = 0) +{ + constexpr auto num_channels = boost::gil::num_channels{}; + if (channel >= num_channels) { + throw std::invalid_argument("channel index exceeds available channels in the view"); + } + using channel_type = typename boost::gil::channel_type::type; + blaze::DynamicMatrix> result(view.height(), view.width()); + to_matrix(view, result, channel); + return result; +} + +namespace detail +{ +template +auto pixel_to_vector_impl(const PixelType& pixel, std::integer_sequence) +{ + using channel_t = typename boost::gil::channel_type::type; + return blaze::StaticVector, sizeof...(indices)>{ + pixel[indices]...}; +} +} // namespace detail + +/** \brief Converts a pixel into `StaticVector` + + Useful when working with multi-channel images + + \tparam PixelType The source pixel type + \arg pixel The source pixel to convert into `StaticVector` + + \return a `StaticVector` where elements correspond to channel values, in the same order +*/ +template +auto pixel_to_vector(const PixelType& pixel) +{ + return detail::pixel_to_vector_impl( + pixel, std::make_index_sequence{}>{}); +} + +/** \brief Convert possibly multi-channel view into `DynamicMatrix` of `StaticVector`s + + Works as if applying `pixel_to_vector` on each pixel to generate corresponding matrix entry + + \tparam View The source view type to convert into `DynamicMatrix` + \arg view The source view + + \return a `DynamicMatrix>`, where each element + corresponds to pixel in the view with `pixel_to_vector` applied. +*/ +template +auto to_matrix_channeled(View view) +{ + return blaze::evaluate( + blaze::generate(view.height(), view.width(), [&view](std::size_t i, std::size_t j) { + return pixel_to_vector(view(j, i)); + })); +} + +/** \brief constructs `blaze::CustomMatrix` out of `image_view` + + Creates a matrix which is semantically like pointer, e.g. changes in the matrix + will be reflected inside the view, and vice versa. Do note that assigning to another + `blaze::CustomMatrix` will behave like shallow copy, thus it is important to perform + deep copy using e.g. `blaze::DynamicMatrix` to have independent copy. + + The input view must have single channel pixels like `boost::gil::gray8_view_t`, + for multi-channel matrices please first check layout compatibility of the pixel type + and corresponding `blaze::StaticVector`, then use + `as_matrix_channeled`. + + \tparam IsAligned A flag that indicates if the source view is aligned + \tparam IsPAdded A flag that indicates if the source view is padded + \tparam StorageOrder rowMajor or columnMajor + \tparam SingleChannelView The type of the view to get matrix view from, must have single channel + pixels + \arg source The source image view to get matrix view from + + \return A `CustomMatrix` with all the alignment, padding and storage order flags +*/ +template +auto as_matrix(SingleChannelView source) +{ + using channel_t = typename boost::gil::channel_type::type; + return blaze::CustomMatrix( + reinterpret_cast*>(&source(0, 0)), + source.height(), + source.width()); +} + +/** \brief constructs `blaze::CustomMatrix` out of `image_view` whose elements are + `StaticVector` + + Please note that there are layout incompatibilities between Blaze's `StaticVector` and + GIL's pixel types. GIL's pixel types are padded to 4 byte boundary if the size is less than + 8 bytes, while Blaze pads to 16. There is a `static_assert` that prevents obviously wrong + use cases, but it is advised to first check if the pixel type and resulting `StaticVector` + are compatible, then using the function. For already tested types, please run tests for + this function (tagged with the function name). + + \tparam IsPixelAligned Flag that indicates if individual pixels inside view are aligned. + + It seems like Blaze automatically pads if the flag is set, so do not set if padding will + be off too. + + \tparam IsPixelPadded Flag that indicates if individual pixels inside view are padded. + + This flag is probably the source of problems. Be careful when setting it and always + check if resulting `StaticVector` and pixel type are compatible + + \tparam IsAligned Flag that indicates if image is aligned in memory + \tparam IsPadded Flag that indicates if individual rows are padded + \tparam StorageOrder either rowMajor or columnMajor + \tparam ImageView The type of image view to get channeled matrix view from + \arg source The image view to get channeled matrix view from + + \return A `CustomMatrix>` with all the alignment, padding + and storage order flags +*/ +template +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 = typename 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"); + return blaze::CustomMatrix( + reinterpret_cast(&source(0, 0)), source.height(), source.width()); +} + +/** \brief Converts vector into pixel + + Useful when working with multi-channel images. + + \tparam PixelType The pixel type to convert to + \tparam VT The concrete vector type + \arg vector The source vector to convert into `PixelType` + \tparam TransposeFlag Transpose flag for the vector + + \return A pixel where each channel corresponds to entry in the vector, in the same order +*/ +template +auto vector_to_pixel(const blaze::DenseVector& vector) +{ + auto num_channels = boost::gil::num_channels{}; + PixelType pixel{}; + for (std::size_t i = 0; i < num_channels; ++i) { + pixel[i] = (~vector)[i]; + } + + return pixel; +} + +namespace detail +{ +template +void from_matrix(std::true_type /*is_vector*/, ImageView view, + const blaze::DenseMatrix& data) +{ + for (std::ptrdiff_t i = 0; i < view.height(); ++i) { + for (std::ptrdiff_t j = 0; j < view.width(); ++j) { + view(j, i) = vector_to_pixel((~data)(i, j)); + } + } +} + +template +void from_matrix(std::false_type /*is not vector*/, ImageView view, + const blaze::DenseMatrix& data) +{ + for (std::ptrdiff_t i = 0; i < view.height(); ++i) { + for (std::ptrdiff_t j = 0; j < view.width(); ++j) { + view(j, i)[0] = (~data)(i, j); + } + } +} +} // namespace detail + +/** \brief Converts a matrix into image with specified type + + This function automatically detects if the matrix is made of + vectors or scalars, and does appropriate conversion from elements + to pixel values and returns the resulting image + + \tparam ImageType The image type to convert into + \tparam PixelType The pixel type that resulting image will consist of + \tparam MT The concrete matrix type + \tparam SO Either rowMajor or columnMajor + \arg data The data to convert into image +*/ +template +ImageType from_matrix(const blaze::DenseMatrix& data) +{ + ImageType result((~data).columns(), (~data).rows()); + auto view = boost::gil::view(result); + detail::from_matrix( + blaze::IsDenseVector>{}, view, data); + return result; +} + +/** \brief Converts the input matrix into image and writes into provided view + + This function automatically detects if the matrix is made of + vectors or scalars, and does appropriate conversion from elements + to pixel values and writes into passed view + + \tparam ImageView the type of ImageView to write image into + \tparam PixelType the type of pixels that the resulting image is made of + \tparam MT The concrete type of matrix + \tparam SO Either rowMajor or columnMajor +*/ +template +void from_matrix(const blaze::DenseMatrix& data, ImageView view) +{ + detail::from_matrix( + blaze::IsDenseVector>{}, view, data); +} + +}} + + +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/extension/CMakeLists.txt b/test/extension/CMakeLists.txt index b642d2b296..a67dfc77e9 100644 --- a/test/extension/CMakeLists.txt +++ b/test/extension/CMakeLists.txt @@ -20,3 +20,7 @@ endif() if(BOOST_GIL_ENABLE_EXT_IO) add_subdirectory(io) endif() + +if (BOOST_GIL_ENABLE_EXT_BLAZE) + add_subdirectory(blaze) +endif() diff --git a/test/extension/blaze/CMakeLists.txt b/test/extension/blaze/CMakeLists.txt new file mode 100644 index 0000000000..a1523506a0 --- /dev/null +++ b/test/extension/blaze/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright (c) 2019 Mateusz Loskot +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +message(STATUS "Boost.GIL: Configuring tests in test/extension/blaze") +foreach(_name + true_channel_type + to_matrix + to_matrix_channeled + as_matrix + as_matrix_channeled + pixel_to_vector + vector_to_pixel + from_matrix) + set(_test t_ext_blaze_${_name}) + set(_target test_ext_blaze_${_name}) + + add_executable(${_target} "") + target_sources(${_target} PRIVATE ${_name}.cpp) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK) + add_test(NAME ${_test} COMMAND ${_target}) + + unset(_name) + unset(_target) + unset(_test) +endforeach() diff --git a/test/extension/blaze/as_matrix.cpp b/test/extension/blaze/as_matrix.cpp new file mode 100644 index 0000000000..2a65ca017f --- /dev/null +++ b/test/extension/blaze/as_matrix.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include + +namespace gil = boost::gil; + +gil::gray8_pixel_t zero_gray8_pixel(0); + +void as_matrix_typecheck_for_gray8_image() +{ + gil::gray8_image_t image(16, 16, zero_gray8_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + using expected_t = blaze::CustomMatrix; + BOOST_TEST_TRAIT_TRUE((std::is_same)); +} + +void as_matrix_gray8_image_zero_value_check() +{ + gil::gray8_image_t image(16, 16, zero_gray8_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + BOOST_TEST(blaze::isZero(result)); +} + +void as_matrix_gray8_image_modify_matrix() +{ + gil::gray8_image_t image(16, 16, zero_gray8_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + gil::gray8_image_t image2(16, 16, zero_gray8_pixel); + auto expected_before_modify = gil::view(image2); + BOOST_TEST(gil::equal_pixels(view, expected_before_modify)); + + gil::uint8_t value = 23; + result(1, 0) = value; // do not forget Blaze's different indexing + BOOST_TEST(view(0, 1)[0] == value); + + std::uint8_t value2 = 40; + result(3, 2) = value2; + BOOST_TEST(view(2, 3)[0] == value2); +} + +void as_matrix_gray8_image_modify_image() +{ + gil::gray8_image_t image(16, 16, zero_gray8_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + auto expected_before_modify = blaze::zero(16, 16); + BOOST_TEST(result == expected_before_modify); + gil::uint8_t value = 23; + view(1, 0)[0] = value; + BOOST_TEST(result(0, 1) == value); + + std::uint8_t value2 = 40; + view(3, 2)[0] = value2; + BOOST_TEST(result(2, 3) == value2); +} + +void as_matrix_typecheck_for_gray16_image() +{ + gil::gray16_image_t image(16, 16); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + using expected_t = blaze::CustomMatrix; + BOOST_TEST_TRAIT_TRUE((std::is_same)); +} + +gil::gray16_pixel_t zero_gray16_pixel(0); + +void as_matrix_gray16_image_zero_value_check() +{ + gil::gray16_image_t image(16, 16, zero_gray16_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + BOOST_TEST(blaze::isZero(result)); +} + +void as_matrix_gray16_image_modify_matrix() +{ + gil::gray16_image_t image(16, 16, zero_gray16_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + gil::gray16_image_t image2(16, 16, zero_gray16_pixel); + auto expected_before_modify = gil::view(image2); + BOOST_TEST(gil::equal_pixels(view, expected_before_modify)); + + gil::uint16_t value = 23; + result(1, 0) = value; // do not forget Blaze's different indexing + BOOST_TEST(view(0, 1)[0] == value); + + std::uint16_t value2 = 40; + result(3, 2) = value2; + BOOST_TEST(view(2, 3)[0] == value2); +} + +void as_matrix_gray16_image_modify_image() +{ + gil::gray16_image_t image(16, 16, zero_gray16_pixel); + auto view = gil::view(image); + + auto result = gil::as_matrix(view); + auto expected_before_modify = blaze::zero(16, 16); + BOOST_TEST(result == expected_before_modify); + gil::uint16_t value = 23; + view(1, 0)[0] = value; + BOOST_TEST(result(0, 1) == value); + + std::uint16_t value2 = 40; + view(3, 2)[0] = value2; + BOOST_TEST(result(2, 3) == value2); +} + +int main() +{ + as_matrix_typecheck_for_gray8_image(); + as_matrix_gray8_image_zero_value_check(); + as_matrix_gray8_image_modify_matrix(); + as_matrix_gray8_image_modify_image(); + as_matrix_typecheck_for_gray16_image(); + as_matrix_gray16_image_zero_value_check(); + as_matrix_gray16_image_modify_matrix(); + as_matrix_gray16_image_modify_image(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/extension/blaze/as_matrix_channeled.cpp b/test/extension/blaze/as_matrix_channeled.cpp new file mode 100644 index 0000000000..bf1e551a20 --- /dev/null +++ b/test/extension/blaze/as_matrix_channeled.cpp @@ -0,0 +1,215 @@ +#include +#include + +#include + +namespace gil = boost::gil; + +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 = gil::as_matrix_channeled(view); + BOOST_TEST(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 = gil::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 (std::ptrdiff_t i = 0; i < matrix_view.rows(); ++i) { + for (std::ptrdiff_t j = 0; j < matrix_view.columns(); ++j) { + PixelType expected_pixel( + static_cast(i), static_cast(j), value); + BOOST_TEST(view(j, i) == expected_pixel); + } + } +} + +template +void pixel4_uint8_zeroes_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = gil::as_matrix_channeled(view); + BOOST_TEST(blaze::isZero(matrix_view)); +} + +template +void pixel4_uint8_different_values_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = gil::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, 0}; + } + } + + for (std::ptrdiff_t i = 0; i < matrix_view.rows(); ++i) { + for (std::ptrdiff_t j = 0; j < matrix_view.columns(); ++j) { + PixelType expected_pixel( + static_cast(i), static_cast(j), value, 0); + BOOST_TEST(view(j, i) == expected_pixel); + } + } +} + +template +void pixel4_float32_zeroes_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = gil::as_matrix_channeled(view); + BOOST_TEST(blaze::isZero(matrix_view)); +} + +template +void pixel4_float32_different_values_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = gil::as_matrix_channeled(view); + float value = 0.125; + 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, 0.0f}; + } + } + + for (std::ptrdiff_t i = 0; i < matrix_view.rows(); ++i) { + for (std::ptrdiff_t j = 0; j < matrix_view.columns(); ++j) { + PixelType expected_pixel(static_cast(i), static_cast(j), value, 0.0f); + BOOST_TEST(view(j, i) == expected_pixel); + } + } +} + +template +void pixel3_float32_zeroes_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = gil::as_matrix_channeled(view); + BOOST_TEST(blaze::isZero(matrix_view)); +} + +template +void pixel3_float32_different_values_test() +{ + PixelType zero_pixel = PixelType(0, 0, 0); + ImageType image(16, 16, zero_pixel); + auto view = gil::view(image); + + auto matrix_view = gil::as_matrix_channeled(view); + float value = 0.125; + 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 (std::ptrdiff_t i = 0; i < matrix_view.rows(); ++i) { + for (std::ptrdiff_t j = 0; j < matrix_view.columns(); ++j) { + PixelType expected_pixel(static_cast(i), static_cast(j), value); + BOOST_TEST(view(j, i) == expected_pixel); + } + } +} + +void check_rgba8_as_matrix_channeled() +{ + using pixel_type = gil::rgba8_pixel_t; + using image_type = gil::rgba8_image_t; + pixel4_uint8_zeroes_test(); + pixel4_uint8_different_values_test(); +} + +void check_rgb8_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(); +} + +void check_bgr8_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(); +} + +void check_rgb32f_as_matrix_channeled() +{ + using pixel_type = gil::rgb32f_pixel_t; + using image_type = gil::rgb32f_image_t; + pixel3_float32_zeroes_test(); + pixel3_float32_different_values_test(); +} + +void check_rgba32f_as_matrix_channeled() +{ + using pixel_type = gil::rgba32f_pixel_t; + using image_type = gil::rgba32f_image_t; + pixel4_float32_zeroes_test(); + pixel4_float32_different_values_test(); +} + +void check_cmyk8_as_matrix_channeled() +{ + using pixel_type = gil::cmyk8_pixel_t; + using image_type = gil::cmyk8_image_t; + pixel4_uint8_zeroes_test(); + pixel4_uint8_different_values_test(); +} + +void check_cmyk32f_as_matrix_channeled() +{ + using pixel_type = gil::cmyk32f_pixel_t; + using image_type = gil::cmyk32f_image_t; + pixel4_float32_zeroes_test(); + pixel4_float32_different_values_test(); +} + +int main() +{ + check_rgba8_as_matrix_channeled(); + check_rgb8_as_matrix_channeled(); + check_bgr8_as_matrix_channeled(); + check_rgb32f_as_matrix_channeled(); + check_rgba32f_as_matrix_channeled(); + check_cmyk8_as_matrix_channeled(); + check_cmyk32f_as_matrix_channeled(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/extension/blaze/from_matrix.cpp b/test/extension/blaze/from_matrix.cpp new file mode 100644 index 0000000000..7b23d5ae8e --- /dev/null +++ b/test/extension/blaze/from_matrix.cpp @@ -0,0 +1,187 @@ +#include +#include + +#include +#include +#include + +namespace gil = boost::gil; + +template +PixelType create_pixel(std::uint8_t value) +{ + PixelType pixel; + auto num_channels = gil::num_channels::value; + for (std::ptrdiff_t i = 0; i < num_channels; ++i) { + pixel[i] = value; + } + + return pixel; +} + +template +PixelType create_zero_pixel() +{ + return create_pixel(0); +} + +template +void test_vector_matrix_type() +{ + blaze::DynamicMatrix input(16, 16, VectorType({0})); + auto result = gil::from_matrix(input); + using result_type = decltype(result); + BOOST_TEST_TRAIT_TRUE(( std::is_same )); + + ImageType expected(16, 16, create_zero_pixel()); + + BOOST_TEST(result == expected); + + std::uint8_t value = 23; + constexpr auto num_channels = gil::num_channels{}; + for (std::ptrdiff_t i = 0; i < num_channels; ++i) { + auto pixel = create_zero_pixel(); + pixel[i] = value; + ImageType expected(16, 16, pixel); + + auto vector = VectorType{0}; + vector[i] = value; + blaze::DynamicMatrix input(16, 16, vector); + + auto result = gil::from_matrix(input); + BOOST_TEST(result == expected); + } + + // different scope + { + PixelType pixel; + VectorType vector; + auto num_channels = gil::num_channels::value; + for (std::ptrdiff_t i = 0; i < num_channels; ++i) { + pixel[i] = i; + vector[static_cast(i)] = i; + } + + ImageType expected(16, 16, pixel); + blaze::DynamicMatrix input(16, 16, vector); + + auto result = gil::from_matrix(input); + BOOST_TEST(result == expected); + } +} + +template +void test_scalar_matrix_type() +{ + blaze::DynamicMatrix input(16, 16, 0); + ImageType expected(16, 16, create_zero_pixel()); + + auto result = gil::from_matrix(input); + using result_type = decltype(result); + BOOST_TEST_TRAIT_TRUE((std::is_same)); + BOOST_TEST(result == expected); + + // same but non-zero value + ScalarType value = 23; + input = value; + gil::fill_pixels(gil::view(expected), PixelType(value)); + auto uniform_test_result = gil::from_matrix(input); + BOOST_TEST(uniform_test_result == expected); + + auto view = gil::view(expected); + + input(0, 0) = 0; + input(0, 10) = 0; + view(0, 0)[0] = 0; + view(10, 0)[0] = 0; + + auto nonuniform_test_result = gil::from_matrix(input); + BOOST_TEST(nonuniform_test_result == expected); +} + +void matrix_to_rgb8_image() +{ + using image_type = gil::rgb8_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void matrix_to_rgb32f_image() +{ + using image_type = gil::rgb32f_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void matrix_to_rgba32f_image() +{ + using image_type = gil::rgba32f_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void matrix_to_rgba8_image() +{ + using image_type = gil::rgba8_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void matrix_to_gray8_image() +{ + using image_type = gil::gray8_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void matrix_to_gray16_image() +{ + using image_type = gil::gray8_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void matrix_to_gray32f_image() +{ + using image_type = gil::gray32f_image_t; + using vector_type = blaze::StaticVector; + test_vector_matrix_type(); +} + +void scalar_matrix_to_gray8_image() +{ + using image_type = gil::gray8_image_t; + using scalar_type = std::uint8_t; + test_scalar_matrix_type(); +} + +void scalar_matrix_to_gray16_image() +{ + using image_type = gil::gray16_image_t; + using scalar_type = std::uint16_t; + test_scalar_matrix_type(); +} + +void scalar_matrix_to_gray32f_image() +{ + using image_type = gil::gray32f_image_t; + using scalar_type = float; + test_scalar_matrix_type(); +} + +int main() { + matrix_to_rgb8_image(); + matrix_to_rgb32f_image(); + matrix_to_rgba32f_image(); + matrix_to_rgba8_image(); + matrix_to_gray8_image(); + matrix_to_gray16_image(); + matrix_to_gray32f_image(); + scalar_matrix_to_gray8_image(); + scalar_matrix_to_gray16_image(); + scalar_matrix_to_gray32f_image(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/extension/blaze/pixel_to_vector.cpp b/test/extension/blaze/pixel_to_vector.cpp new file mode 100644 index 0000000000..398468ee4c --- /dev/null +++ b/test/extension/blaze/pixel_to_vector.cpp @@ -0,0 +1,61 @@ +#include + +#include + +namespace gil = boost::gil; + +void gray8_zero_to_vector() +{ + gil::gray8_pixel_t p(0); + auto v = gil::pixel_to_vector(p); + BOOST_TEST_EQ(v[0], 0); +} + +void gray8_to_vector() +{ + gil::gray8_pixel_t p(23); + auto v = gil::pixel_to_vector(p); + BOOST_TEST_EQ(v[0], 23); +} + +void rgb8_zero_to_vector() +{ + gil::rgb8_pixel_t p(0, 0, 0); + auto vector = gil::pixel_to_vector(p); + BOOST_TEST_EQ(vector[0], 0); + BOOST_TEST_EQ(vector[1], 0); + BOOST_TEST_EQ(vector[2], 0); +} + +void rgb8_uniform_to_vector() +{ + auto value = std::uint8_t(23); + gil::rgb8_pixel_t p(value, value, value); + auto vector = gil::pixel_to_vector(p); + BOOST_TEST_EQ(vector[0], value); + BOOST_TEST_EQ(vector[1], value); + BOOST_TEST_EQ(vector[2], value); +} + +void rgb8_distinct_to_vector() +{ + auto value0 = std::uint8_t(23); + auto value1 = std::uint8_t(11); + auto value2 = std::uint8_t(1); + gil::rgb8_pixel_t p(value0, value1, value2); + auto vector = gil::pixel_to_vector(p); + BOOST_TEST_EQ(vector[0], value0); + BOOST_TEST_EQ(vector[1], value1); + BOOST_TEST_EQ(vector[2], value2); +} + +int main() +{ + gray8_zero_to_vector(); + gray8_to_vector(); + rgb8_zero_to_vector(); + rgb8_uniform_to_vector(); + rgb8_distinct_to_vector(); + + return boost::report_errors(); +} diff --git a/test/extension/blaze/to_matrix.cpp b/test/extension/blaze/to_matrix.cpp new file mode 100644 index 0000000000..6e1068a038 --- /dev/null +++ b/test/extension/blaze/to_matrix.cpp @@ -0,0 +1,144 @@ +#include + +#include +#include + +namespace gil = boost::gil; + +template +void test_to_matrix_type() +{ + using pixel_type = typename ImageType::value_type; + using channel_type = typename gil::channel_type::type; + constexpr auto num_channels = gil::num_channels{}; + + pixel_type zero_pixel{}; + for (std::size_t i = 0; i < num_channels; ++i) { + zero_pixel[i] = 0; + } + + ImageType zero_image(16, 16, zero_pixel); + auto view = gil::view(zero_image); + + for (std::size_t i = 0; i < num_channels; ++i) { + auto result = gil::to_matrix(view, i); + BOOST_TEST(blaze::isZero(result)); + } + + gil::transform_pixels(view, view, [num_channels](auto) { + pixel_type pixel; + for (std::size_t i = 0; i < num_channels; ++i) { + pixel[i] = i; + if constexpr (std::is_same_v) { + pixel[i] /= num_channels; + } + } + return pixel; + }); + + for (std::size_t channel_index; channel_index < num_channels; ++channel_index) { + auto result = gil::to_matrix(view, channel_index); + for (std::size_t i = 0; i < view.height(); ++i) { + for (std::size_t j = 0; j < view.width(); ++j) { + BOOST_TEST(result(j, i) == view(i, j)[channel_index]); + } + } + } + + BOOST_TEST_THROWS(gil::to_matrix(view, num_channels), std::invalid_argument); + BOOST_TEST_THROWS(gil::to_matrix(view, num_channels + 1), std::invalid_argument); +} + +template ::type> +void test_to_matrix_out() +{ + using pixel_type = typename ImageType::value_type; + using channel_type = typename gil::channel_type::type; + constexpr auto num_channels = gil::num_channels{}; + + pixel_type zero_pixel{}; + for (std::size_t i = 0; i < num_channels; ++i) { + zero_pixel[i] = 0; + } + + ImageType zero_image(16, 16, zero_pixel); + auto view = gil::view(zero_image); + + for (std::size_t i = 0; i < num_channels; ++i) { + blaze::DynamicMatrix result(view.height(), view.width()); + gil::to_matrix(view, result, i); + BOOST_TEST(blaze::isZero(result)); + } + + gil::transform_pixels(view, view, [num_channels](auto) { + pixel_type pixel; + for (std::size_t i = 0; i < num_channels; ++i) { + pixel[i] = i; + if constexpr (std::is_same_v) { + pixel[i] /= num_channels; + } + } + return pixel; + }); + + for (std::size_t channel_index; channel_index < num_channels; ++channel_index) { + blaze::DynamicMatrix result(view.height(), view.width()); + gil::to_matrix(view, result, channel_index); + for (std::size_t i = 0; i < view.height(); ++i) { + for (std::size_t j = 0; j < view.width(); ++j) { + BOOST_TEST(result(j, i) == view(i, j)[channel_index]); + } + } + } + + blaze::DynamicMatrix result(view.height(), view.width()); + BOOST_TEST_THROWS(gil::to_matrix(view, result, num_channels), std::invalid_argument); + BOOST_TEST_THROWS(gil::to_matrix(view, result, num_channels + 1), std::invalid_argument); +} + +void gray8_to_matrix() +{ + using image_type = gil::gray8_image_t; + test_to_matrix_type(); + test_to_matrix_out(); +} + +void rgb8_to_matrix() +{ + using image_type = gil::rgb8_image_t; + test_to_matrix_type(); + test_to_matrix_out(); +} + +void rgb32f_to_matrix() +{ + using image_type = gil::rgb32f_image_t; + test_to_matrix_type(); + test_to_matrix_out(); +} + +void rgb16_to_matrix() +{ + using image_type = gil::rgb16_image_t; + test_to_matrix_type(); + test_to_matrix_out(); +} + +void rgb8s_to_matrix() +{ + using image_type = gil::rgb8s_image_t; + test_to_matrix_type(); + test_to_matrix_out(); +} + +int main() +{ + gray8_to_matrix(); + rgb8_to_matrix(); + rgb32f_to_matrix(); + rgb16_to_matrix(); + rgb8s_to_matrix(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/extension/blaze/to_matrix_channeled.cpp b/test/extension/blaze/to_matrix_channeled.cpp new file mode 100644 index 0000000000..c6b185339f --- /dev/null +++ b/test/extension/blaze/to_matrix_channeled.cpp @@ -0,0 +1,92 @@ +#include +#include + +#include +#include + +namespace gil = boost::gil; + +void gray8_image_to_matrix_conversion_typecheck() +{ + gil::gray8_image_t input(16, 16, gil::gray8_pixel_t(13)); + auto matrix = gil::to_matrix_channeled(gil::view(input)); + BOOST_TEST_TRAIT_TRUE((std::is_same, + blaze::UnderlyingElement_t>)); +} + +void gray8_image_to_matrix_values_check() +{ + gil::gray8_image_t input(16, 16, gil::gray8_pixel_t(13)); + auto matrix = gil::to_matrix_channeled(gil::view(input)); + blaze::DynamicMatrix> expected(16, 16, {13}); + + BOOST_TEST(matrix == expected); +} + +void gray8_image_with_differing_values_to_matrix_value_check() +{ + gil::gray8_image_t input(16, 16, gil::gray8_pixel_t(13)); + auto view = gil::view(input); + view(0, 0)[0] = 0; + view(1, 0)[0] = 1; // rows and cols are different for GIL vs Blaze + + auto matrix = gil::to_matrix_channeled(gil::view(input)); + blaze::DynamicMatrix> expected(16, 16, {13}); + expected(0, 0)[0] = 0; + expected(0, 1)[0] = 1; + + BOOST_TEST(matrix == expected); +} + +void rgb8_image_to_matrix_typecheck() +{ + gil::rgb8_pixel_t default_pixel(1, 2, 3); + gil::rgb8_image_t input(16, 16, default_pixel); + + blaze::StaticVector default_vector({1, 2, 3}); + blaze::DynamicMatrix> expected(16, 16, default_vector); + + auto result = gil::to_matrix_channeled(gil::view(input)); + BOOST_TEST_TRAIT_TRUE((std::is_same, + blaze::UnderlyingElement_t>)); +} + +void rgb8_image_to_matrix_value_check() +{ + gil::rgb8_pixel_t default_pixel(1, 2, 3); + gil::rgb8_image_t input(16, 16, default_pixel); + + blaze::StaticVector default_vector({1, 2, 3}); + blaze::DynamicMatrix> expected(16, 16, default_vector); + + auto result = gil::to_matrix_channeled(gil::view(input)); + BOOST_TEST(result == expected); +} + +void rgb8_image_with_differing_values_to_matrix_value_check() +{ + gil::rgb8_pixel_t default_pixel(1, 2, 3); + gil::rgb8_image_t input(16, 16, default_pixel); + auto view = gil::view(input); + view(0, 0) = gil::rgb8_pixel_t(10, 20, 30); + view(1, 0) = gil::rgb8_pixel_t(50, 50, 50); + + blaze::StaticVector default_vector({1, 2, 3}); + blaze::DynamicMatrix> expected(16, 16, default_vector); + expected(0, 0) = {10, 20, 30}; + expected(0, 1) = {50, 50, 50}; + + auto result = gil::to_matrix_channeled(gil::view(input)); + BOOST_TEST(result == expected); +} + +int main() +{ + gray8_image_to_matrix_conversion_typecheck(); + gray8_image_to_matrix_values_check(); + gray8_image_with_differing_values_to_matrix_value_check(); + rgb8_image_to_matrix_typecheck(); + rgb8_image_to_matrix_value_check(); + rgb8_image_with_differing_values_to_matrix_value_check(); + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/extension/blaze/true_channel_type.cpp b/test/extension/blaze/true_channel_type.cpp new file mode 100644 index 0000000000..75001d81aa --- /dev/null +++ b/test/extension/blaze/true_channel_type.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include + +namespace gil = boost::gil; + +void identity_case() +{ + BOOST_TEST_TRAIT_TRUE((std::is_same>)); + BOOST_TEST_TRAIT_TRUE((std::is_same>)); +} + +void underlying_float_case() +{ + BOOST_TEST_TRAIT_TRUE((std::is_same>)); + BOOST_TEST_TRAIT_TRUE((std::is_same>)); +} + +int main() { + identity_case(); + underlying_float_case(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/extension/blaze/vector_to_pixel.cpp b/test/extension/blaze/vector_to_pixel.cpp new file mode 100644 index 0000000000..9fa8ccb3e8 --- /dev/null +++ b/test/extension/blaze/vector_to_pixel.cpp @@ -0,0 +1,67 @@ +#include + +#include + +namespace gil = boost::gil; + +void vector1d_zero_to_gray8() +{ + blaze::StaticVector v({0}); + auto p = gil::vector_to_pixel(v); + + BOOST_TEST_EQ(p[0], 0); +} + +void vector1d_to_gray8() +{ + blaze::StaticVector v({23}); + auto p = gil::vector_to_pixel(v); + + BOOST_TEST_EQ(p[0], 23); +} + +void vector3d_zero_to_rgb8() +{ + blaze::StaticVector v({0, 0, 0}); + auto p = gil::vector_to_pixel(v); + + BOOST_TEST_EQ(p[0], 0); + BOOST_TEST_EQ(p[1], 0); + BOOST_TEST_EQ(p[2], 0); +} + +void vector3d_uniform_to_rgb8() +{ + std::uint8_t value = 23; + blaze::StaticVector v({value, value, value}); + + auto p = gil::vector_to_pixel(v); + + BOOST_TEST_EQ(p[0], value); + BOOST_TEST_EQ(p[1], value); + BOOST_TEST_EQ(p[2], value); +} + +void vector3d_distinct_to_rgb8() +{ + std::uint8_t value0 = 23; + std::uint8_t value1 = 11; + std::uint8_t value2 = 1; + blaze::StaticVector v({value0, value1, value2}); + auto p = gil::vector_to_pixel(v); + + BOOST_TEST_EQ(p[0], value0); + BOOST_TEST_EQ(p[1], value1); + BOOST_TEST_EQ(p[2], value2); +} + +int main() +{ + vector1d_zero_to_gray8(); + vector1d_to_gray8(); + vector3d_zero_to_rgb8(); + vector3d_uniform_to_rgb8(); + vector3d_distinct_to_rgb8(); + + return boost::report_errors(); +}