diff --git a/.appveyor.yml b/.appveyor.yml index 2608d1e6eb..6ac0c704ee 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,7 +7,7 @@ # version: 1.0.{build}-{branch} -# Current Boost.GIL develop branch (future Boost 1.68) requires C++11 +# Current Boost.GIL develop branch requires C++14 # Since VS2017, MSVC default is /std:c++14, so no explicit switch is required. image: Visual Studio 2017 @@ -17,29 +17,6 @@ shallow_clone: true environment: matrix: - - TOOLSET: msvc-14.0 - ARCH: x86_64 - VARIANT: debug - CXXSTD: 11 - TEST_HEADERS: 1 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - TOOLSET: msvc-14.0 - ARCH: x86_64 - VARIANT: release - CXXSTD: 11 - TEST_HEADERS: 1 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - TOOLSET: msvc-14.1 - ARCH: x86_64 - VARIANT: debug - CXXSTD: 17 - TEST_HEADERS: 1 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - TOOLSET: msvc-14.1 - ARCH: x86_64 - VARIANT: release - CXXSTD: 17 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - TOOLSET: msvc-14.1 ARCH: x86_64 VARIANT: debug @@ -64,10 +41,21 @@ environment: - TOOLSET: msvc-14.1 ARCH: x86_64 VARIANT: debug - CXXSTD: 14 - GENERATOR: "Visual Studio 15 2017 Win64" - CMAKE_CONFIG: Debug + CXXSTD: 17 + TEST_HEADERS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - TOOLSET: msvc-14.1 + ARCH: x86_64 + VARIANT: release + CXXSTD: 17 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + # - TOOLSET: msvc-14.1 + # ARCH: x86_64 + # VARIANT: debug + # CXXSTD: 14 + # GENERATOR: "Visual Studio 15 2017 Win64" + # CMAKE_CONFIG: Debug + # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 matrix: fast_finish: true diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml deleted file mode 100644 index 524365e51f..0000000000 --- a/.azure-pipelines.yml +++ /dev/null @@ -1,99 +0,0 @@ -# Azure Pipelines for Boost.GIL -# -# Copyright 2018-2019 Mateusz Loskot -# 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) -# -variables: - system.debug: true - configuration: release - -trigger: - - master - - develop - - azure-pipelines - - ml/* - -jobs: - - job: 'ubuntu1604_gcc5_cxx11_cmake' - pool: - vmImage: 'ubuntu-16.04' - steps: - - script: which g++ && g++ --version - displayName: 'Check GCC' - - template: .ci/azure-pipelines/steps-check-cmake.yml - - script: | - 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-boost.yml - - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml - - - job: 'ubuntu1604_gcc8_cxx14_cmake' - pool: - vmImage: 'ubuntu-16.04' - steps: - - template: .ci/azure-pipelines/steps-install-gcc.yml - - script: which g++ && g++ --version - displayName: 'Check GCC' - - template: .ci/azure-pipelines/steps-check-cmake.yml - - script: | - 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-boost.yml - - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml - parameters: - cxxver: '14' - - - job: 'win2016_vs2017_cxx14_cmake' - pool: - vmImage: 'vs2017-win2016' - steps: - - task: UsePythonVersion@0 - displayName: 'Setup Python' - inputs: - versionSpec: '3.6' - addToPath: true - architecture: 'x64' - - template: .ci/azure-pipelines/steps-check-cmake.yml - - template: .ci/azure-pipelines/steps-install-conan.yml - - template: .ci/azure-pipelines/steps-install-boost.yml - - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml - parameters: - use_conan: 'ON' - - - job: 'win2016_vs2017_cxx17_cmake' - pool: - vmImage: 'vs2017-win2016' - steps: - - task: UsePythonVersion@0 - displayName: 'Setup Python' - inputs: - versionSpec: '3.6' - addToPath: true - architecture: 'x64' - - template: .ci/azure-pipelines/steps-check-cmake.yml - - template: .ci/azure-pipelines/steps-install-conan.yml - - template: .ci/azure-pipelines/steps-install-boost.yml - - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml - parameters: - cxxver: '17' - use_conan: 'ON' - - - job: 'macos1014_xcode11_cmake' - pool: - vmImage: 'macOS-10.14' - steps: - - script: which clang++ && clang++ --version - displayName: 'Check clang' - - template: .ci/azure-pipelines/steps-check-cmake.yml - - template: .ci/azure-pipelines/steps-install-conan.yml - parameters: - python: python3 - - template: .ci/azure-pipelines/steps-install-boost.yml - parameters: - toolset: darwin - - template: .ci/azure-pipelines/steps-cmake-build-and-test.yml - parameters: - use_conan: 'ON' diff --git a/.ci/azure-pipelines/steps-check-cmake.yml b/.ci/azure-pipelines/steps-check-cmake.yml deleted file mode 100644 index 8b4cd9109a..0000000000 --- a/.ci/azure-pipelines/steps-check-cmake.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Azure Pipelines for Boost.GIL -# -# Copyright 2018-2019 Mateusz Loskot -# 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: - - script: cmake --version - displayName: 'Check CMake' diff --git a/.ci/azure-pipelines/steps-cmake-build-and-test.yml b/.ci/azure-pipelines/steps-cmake-build-and-test.yml deleted file mode 100644 index c60d6b2b26..0000000000 --- a/.ci/azure-pipelines/steps-cmake-build-and-test.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Azure Pipelines for Boost.GIL -# -# Copyright 2018-2019 Mateusz Loskot -# 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) -# -parameters: - # defaults, if not specified - configuration: 'Release' - cxxver: '11' - enable_ext_io: 'OFF' - enable_ext_numeric: 'ON' - enable_ext_toolbox: 'ON' - use_conan: 'OFF' - -steps: - - script: | - 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 }} - workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil - displayName: 'Run CMake to configure build on Unix' - condition: ne(variables['Agent.OS'], 'Windows_NT') - - - script: | - 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 }} - workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil - displayName: 'Run CMake to configure build on Windows' - condition: eq(variables['Agent.OS'], 'Windows_NT') - - - script: cmake --build _build --config ${{ parameters.configuration }} -j 4 - workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil - displayName: 'Run CMake to build' - - - script: cd _build && ctest -V --output-on-failure --build-config ${{ parameters.configuration }} - workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil - displayName: 'Run CTest to test' - condition: ne(variables['Agent.OS'], 'Darwin') - - - script: | - # Set DYLD_FALLBACK_LIBRARY_PATH to avoid 'dyld: Library not loaded: libboost_*.dylib' error - export DYLD_FALLBACK_LIBRARY_PATH=$(Build.SourcesDirectory)/boost-root/stage/lib:$DYLD_FALLBACK_LIBRARY_PATH - echo "DYLD_FALLBACK_LIBRARY_PATH=$DYLD_FALLBACK_LIBRARY_PATH" - cd _build && ctest -V --output-on-failure --build-config ${{ parameters.configuration }} - workingDirectory: $(Build.SourcesDirectory)/boost-root/libs/gil - displayName: 'Run CTest to test on macOS' - condition: eq(variables['Agent.OS'], 'Darwin') diff --git a/.ci/azure-pipelines/steps-install-boost.yml b/.ci/azure-pipelines/steps-install-boost.yml deleted file mode 100644 index f921737306..0000000000 --- a/.ci/azure-pipelines/steps-install-boost.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Azure Pipelines for Boost.GIL -# -# Copyright 2018-2019 Mateusz Loskot -# 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) -# -parameters: - # defaults, if not specified - variant: 'release' - toolset: 'gcc' - -steps: - # The $BSD_DIR is the same path as $(Build.SourcesDirectory) - # but guaranteed in Unix format /a/b/c on every OS. - - bash: | - export BSD_DIR=$PWD - cd .. - export GIL_DIR=$PWD/gil - echo "Copying $BSD_DIR to $GIL_DIR" - cp -a $BSD_DIR $GIL_DIR - cd $BSD_DIR - $GIL_DIR/.ci/get-boost.sh $(Build.SourceBranchName) $GIL_DIR - displayName: 'Download Boost' - - - script: | - cd boost-root - ./bootstrap.sh - sudo ./b2 headers - sudo ./b2 -j4 toolset=${{ parameters.toolset }} variant=${{ parameters.variant }} --with-filesystem stage - ls stage/lib - displayName: 'Install Boost on Unix' - condition: ne(variables['Agent.OS'], 'Windows_NT') - - - script: | - cd boost-root - call .\bootstrap.bat - .\b2 headers - .\b2 -j4 address-model=32 variant=${{ parameters.variant }} --layout=system --with-filesystem stage - dir stage\lib - displayName: 'Install Boost on Windows' - condition: eq(variables['Agent.OS'], 'Windows_NT') diff --git a/.ci/azure-pipelines/steps-install-conan.yml b/.ci/azure-pipelines/steps-install-conan.yml deleted file mode 100644 index 70507a4bb6..0000000000 --- a/.ci/azure-pipelines/steps-install-conan.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Azure Pipelines for Boost.GIL -# -# Copyright 2018-2019 Mateusz Loskot -# 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) -# -parameters: - python: 'python' # default, if not specified - -steps: - - script: | - ${{ parameters.python }} --version - ${{ parameters.python }} -m ensurepip - ${{ parameters.python }} -m pip install --upgrade pip - ${{ parameters.python }} -m pip install --upgrade conan - conan --version - displayName: 'Install Conan' diff --git a/.ci/azure-pipelines/steps-install-gcc.yml b/.ci/azure-pipelines/steps-install-gcc.yml deleted file mode 100644 index 2f11eb5d81..0000000000 --- a/.ci/azure-pipelines/steps-install-gcc.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Azure Pipelines for Boost.GIL -# -# Copyright 2019 Mateusz Loskot -# 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) -# -parameters: - # defaults, if not specified - major_version: 8 - -steps: - - bash: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update -qq - sudo apt-get install g++-${{ parameters.major_version }} - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${{ parameters.major_version }} 90 - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ parameters.major_version }} 90 - displayName: 'Install GCC' diff --git a/.ci/get-boost.sh b/.ci/get-boost.sh index f0765f61d2..77604b5c30 100755 --- a/.ci/get-boost.sh +++ b/.ci/get-boost.sh @@ -44,12 +44,12 @@ git submodule --quiet update --init $GIT_SUBMODULE_OPTS \ libs/iterator \ libs/mp11 \ libs/mpl \ - libs/numeric/conversion \ libs/preprocessor \ libs/type_traits \ libs/variant2 # Transitive (of GIL tests too) git submodule --quiet update --init $GIT_SUBMODULE_OPTS \ + libs/align \ libs/atomic \ libs/bind \ libs/chrono \ diff --git a/.ci/upload_docs.sh b/.ci/upload_docs.sh deleted file mode 100755 index f8b98b5f55..0000000000 --- a/.ci/upload_docs.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -set -e # Exit with nonzero exit code if anything fails - -SOURCE_BRANCH="master" -TARGET_BRANCH="gh-pages" - -# Pull requests and commits to other branches shouldn't try to deploy, just build to verify -if [ "$TRAVIS_PULL_REQUEST" != "false" ] || \ - [ "$TRAVIS_BRANCH" != master -a \ - "$TRAVIS_BRANCH" != develop -a \ - "$TRAVIS_BRANCH" != doc -a \ - "$TRAVIS_BRANCH" != ci ]; then - echo "No docs to upload." - exit 0 -fi - -if [ -z "$GH_TOKEN" ]; then - echo "Error: GH_TOKEN is undefined" - exit 1 -fi - -# Save some useful information -REPO=`git config remote.origin.url` -SHA=`git rev-parse --verify HEAD` - -# doc happens to contain the "html" tree that we want to push -# into the gh-pages branch. So we step into that directory, create a new repo, -# set the remote appropriately, then commit and push. -cd doc -git init -git config user.name "Travis CI" -git config user.email "travis-ci" - -# Make sure 'GH_TOKEN' is set (as 'secure' variable) in .travis.yml -git remote add upstream "https://$GH_TOKEN@github.com/boostorg/gil.git" -git fetch upstream -git reset upstream/gh-pages - -# Prepare version. -if [ "$TRAVIS_BRANCH" = develop -o "$TRAVIS_BRANCH" = doc ]; then - mkdir -p develop/doc/ - cp ../index.html develop/ - cp ../doc/index.html develop/doc/ - cp -a html develop/doc/ - git add -A develop -else - git add html index.html -fi -# Commit the new version. -git commit -m "Deploy to GitHub Pages: ${SHA}" - -# Now that we're all set up, we can push. -git push -q upstream HEAD:gh-pages diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 5779c9a34c..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,650 +0,0 @@ -############################################################################## -# CircleCI 2.0 for Boost.GIL -# -# Copyright (c) 2018 Mateusz Loskot -# -# Use, modification and distribution is subject to 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) -############################################################################## -version: 2.0 - -############################################################################## -# Docker images -############################################################################## -docker_gcc48: &docker_gcc48 - docker: - - image: gcc:4.8 -docker_gcc49: &docker_gcc49 - docker: - - image: gcc:4.9 - -docker_gcc51: &docker_gcc51 - docker: - - image: gcc:5.1 -docker_gcc52: &docker_gcc52 - docker: - - image: gcc:5.2 -docker_gcc53: &docker_gcc53 - docker: - - image: gcc:5.3 -docker_gcc54: &docker_gcc54 - docker: - - image: gcc:5.4 -docker_gcc55: &docker_gcc55 - docker: - - image: gcc:5.5 - -docker_gcc61: &docker_gcc61 - docker: - - image: gcc:6.1 -docker_gcc62: &docker_gcc62 - docker: - - image: gcc:6.2 -docker_gcc63: &docker_gcc63 - docker: - - image: gcc:6.3 -docker_gcc64: &docker_gcc64 - docker: - - image: gcc:6.4 - -docker_gcc71: &docker_gcc71 - docker: - - image: gcc:7.3 -docker_gcc72: &docker_gcc72 - docker: - - image: gcc:7.2 -docker_gcc73: &docker_gcc73 - docker: - - image: gcc:7.3 - -docker_gcc82: &docker_gcc82 - docker: - - image: gcc:8.2 -docker_gcc83: &docker_gcc83 - docker: - - image: gcc:8.3 - -docker_clang39: &docker_clang39 - docker: - - image: rferraro/cxx-clang:3.9 - -docker_clang40: &docker_clang40 - docker: - - image: rferraro/cxx-clang:4.0 - -docker_clang50: &docker_clang50 - docker: - - image: rferraro/cxx-clang:5.0 - -############################################################################## -# Build variants -############################################################################## -env_gcc_cpp11_dbg: &env_gcc_cpp11_dbg - environment: - - TOOLSET: gcc - - VARIANT: "variant=debug" - - OPTIMIZ: "" - - CXXSTD: "11" - - LNKFLAG: "" - -env_gcc_cpp11_opt_speed: &env_gcc_cpp11_opt_speed - environment: - - TOOLSET: gcc - - VARIANT: "variant=release" - - OPTIMIZ: "optimization=speed" - - CXXSTD: "11" - - LNKFLAG: "" - -env_clang_cpp11_dbg: &env_clang_cpp11_dbg - environment: - - TOOLSET: clang - - VARIANT: "variant=debug" - - OPTIMIZ: "" - - CXXSTD: "11" - - LNKFLAG: "" - -env_clang_cpp11_opt_speed: &env_clang_cpp11_opt_speed - environment: - - TOOLSET: clang - - VARIANT: "variant=release" - - OPTIMIZ: "optimization=speed" - - CXXSTD: "11" - - LNKFLAG: "" - -############################################################################## -# Build configurations -############################################################################## -# GCC 4.8 is used to build `b2` driver -config_gcc48_cpp11_opt_speed: &config_gcc48_cpp11_opt_speed - <<: *docker_gcc48 - <<: *env_gcc_cpp11_opt_speed - -config_gcc49_cpp11_dbg: &config_gcc49_cpp11_dbg - <<: *docker_gcc49 - <<: *env_gcc_cpp11_dbg -config_gcc49_cpp11_opt_speed: &config_gcc49_cpp11_opt_speed - <<: *docker_gcc49 - <<: *env_gcc_cpp11_opt_speed - -config_gcc51_cpp11_dbg: &config_gcc51_cpp11_dbg - <<: *docker_gcc51 - <<: *env_gcc_cpp11_dbg -config_gcc51_cpp11_opt_speed: &config_gcc51_cpp11_opt_speed - <<: *docker_gcc51 - <<: *env_gcc_cpp11_opt_speed - -config_gcc52_cpp11_dbg: &config_gcc52_cpp11_dbg - <<: *docker_gcc52 - <<: *env_gcc_cpp11_dbg -config_gcc52_cpp11_opt_speed: &config_gcc52_cpp11_opt_speed - <<: *docker_gcc52 - <<: *env_gcc_cpp11_opt_speed - -config_gcc53_cpp11_dbg: &config_gcc53_cpp11_dbg - <<: *docker_gcc53 - <<: *env_gcc_cpp11_dbg -config_gcc53_cpp11_opt_speed: &config_gcc53_cpp11_opt_speed - <<: *docker_gcc53 - <<: *env_gcc_cpp11_opt_speed - -config_gcc54_cpp11_dbg: &config_gcc54_cpp11_dbg - <<: *docker_gcc54 - <<: *env_gcc_cpp11_dbg -config_gcc54_cpp11_opt_speed: &config_gcc54_cpp11_opt_speed - <<: *docker_gcc54 - <<: *env_gcc_cpp11_opt_speed - -config_gcc55_cpp11_dbg: &config_gcc55_cpp11_dbg - <<: *docker_gcc55 - <<: *env_gcc_cpp11_dbg -config_gcc55_cpp11_opt_speed: &config_gcc55_cpp11_opt_speed - <<: *docker_gcc55 - <<: *env_gcc_cpp11_opt_speed - -config_gcc61_cpp11_dbg: &config_gcc61_cpp11_dbg - <<: *docker_gcc61 - <<: *env_gcc_cpp11_dbg -config_gcc61_cpp11_opt_speed: &config_gcc61_cpp11_opt_speed - <<: *docker_gcc61 - <<: *env_gcc_cpp11_opt_speed - -config_gcc62_cpp11_dbg: &config_gcc62_cpp11_dbg - <<: *docker_gcc62 - <<: *env_gcc_cpp11_dbg -config_gcc62_cpp11_opt_speed: &config_gcc62_cpp11_opt_speed - <<: *docker_gcc62 - <<: *env_gcc_cpp11_opt_speed - -config_gcc63_cpp11_dbg: &config_gcc63_cpp11_dbg - <<: *docker_gcc63 - <<: *env_gcc_cpp11_dbg -config_gcc63_cpp11_opt_speed: &config_gcc63_cpp11_opt_speed - <<: *docker_gcc63 - <<: *env_gcc_cpp11_opt_speed - -config_gcc64_cpp11_dbg: &config_gcc64_cpp11_dbg - <<: *docker_gcc64 - <<: *env_gcc_cpp11_dbg -config_gcc64_cpp11_opt_speed: &config_gcc64_cpp11_opt_speed - <<: *docker_gcc64 - <<: *env_gcc_cpp11_opt_speed - -config_gcc71_cpp11_dbg: &config_gcc71_cpp11_dbg - <<: *docker_gcc71 - <<: *env_gcc_cpp11_dbg -config_gcc71_cpp11_opt_speed: &config_gcc71_cpp11_opt_speed - <<: *docker_gcc71 - <<: *env_gcc_cpp11_opt_speed - -config_gcc72_cpp11_dbg: &config_gcc72_cpp11_dbg - <<: *docker_gcc72 - <<: *env_gcc_cpp11_dbg -config_gcc72_cpp11_opt_speed: &config_gcc72_cpp11_opt_speed - <<: *docker_gcc72 - <<: *env_gcc_cpp11_opt_speed - -config_gcc73_cpp11_dbg: &config_gcc73_cpp11_dbg - <<: *docker_gcc73 - <<: *env_gcc_cpp11_dbg -config_gcc73_cpp11_opt_speed: &config_gcc73_cpp11_opt_speed - <<: *docker_gcc73 - <<: *env_gcc_cpp11_opt_speed - -config_gcc82_cpp11_dbg: &config_gcc82_cpp11_dbg - <<: *docker_gcc82 - <<: *env_gcc_cpp11_dbg -config_gcc82_cpp11_opt_speed: &config_gcc82_cpp11_opt_speed - <<: *docker_gcc82 - <<: *env_gcc_cpp11_opt_speed - -config_gcc83_cpp11_dbg: &config_gcc83_cpp11_dbg - <<: *docker_gcc83 - <<: *env_gcc_cpp11_dbg -config_gcc83_cpp11_opt_speed: &config_gcc83_cpp11_opt_speed - <<: *docker_gcc83 - <<: *env_gcc_cpp11_opt_speed - -config_clang39_cpp11_dbg: &config_clang39_cpp11_dbg - <<: *docker_clang39 - <<: *env_clang_cpp11_dbg -config_clang39_cpp11_opt_speed: &config_clang39_cpp11_opt_speed - <<: *docker_clang39 - <<: *env_clang_cpp11_opt_speed - -config_clang40_cpp11_dbg: &config_clang40_cpp11_dbg - <<: *docker_clang40 - <<: *env_clang_cpp11_dbg -config_clang40_cpp11_opt_speed: &config_clang40_cpp11_opt_speed - <<: *docker_clang40 - <<: *env_clang_cpp11_opt_speed - -config_clang50_cpp11_dbg: &config_clang50_cpp11_dbg - <<: *docker_clang50 - <<: *env_clang_cpp11_dbg -config_clang50_cpp11_opt_speed: &config_clang50_cpp11_opt_speed - <<: *docker_clang50 - <<: *env_clang_cpp11_opt_speed - -############################################################################## -# Workspace persistance -############################################################################## -attach_workspace: &attach_workspace - attach_workspace: - at: ~/project - -############################################################################## -# Build steps (in alphabetical order by name of steps group) -############################################################################## -run_compiler_version: &run_compiler_version - run: - name: Checking compiler version - command: | - echo TOOLSET=$TOOLSET - echo VARIANT=$VARIANT - echo OPTIMIZ=$OPTIMIZ - echo CXXSTD=$CXXSTD - echo LNKFLAG=$LNKFLAG - $TOOLSET --version - -steps_bootstrap: &steps_bootstrap - steps: - - *attach_workspace - - *run_compiler_version - - run: cd boost-root && ./bootstrap.sh -with-toolset=$TOOLSET || cat ./bootstrap.log - - run: cd boost-root && ./b2 -j2 headers > /dev/null - - persist_to_workspace: - root: ~/project - paths: boost-root - -steps_test_core: &steps_test_core - steps: - - *attach_workspace - - *run_compiler_version - - run: cd boost-root && ./b2 -j2 toolset=$TOOLSET $VARIANT $OPTIMIZ cxxstd="$CXXSTD" libs/gil/test/core - -steps_test_numeric: &steps_test_numeric - steps: - - *attach_workspace - - *run_compiler_version - - run: cd boost-root && ./b2 -j2 toolset=$TOOLSET $VARIANT $OPTIMIZ cxxstd="$CXXSTD" libs/gil/test/extension/numeric - -steps_test_toolbox: &steps_test_toolbox - steps: - - *attach_workspace - - *run_compiler_version - - run: cd boost-root && ./b2 -j2 toolset=$TOOLSET $VARIANT $OPTIMIZ cxxstd="$CXXSTD" libs/gil/test/extension/toolbox - -steps_test_io: &steps_test_io - steps: - - *attach_workspace - - *run_compiler_version - - run: cd boost-root && ./b2 -j2 toolset=$TOOLSET $VARIANT $OPTIMIZ cxxstd="$CXXSTD" libs/gil/test/extension/io//simple - -############################################################################## -# Build jobs -############################################################################## -jobs: - bootstrap_checkout: - working_directory: ~/project/gil - # Build `b2` using the ancient compiler to hopefully avoid run-time failures: - # ./b2: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found (required by ./b2) - <<: *config_gcc48_cpp11_opt_speed - steps: - - checkout - - run: - # CONTRIBUTING.md: Modular Boost Library Maintenance guide, for more realistic - # test environment, recommends to develop and test individual Boost library - # against other Boost libraries as defined by the Boost super-project master branch - name: Checkout Boost superproject - command: | - echo $PWD && ls -a $PWD - cd .. - echo $PWD && ls -a $PWD - ~/project/gil/.ci/get-boost.sh $CIRCLE_BRANCH ~/project/gil - cd boost-root - - persist_to_workspace: - root: ~/project - paths: boost-root - - bootstrap_gcc: - <<: *config_gcc48_cpp11_opt_speed - <<: *steps_bootstrap - - bootstrap_clang: - <<: *config_clang39_cpp11_opt_speed - <<: *steps_bootstrap - - ### GCC 4.x ################################################################ - gcc49_11_core: - <<: *config_gcc49_cpp11_dbg - <<: *steps_test_core - - gcc49_11_core_speed: - <<: *config_gcc49_cpp11_opt_speed - <<: *steps_test_core - - ### GCC 5.x ################################################################ - gcc51_11_core: - <<: *config_gcc51_cpp11_dbg - <<: *steps_test_core - - gcc51_11_core_speed: - <<: *config_gcc51_cpp11_opt_speed - <<: *steps_test_core - - gcc52_11_core: - <<: *config_gcc52_cpp11_dbg - <<: *steps_test_core - - gcc52_11_core_speed: - <<: *config_gcc52_cpp11_opt_speed - <<: *steps_test_core - - gcc53_11_core: - <<: *config_gcc53_cpp11_dbg - <<: *steps_test_core - - gcc53_11_core_speed: - <<: *config_gcc53_cpp11_opt_speed - <<: *steps_test_core - - gcc54_11_core: - <<: *config_gcc54_cpp11_dbg - <<: *steps_test_core - - gcc54_11_core_speed: - <<: *config_gcc54_cpp11_opt_speed - <<: *steps_test_core - - gcc55_11_core: - <<: *config_gcc55_cpp11_dbg - <<: *steps_test_core - - gcc55_11_core_speed: - <<: *config_gcc55_cpp11_opt_speed - <<: *steps_test_core - - ### GCC 6.x ################################################################ - gcc61_11_core: - <<: *config_gcc61_cpp11_dbg - <<: *steps_test_core - - gcc61_11_core_speed: - <<: *config_gcc61_cpp11_opt_speed - <<: *steps_test_core - - gcc62_11_core: - <<: *config_gcc62_cpp11_dbg - <<: *steps_test_core - - gcc62_11_core_speed: - <<: *config_gcc62_cpp11_opt_speed - <<: *steps_test_core - - gcc63_11_core: - <<: *config_gcc63_cpp11_dbg - <<: *steps_test_core - - gcc63_11_core_speed: - <<: *config_gcc63_cpp11_opt_speed - <<: *steps_test_core - - gcc64_11_core: - <<: *config_gcc64_cpp11_dbg - <<: *steps_test_core - - gcc64_11_core_speed: - <<: *config_gcc64_cpp11_opt_speed - <<: *steps_test_core - - ### GCC 7.x ################################################################ - gcc71_11_core: - <<: *config_gcc71_cpp11_dbg - <<: *steps_test_core - - gcc71_11_core_speed: - <<: *config_gcc71_cpp11_opt_speed - <<: *steps_test_core - - gcc72_11_core: - <<: *config_gcc72_cpp11_dbg - <<: *steps_test_core - - gcc72_11_core_speed: - <<: *config_gcc72_cpp11_opt_speed - <<: *steps_test_core - - gcc73_11_core: - <<: *config_gcc73_cpp11_dbg - <<: *steps_test_core - - gcc73_11_core_speed: - <<: *config_gcc73_cpp11_opt_speed - <<: *steps_test_core - - gcc82_11_core: - <<: *config_gcc82_cpp11_dbg - <<: *steps_test_core - - gcc82_11_core_speed: - <<: *config_gcc82_cpp11_opt_speed - <<: *steps_test_core - - gcc83_11_core: - <<: *config_gcc83_cpp11_dbg - <<: *steps_test_core - - gcc83_11_core_speed: - <<: *config_gcc83_cpp11_opt_speed - <<: *steps_test_core - - ### clang 3.9 ################################################################ - clang39_11_core: - <<: *config_clang39_cpp11_dbg - <<: *steps_test_core - - clang39_11_core_speed: - <<: *config_clang39_cpp11_opt_speed - <<: *steps_test_core - - ### clang 4.0 ################################################################ - clang40_11_core: - <<: *config_clang40_cpp11_dbg - <<: *steps_test_core - - clang40_11_core_speed: - <<: *config_clang40_cpp11_opt_speed - <<: *steps_test_core - - ### clang 5.0 ################################################################ - clang50_11_core: - <<: *config_clang50_cpp11_dbg - <<: *steps_test_core - - clang50_11_core_speed: - <<: *config_clang50_cpp11_opt_speed - <<: *steps_test_core - -############################################################################## -# Workflows -############################################################################## -workflows: - version: 2 - build-and-test: - jobs: - - bootstrap_checkout: - filters: { branches: { only: [ develop, master ] } } - - bootstrap_gcc: - requires: [ bootstrap_checkout ] - filters: { branches: { only: [ develop, master ] } } - - bootstrap_clang: - requires: [ bootstrap_checkout ] - filters: { branches: { only: [ develop, master ] } } - - ### GCC 4.x ############################################################ - - gcc49_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc49_11_core_speed: - requires: [ gcc49_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### GCC 5.x ############################################################ - - gcc51_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc51_11_core_speed: - requires: [ gcc51_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc52_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc52_11_core_speed: - requires: [ gcc52_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc53_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc53_11_core_speed: - requires: [ gcc53_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc54_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc54_11_core_speed: - requires: [ gcc54_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc55_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc55_11_core_speed: - requires: [ gcc55_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### GCC 6.x ############################################################ - - gcc61_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc61_11_core_speed: - requires: [ gcc61_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc62_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc62_11_core_speed: - requires: [ gcc62_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc63_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc63_11_core_speed: - requires: [ gcc63_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc64_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc64_11_core_speed: - requires: [ gcc64_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### GCC 7.x ############################################################ - - gcc71_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc71_11_core_speed: - requires: [ gcc71_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc72_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc72_11_core_speed: - requires: [ gcc72_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc73_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc73_11_core_speed: - requires: [ gcc73_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### GCC 8.x ############################################################ - - gcc82_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc82_11_core_speed: - requires: [ gcc82_11_core ] - filters: { branches: { only: [ develop, master ] } } - - - gcc83_11_core: - requires: [ bootstrap_gcc ] - filters: { branches: { only: [ develop, master ] } } - - gcc83_11_core_speed: - requires: [ gcc83_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### clang 3.9 ########################################################## - - clang39_11_core: - requires: [ bootstrap_clang ] - filters: { branches: { only: [ develop, master ] } } - - clang39_11_core_speed: - requires: [ clang39_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### clang 4.0 ########################################################## - - clang40_11_core: - requires: [ bootstrap_clang ] - filters: { branches: { only: [ develop, master ] } } - - clang40_11_core_speed: - requires: [ clang40_11_core ] - filters: { branches: { only: [ develop, master ] } } - - ### clang 5.0 ########################################################## - - clang50_11_core: - requires: [ bootstrap_clang ] - filters: { branches: { only: [ develop, master ] } } - - clang50_11_core_speed: - requires: [ clang50_11_core ] - filters: { branches: { only: [ develop, master ] } } - -############################################################################## -# Notifications -############################################################################## -# Circle CI 1.0 notifications seem to work for Circle CI 2.0 (not 2.1!) -# https://github.com/circleci/circleci-docs/issues/2411 -# https://discuss.circleci.com/t/circleci-2-0-notifications-webhooks/19075/9 -# -# TODO: Disabled until CircleCI supports single notification per whole workflow. -# Currently, it floods the webhook with a notification per job. -#notify: -# webhooks: -# - url: https://webhooks.gitter.im/e/9538066016dc0f9b6511 diff --git a/.github/actions/docs-prerequisites/action.yml b/.github/actions/docs-prerequisites/action.yml new file mode 100644 index 0000000000..bc5a6a426c --- /dev/null +++ b/.github/actions/docs-prerequisites/action.yml @@ -0,0 +1,7 @@ +name: 'Get Docs Prerequisites' +description: 'Downloads all the necessary packages for building documentation' +runs: + using: composite + steps: + - run: sudo apt-get install doxygen python3 python3-pip python3-setuptools python3-sphinx + shell: bash diff --git a/.github/actions/generate-doc/action.yml b/.github/actions/generate-doc/action.yml new file mode 100644 index 0000000000..3ab8995f32 --- /dev/null +++ b/.github/actions/generate-doc/action.yml @@ -0,0 +1,13 @@ +name: 'Generate Doc' +description: 'Runs b2 on lib/gil/doc to generate documentation' +runs: + using: composite + steps: + - run: | + echo "using doxygen ;" > ~/user-config.jam + cd ../boost-root/libs + ../b2 gil/doc + cd gil + chmod +x $GITHUB_WORKSPACE/.github/actions/generate-doc/docs-config.sh + $GITHUB_WORKSPACE/.github/actions/generate-doc/docs-config.sh + shell: bash diff --git a/.github/actions/generate-doc/docs-config.sh b/.github/actions/generate-doc/docs-config.sh new file mode 100644 index 0000000000..695d6d0eb0 --- /dev/null +++ b/.github/actions/generate-doc/docs-config.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e # Exit with Non Zero exit Code + +mkdir temp-doc +cd temp-doc + +git init + + +git remote add upstream "https://github.com/boostorg/gil.git" + +git fetch upstream +git switch gh-pages + + +if [ "${GITHUB_REF##*/}" = develop ]; then + # Only updates develop directory and keeps others intact + rm -r develop + mkdir -p develop/doc + cp ../index.html develop/ + cp ../doc/index.html develop/doc + cp -a ../doc/html develop/doc/ +else + # main branch + rm index.html + rm -r html + cp ../doc/index.html . + cp -r ../doc/html . +fi + +# Remove version control +rm -rf .git \ No newline at end of file diff --git a/.github/actions/setup-boost/action.yml b/.github/actions/setup-boost/action.yml new file mode 100644 index 0000000000..89ba925f37 --- /dev/null +++ b/.github/actions/setup-boost/action.yml @@ -0,0 +1,17 @@ +name: 'Setup-Boost' +description: 'Downloads and sets up the necessary dependencies of Boost' +runs: + using: composite + steps: + - run: | + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true + cd .. + git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + cp -r $GITHUB_WORKSPACE/* libs/gil + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" gil + ./bootstrap.sh + ./b2 -d0 headers + shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..0f8d379dc8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,173 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + - develop + - feature/** + +env: + LIBRARY: gil + UBSAN_OPTIONS: print_stacktrace=1 + +jobs: + posix: + strategy: + fail-fast: false + matrix: + include: + - toolset: gcc-6 + cxxstd: "14,1z" + os: ubuntu-18.04 + install: g++-6 + - toolset: gcc-7 + cxxstd: "14,17" + os: ubuntu-18.04 + - toolset: gcc + compiler: g++-8 + cxxstd: "14" + os: ubuntu-20.04 + install: g++-8 + - toolset: gcc-9 + cxxstd: "14,17" + os: ubuntu-20.04 + install: g++-9 + - toolset: gcc-10 + cxxstd: "14,17" + os: ubuntu-20.04 + install: g++-10 + - toolset: clang + compiler: clang++-3.9 + cxxstd: "14" + os: ubuntu-18.04 + install: clang-3.9 + - toolset: clang + compiler: clang++-4.0 + cxxstd: "14" + os: ubuntu-18.04 + install: clang-4.0 + - toolset: clang + compiler: clang++-5.0 + cxxstd: "14,1z" + os: ubuntu-18.04 + install: clang-5.0 + - toolset: clang + compiler: clang++-6.0 + cxxstd: "14,17" + os: ubuntu-20.04 + install: clang-6.0 + - toolset: clang + compiler: clang++-7 + cxxstd: "14,17" + os: ubuntu-20.04 + install: clang-7 + - toolset: clang + compiler: clang++-8 + cxxstd: "14,17" + os: ubuntu-20.04 + install: clang-8 + - toolset: clang + compiler: clang++-9 + cxxstd: "14,17" + os: ubuntu-20.04 + install: clang-9 + - toolset: clang + compiler: clang++-10 + cxxstd: "14,17" + os: ubuntu-20.04 + - toolset: clang + cxxstd: "14,17" + os: macos-11 + + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v3 + + - name: Install packages + if: matrix.install + run: sudo apt install ${{matrix.install}} + + - name: Setup Boost + run: | + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true + cd .. + git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY + ./bootstrap.sh + ./b2 -d0 headers + + - name: Create user-config.jam + if: matrix.compiler + run: | + echo "using ${{matrix.toolset}} : : ${{matrix.compiler}} ;" > ~/user-config.jam + cat ~/user-config.jam + + - name: Run tests + if: "!matrix.define" + run: | + cd ../boost-root + ./b2 -j3 libs/$LIBRARY/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} variant=debug,release + + - name: Run tests + if: matrix.define + run: | + cd ../boost-root + ./b2 -j3 libs/$LIBRARY/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} define=${{matrix.define}} variant=debug,release + + windows: + strategy: + fail-fast: false + matrix: + include: + - toolset: msvc-14.2 + cxxstd: "14,17" + addrmd: 32,64 + os: windows-2019 + - toolset: gcc + cxxstd: "14,17" + addrmd: 64 + os: windows-2019 + - toolset: msvc-14.3 + cxxstd: "14,17" + addrmd: 32,64 + os: windows-2022 + + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v3 + + - name: Setup Boost + shell: cmd + run: | + if "%GITHUB_BASE_REF%" == "" set GITHUB_BASE_REF=%GITHUB_REF% + set BOOST_BRANCH=develop + if "%GITHUB_BASE_REF%" == "master" set BOOST_BRANCH=master + cd .. + git clone -b %BOOST_BRANCH% --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\ + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% + cmd /c bootstrap + b2 -d0 headers + + - name: Run tests + if: startsWith(matrix.toolset, 'msvc') + shell: cmd + run: | + cd ../boost-root + b2 -j3 --abbreviate-paths libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release + - name: Run tests + if: startsWith(matrix.toolset, 'gcc') + shell: cmd + run: | + cd ../boost-root + b2 -j3 --abbreviate-paths libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} cxxflags=-mbig-obj variant=debug,release diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000000..a21a27549c --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,54 @@ +name: Code Coverage + +on: + pull_request: + push: + branches: + - master + - develop + +env: + LIBRARY: gil + UBSAN_OPTIONS: print_stacktrace=1 + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: install packages + run: | + sudo apt update + sudo apt install g++ libpng-dev libjpeg-dev libtiff5-dev libraw-dev lcov python -y + + - name: Setup Boost + run: | + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true + cd .. + git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY + ./bootstrap.sh + ./b2 -d0 headers + + - name: Create user-config.jam + run: | + echo "using gcc : : g++ ;" > ~/user-config.jam + + - name: Run tests + run: | + cd ../boost-root + ./b2 -j3 libs/$LIBRARY/test coverage=on toolset=gcc cxxstd=14 variant=debug + lcov --directory bin.v2 --capture --no-external --directory $(pwd) --output-file coverage.info > /dev/null 2>&1 + lcov --extract coverage.info $(pwd)'/boost/gil/*' --output-file coverage.info > /dev/null + lcov --list coverage.info + + - name: Upload to Codecov + uses: codecov/codecov-action@v1.2.1 + with: + files: ../boost-root/coverage.info diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000000..1308258f91 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,22 @@ +name: Docs-Build +on: + push: + branches: + - master + - develop + +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/docs-prerequisites + - uses: ./.github/actions/setup-boost + - uses: ./.github/actions/generate-doc + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ../boost-root/libs/gil/temp-doc + user_name: 'github-actions[bot]' + user_email: 'github-actions[bot]@users.noreply.github.com' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3e46e5a11b..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,267 +0,0 @@ -# Copyright 2016 Peter Dimov -# 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) -dist: xenial - -language: cpp - -sudo: false - -os: - - linux - - osx - -env: - matrix: - - BOGUS_JOB=true - -matrix: - fast_finish: true - exclude: - - env: BOGUS_JOB=true - include: - - os: linux - env: COMPILER=g++-5 VARIANT=debug TOOLSET=gcc CXXSTD=11 TEST_HEADERS=1 - addons: - apt: - packages: - - g++-5 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-5 VARIANT=release TOOLSET=gcc CXXSTD=14 - addons: - apt: - packages: - - g++-5 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-6 VARIANT=debug TOOLSET=gcc CXXSTD=14 - addons: - apt: - packages: - - g++-6 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-6 VARIANT=release TOOLSET=gcc CXXSTD=11 - addons: - apt: - packages: - - g++-6 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-7 VARIANT=debug TOOLSET=gcc CXXSTD=17 - addons: - apt: - packages: - - g++-7 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-7 VARIANT=release TOOLSET=gcc CXXSTD=17 - addons: - apt: - packages: - - g++-7 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-9 VARIANT=debug TOOLSET=gcc CXXSTD=2a - addons: - apt: - packages: - - g++-9 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=g++-9 VARIANT=release TOOLSET=gcc CXXSTD=2a - addons: - apt: - packages: - - g++-9 - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=clang++-3.9 VARIANT=debug TOOLSET=clang CXXSTD=11 - addons: - apt: - packages: - - clang-3.9 - - libstdc++-6-dev - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.9 - - - os: linux - env: COMPILER=clang++-3.9 VARIANT=release TOOLSET=clang CXXSTD=11 - addons: - apt: - packages: - - clang-3.9 - - libstdc++-6-dev - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.9 - - - os: linux - env: COMPILER=clang++-6.0 VARIANT=gil_ubsan_integer TOOLSET=clang CXXSTD=11 B2_OPTIONS="visibility=global" UBSAN_OPTIONS='print_stacktrace=1' - addons: - apt: - packages: - - clang-6.0 - - libstdc++-7-dev - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=clang++-6.0 VARIANT=gil_ubsan_nullability TOOLSET=clang CXXSTD=11 B2_OPTIONS="visibility=global" UBSAN_OPTIONS='print_stacktrace=1' - addons: - apt: - packages: - - clang-6.0 - - libstdc++-7-dev - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: linux - env: COMPILER=clang++-6.0 VARIANT=gil_ubsan_undefined TOOLSET=clang CXXSTD=11 B2_OPTIONS="visibility=global" UBSAN_OPTIONS='print_stacktrace=1' - addons: - apt: - packages: - - clang-6.0 - - libstdc++-7-dev - - libpng-dev - - libjpeg-dev - - libtiff5-dev - - libraw-dev - sources: - - ubuntu-toolchain-r-test - - - os: osx - env: COMPILER=clang++ VARIANT=debug TOOLSET=clang CXXSTD=11 - - - os: osx - env: COMPILER=clang++ VARIANT=release TOOLSET=clang CXXSTD=11 - - - env: - - DOC=1 - - secure: "UHit2f6Hq2pkHvx8rfrQvFacYiQKVO3vrCbNuDi/VSAIzQjRnqCqE06y4vpXLMsXf62TvBeCBStIuI8g+HP8B+f39oGb/9Om+yIgN/yes47R4sLFKFbRiOS6sfCIefJp7Kx7GSFf81xWxStpIU4QaSsk8Dlt5xyurTWXFSde+lQ=" - addons: - apt: - packages: - - doxygen - - python3 - - python3-pip - - python3-setuptools - -install: - - |- - if [ "$DOC" ]; then - pip3 --version - pip3 install --upgrade pip - pip3 install --user sphinx>=2.1 - sphinx-build --version - fi - - cd .. - - $TRAVIS_BUILD_DIR/.ci/get-boost.sh $TRAVIS_BRANCH $TRAVIS_BUILD_DIR - - cd boost-root - - export BOOST_ROOT=$(pwd) - - ./bootstrap.sh - -script: - - |- - if [ "$DOC" ]; then - echo "using doxygen ;" > ~/user-config.jam - ./b2 libs/gil/doc - else - echo "using $TOOLSET : : $COMPILER : ;" > ~/user-config.jam - ./b2 -j 2 $B2_OPTIONS toolset=$TOOLSET variant=$VARIANT cxxstd=$CXXSTD libs/gil/test/core - ./b2 -j 2 $B2_OPTIONS toolset=$TOOLSET variant=$VARIANT cxxstd=$CXXSTD libs/gil/test/legacy - ./b2 -j 2 $B2_OPTIONS toolset=$TOOLSET variant=$VARIANT cxxstd=$CXXSTD libs/gil/test/extension/dynamic_image - ./b2 -j 2 $B2_OPTIONS toolset=$TOOLSET variant=$VARIANT cxxstd=$CXXSTD libs/gil/test/extension/numeric - ./b2 -j 2 $B2_OPTIONS toolset=$TOOLSET variant=$VARIANT cxxstd=$CXXSTD libs/gil/test/extension/toolbox - ./b2 -j 2 $B2_OPTIONS toolset=$TOOLSET variant=$VARIANT cxxstd=$CXXSTD libs/gil/test/extension/io//simple - fi - -after_success: -# Upload docs only when building upstream. - - |- - if [ "$DOC" -a \ - "$TRAVIS_REPO_SLUG" = "boostorg/gil" -a \ - "$TRAVIS_PULL_REQUEST" = "false" ]; then - export GH_TOKEN - cd libs/gil - .ci/upload_docs.sh - fi - -notifications: - email: - on_success: always - webhooks: - urls: - - https://webhooks.gitter.im/e/f59b626f2ed08f3d30ab - # options: [always|never|change] - on_success: change # default: always - on_failure: always # default: always - on_start: change # default: never - on_cancel: always # default: always - on_error: always # default: always diff --git a/CMakeLists.txt b/CMakeLists.txt index 52327b933a..6c24809546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,36 +1,74 @@ # -# Copyright (c) 2017-2019 Mateusz Loskot +# Copyright (c) 2017-2022 Mateusz Loskot +# Copyright (c) 2020-2021 Peter Dimov # # 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) # + +if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + +# Generated by `boostdep --cmake gil` + +cmake_minimum_required(VERSION 3.8...3.23) + +project(boost_gil VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) + +add_library(boost_gil INTERFACE) +add_library(Boost::gil ALIAS boost_gil) + +target_include_directories(boost_gil INTERFACE include) + +target_link_libraries(boost_gil + INTERFACE + Boost::assert + Boost::concept_check + Boost::config + Boost::container_hash + Boost::core + Boost::filesystem + Boost::integer + Boost::iterator + Boost::mp11 + Boost::preprocessor + Boost::type_traits + Boost::variant2 +) + +target_compile_features(boost_gil INTERFACE cxx_std_14) + +else() + # **WARNING:** # The CMake configuration is only provided for convenience # of contributors. It does not export or install any targets, # deploy config files or support subproject workflow. -# + cmake_minimum_required(VERSION 3.10) #----------------------------------------------------------------------------- # Options #----------------------------------------------------------------------------- +option(BOOST_GIL_BUILD_BENCHMARKS "Builds all available benchmarks in the benchmark directory" OFF) option(BOOST_GIL_BUILD_EXAMPLES "Build examples" ON) option(BOOST_GIL_BUILD_HEADER_TESTS "Enable self-contained header tests" ON) option(BOOST_GIL_ENABLE_EXT_DYNAMIC_IMAGE "Enable Dynamic Image extension, tests and examples" ON) option(BOOST_GIL_ENABLE_EXT_IO "Enable IO extension, tests and examples (require libjpeg, libpng, libtiff)" ON) option(BOOST_GIL_ENABLE_EXT_NUMERIC "Enable Numeric extension, tests and examples" ON) option(BOOST_GIL_ENABLE_EXT_TOOLBOX "Enable Toolbox extension, tests and examples" ON) +option(BOOST_GIL_ENABLE_EXT_RASTERIZATION "Enable Rasterization extension and tests" ON) +option(BOOST_GIL_ENABLE_EXT_IMAGE_PROCESSING "Enable Image Processing extension (!) and tests" 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)") +set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard version to use (default is 14)") #----------------------------------------------------------------------------- # Project #----------------------------------------------------------------------------- project(Boost.GIL LANGUAGES CXX - DESCRIPTION "Boost.GIL - Generic Image Library | Requires C++11 since Boost 1.68") + DESCRIPTION "Boost.GIL - Generic Image Library | Requires C++14 since Boost 1.80") list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_BINARY_DIR}/cmake) @@ -41,7 +79,7 @@ message(STATUS "Boost.GIL: Require C++${CMAKE_CXX_STANDARD}") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -# Avoid warnings flood on Travis CI, AppVeyor, CircleCI, Azure Pipelines +# Avoid warnings flood from CI builds if(DEFINED ENV{CI} OR DEFINED ENV{AGENT_JOBSTATUS} OR DEFINED ENV{GITHUB_ACTIONS}) set(BOOST_GIL_BUILD_CI ON) message(STATUS "Boost.GIL: Turning off detailed compiler warnings for CI build short log") @@ -103,7 +141,7 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set(Boost_USE_STATIC_RUNTIME OFF) endif() -find_package(Boost 1.72.0 REQUIRED COMPONENTS filesystem) +find_package(Boost 1.80.0 REQUIRED COMPONENTS filesystem) message(STATUS "Boost.GIL: Using Boost_INCLUDE_DIRS=${Boost_INCLUDE_DIRS}") message(STATUS "Boost.GIL: Using Boost_LIBRARY_DIRS=${Boost_LIBRARY_DIRS}") @@ -124,7 +162,7 @@ if(BOOST_GIL_USE_CONAN) # Download automatically, you can also just copy the conan.cmake file if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") message(STATUS "Boost.GIL: Downloading conan.cmake from https://github.com/conan-io/cmake-conan") - file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.15/conan.cmake" + file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/0.18.1/conan.cmake" "${CMAKE_BINARY_DIR}/conan.cmake") endif() @@ -250,3 +288,12 @@ endif() if(BOOST_GIL_BUILD_EXAMPLES AND BOOST_GIL_ENABLE_EXT_IO) add_subdirectory(example) endif() + +#----------------------------------------------------------------------------- +# Benchmarks +#----------------------------------------------------------------------------- +if(BOOST_GIL_BUILD_BENCHMARKS) + add_subdirectory(benchmark) +endif() + +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 248d4471a1..78efc6134a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,14 +17,14 @@ please follow the workflow explained in this document. - [5. Update your pull request](#5-update-your-pull-request) - [Development](#development) - [Install dependencies](#install-dependencies) - - [Using Boost.Build](#using-boostbuild) + - [Using B2](#using-b2) - [Using CMake](#using-cmake) - [Running clang-tidy](#running-clang-tidy) - [Guidelines](#guidelines) ## Prerequisites -- C++11 compiler +- C++14 compiler - Build and run-time dependencies for tests and examples: - Boost.Filesystem - Boost.Test @@ -35,10 +35,12 @@ please follow the workflow explained in this document. it may be a good idea to skim through the [Boost Getting Started](https://www.boost.org/more/getting_started/index.html) chapters, especially if you are going to use - [Boost.Build](https://boostorg.github.io/build/) for the first time. + [B2](https://www.bfgroup.xyz/b2/) for the first time. ## Pull Requests + +- **DO** all your work in fork this repository. - **DO** base your work against the `develop` branch, not the `master`. - **DO** submit all major changes to code via pull requests (PRs) rather than through a direct commit. PRs will be CI-checked first, then reviewed and potentially merged @@ -61,12 +63,13 @@ please follow the workflow explained in this document. - **DO** ensure each commit successfully builds. The entire PR must pass all tests in the Continuous Integration (CI) system before it'll be merged. - **DO** ensure any new features or changes to existing behaviours are covered with test cases. -- **DO** address PR feedback in an additional commit(s) rather than amending the existing - commits, and only rebase/squash them when necessary. This makes it easier for reviewers - to track changes. +- **DO** address PR feedback in an additional commit(s) rather than amending the existing commits. + This makes it easier for reviewers to track changes. +- **DO** sync your PR branch with the upstream `develop` branch frequently resolving any conflicts if necessary. + You can either `git merge upstream/develop` or `git rebase upstream/develop` with `git push --force` for the latter. + The merge may make it easier for reviewers to track changes though. - **DO** assume that the [Squash and Merge] will be used to merge your commit unless you request otherwise in the PR. -- **DO** NOT fix merge conflicts using a merge commit. Prefer git rebase. - **DO** NOT submit changes to the original legacy tests, see [test/legacy/README.md](test/legacy/README.md). @@ -123,7 +126,7 @@ The preparation involves the following steps: git submodule update --init --recursive --jobs 8 ``` -3. Build the `b2` driver program for Boost.Build engine. +3. Build the `b2` driver program for B2 engine. ```shell ./bootstrap.sh @@ -233,10 +236,10 @@ git push feature/foo Finally, sign in to your GitHub account and [create a pull request](https://help.github.com/articles/creating-a-pull-request/). -Your pull request will be automatically built and tests will run on Travis CI -and AppVeyor (see [README](README.md) for builds status). Please, keep an eye -on those CI builds and correct any problems detected in your contribution -by updating your pull request. +Your pull request will be automatically built and tests will run on GitHub ACtions, +Azure Pipelines, AppVeyor and Circle CI (see [README](README.md) for builds status). +Please, keep an eye on those CI builds and correct any problems detected in your +contribution by updating your pull request. ### 5. Update your pull request @@ -300,7 +303,7 @@ request from reviewer, just add new commits: cd libs/gil git checkout feature/foo git add -A -git commit -m "Fix build Travis CI failures" +git commit -m "Fix CI build failure" git push feature/foo ``` @@ -310,15 +313,15 @@ Boost.GIL is a [header-only library](https://en.wikipedia.org/wiki/Header-only) which does not require sources compilation. Only test runners and [example](example/README.md) programs have to be compiled. -By default, Boost.GIL uses Boost.Build to build all the executables. +By default, Boost.GIL uses B2 to build all the executables. We also provide configuration for two alternative build systems: - [CMake](https://cmake.org) **NOTE:** The CMake is optional and the corresponding build configurations -for Boost.GIL do not offer equivalents for all Boost.Build features. -Most important difference to recognise is that Boost.Build will automatically +for Boost.GIL do not offer equivalents for all B2 features. +Most important difference to recognise is that B2 will automatically build any other Boost libraries required by Boost.GIL as dependencies. ### Install dependencies @@ -332,9 +335,9 @@ sudo apt-get install libjpeg-dev libpng-dev libtiff5-dev libraw-dev **TIP:** On Windows, use vcpkg with `user-config.jam` configuration provided in [example/b2/user-config-windows-vcpkg.jam](example/b2/). -### Using Boost.Build +### Using B2 -The [b2 invocation](https://boostorg.github.io/build/manual/develop/index.html#bbv2.overview.invocation) +The [b2 invocation](https://www.bfgroup.xyz/b2/manual/release/index.html#bbv2.overview.invocation) explains available options like `toolset`, `variant` and others. Simply, just execute `b2` to run all tests built using default @@ -345,35 +348,36 @@ development environment. as they are executed. It is useful to inspect compilation flags. If no target or directory is specified, everything in the current directory -is built. For example, all Boost.GIL tests can be built and run using: +is built. For example, **all** Boost.GIL tests can be built and run using: ```shell cd libs/gil -../../b2 +b2 ``` Run core tests only specifying location of directory with tests: ```shell cd libs/gil -../../b2 cxxstd=11 test/core +b2 cxxstd=14 test/core ``` Run all tests for selected extension (from Boost root directory, as alternative): ```shell -./b2 cxxstd=11 libs/gil/test/io -./b2 cxxstd=11 libs/gil/test/numeric -./b2 cxxstd=11 libs/gil/test/toolbox +b2 cxxstd=14 libs/gil/test/extension/dynamic_image +b2 cxxstd=14 libs/gil/test/extension/io +b2 cxxstd=14 libs/gil/test/extension/numeric +b2 cxxstd=14 libs/gil/test/extension/toolbox ``` Run I/O extension tests bundled in target called `simple`: ```shell -./b2 cxxstd=11 libs/gil/test/io//simple +b2 cxxstd=14 libs/gil/test/io//simple ``` -**TIP:** Pass `b2` feature `cxxstd=11,14,17,2a` to request compilation for +**TIP:** Pass `b2` feature `cxxstd=14,17,2a` to request compilation for multiple C++ standard versions in single build run. ### Using CMake @@ -401,17 +405,17 @@ The provided CMake configuration allows a couple of ways to develop Boost.GIL: For CMake, you only need to build Boost libraries required by Boost.Test library which is used to run GIL tests. Since the `CMAKE_CXX_STANDARD` option in the current -[CMakeLists.txt](CMakeLists.txt) defaults to C++11, pass the default `cxxstd=11` to `b2`: +[CMakeLists.txt](CMakeLists.txt) defaults to C++14, pass the default `cxxstd=14` to `b2`: ```shell ./b2 headers - ./b2 variant=debug,release cxxstd=11 --with-filesystem stage + ./b2 variant=debug,release cxxstd=14 --with-filesystem stage ``` or, depending on specific requirements, more complete build: ```shell - ./b2 variant=debug,release address-model=32,64 cxxstd=11 --layout=versioned --with-filesystem stage + ./b2 variant=debug,release address-model=32,64 cxxstd=14 --layout=versioned --with-filesystem stage ``` If you wish to build tests using different C++ standard version, then adjust the `cxxstd` accordingly. @@ -443,50 +447,52 @@ Here is an example of such lightweight workflow in Linux environment (Debian-bas - Configure build with CMake ```shell - mkdir _build - cd _build/ - cmake .. + cmake -S . -B _build -DBoost_ADDITIONAL_VERSIONS=1.80 -DBoost_COMPILER=-gcc11 -DBoost_ARCHITECTURE=-x64 ``` - **TIP:** By default, tests and [examples](example/README.md) are compiled using - the minimum required C++11. - Specify `-DCMAKE_CXX_STANDARD=14|17|20` to use newer version. + By default, tests and [examples](example/README.md) are compiled using + the minimum required C++14. + Specify `-DCMAKE_CXX_STANDARD=17|20` to use newer version. For more CMake options available for GIL, check `option`-s defined in the top-level `CMakeLists.txt`. - **TIP:** If CMake is failing to find Boost libraries, especially built - with `--layout=versioned`, you can try a few hacks: - - option `-DBoost_ARCHITECTURE=-x64` to help CMake find Boost 1.66 and above - add an architecture tag to the library file names in versioned build - The option added in CMake 3.13.0. + CMake is failing to find Boost libraries, especially built with `--layout=versioned`, + you can try a few hacks as displayed in the command above: - option `-DBoost_COMPILER=-gcc5` or `-DBoost_COMPILER=-vc141` to help CMake earlier than 3.13 match your compiler with toolset used in the Boost library file names (i.e. `libboost_filesystem-gcc5-mt-x64-1_69` and not `-gcc55-`). Fixed in CMake 3.13.0. + - option `-DBoost_ARCHITECTURE=-x64` to help CMake find Boost 1.66 and above + add an architecture tag to the library file names in versioned build + The option added in CMake 3.13.0. + - option `-DDBoost_ADDITIONAL_VERSIONS=` is especially usefull + when testing GIL with staged build of Boost from its `develop` branch. - if CMake is still failing to find Boost, you may try `-DBoost_DEBUG=ON` to get detailed diagnostics output from `FindBoost.cmake` module. - List available CMake targets ```shell - cmake --build . --target help + cmake --build _build --target help ``` - Build selected target with CMake ```shell - cmake --build . --target gil_test_pixel + cmake --build _build --target gil_test_pixel ``` - List available CTest targets ```shell + cd __build ctest --show-only | grep Test ``` - Run selected test with CTest ```shell + cd __build ctest -R gil.tests.core.pixel ``` @@ -563,10 +569,10 @@ Thus, below a few basic guidelines are listed. First and foremost, make sure you are familiar with the official [Boost Library Requirements and Guidelines](https://www.boost.org/development/requirements.html). -Second, strive for writing idiomatic C++11, clean and elegant code. +Second, strive for writing idiomatic C++14, clean and elegant code. **NOTE:** *The Boost.GIL source code does not necessary represent clean and elegant -code to look up to. The library has recently entered the transition to C++11. +code to look up to. The library has recently entered the transition to C++14. Major refactoring overhaul is ongoing.* Maintain structure your source code files according to the following guidelines: diff --git a/Jamfile b/Jamfile index 739260ce92..ee18a21f23 100644 --- a/Jamfile +++ b/Jamfile @@ -7,4 +7,5 @@ # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # please order by name to ease maintenance +build-project example ; build-project test ; diff --git a/README.md b/README.md index 055555f8aa..d47a5048d1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ +# WIP: Benchmark Prototype + ![Boost Generic Image Library (GIL)](https://raw.githubusercontent.com/boostorg/gil/develop/doc/_static/gil.png) -[![Language](https://img.shields.io/badge/C%2B%2B-11-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) +[![Language](https://img.shields.io/badge/C%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![License](https://img.shields.io/badge/license-BSL-blue.svg)](https://opensource.org/licenses/BSL-1.0) [![Documentation](https://img.shields.io/badge/gil-documentation-blue.svg)](http://boostorg.github.com/gil/) [![Wiki](https://img.shields.io/badge/gil-wiki-blue.svg)](https://github.com/boostorg/gil/wiki) @@ -10,10 +12,10 @@ [![Conan](https://img.shields.io/badge/on-conan-blue.svg)](https://bintray.com/bincrafters/public-conan/boost_gil%3Abincrafters) [![Vcpkg](https://img.shields.io/badge/on-vcpkg-blue.svg)](https://github.com/Microsoft/vcpkg/tree/master/ports/boost-gil) -Documentation | AppVeyor | Azure Pipelines | Travis CI | CircleCI | Regression ---------------|-----------------|-----------------|-----------------|-----------------|------------ -[![develop](https://img.shields.io/badge/doc-develop-blue.svg)](https://boostorg.github.io/gil/develop/) | [![AppVeyor](https://ci.appveyor.com/api/projects/status/w4k19d8io2af168h/branch/develop?svg=true)](https://ci.appveyor.com/project/stefanseefeld/gil/branch/develop) | [![Azure](https://dev.azure.com/boostorg/gil/_apis/build/status/boostorg.gil?branchName=develop)](https://dev.azure.com/boostorg/gil/_build/latest?definitionId=7&branchName=develop) | [![Travis](https://travis-ci.org/boostorg/gil.svg?branch=develop)](https://travis-ci.org/boostorg/gil) | [![CircleCI](https://circleci.com/gh/boostorg/gil/tree/develop.svg?style=shield)](https://circleci.com/gh/boostorg/workflows/gil/tree/develop) | [![gil](https://img.shields.io/badge/gil-develop-blue.svg)](http://www.boost.org/development/tests/develop/developer/gil.html) -[![master](https://img.shields.io/badge/doc-master-blue.svg)](https://boostorg.github.io/gil/) | [![AppVeyor](https://ci.appveyor.com/api/projects/status/w4k19d8io2af168h?svg=true)](https://ci.appveyor.com/project/stefanseefeld/gil/branch/master) | [![Azure](https://dev.azure.com/boostorg/gil/_apis/build/status/boostorg.gil?branchName=master)](https://dev.azure.com/boostorg/gil/_build/latest?definitionId=7&branchName=master) | [![Travis](https://travis-ci.org/boostorg/gil.svg?branch=master)](https://travis-ci.org/boostorg/gil) | [![CircleCI](https://circleci.com/gh/boostorg/gil/tree/master.svg?style=shield)](https://circleci.com/gh/boostorg/workflows/gil/tree/master) | [![gil](https://img.shields.io/badge/gil-master-blue.svg)](http://www.boost.org/development/tests/master/developer/gil.html) +Documentation | GitHub Actions | AppVeyor | Regression | Codecov +--------------|----------------|----------|------------|---------- +[![develop](https://img.shields.io/badge/doc-develop-blue.svg)](https://boostorg.github.io/gil/develop/) | [![GitHub Actions](https://github.com/boostorg/gil/workflows/CI/badge.svg?branch=develop)](https://github.com/boostorg/gil/actions?query=branch:develop) | [![AppVeyor](https://ci.appveyor.com/api/projects/status/w4k19d8io2af168h/branch/develop?svg=true)](https://ci.appveyor.com/project/stefanseefeld/gil/branch/develop) | [![gil](https://img.shields.io/badge/gil-develop-blue.svg)](http://www.boost.org/development/tests/develop/developer/gil.html) | [![codecov](https://codecov.io/gh/boostorg/gil/branch/develop/graphs/badge.svg)](https://app.codecov.io/gh/boostorg/gil/branch/develop) +[![master](https://img.shields.io/badge/doc-master-blue.svg)](https://boostorg.github.io/gil/) | [![GitHub Actions](https://github.com/boostorg/gil/workflows/CI/badge.svg?branch=master)](https://github.com/boostorg/gil/actions?query=branch:master) | [![AppVeyor](https://ci.appveyor.com/api/projects/status/w4k19d8io2af168h?svg=true)](https://ci.appveyor.com/project/stefanseefeld/gil/branch/master) | [![gil](https://img.shields.io/badge/gil-master-blue.svg)](http://www.boost.org/development/tests/master/developer/gil.html) | [![codecov](https://codecov.io/gh/boostorg/gil/branch/master/graphs/badge.svg)](https://app.codecov.io/gh/boostorg/gil/branch/master) # Boost.GIL @@ -29,7 +31,7 @@ Documentation | AppVeyor | Azure Pipelines | Travis CI | CircleCI Boost.GIL is a part of the [Boost C++ Libraries](http://github.com/boostorg). -The Boost Generic Image Library (GIL) is a **C++11** library that abstracts image +The Boost Generic Image Library (GIL) is a **C++14** header-only library that abstracts image representations from algorithms and allows writing code that can work on a variety of images with performance similar to hand-writing for a specific image type. @@ -52,11 +54,9 @@ See [example/cmake/README.md](example/cmake/README.md) for CMake configuration e ## Requirements -**NOTE:** The library source code is currently being modernized for C++11. - The Boost Generic Image Library (GIL) requires: -- C++11 compiler (GCC 4.9, clang 3.3, MSVC++ 14.0 (1900) or any later version) +- C++14 compiler (GCC 6, clang 3.9, MSVC++ 14.1 (1910) or any later version) - Boost header-only libraries Optionally, in order to build and run tests and examples: @@ -79,6 +79,7 @@ The official repository contains the following branches: There is number of communication channels to ask questions and discuss Boost.GIL issues: +- [GitHub Discussions](https://github.com/boostorg/gil/discussions/) - Mailing lists ([Boost discussion policy](https://www.boost.org/more/discussion_policy.html)) - [boost-gil](https://lists.boost.org/mailman/listinfo.cgi/boost-gil) (*recommended*) official Boost.GIL mailing list ([archive](https://lists.boost.org/boost-gil/)) - [boost-users](https://lists.boost.org/mailman/listinfo.cgi/boost-users) for all Boost users diff --git a/RELEASES.md b/RELEASES.md index f759b37272..da984ef640 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,6 +3,77 @@ All notable changes to [Boost.GIL](https://github.com/boostorg/gil/) project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.80.0] - 2022-08-10 + +NOTICE: We are planning BREAKING switch to C++17 as minimum required C++ language version in one or two releases after Boost 1.80 ([Discussion #676](https://github.com/boostorg/gil/discussions/676)) + +### Added +- GSoC 2020: Added Perona-Malik anisotropic diffusion algorithm [[PR #500](https://github.com/boostorg/gil/pull/500)) +- GSoC 2020: Added histogram class and related functionality ([PR #499](https://github.com/boostorg/gil/pull/499)) +- GSoC 2020: Added histogram equalization feature ([PR #514](https://github.com/boostorg/gil/pull/514)) +- GSoC 2020: Added histogram matching algorithm ([PR #515](https://github.com/boostorg/gil/pull/515)) +- GSoC 2020: Added ability to stack images either horizontally (`hstack`) or vertically (`vstack`) ([PR #506](https://github.com/boostorg/gil/pull/506)) +- GSoC 2020: Added adaptive histogram equalization algorithm ([PR #516](https://github.com/boostorg/gil/pull/516)) +- GSoC 2020: Added Standard Hough Transform and circle rasterization ([PR #512](https://github.com/boostorg/gil/pull/512)) +- GSoC 2020: Added Bresenham's algorithm for line rasterization ([PR #512](https://github.com/boostorg/gil/pull/512)) +- GSoC 2021: Added rotation of image by arbitrary angle around its center ([PR #565](https://github.com/boostorg/gil/pull/565)) +- GSoC 2021: Added rasterization support for ellipse based on "An Efficient Ellipse-Drawing Algorithm" by Jerry Van Aken ([PR #585](https://github.com/boostorg/gil/pull/585)) +- Added `image` constructor from compatible view ([PR #520](https://github.com/boostorg/gil/pull/520)) +- Added inverse function for affine `matrix3x2` ([PR #527](https://github.com/boostorg/gil/pull/527)) +- Added standard morphological transformations ([PR #541](https://github.com/boostorg/gil/pull/541)) +- Added `for_each_pixel` overload for `any_image` ([PR #648](https://github.com/boostorg/gil/pull/648)) +- Added C++17 polymorphic memory resource typedefs for `image` class ([PR #529](https://github.com/boostorg/gil/pull/529)) + +### Changed +- BREAKING: The required minimum C++ version is changed from from C++11 to C++14. + Currently, large parts of GIL still compile with a C++11 compiler. However, + there is no guarantee that it stays that way, and any compilers that do not + support at least C++14 are considered unsupported as of now. +- BREAKING: `any_color_converted_view()` is deprecated and will be removed in the next release. + Use `color_converted_view()` instead, which provides the same feature. +- BREAKING: `apply_operation` for `any_image` is deprecated and will be removed in the next release. + Use `variant2::visit` instead, which provides the same feature. ([PR #656](https://github.com/boostorg/gil/pull/656)) +- documentation: Display that GIL is a header-only library +- Moved numeric extension to core ([PR #573](https://github.com/boostorg/gil/pull/573)) +- Added support for C++17's `` ([PR #636](https://github.com/boostorg/gil/pull/636)) + The availability of the ``std::filesystem`` is detected automatically, + unless the `BOOST_GIL_IO_USE_BOOST_FILESYSTEM` macro is defined that forces + the preference of the Boost.Filesystem. +- Renamed `pixel_multiply_t` to `pixel_multiplies_t` and `pixel_divide_t` to `pixel_divides_t`([PR #655](https://github.com/boostorg/gil/pull/655)) +- Renamed `io/dynamic_io_new.hpp` to `io/detail/dynamic.hpp` ([PR #653](https://github.com/boostorg/gil/pull/653)) +- Moved function `construct_matched` into `boost::gil::detail` namespace as it was only used by other implementation details ([PR #653](https://github.com/boostorg/gil/pull/653)) +- Made `packed_pixel` trivially copyable and assignable ([PR #679](https://github.com/boostorg/gil/pull/679)) +- Replace deprecated libtiff v4.3 typedefs with C99 fixed-size integers ([#685](https://github.com/boostorg/gil/pull/685)) + +### Removed +- BREAKING: Removed support for GCC 5 ([PR #572](https://github.com/boostorg/gil/pull/572)) +- Removed deprecated.hpp ([PR #627](https://github.com/boostorg/gil/pull/627)) + +### Fixed +- Fixed conversion from RGB to HSL ([PR #505](https://github.com/boostorg/gil/pull/505)) +- Fixed conversion from RGB to signed CMYK ([PR #522](https://github.com/boostorg/gil/pull/522)) +- Removed unnecessary numeric cast in hsv.hpp ([PR #530](https://github.com/boostorg/gil/pull/530)) +- Fixed default constructor for `homogeneous_color_base` for reference pixel elements ([PR #542](https://github.com/boostorg/gil/pull/542)) +- Fixed returning reference to local temporary object in `subchroma_image_view` ([PR #556](https://github.com/boostorg/gil/pull/556)) +- Added missing header guards in diffusion.hpp ([PR #568](https://github.com/boostorg/gil/pull/568)) +- Fixed `any_image_view<>::const_t` ([PR #526](https://github.com/boostorg/gil/pull/526)) +- Fixed C++20 incompatibilities in I/O extensions ([PR #617](https://github.com/boostorg/gil/pull/617)) +- Ensured all examples build without errors ([PR #628](https://github.com/boostorg/gil/pull/628)) +- Fixed `convolve_2d` for images with `float32_t` channel model ([PR #577](https://github.com/boostorg/gil/pull/577)) +- Fixed `for_each_pixel` for non-1d iterable views ([PR #621](https://github.com/boostorg/gil/pull/621)) +- Fixed: `is_equal_to_sixteen` in PNG I/O was less-than test ([PR #650](https://github.com/boostorg/gil/pull/650)) +- Re-allow `devicen_t` with two components ([PR #654](https://github.com/boostorg/gil/pull/654)) + It was unintentionally removed in Boost 1.72 +- Fixed memory leak in `image` class for empty dimensions ([PR #649](https://github.com/boostorg/gil/pull/649)) + +### Acknowledgements + +Cypre55, Samuel Debionne, Mike-Devel, Edward Diener, Peter Dimov, Omar Emara, Dhruva Gole, Nicolas Herry, Eugene K, Avinal Kumar, Gaurav Kumar, Marco Langer, Pranam Lashkari, Mateusz Łoskot, Giovanni Mascellani, Debabrata Mandal, Gopi Krishna Menon, René Ferdinand Rivera Morell, Felix Morgner, Harshit Pant, Paul92, André Schröder, Scramjet911, Siddharth, Dirk Stolle, Prathamesh Tagore, theroyn, Olzhas Zhumabek + +## [1.75.0] - 2020-12-09 + +BREAKING: In next release, we are going to drop support for GCC 5. We may also change the required minimum C++ version from C++11 to C++14. + ## [1.74.0] - 2020-08-12 ### Added @@ -267,10 +338,10 @@ linked PDF documents with detailed changes. ### Changed - Updated the design guide and tutorial, updated syntax of concepts to the latest concepts proposal. -- In `image`, `image_view`, `any_image`, `any_image_view`: +- In `image`, `image_view`, `any_image`, `any_image_view`: There are no longer global functions `get_width()`, `get_height()`, `get_dimensions()`, `num_channels()`. Use methods `width()`, `height()`, `dimensions()` instead. -- In models of pixel, pixel iterator, pixel locator, image view and image: +- In models of pixel, pixel iterator, pixel locator, image view and image: There used to be different ways of getting to a pixel, channel, color space, etc. of an image view, pixel, locator, iterator and image (e.g. traits, member typedefs). Now all pixel-based GIL constructs (pixels, pixel iterators, locators, image views and images) model @@ -278,7 +349,7 @@ linked PDF documents with detailed changes. and for homogeneous constructs we also have: `channel_type`. To get the pixel type or pixel reference/const reference type of an image, image view, locator and pixel, use member typedefs `value_type`, `reference` and `const_reference`. -- In `locator`, `image`, `image_view`, `any_image` and `any_image_view`: +- In `locator`, `image`, `image_view`, `any_image` and `any_image_view`: Removed `dynamic_x_step_t`, `dynamic_y_step_t`, `dynamic_xy_step_t` and `dynamic_xy_step_transposed_t` as member typedefs of locators and image views. Instead, there are separate concepts `HasDynamicXStepTypeConcept`, `HasDynamicYStepTypeConcept`, diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt new file mode 100644 index 0000000000..4752f9b3fd --- /dev/null +++ b/benchmark/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(celero) +add_subdirectory(google) diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000000..6d2a233698 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,118 @@ +# GIL Benchmarks + +This guide assumes that minimal GIL can already be built, e.g. dependency on +dependent Boost libraries are satisfied. + +## Dependencies + +The benchmarks depend on popular micro-benchmarking frameworks: + +* https://github.com/DigitalInBlue/Celero +* https://github.com/google/benchmark + +All are configured using +[ExternalProject_Add](https://cmake.org/cmake/help/latest/module/ExternalProject.html) +with custom targets that drive download, build and deployment of the dependencies. + +## Writing Benchmarks + +Both frameworks provide facilities to avoid optimizing away some portions of the code or +effects on variables. It might get bypassed by compilers in the future though, as they get +smarter. Thus, providing some side effect outside of benchmarked section is advised. + +--- + +### Using Google Benchmark + +Overall structure of the file should look roughly like this: + + + + BENCHMARK(function1_name) + BENCHMARK(function2_name) + ... + + BENCHMARK_MAIN() + +Lets have a closer look at a benchmarking function: + + void function_name(benchmark::State& state) { + + + for (auto _: state) { + + } + + } + +Do note that as long as `_` is in scope, the runtime will be counted towards benchmarking results, +whereas the rest will not be included. Not writing a side effect (like a check can throw, +or printing on the console) might result in code being optimized away, thus making whole +benchmarking function useless. + +--- + +### Using Celero + +Writing a Celero benchmark is a bit more involved process. +As most benchmarks will require *some* setup code not to be counted in the benchmarking section, +the guide ignores function only benchmarks. +To provide setup code and side effects, one has to write a fixture: + + class FixtureName : public celero::TextFixture { + public: + + FixtureName() = default; //trivial default constructor + + virtual std::vector getExperimentValues() const override { + + } + + virtual void setUp(const celero::TestFixture::ExperimentValue& experimentValue) override { + + } + + virtual void tearDown() override { + + + } + } + +Fixture is sort of data provider. It can be used to control what input will be generated, +e.g. size, variety, data type. Do note that `celero::TestFixture::ExperimentValue` +can be extended using inheritance, and in original form is just a combination of +numerical value and number of iterations. + +Then, to specify code to be benchmarked, use the following: + + BASELINE_F(GroupName, BenchmarkName, FixtureName, SampleCount, IterationCount) { + + } + + BENCHMARK_F(GroupName, BenchmarkName, FixtureName, SampleCount, IterationCount) { + + } + + + + + +Baseline should be the code to be used as a reference: performance goal, original performance, etc. +Other benchmarks should be competitors of the baseline. Do note that the code inside +`BASELINE_F` and `BENCHMARK_F` will be timed, the rest will not. + +--- + +### Building the benchmarks + +To build a benchmark, simply put the benchmark `.cpp` file into `/benchmark` +and reconfigure the project. +Then, the targets to build are `benchmark__`. + +--- + +### Running benchmarks + +Pairing both benchmarking frameworks with a good plotting tool of your choice is advised. +Both frameworks provide options to control which benchmarks to run, for how long, +and output formats. Running `--help` on executables will print available options. diff --git a/benchmark/celero/CMakeLists.txt b/benchmark/celero/CMakeLists.txt new file mode 100644 index 0000000000..93483c6c14 --- /dev/null +++ b/benchmark/celero/CMakeLists.txt @@ -0,0 +1,71 @@ +message(STATUS "Boost.GIL: Configuring benchmarks: celero") + +#----------------------------------------------------------------------------- +# Deploy Celero +#----------------------------------------------------------------------------- +include(ExternalProject) + +# NOTE for Ninja generator: +# Ninja generator will try to find external project library at the expected location. +# BUILD_BYPRODUCTS option is added to specify build output and satisfy dependencies. +ExternalProject_Add(project_celero + GIT_REPOSITORY https://github.com/DigitalInBlue/Celero.git + GIT_TAG "v2.5.0" + GIT_SHALLOW 1 + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/vendor/celero + TIMEOUT 10 + BUILD_BYPRODUCTS + /lib/static/${CMAKE_STATIC_LIBRARY_PREFIX}celerod${CMAKE_STATIC_LIBRARY_SUFFIX} + /lib/static/${CMAKE_STATIC_LIBRARY_PREFIX}celero${CMAKE_DEBUG_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX} + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/vendor/celero + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCELERO_COMPILE_DYNAMIC_LIBRARIES:BOOL=OFF + -DCELERO_ENABLE_TESTS:BOOL=OFF + -DCELERO_ENABLE_EXPERIMENTS:BOOL=OFF + -DCELERO_ENABLE_FOLDERS:BOOL=OFF) + +ExternalProject_Get_Property(project_celero INSTALL_DIR) + +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) +add_library(gil_celero STATIC IMPORTED) +target_include_directories(gil_celero INTERFACE ${INSTALL_DIR}/include) +target_compile_definitions(gil_celero INTERFACE CELERO_STATIC=1) +set_property(TARGET gil_celero PROPERTY + IMPORTED_LOCATION_DEBUG + "${INSTALL_DIR}/lib/static/${CMAKE_STATIC_LIBRARY_PREFIX}celerod${CMAKE_STATIC_LIBRARY_SUFFIX}") +set_property(TARGET gil_celero PROPERTY + IMPORTED_LOCATION_RELEASE + "${INSTALL_DIR}/lib/static/${CMAKE_STATIC_LIBRARY_PREFIX}celero${CMAKE_STATIC_LIBRARY_SUFFIX}") +set_target_properties(gil_celero PROPERTIES + MAP_IMPORTED_CONFIG_MINSIZEREL Release + MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + find_library(POWRPROF PowrProf.lib) + target_link_libraries(gil_celero INTERFACE POWRPROF) +endif() + +add_dependencies(gil_celero project_celero) + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + file(GLOB_RECURSE _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp CONFIGURE_DEPEND) +else() + file(GLOB_RECURSE _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +endif() + +foreach(_benchmark ${_benchmarks}) + get_filename_component(_name ${_benchmark} NAME_WE) + set(_target benchmark_celero_${_name}) + add_executable(${_target} ${_name}.cpp) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK=1) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies + gil_celero) +endforeach() + +unset(_benchmarks) +unset(_benchmark) diff --git a/benchmark/celero/charts/histogram_gray8_msvc1421_HistogramGray8.html b/benchmark/celero/charts/histogram_gray8_msvc1421_HistogramGray8.html new file mode 100644 index 0000000000..dcf9f6aeac --- /dev/null +++ b/benchmark/celero/charts/histogram_gray8_msvc1421_HistogramGray8.html @@ -0,0 +1,85 @@ + + + + + + + + + + + Benchmark results for 'HistogramGray8' + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + \ No newline at end of file diff --git a/benchmark/celero/charts/index.html b/benchmark/celero/charts/index.html new file mode 100644 index 0000000000..0428ca1fd2 --- /dev/null +++ b/benchmark/celero/charts/index.html @@ -0,0 +1,6 @@ + + Celero Benchmarks + +

Celero Benchmarks

+

List of benchmark groups:

+
Generated by pycelerograph | Benchmark by Celero \ No newline at end of file diff --git a/benchmark/celero/histogram_gray8.cpp b/benchmark/celero/histogram_gray8.cpp new file mode 100644 index 0000000000..6b58200f21 --- /dev/null +++ b/benchmark/celero/histogram_gray8.cpp @@ -0,0 +1,249 @@ +// +// Copyright 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 +// +// Benchmark of various pixel iteration and access methods for a contiguous images +// composed of homogeneous pixels with single 8-bit integer channel (grayscale). +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace gil = boost::gil; + +class RandomImageGray8Fixture : public celero::TestFixture +{ +public: + RandomImageGray8Fixture() : gen_(rd_()), uid_(0, 255), histogram_{0} + { + } + + std::vector getExperimentValues() const final + { + // Generate sequence of square image sizes + int const initial_power = static_cast(std::ceil(std::log(initial_image_size_) / std::log(2))); + + std::vector problem_space; + problem_space.resize(this->number_of_images_); + for (int i = 0; i < this->number_of_images_; i++) + { + problem_space[i] = { int64_t(pow(2, initial_power + i)) }; + } + return problem_space; + } + + void setUp(const celero::TestFixture::ExperimentValue& experimentValue) final + { + auto const size = static_cast(experimentValue.Value); + this->image_.recreate(size, size); + auto view = gil::view(this->image_); + for (auto it = view.begin(); it != view.end(); ++it) + *it = static_cast(uid_(gen_)); + } + + void clear_histogram() + { + this->histogram_.fill(0); + } + + void assert_histogram() + { +#ifndef NDEBUG + std::size_t const total = std::accumulate(this->histogram_.cbegin(), this->histogram_.cend(), 0U); + assert(total == gil::view(this->image_).size()); +#endif + } + + std::random_device rd_; + std::mt19937 gen_; + std::uniform_int_distribution uid_; + + std::array histogram_; + gil::gray8_image_t image_; +#ifdef NDEBUG + int const initial_image_size_{64}; + int const number_of_images_{6}; +#else + int const initial_image_size_{2}; + int const number_of_images_{1}; +#endif +}; + +#ifdef NDEBUG +constexpr int samples = 30; // Celero default minimum +constexpr int iterations = 100; +#else +constexpr int samples = 1; +constexpr int iterations = 1; +#endif + +BASELINE_F(HistogramGray8, 1d_raw_pointer, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::view(this->image_); + std::uint8_t* p = gil::interleaved_view_get_raw_data(view); + for (std::size_t i = 0, size = view.size(); i < size; i++) + ++h[p[i]]; + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 1d_index_operator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::view(this->image_); + std::ptrdiff_t const pixel_count = view.width() * view.height(); + for (std::ptrdiff_t i = 0; i < pixel_count; i++) + ++h[view[i]]; + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 1d_index_at, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::view(this->image_); + std::ptrdiff_t const pixel_count = view.width() * view.height(); + for (std::ptrdiff_t i = 0; i < pixel_count; i++) + ++h[*view.at(i)]; + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 1d_iterator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + for (auto it = view.begin(), end = view.end(); it != end; ++it) + ++h[*it]; + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 1d_reverse_iterator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + for (auto it = view.rbegin(), end = view.rend(); it != end; ++it) + ++h[*it]; + + this->assert_histogram(); +} + + +BENCHMARK_F(HistogramGray8, 1d_for_each_pixel, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + assert(view.is_1d_traversable()); + + gil::for_each_pixel(view, [&h](gil::gray8c_pixel_t const& pixel) { + ++h[pixel]; + }); + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 2d_call_operator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + auto const view_h = view.height(); + auto const view_w = view.width(); + for (std::ptrdiff_t y = 0; y < view_h; y++) + { + for (std::ptrdiff_t x = 0; x < view_w; x++) + { + // The binary call operator() computes the location of the pixel in a 2D grid, + // which involves addition and multiplication, thus it costs performance. + ++h[view(x, y)]; + } + } + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 2d_xy_locator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + auto loc = view.xy_at(0, 0); + auto const view_h = view.height(); + auto const view_w = view.width(); + for (std::ptrdiff_t y = 0; y < view_h; y++) + { + for (std::ptrdiff_t x = 0; x < view_w; x++) + { + ++h[loc(0, 0)]; + ++loc.x(); + } + // carriage return, equivalent of: loc.x() -= view.width(); ++loc.y(); + loc += gil::point_t{-view.width(), 1}; + } + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 2d_x_iterator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + auto const view_h = view.height(); + for (std::ptrdiff_t y = 0; y < view_h; y++) + { + // The two iterator types are raw C pointers, so the dereferencing + // and arithmetic is a fast pointer indexing operator. + gil::gray8c_view_t::x_iterator it = view.row_begin(y); + gil::gray8c_view_t::x_iterator end = view.row_end(y); + while (it != end) + ++h[*it++]; + } + + this->assert_histogram(); +} + +BENCHMARK_F(HistogramGray8, 2d_y_iterator, RandomImageGray8Fixture, samples, iterations) +{ + this->clear_histogram(); + auto& h = this->histogram_; + + auto view = gil::const_view(this->image_); + auto const view_w = view.width(); + for (std::ptrdiff_t x = 0; x < view_w; x++) + { + gil::gray8c_view_t::y_iterator it = view.col_begin(x); + gil::gray8c_view_t::y_iterator end = view.col_end(x); + while (it != end) + ++h[*it++]; + } + + this->assert_histogram(); +} + +CELERO_MAIN diff --git a/benchmark/celero/histogram_gray8_msvc1421.csv b/benchmark/celero/histogram_gray8_msvc1421.csv new file mode 100644 index 0000000000..56bfa7acfa --- /dev/null +++ b/benchmark/celero/histogram_gray8_msvc1421.csv @@ -0,0 +1,61 @@ +Group,Experiment,Problem Space,Samples,Iterations,Failure,Baseline,us/Iteration,Iterations/sec,Min (us),Mean (us),Max (us),Variance,Standard Deviation,Skewness,Kurtosis,Z Score +HistogramGray8,1d_raw_pointer,64,30,100,0,1,2.32,431034,232,280.467,322,697.016,26.4011,-0.432173,-0.936804,1.83578 +HistogramGray8,1d_raw_pointer,128,30,100,0,1,9.79,102145,979,1126.37,1240,4270.79,65.3513,-0.355228,-0.486781,2.25499 +HistogramGray8,1d_raw_pointer,256,30,100,0,1,39.39,25387.2,3939,4276.27,4680,35999.6,189.736,0.383771,-0.483897,1.77756 +HistogramGray8,1d_raw_pointer,512,30,100,0,1,164.65,6073.49,16465,16804.3,17484,61781,248.558,1.14495,0.919333,1.36507 +HistogramGray8,1d_raw_pointer,1024,30,100,0,1,671.87,1488.38,67187,69849.2,73740,3.51459e+06,1874.72,0.810687,-0.450895,1.42005 +HistogramGray8,1d_raw_pointer,2048,30,100,0,1,2664.44,375.313,266444,276317,295826,8.60814e+07,9278,0.776554,-0.897073,1.06416 +HistogramGray8,1d_index_operator,64,30,100,0,18.7198,43.43,23025.6,4343,4688.37,5251,51378.2,226.668,0.676256,-0.011044,1.52367 +HistogramGray8,1d_index_operator,128,30,100,0,18.6139,182.23,5487.57,18223,18628.8,20005,164651,405.772,2.18133,4.14667,1.00015 +HistogramGray8,1d_index_operator,256,30,100,0,18.5359,730.13,1369.62,73013,74479.9,78574,1.40907e+06,1187.04,1.99851,4.03504,1.23573 +HistogramGray8,1d_index_operator,512,30,100,0,17.8932,2946.12,339.429,294612,298057,304145,7.9811e+06,2825.08,0.641686,-0.823317,1.21959 +HistogramGray8,1d_index_operator,1024,30,100,0,17.6477,11857,84.3385,1185698,1.19325e+06,1206125,3.6547e+07,6045.41,0.829102,-0.460791,1.24976 +HistogramGray8,1d_index_operator,2048,30,100,0,17.7861,47389.9,21.1015,4738990,4.78771e+06,4879534,8.66826e+08,29441.9,1.11416,1.74636,1.65492 +HistogramGray8,1d_index_at,64,30,100,0,17.1293,39.74,25163.6,3974,4430.83,4855,42511.5,206.183,-0.313806,-0.464758,2.21567 +HistogramGray8,1d_index_at,128,30,100,0,16.8069,164.54,6077.55,16454,16824.5,17412,44019.2,209.808,0.715802,0.423865,1.76574 +HistogramGray8,1d_index_at,256,30,100,0,16.834,663.09,1508.09,66309,68026.6,73972,3.43666e+06,1853.82,1.92032,2.72669,0.9265 +HistogramGray8,1d_index_at,512,30,100,0,16.2039,2667.98,374.815,266798,274866,293504,4.09254e+07,6397.29,1.05313,0.685735,1.26118 +HistogramGray8,1d_index_at,1024,30,100,0,16.1346,10840.4,92.2479,1084036,1.09391e+06,1121849,9.23607e+07,9610.45,1.2973,0.911113,1.02707 +HistogramGray8,1d_index_at,2048,30,100,0,15.9902,42604.9,23.4715,4260491,4.34556e+06,4442077,1.67326e+09,40905.5,0.425214,0.56668,2.07969 +HistogramGray8,1d_iterator,64,30,100,0,1.73707,4.03,248139,403,427.667,501,1011.54,31.8047,1.05332,-0.410291,0.775566 +HistogramGray8,1d_iterator,128,30,100,0,1.64045,16.06,62266.5,1606,1804.7,2071,19371.5,139.182,0.408106,-1.09814,1.42763 +HistogramGray8,1d_iterator,256,30,100,0,1.68291,66.29,15085.2,6629,7421.97,8110,114491,338.364,-0.0201391,-0.213858,2.34353 +HistogramGray8,1d_iterator,512,30,100,0,1.62903,268.22,3728.28,26822,27803,28930,453091,673.12,0.3422,-1.2403,1.45744 +HistogramGray8,1d_iterator,1024,30,100,0,1.60844,1080.66,925.36,108066,113580,120037,1.17025e+07,3420.89,0.12696,-1.01499,1.61199 +HistogramGray8,1d_iterator,2048,30,100,0,1.63676,4361.04,229.303,436104,443403,454410,2.48219e+07,4982.16,0.363708,-0.518366,1.46495 +HistogramGray8,1d_reverse_iterator,64,30,100,0,10.2026,23.67,42247.6,2367,2522.4,2785,11358.9,106.578,0.470624,-0.539924,1.45808 +HistogramGray8,1d_reverse_iterator,128,30,100,0,9.57099,93.7,10672.4,9370,9735.77,10469,57530.1,239.854,0.80815,0.991489,1.52495 +HistogramGray8,1d_reverse_iterator,256,30,100,0,9.66844,380.84,2625.77,38084,38736.8,41276,497834,705.573,2.27396,5.24665,0.925252 +HistogramGray8,1d_reverse_iterator,512,30,100,0,9.21427,1517.13,659.139,151713,154460,159563,4.09464e+06,2023.52,1.07982,0.213067,1.35752 +HistogramGray8,1d_reverse_iterator,1024,30,100,0,9.08338,6102.85,163.858,610285,619071,651357,6.87241e+07,8290,2.32861,6.05929,1.05984 +HistogramGray8,1d_reverse_iterator,2048,30,100,0,9.20704,24531.6,40.7637,2453160,2.48654e+06,2581765,9.35506e+08,30586,1.32276,1.45197,1.09129 +HistogramGray8,1d_for_each_pixel,64,30,100,0,1.00431,2.33,429185,233,248.333,313,384.437,19.6071,1.52322,2.03799,0.782031 +HistogramGray8,1d_for_each_pixel,128,30,100,0,0.947906,9.28,107759,928,993.467,1107,2591.57,50.9074,0.862133,-0.0363922,1.28599 +HistogramGray8,1d_for_each_pixel,256,30,100,0,0.980452,38.62,25893.3,3862,4066.2,4603,29286.2,171.132,1.36057,1.57314,1.19323 +HistogramGray8,1d_for_each_pixel,512,30,100,0,0.960887,158.21,6320.71,15821,16276.6,16725,45932.7,214.319,0.152992,-0.527451,2.12596 +HistogramGray8,1d_for_each_pixel,1024,30,100,0,0.959977,644.98,1550.44,64498,67003.9,69976,3.28565e+06,1812.64,0.192411,-1.44393,1.38248 +HistogramGray8,1d_for_each_pixel,2048,30,100,0,0.973158,2592.92,385.666,259292,264311,284740,3.37305e+07,5807.8,1.93337,3.57344,0.864102 +HistogramGray8,2d_call_operator,64,30,100,0,1.15948,2.69,371747,269,284.5,328,284.397,16.8641,0.867782,-0.398824,0.919114 +HistogramGray8,2d_call_operator,128,30,100,0,1.02962,10.08,99206.3,1008,1086.73,1373,5873.03,76.6357,2.1662,5.30694,1.02737 +HistogramGray8,2d_call_operator,256,30,100,0,1.02209,40.26,24838.5,4026,4232.7,4772,34821.9,186.606,1.38605,1.60431,1.10768 +HistogramGray8,2d_call_operator,512,30,100,0,0.991862,163.31,6123.32,16331,16726.8,17962,120025,346.446,1.62892,3.44857,1.14255 +HistogramGray8,2d_call_operator,1024,30,100,0,0.973983,654.39,1528.14,65439,67781.8,74904,6.39585e+06,2529,1.86477,2.31278,0.926387 +HistogramGray8,2d_call_operator,2048,30,100,0,0.987498,2631.13,380.065,263113,269756,283907,1.77622e+07,4214.52,1.08199,2.37988,1.57616 +HistogramGray8,2d_xy_locator,64,30,100,0,1.28017,2.97,336700,297,335.3,418,1101.8,33.1934,0.657576,-0.443954,1.15384 +HistogramGray8,2d_xy_locator,128,30,100,0,1.09602,10.73,93196.6,1073,1167.1,1400,8416.23,91.74,0.996443,-0.0239161,1.02572 +HistogramGray8,2d_xy_locator,256,30,100,0,1.06017,41.76,23946.4,4176,4497.53,5272,64497.6,253.964,1.19077,1.07985,1.26606 +HistogramGray8,2d_xy_locator,512,30,100,0,1.01688,167.43,5972.65,16743,17217.8,18244,101075,317.923,1.52556,2.42308,1.49355 +HistogramGray8,2d_xy_locator,1024,30,100,0,1.00134,672.77,1486.39,67277,68350.1,72583,1.43459e+06,1197.74,2.0853,4.12646,0.895908 +HistogramGray8,2d_xy_locator,2048,30,100,0,1.00278,2671.85,374.273,267185,273451,290045,2.51464e+07,5014.62,1.52825,2.64603,1.24964 +HistogramGray8,2d_x_iterator,64,30,100,0,1.22845,2.85,350877,285,305.667,381,657.264,25.6372,1.18695,0.627509,0.806121 +HistogramGray8,2d_x_iterator,128,30,100,0,1.04903,10.27,97371,1027,1106.03,1312,4123.48,64.2143,1.27747,1.78535,1.23077 +HistogramGray8,2d_x_iterator,256,30,100,0,0.997715,39.3,25445.3,3930,4137.2,4527,22703.5,150.677,0.807551,0.0156739,1.37513 +HistogramGray8,2d_x_iterator,512,30,100,0,0.966535,159.14,6283.78,15914,16372.9,17810,113398,336.746,2.63579,9.20267,1.36265 +HistogramGray8,2d_x_iterator,1024,30,100,0,0.937369,629.79,1587.83,62979,64549.3,67035,1.06198e+06,1030.52,1.19112,0.780298,1.52382 +HistogramGray8,2d_x_iterator,2048,30,100,0,0.958025,2552.6,391.757,255260,258531,269247,8.45361e+06,2907.51,1.93487,4.44336,1.12499 +HistogramGray8,2d_y_iterator,64,30,100,0,9.35345,21.7,46082.9,2170,2328.73,2594,9612.89,98.0454,0.502065,0.0544133,1.61898 +HistogramGray8,2d_y_iterator,128,30,100,0,9.03984,88.5,11299.4,8850,9267.2,9993,109674,331.171,0.830047,-0.446344,1.25977 +HistogramGray8,2d_y_iterator,256,30,100,0,10.1318,399.09,2505.7,39909,40807.6,43780,466008,682.648,2.80346,10.1115,1.31635 +HistogramGray8,2d_y_iterator,512,30,100,0,10.8486,1786.22,559.841,178622,180727,185148,2.59968e+06,1612.35,0.994626,0.512186,1.30553 +HistogramGray8,2d_y_iterator,1024,30,100,0,18.3811,12349.7,80.9734,1234974,1.25622e+06,1290899,2.7257e+08,16509.7,0.509655,-0.913002,1.28703 +HistogramGray8,2d_y_iterator,2048,30,100,0,21.9414,58461.5,17.1053,5846148,6.05445e+06,6154576,3.74868e+09,61226.5,-1.51749,3.13101,3.40209 diff --git a/benchmark/celero/pixel_channel_arithmetics.cpp b/benchmark/celero/pixel_channel_arithmetics.cpp new file mode 100644 index 0000000000..fa81e1bd14 --- /dev/null +++ b/benchmark/celero/pixel_channel_arithmetics.cpp @@ -0,0 +1,102 @@ +#include + +#include +#include + +#include +#include +#include +#include + +namespace gil = boost::gil; + +constexpr std::size_t imageSize = 1024 * 1024; + +class ChannelArithmeticsIntVectorFixture : public celero::TestFixture { +public: + std::vector img; + ChannelArithmeticsIntVectorFixture() {} + + virtual std::vector getExperimentValues() const override { + std::vector problemSpace; + return { celero::TestFixture::ExperimentValue(imageSize, 0) }; + } + + virtual void setUp(const celero::TestFixture::ExperimentValue& experimentValue) override { + img = std::vector(imageSize, gil::gray16_pixel_t(0)); + } + + virtual void tearDown() override { + if (!std::all_of(img.begin(), img.end(), [](auto p) { return p > 0; })) + throw std::logic_error("doesn't calculate correctly"); + } +}; + +class ChannelArithmeticsPixelVectorFixture : public celero::TestFixture { +public: + std::vector img; + + ChannelArithmeticsPixelVectorFixture() {} + + virtual std::vector getExperimentValues() const override { + std::vector problemSpace; + return { celero::TestFixture::ExperimentValue(imageSize, 0) }; + } + + virtual void setUp(const celero::TestFixture::ExperimentValue& experimentValue) override { + img = std::vector(imageSize, gil::gray16_pixel_t(0)); + } + + virtual void tearDown() override { + if (!std::all_of(img.begin(), img.end(), [](auto p) { return p > 0; })) + throw std::logic_error("doesn't calculate correctly"); + } +}; + +class ChannelArithmeticsImageFixture : public celero::TestFixture { +public: + gil::gray16_image_t img; + ChannelArithmeticsImageFixture() {} + + virtual std::vector getExperimentValues() const override { + std::vector problemSpace; + return { celero::TestFixture::ExperimentValue(imageSize, 0) }; + } + + virtual void setUp(const celero::TestFixture::ExperimentValue& experimentValue) override { + img = gil::gray16_image_t(1024, 1024, gil::gray16_pixel_t{ 0 }); + + } + + virtual void tearDown() override { + auto view = gil::view(img); + if (!std::all_of(view.begin(), view.end(), [](auto p) { return p > 0; })) + throw std::logic_error("doesn't calculate correctly"); + } +}; + +BASELINE_F(RawMemory, RawMemoryVector, ChannelArithmeticsIntVectorFixture, 2, 1'000) { + std::cout << img.size() << '\n'; + for (auto&& p : img) + p += 1; +} + +BENCHMARK_F(GilImage, GilImageAlgorithm, ChannelArithmeticsImageFixture, 2, 1'000) { + std::cout << img.height() * img.width() << '\n'; + celero::DoNotOptimizeAway(img); + auto op = std::bind( + gil::pixel_plus_t(), + std::placeholders::_1, gil::gray16_pixel_t{ 1 }); + gil::transform_pixels(gil::const_view(img), gil::view(img), op); +} + +BENCHMARK_F(GilPixelVector, PixelVectorReinterpret, ChannelArithmeticsPixelVectorFixture, 2, 1'000) { + std::cout << img.size() << '\n'; + auto start = reinterpret_cast(img.data()); + auto end = start + img.size(); + for (; start != end; ++start) + *start += 1; + +} + +CELERO_MAIN diff --git a/benchmark/google/CMakeLists.txt b/benchmark/google/CMakeLists.txt new file mode 100644 index 0000000000..77fd067acd --- /dev/null +++ b/benchmark/google/CMakeLists.txt @@ -0,0 +1,86 @@ +message(STATUS "Boost.GIL: Configuring benchmarks: google") + +#----------------------------------------------------------------------------- +# Deploy google/benchmark +#----------------------------------------------------------------------------- +include(ExternalProject) + +# NOTE for Ninja generator: +# Ninja generator will try to find external project library at the expected location. +# BUILD_BYPRODUCTS option is added to specify build output and satisfy dependencies. +ExternalProject_Add(project_googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG "v1.5.0" + GIT_SHALLOW 1 + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/vendor/benchmark + TIMEOUT 10 + BUILD_BYPRODUCTS /lib/${CMAKE_STATIC_LIBRARY_PREFIX}benchmark${CMAKE_STATIC_LIBRARY_SUFFIX} + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/vendor/benchmark + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DBENCHMARK_ENABLE_TESTING=OFF + UPDATE_COMMAND "" + TEST_COMMAND "") + +ExternalProject_Get_Property(project_googlebenchmark INSTALL_DIR) + +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) +add_library(gil_googlebenchmark STATIC IMPORTED) +target_include_directories(gil_googlebenchmark INTERFACE ${INSTALL_DIR}/include) +set_property(TARGET gil_googlebenchmark PROPERTY IMPORTED_LOCATION + "${INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}benchmark${CMAKE_STATIC_LIBRARY_SUFFIX}") + +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + find_library(SHLWAPI Shlwapi.lib) + target_link_libraries(gil_googlebenchmark INTERFACE SHLWAPI) +else() + find_package(Threads) + target_link_libraries(gil_googlebenchmark INTERFACE ${CMAKE_THREAD_LIBS_INIT} rt) +endif() + +add_dependencies(gil_googlebenchmark project_googlebenchmark) + +#----------------------------------------------------------------------------- +# Build benchmarks +#----------------------------------------------------------------------------- +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + file(GLOB _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp CONFIGURE_DEPEND) +else() + file(GLOB _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +endif() + +foreach(_benchmark ${_benchmarks}) + get_filename_component(_name ${_benchmark} NAME_WE) + set(_target benchmark_google_${_name}) + add_executable(${_target} ${_name}.cpp) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK=1) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies + gil_googlebenchmark) +endforeach() + +unset(_benchmarks) +unset(_benchmark) + + +#----------------------------------------------------------------------------- +# Options +#----------------------------------------------------------------------------- +option(BOOST_GIL_BUILD_BENCHMARKS_OPENCV "Builds all OpenCV benchmarks" OFF) +option(BOOST_GIL_BUILD_BENCHMARKS_BLAZE "Builds all BLAZE benchmarks" OFF) +option(BOOST_GIL_BUILD_BENCHMARKS_IPP "Builds all IPP benchmarks" OFF) + +if(BOOST_GIL_BUILD_BENCHMARKS_OPENCV) + add_subdirectory(opencv) +endif() + +if(BOOST_GIL_BUILD_BENCHMARKS_BLAZE) + add_subdirectory(blaze) +endif() + +if(BOOST_GIL_BUILD_BENCHMARKS_IPP) + add_subdirectory(ipp) +endif() diff --git a/benchmark/google/blaze/CMakeLists.txt b/benchmark/google/blaze/CMakeLists.txt new file mode 100644 index 0000000000..0aa857029f --- /dev/null +++ b/benchmark/google/blaze/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (C) 2020 Samuel Debionne, ESRF. + +# Use, modification and distribution is subject to 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) + +#----------------------------------------------------------------------------- +# Find package Blaze +#----------------------------------------------------------------------------- +find_package(blaze REQUIRED) + +#----------------------------------------------------------------------------- +# Build benchmarks +#----------------------------------------------------------------------------- +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + file(GLOB_RECURSE _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp CONFIGURE_DEPEND) +else() + file(GLOB_RECURSE _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +endif() + +foreach(_benchmark ${_benchmarks}) + get_filename_component(_name ${_benchmark} NAME_WE) + set(_target benchmark_google_blaze_${_name}) + add_executable(${_target} ${_name}.cpp) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK=1) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies + gil_googlebenchmark + blaze::blaze) +endforeach() + +unset(_benchmarks) +unset(_benchmark) diff --git a/benchmark/google/blaze/view_transpose.cpp b/benchmark/google/blaze/view_transpose.cpp new file mode 100644 index 0000000000..9d08c105d2 --- /dev/null +++ b/benchmark/google/blaze/view_transpose.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +#include + +template +auto as_matrix(GrayView const& source) +{ + using channel_t = typename boost::gil::channel_type::type; + + return blaze::CustomMatrix( + boost::gil::interleaved_view_get_raw_data(source), + source.height(), + source.width()); +} + +static void blaze_transpose(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t out(dim, dim); + + auto mat_in = as_matrix(view(in)); + auto mat_out = as_matrix(view(in)); + + for (auto _ : state) { + // The code to benchmark + mat_out = blaze::trans(mat_in); + } + + if (!equal_pixels(transposed_view(const_view(in)), const_view(in))) + state.SkipWithError("blaze_transpose wrong result"); +} +BENCHMARK(blaze_transpose)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void blaze_transpose_inplace(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t in_ref(in); + + auto mat_in_out = as_matrix(view(in)); + + for (auto _ : state) { + // The code to benchmark + blaze::transpose(mat_in_out); + } + + if (!equal_pixels(transposed_view(const_view(in_ref)), const_view(in))) + state.SkipWithError("blaze_transpose_inplace wrong result"); +} +BENCHMARK(blaze_transpose_inplace)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/ipp/CMakeLists.txt b/benchmark/google/ipp/CMakeLists.txt new file mode 100644 index 0000000000..7937643805 --- /dev/null +++ b/benchmark/google/ipp/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (C) 2020 Samuel Debionne, ESRF. + +# Use, modification and distribution is subject to 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) + +#----------------------------------------------------------------------------- +# Find package IPP +#----------------------------------------------------------------------------- +#find_package(IPP REQUIRED) +find_path(IPP_INCLUDE_DIR ipp.h PATHS ${IPP_ROOT}/include) +message("IPP_INCLUDE_DIR: ${IPP_INCLUDE_DIR}") +find_library(IPP_LIB_CORE ippcore PATHS ${IPP_ROOT}/lib) +find_library(IPP_LIB_I ippi PATHS ${IPP_ROOT}/lib) +message("IPP_LIB_CORE: ${IPP_LIB_CORE}") +message("IPP_LIB_I: ${IPP_LIB_I}") +add_library(ipp::ipp INTERFACE IMPORTED) +target_include_directories(ipp::ipp INTERFACE ${IPP_INCLUDE_DIR}) +target_link_libraries(ipp::ipp INTERFACE ${IPP_LIB_CORE} ${IPP_LIB_I}) + +#----------------------------------------------------------------------------- +# Build benchmarks +#----------------------------------------------------------------------------- +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + file(GLOB _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp CONFIGURE_DEPEND) +else() + file(GLOB _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +endif() + +foreach(_benchmark ${_benchmarks}) + get_filename_component(_name ${_benchmark} NAME_WE) + set(_target benchmark_google_ipp_${_name}) + add_executable(${_target} ${_name}.cpp) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK=1) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies + gil_googlebenchmark + ipp::ipp) +endforeach() + +unset(_benchmarks) +unset(_benchmark) diff --git a/benchmark/google/ipp/view_flip.cpp b/benchmark/google/ipp/view_flip.cpp new file mode 100644 index 0000000000..8a3b67ad11 --- /dev/null +++ b/benchmark/google/ipp/view_flip.cpp @@ -0,0 +1,141 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +#include + +inline bool is_odd(benchmark::IterationCount cnt) { return (cnt % 2); } + +static void ipp_flip_left_right(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t out(dim, dim); + + IppiSize srcRoi = { dim, dim }; + + for (auto _ : state) { + // The code to benchmark + auto res = ippiMirror_8u_C1R( + boost::gil::interleaved_view_get_raw_data(const_view(in)), (int)const_view(in).pixels().row_size(), + boost::gil::interleaved_view_get_raw_data(view(out)), (int)view(in).pixels().row_size(), + srcRoi, + ippAxsVertical); + + if (res != ippStsNoErr) + state.SkipWithError("ipp_flip_left_right failed"); + } + + if (!equal_pixels(flipped_left_right_view(const_view(in)), const_view(out))) + state.SkipWithError("ipp_flip_left_right wrong result"); +} +BENCHMARK(ipp_flip_left_right)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void ipp_flip_left_right_inplace(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t in_ref(in); + + IppiSize srcRoi = { dim, dim }; + + for (auto _ : state) { + // The code to benchmark + auto res = ippiMirror_8u_C1IR( + boost::gil::interleaved_view_get_raw_data(view(in)), (int)view(in).pixels().row_size(), + srcRoi, + ippAxsVertical); + + if (res != ippStsNoErr) + state.SkipWithError("ipp_flip_left_right_inplace failed"); + } + + if (is_odd(state.iterations())) { + if (!equal_pixels(flipped_left_right_view(const_view(in_ref)), const_view(in))) + state.SkipWithError("ipp_flip_left_right_inplace wrong result"); + } + else { + if (!equal_pixels(const_view(in_ref), const_view(in))) + state.SkipWithError("ipp_flip_left_right_inplace wrong result"); + } +} +BENCHMARK(ipp_flip_left_right_inplace)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void ipp_flip_up_down(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t out(dim, dim); + + IppiSize srcRoi = { dim, dim }; + + for (auto _ : state) { + // The code to benchmark + auto res = ippiMirror_8u_C1R( + boost::gil::interleaved_view_get_raw_data(const_view(in)), (int)const_view(in).pixels().row_size(), + boost::gil::interleaved_view_get_raw_data(view(out)), (int)view(out).pixels().row_size(), + srcRoi, + ippAxsHorizontal); + + if (res != ippStsNoErr) + state.SkipWithError("ipp_flip_up_down failed"); + } + + if (is_odd(state.iterations())) { + if (!equal_pixels(flipped_up_down_view(const_view(in)), const_view(out))) + state.SkipWithError("ipp_flip_up_down wrong result"); + } + else { + if (!equal_pixels(const_view(in), const_view(out))) + state.SkipWithError("ipp_flip_up_down wrong result"); + } +} +BENCHMARK(ipp_flip_up_down)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void ipp_flip_up_down_inplace(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t in_ref(in); + + IppiSize srcRoi = { dim, dim }; + + for (auto _ : state) { + // The code to benchmark + auto res = ippiMirror_8u_C1IR( + boost::gil::interleaved_view_get_raw_data(view(in)), (int)view(in).pixels().row_size(), + srcRoi, + ippAxsHorizontal); + + if (res != ippStsNoErr) + state.SkipWithError("ipp_flip_up_down_inplace failed"); + } + + if (!equal_pixels(flipped_up_down_view(const_view(in_ref)), const_view(in))) + state.SkipWithError("ipp_flip_up_down_inplace wrong result"); +} +BENCHMARK(ipp_flip_up_down_inplace)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/ipp/view_transpose.cpp b/benchmark/google/ipp/view_transpose.cpp new file mode 100644 index 0000000000..037dc8b92b --- /dev/null +++ b/benchmark/google/ipp/view_transpose.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +#include + +inline bool is_odd(benchmark::IterationCount cnt) { return (cnt % 2); } + +static void ipp_transpose(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t out(dim, dim); + + IppiSize srcRoi = { dim, dim }; + + for (auto _ : state) { + // The code to benchmark + auto res = ippiTranspose_8u_C1R( + boost::gil::interleaved_view_get_raw_data(const_view(in)), (int) const_view(in).pixels().row_size(), + boost::gil::interleaved_view_get_raw_data(view(out)), (int)const_view(out).pixels().row_size(), + srcRoi); + + if (res != ippStsNoErr) + state.SkipWithError("ipp_transpose_inplace failed"); + } + + if (!equal_pixels(transposed_view(const_view(in)), const_view(out))) + state.SkipWithError("ipp_transpose wrong result"); +} +BENCHMARK(ipp_transpose)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void ipp_transpose_inplace(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + generate_pixels(view(in), [i = 0]() mutable -> std::uint8_t { return ++i; }); + gray8_image_t in_ref(in); + + if (!equal_pixels(const_view(in_ref), const_view(in))) + state.SkipWithError("ipp_transpose_inplace wrong init"); + + IppiSize srcRoi = { dim, dim }; + + for (auto _ : state) { + // The code to benchmark + auto res = ippiTranspose_8u_C1IR( + boost::gil::interleaved_view_get_raw_data(view(in)), (int)const_view(in).pixels().row_size(), + srcRoi); + + if (res != ippStsNoErr) + state.SkipWithError("ipp_transpose_inplace failed"); + } + + if (is_odd(state.iterations())) { + if (!equal_pixels(transposed_view(const_view(in_ref)), const_view(in))) + state.SkipWithError("ipp_transpose_inplace wrong result"); + } + else { + if (!equal_pixels(const_view(in_ref), const_view(in))) + state.SkipWithError("ipp_transpose_inplace wrong result"); + } +} +BENCHMARK(ipp_transpose_inplace)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/opencv/CMakeLists.txt b/benchmark/google/opencv/CMakeLists.txt new file mode 100644 index 0000000000..44d13ec7ba --- /dev/null +++ b/benchmark/google/opencv/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright (C) 2020 Samuel Debionne, ESRF. + +# Use, modification and distribution is subject to 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) + +#----------------------------------------------------------------------------- +# Find package OpenCV +#----------------------------------------------------------------------------- +find_package(OpenCV REQUIRED) + +#----------------------------------------------------------------------------- +# Build benchmarks +#----------------------------------------------------------------------------- +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) + file(GLOB_RECURSE _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp CONFIGURE_DEPEND) +else() + file(GLOB_RECURSE _benchmarks ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +endif() + +foreach(_benchmark ${_benchmarks}) + get_filename_component(_name ${_benchmark} NAME_WE) + set(_target benchmark_google_ocv_${_name}) + add_executable(${_target} ${_name}.cpp) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK=1) + target_include_directories(${_target} + PRIVATE + ${OpenCV_INCLUDE_DIRS} + ) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies + gil_googlebenchmark + ${OpenCV_LIBS}) +endforeach() + +unset(_benchmarks) +unset(_benchmark) diff --git a/benchmark/google/opencv/view_flip.cpp b/benchmark/google/opencv/view_flip.cpp new file mode 100644 index 0000000000..51d3d9e34a --- /dev/null +++ b/benchmark/google/opencv/view_flip.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +static void flip_up_down(benchmark::State& state) +{ + using namespace cv; + + int dim = state.range(0); + + Mat1b in(dim, dim); + Mat1b out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + flip(in,out,0); + } +} +BENCHMARK(flip_up_down)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void flip_left_right(benchmark::State& state) +{ + using namespace cv; + + int dim = state.range(0); + + Mat1b in(dim, dim); + Mat1b out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + flip(in,out,1); + } +} +BENCHMARK(flip_left_right)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/opencv/view_rotation.cpp b/benchmark/google/opencv/view_rotation.cpp new file mode 100644 index 0000000000..3e5270c13b --- /dev/null +++ b/benchmark/google/opencv/view_rotation.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +static void rotation_cc90(benchmark::State& state) +{ + using namespace cv; + + size_t dim = state.range(0); + + Mat1b in(dim, dim); + Mat1b out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + rotate(in,out,ROTATE_90_CLOCKWISE); + } +} +BENCHMARK(rotation_cc90)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void rotation_ccw90(benchmark::State& state) +{ + using namespace cv; + + size_t dim = state.range(0); + + Mat1b in(dim, dim); + Mat1b out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + rotate(in,out,ROTATE_90_COUNTERCLOCKWISE); + } +} +BENCHMARK(rotation_ccw90)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void rotation_180(benchmark::State& state) +{ + using namespace cv; + + size_t dim = state.range(0); + + Mat1b in(dim, dim); + Mat1b out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + rotate(in,out,ROTATE_180); + } +} +BENCHMARK(rotation_180)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/opencv/view_transpose.cpp b/benchmark/google/opencv/view_transpose.cpp new file mode 100644 index 0000000000..9bb48f0dbe --- /dev/null +++ b/benchmark/google/opencv/view_transpose.cpp @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +static void transpose(benchmark::State& state) +{ + using namespace cv; + + size_t dim = state.range(0); + + Mat1b in(dim, dim); + Mat1b out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + transpose(in, out); + } +} +BENCHMARK(transpose)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void transpose_inplace(benchmark::State& state) +{ + using namespace cv; + + size_t dim = state.range(0); + + Mat1b in(dim, dim); + + for (auto _ : state) { + // The code to benchmark + transpose(in, in); + } +} +BENCHMARK(transpose_inplace)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/pixel_channel_arithmetics.cpp b/benchmark/google/pixel_channel_arithmetics.cpp new file mode 100644 index 0000000000..c2d5d1fb38 --- /dev/null +++ b/benchmark/google/pixel_channel_arithmetics.cpp @@ -0,0 +1,232 @@ +#include +#include +#include + +#include + +#include +#include + +using namespace boost::gil; + +static void std_algo(benchmark::State& state) +{ + std::vector img(1024 * 1024, 0); + + for (auto _ : state) + // The code to benchmark + for (auto&& p : img) + p += 1; + + if (!std::all_of(img.begin(), img.end(), [&state](auto p) { return p == state.iterations(); })) + state.SkipWithError("std_algo wrong result"); +} +BENCHMARK(std_algo); + +static void gil_algo_stdfunction(benchmark::State& state) +{ + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + + //Pixel operation + auto op = std::bind( + gil::pixel_plus_t(), + _1, gray16_pixel_t{ 1 }); + + for (auto _ : state) + // The code to benchmark + gil::transform_pixels(gil::const_view(img), gil::view(img), op); + + if (!std::all_of(gil::view(img).begin(), gil::view(img).end(), [&state](auto p) { return p == state.iterations(); })) + state.SkipWithError("gil_algo_stdfunction wrong result"); +} +BENCHMARK(gil_algo_stdfunction); + +static void gil_algo_lambda(benchmark::State& state) +{ + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + + //Pixel operation + auto op2 = [second_arg = gray16_pixel_t{ 1 }](gray16_pixel_t value) { + return gil::pixel_plus_t()(value, second_arg); + }; + + for (auto _ : state) + // The code to benchmark + gil::transform_pixels(gil::const_view(img), gil::view(img), op2); + + if (!std::all_of(gil::view(img).begin(), gil::view(img).end(), [&state](auto p) { return p == state.iterations(); })) + state.SkipWithError("gil_algo_lambda wrong result"); +} +BENCHMARK(gil_algo_lambda); + +static void gil_algo_lambda_plus(benchmark::State& state) +{ + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + + //Pixel operation + auto op2 = [second_arg = gray16_pixel_t{ 1 }](gray16_pixel_t value) { + return value + second_arg; + }; + + for (auto _ : state) + // The code to benchmark + gil::transform_pixels(gil::const_view(img), gil::view(img), op2); + + if (!std::all_of(gil::view(img).begin(), gil::view(img).end(), [&state](auto p) { return p == state.iterations(); })) + state.SkipWithError("gil_algo_lambda_plus wrong result"); +} +BENCHMARK(gil_algo_lambda_plus); + +static void gil_algo_lambda_copy(benchmark::State& state) +{ + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + gil::gray16_image_t img2(1024, 1024, gray16_pixel_t{ 0 }); + + auto input_view = gil::const_view(img); + auto output_view = gil::view(img2); + auto plus = gil::pixel_plus_t{}; + + //Pixel operation + auto op2 = [second_arg = gray16_pixel_t{ 1 }](gray16_pixel_t value) { + return gil::pixel_plus_t()(value, second_arg); + }; + + for (auto _ : state) + // The code to benchmark + gil::transform_pixels(input_view, output_view, op2); + + if (!std::all_of(output_view.begin(), output_view.end(), [&state](auto p) { return p == 1; })) + state.SkipWithError("gil_algo_lambda_copy wrong result"); +} +BENCHMARK(gil_algo_lambda_copy); + +static void gil_algo_rangefor(benchmark::State& state) { + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + gil::gray16_image_t img2(1024, 1024, gray16_pixel_t{ 0 }); + + auto input_view = gil::const_view(img); + auto output_view = gil::view(img2); + auto plus = gil::pixel_plus_t{}; + + //Pixel operation + auto op2 = [second_arg = gray16_pixel_t{ 1 }](gray16_pixel_t value) { + return gil::pixel_plus_t()(value, second_arg); + }; + + for (auto _ : state) { + auto d_first = output_view.begin(); + for (auto pixel : input_view) { + *d_first++ = op2(pixel); + } + } + + if (!std::all_of(output_view.begin(), output_view.end(), [&state](auto p) { return p == 1; })) + state.SkipWithError("gil_algo_rangefor wrong result"); +} +BENCHMARK(gil_algo_rangefor); + +static void gil_algo_rangefor_plus(benchmark::State& state) { + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + gil::gray16_image_t img2(1024, 1024, gray16_pixel_t{ 0 }); + + auto input_view = gil::const_view(img); + auto output_view = gil::view(img2); + auto plus = gil::pixel_plus_t{}; + + //Pixel operation + auto op2 = [second_arg = gray16_pixel_t{ 1 }](gray16_pixel_t value) { + return gil::pixel_plus_t()(value, second_arg); + }; + + for (auto _ : state) { + auto d_first = output_view.begin(); + for (const auto& pixel : input_view) { + *d_first++ = pixel + 1; + } + } + + if (!std::all_of(output_view.begin(), output_view.end(), [&state](auto p) { return p == 1; })) + state.SkipWithError("gil_algo_rangefor_plus wrong result"); +} +BENCHMARK(gil_algo_rangefor_plus); + +static void gil_algo_copy_mem(benchmark::State& state) { + using namespace std::placeholders; + namespace gil = boost::gil; + + gil::gray16_image_t img(1024, 1024, gray16_pixel_t{ 0 }); + gil::gray16_image_t img2(1024, 1024, gray16_pixel_t{ 0 }); + + auto input_view = gil::const_view(img); + auto output_view = gil::view(img2); + + //Pixel operation + auto op2 = [second_arg = gray16_pixel_t{ 1 }](gray16_pixel_t value) { + return gil::pixel_plus_t()(value, second_arg); + }; + + for (auto _ : state) { + auto start = std::addressof(input_view(0, 0)); + auto end = std::addressof(input_view(input_view.width() - 1, input_view.height() - 1)) + 1; + auto d_start = std::addressof(output_view(0, 0)); + while (start != end) { + *d_start++ = *start++ + 1; + } + } + + if (!std::all_of(output_view.begin(), output_view.end(), [&state](auto p) { return p == 1; })) + state.SkipWithError("gil_algo_copy_mem wrong result"); +} +BENCHMARK(gil_algo_copy_mem); + +static void gil_algo_pixel_vector(benchmark::State& state) { + using namespace std::placeholders; + namespace gil = boost::gil; + std::vector img(1024 * 1024, gil::gray16_pixel_t(0)); + + for (auto _ : state) { + for (auto& pixel : img) + pixel.at(std::integral_constant{}) = pixel.at(std::integral_constant{}) + 1; + } + + if (!std::all_of(img.begin(), img.end(), [&state](auto p) {return p == state.iterations(); })) + state.SkipWithError("gil_algo_pixel_vector wrong result"); +} +BENCHMARK(gil_algo_pixel_vector); + +static void gil_algo_pixel_vector_reinterpret(benchmark::State& state) { + using namespace std::placeholders; + namespace gil = boost::gil; + std::vector img(1024 * 1024, gil::gray16_pixel_t(0)); + + for (auto _ : state) { + auto start = reinterpret_cast(img.data()); + auto end = start + img.size(); + for (; start != end; ++start) + *start += 1; + } + + if (!std::all_of(img.begin(), img.end(), [&state](auto p) {return p == state.iterations(); })) + state.SkipWithError("gil_algo_pixel_vector wrong result"); +} +BENCHMARK(gil_algo_pixel_vector_reinterpret); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/view_flip.cpp b/benchmark/google/view_flip.cpp new file mode 100644 index 0000000000..1d61d09a72 --- /dev/null +++ b/benchmark/google/view_flip.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +static void flip_up_down(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto flipped = flipped_up_down_view(const_view(in)); + copy_pixels(flipped, view(out)); + } +} +BENCHMARK(flip_up_down)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void flip_left_right(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto flipped = flipped_left_right_view(const_view(in)); + copy_pixels(flipped, view(out)); + } +} +BENCHMARK(flip_left_right)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/view_rotation.cpp b/benchmark/google/view_rotation.cpp new file mode 100644 index 0000000000..497b46e2de --- /dev/null +++ b/benchmark/google/view_rotation.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +static void rotation_cc90(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto rotated = rotated90cw_view(const_view(in)); + copy_pixels(rotated, view(out)); + } +} +BENCHMARK(rotation_cc90)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void rotation_cc90_v2(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto transposed = transposed_view(const_view(in)); + copy_pixels(transposed, view(out)); + auto flipped = flipped_left_right_view(const_view(out)); + copy_pixels(flipped, view(out)); + } +} +BENCHMARK(rotation_cc90_v2)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void rotation_ccw90(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto rotated = rotated90ccw_view(const_view(in)); + copy_pixels(rotated, view(out)); + } +} +BENCHMARK(rotation_ccw90)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void rotation_ccw90_v2(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto transposed = transposed_view(const_view(in)); + copy_pixels(transposed, view(out)); + auto flipped = flipped_up_down_view(const_view(out)); + copy_pixels(flipped, view(out)); + } +} +BENCHMARK(rotation_ccw90_v2)->RangeMultiplier(2)->Range(256, 8 << 10); + +static void rotation_180(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto rotated = rotated180_view(const_view(in)); + copy_pixels(rotated, view(out)); + } +} +BENCHMARK(rotation_180)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/view_transpose.cpp b/benchmark/google/view_transpose.cpp new file mode 100644 index 0000000000..887672788f --- /dev/null +++ b/benchmark/google/view_transpose.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +static void transpose(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + auto transposed = transposed_view(const_view(in)); + copy_pixels(transposed, view(out)); + } +} +BENCHMARK(transpose)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); diff --git a/benchmark/google/view_transpose_impl.cpp b/benchmark/google/view_transpose_impl.cpp new file mode 100644 index 0000000000..ca82db09a4 --- /dev/null +++ b/benchmark/google/view_transpose_impl.cpp @@ -0,0 +1,93 @@ +// Copyright (C) 2020 Samuel Debionne, ESRF. + +// Use, modification and distribution is subject to 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) + +#include + +#include + +#define GIL_ENABLE_UNROLLED 1 + +namespace boost { + namespace gil { + + template + static void transpose(const SrcView& src, const DstView& dst) + { + int i, j; + auto m = src.width(); + auto n = src.height(); + + static const int block_size = 4; + + std::array l; + std::generate(l.begin(), l.end(), [loc = src.xy_at(0, 0), i = 0]() mutable { + auto res = loc.cache_location(i % block_size, i / block_size); + i++; + return res; + }); + +#if GIL_ENABLE_UNROLLED + for (i = 0; i <= m - block_size; i += block_size) + { + auto d0 = dst.row_begin(i); + auto d1 = dst.row_begin(i + 1); + auto d2 = dst.row_begin(i + 2); + auto d3 = dst.row_begin(i + 3); + + for (j = 0; j <= n - block_size; j += block_size) + { + auto s = src.xy_at(i, j); + + d0[j] = s[l[0]]; d0[j + 1] = s[l[4]]; d0[j + 2] = s[l[8]]; d0[j + 3] = s[l[12]]; + d1[j] = s[l[1]]; d1[j + 1] = s[l[5]]; d1[j + 2] = s[l[9]]; d1[j + 3] = s[l[13]]; + d2[j] = s[l[2]]; d2[j + 1] = s[l[6]]; d2[j + 2] = s[l[10]]; d2[j + 3] = s[l[14]]; + d3[j] = s[l[3]]; d3[j + 1] = s[l[7]]; d3[j + 2] = s[l[11]]; d3[j + 3] = s[l[15]]; + } + + for (; j < n; j++) + { + auto s = src.xy_at(i, j); + + d0[j] = s[l[0]]; d1[j] = s[l[1]]; d2[j] = s[l[2]]; d3[j] = s[l[3]]; + } + } +#endif + for (; i < m; i++) + { + auto d0 = dst.row_begin(i); +#if GIL_ENABLE_UNROLLED + for (j = 0; j <= n - block_size; j += block_size) + { + auto s = src.xy_at(i, j); + + d0[j] = s[l[0]]; d0[j + 1] = s[l[4]]; d0[j + 2] = s[l[8]]; d0[j + 3] = s[l[12]]; + } +#endif + for (; j < n; j++) + d0[j] = src(i, j); + } + } + + } +} //namespace boost::gil + +static void transpose(benchmark::State& state) +{ + using namespace boost::gil; + + size_t dim = state.range(0); + + gray8_image_t in(dim, dim); + gray8_image_t out(dim, dim); + + for (auto _ : state) { + // The code to benchmark + transpose(const_view(in), view(out)); + } +} +BENCHMARK(transpose)->RangeMultiplier(2)->Range(256, 8 << 10); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..7359abc758 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,7 @@ +comment: + layout: "diff" + behavior: default + branches: + - master + - develop + require_changes: true diff --git a/conanfile.txt b/conanfile.txt index f3f7c6304b..62af46128b 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,14 +1,25 @@ # -# Copyright (c) 2018-2019 Mateusz Loskot +# Copyright (c) 2018-2022 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) # [requires] -libpng/1.6.37@bincrafters/stable -libjpeg/9c@bincrafters/stable -libtiff/4.0.9@bincrafters/stable +libjpeg/9d +libpng/1.6.37 +libtiff/4.1.0 + +# Add required remote: +# conan remote add conan-mpusz https://api.bintray.com/conan/mpusz/conan-mpusz +#google-benchmark/1.4.1@mpusz/stable + +# remote add ppodsiadly https://api.bintray.com/conan/ppodsiadly/conan +#celero/2.4.0@ppodsiadly/stable + +[options] +celero:shared=False [generators] -cmake +CMakeDeps +CMakeToolchain diff --git a/doc/README.md b/doc/README.md index 48ac0b32a1..de7700be63 100644 --- a/doc/README.md +++ b/doc/README.md @@ -5,7 +5,7 @@ A simple guide about writing and building documentation for Boost.GIL. ## Prerequisites - Python 3 -- [Install Sphinx](#install-sphinx) (see `requirements.txt`) +- Install [Sphinx](https://www.sphinx-doc.org/en/master/index.html) (see `requirements.txt`) - Install [Doxygen](http://www.doxygen.org) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index 6be6d55e28..17281c7352 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -49,6 +49,9 @@ {%- for scriptfile in script_files %} {%- endfor %} + + + {%- if use_opensearch %} `_ - - `ChannelValueConcept `_ - - `MutableChannelConcept `_ - - `ChannelsCompatibleConcept `_ - - `ChannelConvertibleConcept `_ + - `ChannelConcept <../reference/structboost_1_1gil_1_1_channel_concept.html>`_ + - `ChannelValueConcept <../reference/structboost_1_1gil_1_1_channel_value_concept.html>`_ + - `MutableChannelConcept <../reference/structboost_1_1gil_1_1_mutable_channel_concept.html>`_ + - `ChannelsCompatibleConcept <../reference/structboost_1_1gil_1_1_channels_compatible_concept.html>`_ + - `ChannelConvertibleConcept <../reference/structboost_1_1gil_1_1_channel_convertible_concept.html>`_ Models ------ diff --git a/doc/design/color_base.rst b/doc/design/color_base.rst index c39cb7aa31..f774d2d62c 100644 --- a/doc/design/color_base.rst +++ b/doc/design/color_base.rst @@ -132,7 +132,7 @@ bases: .. code-block:: cpp - // Metafunction returning an mpl::int_ equal to the number of elements in the color base + // Metafunction returning an integral constant equal to the number of elements in the color base template struct size; // Returns the type of the return value of semantic_at_c(color_base) diff --git a/doc/design/color_space.rst b/doc/design/color_space.rst index 413f09c703..7c74083eb1 100644 --- a/doc/design/color_space.rst +++ b/doc/design/color_space.rst @@ -17,9 +17,9 @@ same set of colors in the same order). .. seealso:: - - `ColorSpaceConcept `_ - - `ColorSpacesCompatibleConcept `_ - - `ChannelMappingConcept `_ + - `ColorSpaceConcept <../reference/structboost_1_1gil_1_1_color_space_concept.html>`_ + - `ColorSpacesCompatibleConcept <../reference/structboost_1_1gil_1_1_color_spaces_compatible_concept.html>`_ + - `ChannelMappingConcept <../reference/structboost_1_1gil_1_1_channel_mapping_concept.html>`_ Models ------ @@ -45,7 +45,7 @@ Besides the standard layouts, it also provides: - ``abgr_layout_t`` - ``argb_layout_t`` -As an example, here is how GIL defines the RGBA color space:: +As an example, here is how GIL defines the RGBA color space: .. code-block:: cpp @@ -53,12 +53,12 @@ As an example, here is how GIL defines the RGBA color space:: struct green_t {}; struct blue_t {}; struct alpha_t {}; - rgba_t = using mpl::vector4; + rgba_t = using mp11::mp_list; The ordering of the channels in the color space definition specifies their semantic order. For example, ``red_t`` is the first semantic channel of ``rgba_t``. While there is a unique semantic ordering of the channels in a -color space, channels may vary in their physical ordering in memory +color space, channels may vary in their physical ordering in memory. The mapping of channels is specified by ``ChannelMappingConcept``, which is an MPL random access sequence of integral types. @@ -71,7 +71,7 @@ Thus they are grouped in GIL's layout: template < typename ColorSpace, - typename ChannelMapping = mpl::range_c::value> + typename ChannelMapping = mp11::make_integer_sequence::value> > struct layout { @@ -84,6 +84,6 @@ Here is how to create layouts for the RGBA color space: .. code-block:: cpp using rgba_layout_t = layout; // default ordering is 0,1,2,3... - using bgra_layout_t = layout>; - using argb_layout_t = layout>; - using abgr_layout_t = layout>; + using bgra_layout_t = layout>; + using argb_layout_t = layout>; + using abgr_layout_t = layout>; diff --git a/doc/design/concepts.rst b/doc/design/concepts.rst index 47faa7e5d5..7aac361380 100644 --- a/doc/design/concepts.rst +++ b/doc/design/concepts.rst @@ -8,7 +8,7 @@ algorithmic guarantees. For example, GIL class ``pixel`` is a model of GIL ``PixelConcept``. The user may substitute the pixel class with one of their own, and, as long as it satisfies the requirements of ``PixelConcept``, all other GIL classes and algorithms can be used with it. -See more about concepts is avaialble at +See more about concepts at `Generic Programming in ConceptC++ `_ In this document we will use a syntax for defining concepts that is described diff --git a/doc/design/dynamic_image.rst b/doc/design/dynamic_image.rst index f43c5eaffd..ec9b01e3fb 100644 --- a/doc/design/dynamic_image.rst +++ b/doc/design/dynamic_image.rst @@ -103,7 +103,7 @@ GIL ``any_image_view`` and ``any_image`` are subclasses of ``variant``: y_coord_t height() const; }; -Operations are invoked on variants via ``apply_operation`` passing a +Operations are invoked on variants via ``variant2::visit`` passing a function object to perform the operation. The code for every allowed type in the variant is instantiated and the appropriate instantiation is selected via a switch statement. Since image view algorithms @@ -129,7 +129,7 @@ pixels. There is no "any_pixel" or "any_pixel_iterator" in GIL. Such constructs could be provided via the ``variant`` mechanism, but doing so would result in inefficient algorithms, since the type resolution would have to be performed per pixel. Image-level algorithms should be -implemented via ``apply_operation``. That said, many common operations +implemented via ``variant2::visit``. That said, many common operations are shared between the static and dynamic types. In addition, all of the image view transformations and many STL-like image view algorithms have overloads operating on ``any_image_view``, as illustrated with @@ -159,14 +159,14 @@ upside down: .. code-block:: cpp - #include + #include template // Could be rgb8_image_t or any_image<...> void save_180rot(const std::string& file_name) { Image img; - jpeg_read_image(file_name, img); - jpeg_write_view(file_name, rotated180_view(view(img))); + read_image(file_name, img, jpeg_tag()); + write_view(file_name, rotated180_view(view(img)), jpeg_tag()); } It can be instantiated with either a compile-time or a runtime image @@ -180,14 +180,13 @@ implemented: template typename dynamic_xy_step_type::type rotated180_view(const View& src) { ... } - namespace detail - { + namespace detail { // the function, wrapped inside a function object template struct rotated180_view_fn { typedef Result result_type; template result_type operator()(const View& src) const - { + { return result_type(rotated180_view(src)); } }; @@ -195,10 +194,11 @@ implemented: // overloading of the function using variant. Takes and returns run-time bound view. // The returned view has a dynamic step - template inline // Models MPL Random Access Container of models of ImageViewConcept - typename dynamic_xy_step_type >::type rotated180_view(const any_image_view& src) + template inline + typename dynamic_xy_step_type>::type rotated180_view(const any_image_view& src) { - return apply_operation(src,detail::rotated180_view_fn >::type>()); + using result_view_t = typename dynamic_xy_step_type>::type; + return variant2::visit(detail::rotated180_view_fn(), src); } Variants should be used with caution (especially algorithms that take diff --git a/doc/design/image.rst b/doc/design/image.rst index a18baa5598..d62ac2eecd 100644 --- a/doc/design/image.rst +++ b/doc/design/image.rst @@ -73,9 +73,9 @@ because immutable images are not very useful. .. seealso:: - - `RandomAccessNDImageConcept `_ - - `RandomAccess2DImageConcept `_ - - `ImageConcept `_ + - `RandomAccessNDImageConcept <../reference/structboost_1_1gil_1_1_random_access_n_d_image_concept.html>`_ + - `RandomAccess2DImageConcept <../reference/structboost_1_1gil_1_1_random_access2_d_image_concept.html>`_ + - `ImageConcept <../reference/structboost_1_1gil_1_1_image_concept.html>`_ Models ------ diff --git a/doc/design/image_view.rst b/doc/design/image_view.rst index a1140a6730..c40d2ab859 100644 --- a/doc/design/image_view.rst +++ b/doc/design/image_view.rst @@ -151,13 +151,13 @@ compatible. .. seealso:: - - `RandomAccessNDImageViewConcept `_ - - `MutableRandomAccessNDImageViewConcept `_ - - `RandomAccess2DImageViewConcept `_ - - `MutableRandomAccess2DImageViewConcept `_ - - `ImageViewConcept `_ - - `MutableImageViewConcept `_ - - `ViewsCompatibleConcept `_ + - `RandomAccessNDImageViewConcept <../reference/structboost_1_1gil_1_1_random_access_n_d_image_view_concept.html>`_ + - `MutableRandomAccessNDImageViewConcept <../reference/structboost_1_1gil_1_1_mutable_random_access_n_d_image_view_concept.html>`_ + - `RandomAccess2DImageViewConcept <../reference/structboost_1_1gil_1_1_random_access2_d_image_view_concept.html>`_ + - `MutableRandomAccess2DImageViewConcept <../reference/structboost_1_1gil_1_1_mutable_random_access2_d_image_view_concept.html>`_ + - `ImageViewConcept <../reference/structboost_1_1gil_1_1_image_view_concept.html>`_ + - `MutableImageViewConcept <../reference/structboost_1_1gil_1_1_mutable_image_view_concept.html>`_ + - `ViewsCompatibleConcept <../reference/structboost_1_1gil_1_1_views_compatible_concept.html>`_ Models ------ diff --git a/doc/design/metafunctions.rst b/doc/design/metafunctions.rst index 1875f822c1..21a9a55bda 100644 --- a/doc/design/metafunctions.rst +++ b/doc/design/metafunctions.rst @@ -209,7 +209,7 @@ just like ``View``, but being grayscale and planar: .. code-block:: cpp - using VT = typename derived_view_type::type; + using VT = typename derived_view_type::type; Type traits ----------- diff --git a/doc/design/pixel.rst b/doc/design/pixel.rst index 1860a7d3c4..1f8478ff1c 100644 --- a/doc/design/pixel.rst +++ b/doc/design/pixel.rst @@ -119,15 +119,15 @@ both, but only pixel values model the latter. .. seealso:: - - `PixelBasedConcept

`_ - - `PixelConcept `_ - - `MutablePixelConcept `_ - - `PixelValueConcept `_ - - `HomogeneousPixelConcept `_ - - `MutableHomogeneousPixelConcept `_ - - `HomogeneousPixelValueConcept `_ - - `PixelsCompatibleConcept `_ - - `PixelConvertibleConcept `_ + - `PixelBasedConcept

<../reference/structboost_1_1gil_1_1_pixel_based_concept.html>`_ + - `PixelConcept <../reference/structboost_1_1gil_1_1_pixel_concept.html>`_ + - `MutablePixelConcept <../reference/structboost_1_1gil_1_1_mutable_pixel_concept.html>`_ + - `PixelValueConcept <../reference/structboost_1_1gil_1_1_pixel_value_concept.html>`_ + - `HomogeneousPixelConcept <../reference/structboost_1_1gil_1_1_homogeneous_pixel_based_concept.html>`_ + - `MutableHomogeneousPixelConcept <../reference/structboost_1_1gil_1_1_mutable_homogeneous_pixel_concept.html>`_ + - `HomogeneousPixelValueConcept <../reference/structboost_1_1gil_1_1_homogeneous_pixel_value_concept.html>`_ + - `PixelsCompatibleConcept <../reference/structboost_1_1gil_1_1_pixels_compatible_concept.html>`_ + - `PixelConvertibleConcept <../reference/structboost_1_1gil_1_1_pixel_convertible_concept.html>`_ Models ------ @@ -183,13 +183,13 @@ such packed pixel formats: .. code-block:: cpp // define an rgb565 pixel - typedef packed_pixel_type, rgb_layout_t>::type rgb565_pixel_t; + typedef packed_pixel_type, rgb_layout_t>::type rgb565_pixel_t; function_requires >(); static_assert(sizeof(rgb565_pixel_t) == 2, ""); // define a bgr556 pixel - typedef packed_pixel_type, bgr_layout_t>::type bgr556_pixel_t; + typedef packed_pixel_type, bgr_layout_t>::type bgr556_pixel_t; function_requires >(); @@ -211,7 +211,7 @@ pixels and pixel iterators: .. code-block:: cpp // Mutable reference to a BGR232 pixel - typedef const bit_aligned_pixel_reference, bgr_layout_t, true> bgr232_ref_t; + typedef const bit_aligned_pixel_reference, bgr_layout_t, true> bgr232_ref_t; // A mutable iterator over BGR232 pixels typedef bit_aligned_pixel_iterator bgr232_ptr_t; @@ -255,7 +255,7 @@ algorithms and metafunctions of color bases can work with them as well: BOOST_MPL_ASSERT(num_channels::value == 3); BOOST_MPL_ASSERT((is_same::type, bits8>)); BOOST_MPL_ASSERT((is_same::type, rgb_t> )); - BOOST_MPL_ASSERT((is_same::type, mpl::vector3_c > )); + BOOST_MPL_ASSERT((is_same::type, mp11::mp_list_c > )); // Pixels contain just the three channels and nothing extra BOOST_MPL_ASSERT(sizeof(rgb8_pixel_t)==3); diff --git a/doc/design/pixel_iterator.rst b/doc/design/pixel_iterator.rst index 877c7a95d7..1bd2c73232 100644 --- a/doc/design/pixel_iterator.rst +++ b/doc/design/pixel_iterator.rst @@ -36,8 +36,8 @@ plain iterators or adaptors over another pixel iterator: .. seealso:: - - `PixelIteratorConcept `_ - - `MutablePixelIteratorConcept `_ + - `PixelIteratorConcept <../reference/group___pixel_iterator_concept_pixel_iterator.html>`_ + - `MutablePixelIteratorConcept <../reference/structboost_1_1gil_1_1_mutable_pixel_iterator_concept.html>`_ Models ^^^^^^ @@ -101,7 +101,7 @@ type, and a metafunction to rebind to another base iterator: concept IteratorAdaptorConcept { - where SameType::type, mpl::true_>; + where SameType::type, mp11::mp_true>; typename iterator_adaptor_get_base; where Metafunction >; @@ -120,8 +120,8 @@ type, and a metafunction to rebind to another base iterator: .. seealso:: - - `IteratorAdaptorConcept `_ - - `MutableIteratorAdaptorConcept `_ + - `IteratorAdaptorConcept <../reference/structboost_1_1gil_1_1_iterator_adaptor_concept.html>`_ + - `MutableIteratorAdaptorConcept <../reference/structboost_1_1gil_1_1_mutable_iterator_adaptor_concept.html>`_ Models ^^^^^^ @@ -262,10 +262,10 @@ support ``HasDynamicXStepTypeConcept``. .. seealso:: - - `StepIteratorConcept `_ - - `MutableStepIteratorConcept `_ - - `MemoryBasedIteratorConcept `_ - - `HasDynamicXStepTypeConcept `_ + - `StepIteratorConcept <../reference/structboost_1_1gil_1_1_step_iterator_concept.html>`_ + - `MutableStepIteratorConcept <../reference/structboost_1_1gil_1_1_mutable_step_iterator_concept.html>`_ + - `MemoryBasedIteratorConcept <../reference/structboost_1_1gil_1_1_memory_based_iterator_concept.html>`_ + - `HasDynamicXStepTypeConcept <../reference/structboost_1_1gil_1_1_has_dynamic_x_step_type_concept.html>`_ Models ^^^^^^ diff --git a/doc/design/pixel_locator.rst b/doc/design/pixel_locator.rst index 2d9d5ace8b..dfad531e90 100644 --- a/doc/design/pixel_locator.rst +++ b/doc/design/pixel_locator.rst @@ -159,14 +159,14 @@ y dimension types are the same. They model the following concept: .. seealso:: - - `HasDynamicYStepTypeConcept `_ - - `HasTransposedTypeConcept `_ - - `RandomAccessNDLocatorConcept `_ - - `MutableRandomAccessNDLocatorConcept `_ - - `RandomAccess2DLocatorConcept `_ - - `MutableRandomAccess2DLocatorConcept `_ - - `PixelLocatorConcept `_ - - `MutablePixelLocatorConcept `_ + - `HasDynamicYStepTypeConcept <../reference/structboost_1_1gil_1_1_has_dynamic_y_step_type_concept.html>`_ + - `HasTransposedTypeConcept <../reference/structboost_1_1gil_1_1_has_transposed_type_concept.html>`_ + - `RandomAccessNDLocatorConcept <../reference/structboost_1_1gil_1_1_random_access_n_d_locator_concept.html>`_ + - `MutableRandomAccessNDLocatorConcept <../reference/structboost_1_1gil_1_1_mutable_random_access_n_d_locator_concept.html>`_ + - `RandomAccess2DLocatorConcept <../reference/structboost_1_1gil_1_1_random_access2_d_locator_concept.html>`_ + - `MutableRandomAccess2DLocatorConcept <../reference/structboost_1_1gil_1_1_mutable_random_access2_d_locator_concept.html>`_ + - `PixelLocatorConcept <../reference/structboost_1_1gil_1_1_pixel_locator_concept.html>`_ + - `MutablePixelLocatorConcept <../reference/structboost_1_1gil_1_1_mutable_pixel_locator_concept.html>`_ Models ------ diff --git a/doc/design/point.rst b/doc/design/point.rst index 035771b50b..28158ab2db 100644 --- a/doc/design/point.rst +++ b/doc/design/point.rst @@ -39,15 +39,15 @@ in which both dimensions are of the same type: typename value_type = axis<0>::type; const value_type& operator[](const T&, size_t i); - value_type& operator[]( T&, size_t i); + value_type& operator[]( T&, size_t i); value_type x,y; }; .. seealso:: - - `PointNDConcept `_ - - `Point2DConcept `_ + - `PointNDConcept <../reference/structboost_1_1gil_1_1_point_n_d_concept.html>`_ + - `Point2DConcept <../reference/structboost_1_1gil_1_1_point2_d_concept.html>`_ Models ------ diff --git a/doc/histogram/create.rst b/doc/histogram/create.rst new file mode 100644 index 0000000000..7df551c63e --- /dev/null +++ b/doc/histogram/create.rst @@ -0,0 +1,42 @@ +.. _create_histogram: + +Create a histogram +================== + +**Method 1** - Using the histogram constructor + +Syntax:: + + histogram + +``Type1`` .. ``TypeN`` correspond to the axis type of the N axes in the histogram + +Example: If we want a 3D histogram of Axis1 of type ``int``, Axis2 of type ``float`` and Axis3 of type ``std::string`` +we would do it this way:: + + histogram h; + +And done. + + +**Method 2** (TODO) - Using make_histogram() + +There is an alternative to create the histogram directly from +a GIL image view. + +This should be the preferred over method-1 when creating +histogram with GIL images, since it creates a histogram with axes configured +to match the GIL image. + +Also it is easier than method-1. + +Syntax:: + + auto h = make_histogram(view(image)); + +where ``image`` could be a ``gray8_image_t``/``rgb8_image_t`` object read from source. + + + + + diff --git a/doc/histogram/cumulative.rst b/doc/histogram/cumulative.rst new file mode 100644 index 0000000000..a769f9ddde --- /dev/null +++ b/doc/histogram/cumulative.rst @@ -0,0 +1,29 @@ +.. _cumulative_histogram: + +Making a cumulative histogram +============================= + +Overview +-------- + +A cumulative histogram is a histogram in which each bin stores the count / frequency of itself +as well as all the bins with keys 'smaller' than the particular bin. +As such, a notion of ordering among its keys should be existant in the histogram. + +The GIL histogram class has the ability to convert itself into its cumulative version. + +Since the container needs to first get an ordering +over the keys a key sorting takes place before calculating the cumulative histogram. + +Example: + + .. code-block:: cpp + + histogram h; + /* + Fill histogram ... + */ + auto h1 = cumulative_histogram(h); + +Tip: *In case you need to store the cumulative histogram elsewhere, consider creating a copy of the histogram +and then call the function*. \ No newline at end of file diff --git a/doc/histogram/extend.rst b/doc/histogram/extend.rst new file mode 100644 index 0000000000..3197af7841 --- /dev/null +++ b/doc/histogram/extend.rst @@ -0,0 +1,68 @@ +.. _extend_support: + +Extending the class +=================== + +.. contents:: + :local: + :depth: 1 + +User defined Axes +----------------- + +In case you need a histogram with an axes of an arbitrary type that is not identified by +the C++ Standard Library, you need to provide a overload for the hashing function that is +used in the histogram class. + +GIL's histogram class uses ``boost::hash_combine`` in a sub routine to generate a hash from +the key. + +So we need to provide an overload of ``boost::hash_combine`` for the purpose. + +For example, let's consider you need a histogram with an axis over class Test. + +.. code-block:: cpp + + // File : ./test.hpp + #include + #include + + struct Test + { + int a{0}; + Test() = default; + Test(int c) : a(c) {} + bool operator==(Test const& other) const + { + return (a == other.a); + } + }; + + namespace boost { + std::size_t hash_value(Test const& t) + { + // Replace with your hashing code + std::hash hasher; + return hasher(t.a); + } + } + +Now lets get to the usage example. + +.. code-block:: cpp + + #include + #include + #include + // Mind the order of include i.e. test.hpp before boost/gil.hpp + + using namespace boost::gil; + + int main() + { + boost::gil::histogram h; + Test t(1); + h(t) = 1; + std::cout< v; + gil::gray8_image_t img; + /* + Fill image ... + */ + gil::fill_histogram(view(img), v, false); + +#. **cumulative_histogram()** + + .. code-block:: cpp + + // Demo for std::vector + std::vector v; + /* + Fill vector... + */ + gil::cumulative_histogram(v); + + + + + + diff --git a/doc/histogram/fill.rst b/doc/histogram/fill.rst new file mode 100644 index 0000000000..cf3f913c6a --- /dev/null +++ b/doc/histogram/fill.rst @@ -0,0 +1,103 @@ +.. _fill_it: + +Fill histogram +============== + +.. contents:: + :local: + :depth: 1 + +Overview +-------- + +We will demonstrate the available options for filling an instance of the `histogram` class with +values that cater from the most simplest to the complex needs that might arise. + +Basic +----- + +#. Use operator() + + **Task** - Add value to a particular cell / key / bin in histogram + + .. code-block:: cpp + + histogram h; + h(1, 2) = 1; + +#. Use operator[] + + This requires to input the indices in a format the histogram internally stores its keys, + which is of ``std::tuple`` due to its simple interface. + + **Task** - Output value of a bin + + .. code-block:: cpp + + histogram h; + h(1, 2) = 1; + h[{1, 2}] += 1; // Note the curly braces reqd. to construct a tuple + std::cout< A; + /* + Fill value in A + */ + histogram B(A), C; + C = A; + +#. Use a GIL image view + + You can also use GIL images to directly fill histograms. + + **Task** - Fill histogram using GIL image view + + .. code-block:: cpp + + gil::gray8_image_t img; + /* + Fill img ... + */ + histogram h; + h.fill(view(img)); + // OR + gil::fill_histogram(view(img), h, false); // false if histogram needs to be cleared before filling + + +Advanced +-------- + +#. Fill histogram using only a few dimensions of image + + **Task** - Make an histogram over Red and Blue channel of an rgb image + + .. code-block:: cpp + + gil::rgb8_image_t img; + /* + Fill img ... + */ + histogram h; + fill_histogram<0, 2>(view(img), h, false); // 0 - red, 1 - green, 2 - blue + +#. Fill histogram using GIL pixel + + **Task** - Fill histogram bin using pixel construct in GIL + + .. code-block:: cpp + + gil::gray8_image_t img; + /* + Fill img ... + */ + histogram h; + gil::for_each_pixel(view(img), [](gil::gray8_pixel_t const& p){ + ++h[h.key_from_pixel(p)]; + }); + diff --git a/doc/histogram/index.rst b/doc/histogram/index.rst new file mode 100644 index 0000000000..ff8cc16bba --- /dev/null +++ b/doc/histogram/index.rst @@ -0,0 +1,20 @@ +Histogram +========= + +The GIL documentation sections listed below are dedicated to describe the +histogram class and functions used in many image processing algorithms. + +.. toctree:: + :maxdepth: 1 + :caption: Table of Contents + + overview + create + fill + subhistogram + cumulative + stl_compatibility + utilities + extend + limitations + extension/index diff --git a/doc/histogram/limitations.rst b/doc/histogram/limitations.rst new file mode 100644 index 0000000000..819d308f6e --- /dev/null +++ b/doc/histogram/limitations.rst @@ -0,0 +1,6 @@ +.. _limitations: + +Limitations +=========== + +*TODO* \ No newline at end of file diff --git a/doc/histogram/overview.rst b/doc/histogram/overview.rst new file mode 100644 index 0000000000..8b222659e8 --- /dev/null +++ b/doc/histogram/overview.rst @@ -0,0 +1,28 @@ +Overview +======== + +.. contents:: + :local: + :depth: 1 + +Description +----------- + +The histogram class is built on top of std::unordered_map to keep it compatible with other +STL algorithms. It can support any number of axes (known at compile time i.e. during class +instantiation). Suitable conversion routines from GIL image constructs to the histogram bin +key are shipped with the class itself. + + +Tutorials +--------- +The following flow is recommended: + #. :ref:`create_histogram` + #. :ref:`fill_it` + #. :ref:`sub_histogram` + #. :ref:`cumulative_histogram` + #. :ref:`stl_compatibility` + #. :ref:`extend_support` + #. :ref:`limitations` + +.. note:: To try out these tutorials you need to get a clone of the repository, since it is not yet released. diff --git a/doc/histogram/stl_compatibility.rst b/doc/histogram/stl_compatibility.rst new file mode 100644 index 0000000000..9b932a2cef --- /dev/null +++ b/doc/histogram/stl_compatibility.rst @@ -0,0 +1,6 @@ +.. _stl_compatibility: + +STL compatibility +================= + +*TODO* \ No newline at end of file diff --git a/doc/histogram/subhistogram.rst b/doc/histogram/subhistogram.rst new file mode 100644 index 0000000000..4e046a45a2 --- /dev/null +++ b/doc/histogram/subhistogram.rst @@ -0,0 +1,66 @@ +.. _sub_histogram: + +Making a sub-histogram +====================== + +Overview +-------- + +Sub-histogram is a subset of a histogram or one that is formed by masking out a +few axis of the parent histogram. + +GIL class histogram provides these functions as members and returns an instance of +the desired sub-class. + +#. Histogram over fewer axes + + **Task** - Get a 2D histogram from a 3D RGB histogram over red and green axes + + .. code-block:: cpp + + histogram h; + gil::rgb8_image_t img; + /* + Fill img ... + */ + fill_histogram(view(img), h, false); + auto sub_h = h.sub_histogram<0, 1>(); // sub_h is a 2D histogram + + Demo output: + + .. code-block:: cpp + + h is {{1, 2, 3} : 1, + {1, 4, 3} : 2, + {1, 2, 5} : 3} + sub_h would be {{1, 2} : 4, {1, 4} : 2} + +#. Histogram using a particular key range + + **Task** - Get a 2D histogram from a 3D RGB histogram for bins whose red color lie between 10 - 20 + and blue color lie between 2 - 10 + + .. code-block:: cpp + + histogram h; + gil::rgb8_image_t img; + /* + Fill img ... + */ + fill_histogram(view(img), h, false); + std::tuple low, high; + low = {10, 0, 2} // Since no check over blue channel pass any dummy value + high = {20, 0, 10} // Since no check over blue channel pass any dummy value + auto sub_h = h.sub_histogram<0, 2>(low, high); // In angle brackets pass the relevant dimensions in order + + Demo Output: + + .. code-block:: cpp + + h is {{11, 2, 3 } : 1, + {1 , 4, 11} : 2, + {1 , 2, 1 } : 3, + {11, 3, 3 } : 4} + sub_h would be {{11, 2, 3} : 1, {11, 3, 3} : 4} + + diff --git a/doc/histogram/utilities.rst b/doc/histogram/utilities.rst new file mode 100644 index 0000000000..5c4bf77554 --- /dev/null +++ b/doc/histogram/utilities.rst @@ -0,0 +1,6 @@ +.. _utilities: + +Utilities +========= + +*TODO* \ No newline at end of file diff --git a/doc/image_processing/contrast_enhancement/histogram_equalization.rst b/doc/image_processing/contrast_enhancement/histogram_equalization.rst new file mode 100644 index 0000000000..2a52118c66 --- /dev/null +++ b/doc/image_processing/contrast_enhancement/histogram_equalization.rst @@ -0,0 +1,100 @@ +.. _he: + +###################### +Histogram Equalization +###################### + +Description +----------- + +Histogram equalization also known as histogram flattening, is a non-linear image enhancement +algorithm that follows the idea that not only should an image cover the entire grayscale space +but also be uniformly distributed over that range. + +An ideal image would be the one having a flat histogram. + +Although care should be taken before applying a non-linear transformation on the image +histogram, there are good mathematical reasons why a flat histogram is the desired goal. + +A simple scenario would be an image with pixels concentrated in an interval, in which case +histogram equalization transforms pixels to achieve a flat histogram image. Thus enhancing +the image contrast. + +.. figure:: he_chart.png + :width: 200px + :align: center + :height: 100px + :alt: Could not load image. + :figclass: align-center + + Pixels concentrated in an interval spread out. + +Algorithm +--------- + +#. First calculate the histogram corresponding to input image. +#. If it is a multi channeled image (e.g. RGB), convert it to a independent color space + (like YCbCr, HSV etc.). +#. Then calculate the cumulative histogram over the input image. +#. Normalize the histogram to bring bin values between 0-1. For multi-channeled images + normalize each channel independently (by the number of pixels in image). +#. If the histogram of image is H(p\ :sub:`x`\) p\ :sub:`x`\ in [0, 255], then apply + the transformation p\ :sub:`x'`\ = H(p\ :sub:`x`\), p\ :sub:`x'`\ is pixel in output + image. + +**Explanation** + +Since we will be transforming the image to match a flat histogram, we match +the cumulative histogram of the image to the cumulative histogram of a flat histogram. + +Cumulative histogram of flat image is H(p\ :sub:`x'`\) = p\ :sub:`x'` . + +Hence, + + => H(p\ :sub:`x'`\) = H(p\ :sub:`x`\) + + => p\ :sub:`x'`\ = H(p\ :sub:`x`\) + +Results +------- +The algorithm is applied on a few standard images. One of the transformations in shown below: + +**Grayscale Image** + +.. figure:: https://github.com/boost-gil/test-images/blob/master/jpeg/suite/barbara.jpg + :width: 512px + :align: center + :height: 256px + :alt: Could not load image. + :figclass: align-center + +**RGB** + +.. figure:: https://github.com/boost-gil/test-images/blob/master/jpeg/suite/church.jpg + :width: 900px + :align: center + :height: 300px + :alt: Could not load image. + :figclass: align-center + + +Demo +---- + +Usage Syntax: + + .. code-block:: cpp + + gray8_image_t inp_img; + read_image("your_image.png", inp_img, png_tag{}); + gray8_image_t dst_img(inp_img.dimensions()); + histogram_equalization(view(inp_img), view(dst_img)); + + // To specify mask over input image + + vector> mask(inp_img.height(), vector(inp_img.width(), true)); + histogram_equalization(view(inp_img), view(dst_img), true, mask); + + .. tip:: Convert an RGB image to a channel independent color space + before trying the histogram equalization algorithm. + diff --git a/doc/image_processing/contrast_enhancement/histogram_matching.rst b/doc/image_processing/contrast_enhancement/histogram_matching.rst new file mode 100644 index 0000000000..0f89a9420c --- /dev/null +++ b/doc/image_processing/contrast_enhancement/histogram_matching.rst @@ -0,0 +1,87 @@ +.. _hm: + +################## +Histogram Matching +################## + +Description +----------- + +Histogram Matching is a technique to match the histograms of two images. + +One use case of this would be when two images of the same location have been taken +under the same local illumination but with different sensors, bringing out different +features in either image. + +The famous histogram equalization is a special case of this algorithm when the reference image +is expected to have a uniform histogram. + + +Algorithm +--------- + +#. Calculate the histogram corresponding to input image and reference image. +#. If it is a multi channeled image (e.g. RGB), convert both to an independent color space + (like YCbCr, HSV etc.). +#. Then calculate the cumulative histogram over the input image and reference image. +#. Normalize both the histogram to bring bin values between 0-1. For multi-channeled images + normalize each channel independently (by the number of pixels in image). +#. If the cumulative histogram of input image is H(p\ :sub:`x`\) and of reference image is R(p\ :sub:`x'`\) + p\ :sub:`x`\ & p\ :sub:`x'`\ in [0, 255], then apply the transformation + p\ :sub:`x'`\ = R\ :sup:`-1`\ (H(p\ :sub:`x`\ )) + +**Explanation** + +Since we will be transforming the image to match a reference image, we match +the cumulative histogram of the image to the cumulative histogram of the reference histogram. + +Hence, + + => R(p\ :sub:`x'`\) = H(p\ :sub:`x`\ ) + + => p\ :sub:`x'`\ = R\ :sup:`-1`\ (H(p\ :sub:`x`\ )) + +Results +------- +The algorithm is applied on a few standard images. One of the transformations in shown below: + +**Original Image(left) & Reference Image(right)** + +.. figure:: https://github.com/boost-gil/test-images/blob/master/jpeg/suite/matching.jpg + :width: 600px + :align: center + :height: 300px + :alt: Could not load image. + :figclass: align-center + +**Histogram matched Image** + +.. figure:: https://github.com/boost-gil/test-images/blob/master/jpeg/suite/matching_out.jpg + :width: 300px + :align: center + :height: 300px + :alt: Could not load image. + :figclass: align-center + + +Demo +---- + +Usage Syntax: + + .. code-block:: cpp + + gray8_image_t inp_img, ref_img; + read_image("your_image.png", inp_img, png_tag{}); + read_image("your_ref_image.png", ref_img, png_tag{}); + gray8_image_t dst_img(inp_img.dimensions()); + histogram_matching(view(inp_img), view(ref_image), view(dst_img)); + + // To specify mask over input image + + vector> mask(inp_img.height(), vector(inp_img.width(), true)); + histogram_matching(view(inp_img), view(ref_image), view(dst_img), true, mask); + + .. tip:: Convert an RGB image to a channel independent color space + before trying the histogram matching algorithm. + diff --git a/doc/image_processing/contrast_enhancement/index.rst b/doc/image_processing/contrast_enhancement/index.rst new file mode 100644 index 0000000000..60c60cd468 --- /dev/null +++ b/doc/image_processing/contrast_enhancement/index.rst @@ -0,0 +1,11 @@ +Contrast Enhancement +==================== + +The GIL documentation sections listed below are dedicated to describe image +processing algorithms used for contrast enhancement. + +.. toctree:: + :maxdepth: 1 + :caption: Table of Contents + + overview \ No newline at end of file diff --git a/doc/image_processing/contrast_enhancement/overview.rst b/doc/image_processing/contrast_enhancement/overview.rst new file mode 100644 index 0000000000..4677055112 --- /dev/null +++ b/doc/image_processing/contrast_enhancement/overview.rst @@ -0,0 +1,15 @@ +Overview +======== + +Contrast Enhancement is a significant part of image processing algorithms. Too dark or too +light images don't look good to the human eyes. Hence while the primary focus of these +algorithms is to enhance visual beauty, some applications of this in medical research is also +prevalent. + +We have a few contrast enhancement algorithms in the GIL image processing suite as well. +These include : + #. :ref:`he` + #. :ref:`hm` + #. :ref:`ahe` + #. Linear and Non-Linear Contrast stretching + diff --git a/doc/image_processing/index.rst b/doc/image_processing/index.rst index 71c01c742f..c5bc61cfad 100644 --- a/doc/image_processing/index.rst +++ b/doc/image_processing/index.rst @@ -11,3 +11,5 @@ features, structures and algorithms, for image processing and analysis. overview basics affine-region-detectors + contrast_enhancement/index + diff --git a/doc/images/mandel.jpg b/doc/images/mandel.jpg index f9bb916bd2..c72001bf89 100644 Binary files a/doc/images/mandel.jpg and b/doc/images/mandel.jpg differ diff --git a/doc/index.rst b/doc/index.rst index 737cdaaba0..40779419c7 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,7 +1,7 @@ Boost Generic Image Library =========================== -The Generic Image Library (GIL) is a C++11 library that abstracts image +The Generic Image Library (GIL) is a C++14 header-only library that abstracts image representations from algorithms and allows writing code that can work on a variety of images with performance similar to hand-writing for a specific image type. @@ -26,6 +26,7 @@ Core Library Documentation design/index image_processing/index + histogram/index API Reference <./reference/index.html#://> Extensions Documentation @@ -37,6 +38,7 @@ Extensions Documentation io toolbox numeric + histogram/extension/index Examples -------- diff --git a/doc/installation.rst b/doc/installation.rst index 175d1d4007..41b910d3cd 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -1,9 +1,9 @@ Installation ============ -The latest version of GIL can be downloaded from https://github.com/boostorg/gil. +The latest version of Boost.GIL can be downloaded from https://github.com/boostorg/gil. -The GIL is a header-only library. Meaning, it consists of header files only, +The Boost.GIL is a header-only library. Meaning, it consists of header files only, it does not require Boost to be built and it does not require any libraries to link against. @@ -13,5 +13,20 @@ to link against. which requires client libraries implementing popular image formats like libpng, libjpeg, etc. -In order to use GIL, including ``boost/gil.hpp`` and telling your compiler +In order to use Boost.GIL, including ``boost/gil.hpp`` and telling your compiler where to find Boost and GIL headers should be sufficient for most projects. + +Compiling +--------- + +The Boost.GIL library source code should successfully compile with any +compiler with complete C++14 support. + +.. note:: + + Boost.GIL requires C++14 compiler since ince Boost 1.80. + +For the actual list of currently tested compilers, check results of the library CI +builds linked from the `README.md `_ +or inspect the CI services configuration files in the `develop branch `_ +of the library repository. diff --git a/doc/io.rst b/doc/io.rst index 0cc920ca03..6f47371675 100644 --- a/doc/io.rst +++ b/doc/io.rst @@ -172,6 +172,8 @@ provides access to the so-called backend. For instance:: + typedef bmp_tag tag_t; + typedef get_reader_backend< const std::string , tag_t >::type backend_t; @@ -205,9 +207,10 @@ If that's the case the user has to use the xxx_and_convert_xxx variants. All functions take the filename or a device as the first parameter. The filename can be anything from a C-string, ``std::string``, -``std::wstring`` and ``boost::filesystem`` path. When using the path -object the user needs to define the ADD_FS_PATH_SUPPORT compiler symbol to -include the boost::filesystem dependency. +``std::wstring`` to ``std::filesystem`` and ``boost::filesystem`` path. +The availability of the ``std::filesystem`` is detected automatically, +unless ``BOOST_GIL_IO_USE_BOOST_FILESYSTEM`` macro is defined that forces +preference of the Boost.Filesystem. Devices could be a ``FILE*``, ``std::ifstream``, and ``TIFF*`` for TIFF images. The second parameter is either an image or view type depending on the @@ -240,13 +243,11 @@ several image types. The IO extension would then pick the matching image type to the current image file. The following example shows this feature:: - typedef mpl::vector< gray8_image_t - , gray16_image_t - , rgb8_image_t - , rgba_image_t - > my_img_types; - - any_image< my_img_types > runtime_image; + any_image< gray8_image_t + , gray16_image_t + , rgb8_image_t + , rgba8_image_t + > runtime_image; read_image( filename , runtime_image @@ -305,9 +306,10 @@ Write Interface There is only one function for writing out images, write_view. Similar to reading the first parameter is either a filename or a device. The filename can be anything from a C-string, ``std::string``, -``std::wstring``, and ``boost::filesystem`` path. When using the path object -the user needs to define the ``ADD_FS_PATH_SUPPORT`` compiler symbol to -include the ``boost::filesystem`` dependency. +``std::wstring`` to ``std::filesystem`` and ``boost::filesystem`` path. +The availability of the ``std::filesystem`` is detected automatically, +unless ``BOOST_GIL_IO_USE_BOOST_FILESYSTEM`` macro is defined that forces +preference of the Boost.Filesystem. Devices could be ``FILE*``, ``std::ifstream``, and ``TIFF*`` for TIFF images. The second parameter is an view object to image being written. @@ -319,14 +321,11 @@ the possible settings. Writing an any_image<...> is supported. See the following example:: - typedef mpl::vector< gray8_image_t - , gray16_image_t - , rgb8_image_t - , rgba_image_t - > my_img_types; - - - any_image< my_img_types > runtime_image; + any_image< gray8_image_t + , gray16_image_t + , rgb8_image_t + , rgba8_image_t + > runtime_image; // fill any_image @@ -347,7 +346,6 @@ that can be set by the user: Symbol Description ======================================================== ======================================================== BOOST_GIL_IO_ENABLE_GRAY_ALPHA Enable the color space "gray_alpha". -BOOST_GIL_IO_ADD_FS_PATH_SUPPORT Enable boost::filesystem 3.0 library. BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED Use libpng in floating point mode. This symbol is incompatible with BOOST_GIL_IO_PNG_FIXED_POINT_SUPPORTED. BOOST_GIL_IO_PNG_FIXED_POINT_SUPPORTED Use libpng in integer mode. This symbol is incompatible with BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED. BOOST_GIL_IO_PNG_DITHERING_SUPPORTED Look up "dithering" in libpng manual for explanation. diff --git a/doc/tutorial/gradient.rst b/doc/tutorial/gradient.rst index bbcb931e19..8d44d3ff2f 100644 --- a/doc/tutorial/gradient.rst +++ b/doc/tutorial/gradient.rst @@ -9,11 +9,13 @@ This comprehensive (and long) tutorial will walk you through an example of using GIL to compute the image gradients. We will start with some very simple and non-generic code and make it more -generic as we go along. Let us start with a horizontal gradient and use the +generic as we go along. Let us start with a horizontal gradient and use the simplest possible approximation to a gradient - central difference. The gradient at pixel x can be approximated with the half-difference of its -two neighboring pixels:: +two neighboring pixels: + +.. code-block:: cpp D[x] = (I[x-1] - I[x+1]) / 2 @@ -33,6 +35,7 @@ Here is how the interface to our algorithm looks like: .. code-block:: cpp #include + using namespace boost::gil; void x_gradient(gray8c_view_t const& src, gray8s_view_t const& dst) @@ -44,19 +47,19 @@ Here is how the interface to our algorithm looks like: ``gray8c_view_t`` is the type of the source image view - an 8-bit grayscale view, whose pixels are read-only (denoted by the "c"). -The output is a grayscale view with a 8-bit signed (denoted by the "s") +The output is a grayscale view with an 8-bit signed (denoted by the "s") integer channel type. See Appendix 1 for the complete convention GIL uses to name concrete types. GIL makes a distinction between an image and an image view. -A GIL **image view**, is a shallow, lightweight view of a rectangular grid of +A GIL *image view* is a shallow, lightweight view of a rectangular grid of pixels. It provides access to the pixels but does not own the pixels. Copy-constructing a view does not deep-copy the pixels. Image views do not propagate their constness to the pixels and should always be taken by a const reference. Whether a view is mutable or read-only (immutable) is a property of the view type. -A GIL `image`, on the other hand, is a view with associated ownership. +A GIL *image*, on the other hand, is a view with associated ownership. It is a container of pixels; its constructor/destructor allocates/deallocates the pixels, its copy-constructor performs deep-copy of the pixels and its ``operator==`` performs deep-compare of the pixels. Images also propagate @@ -78,12 +81,12 @@ your code and GIL: .. code-block:: cpp void ComputeXGradientGray8( - unsigned char const* src_pixels, ptrdiff_t src_row_bytes, + unsigned char const* src_pixels, std::ptrdiff_t src_row_bytes, int w, int h, - signed char* dst_pixels, ptrdiff_t dst_row_bytes) + signed char* dst_pixels, std::ptrdiff_t dst_row_bytes) { - gray8c_view_t src = interleaved_view(w, h, (gray8_pixel_t const*)src_pixels, src_row_bytes); - gray8s_view_t dst = interleaved_view(w, h, (gray8s_pixel_t*)dst_pixels, dst_row_bytes); + gray8c_view_t src = interleaved_view(w, h, reinterpret_cast(src_pixels), src_row_bytes); + gray8s_view_t dst = interleaved_view(w, h, reinterpret_cast(dst_pixels), dst_row_bytes); x_gradient(src, dst); } @@ -108,7 +111,7 @@ gradient like this: We use image view's ``operator(x,y)`` to get a reference to the pixel at a given location and we set it to the half-difference of its left and right -neighbors. ``operator()`` returns a reference to a grayscale pixel. +neighbors. ``operator()`` returns a reference to a grayscale pixel. A grayscale pixel is convertible to its channel type (``unsigned char`` for ``src``) and it can be copy-constructed from a channel. (This is only true for grayscale pixels). @@ -126,7 +129,7 @@ addition and multiplication. Here is a faster version of the above: gray8c_view_t::x_iterator src_it = src.row_begin(y); gray8s_view_t::x_iterator dst_it = dst.row_begin(y); - for (int x=1; x < src.width() - 1; ++x) + for (int x = 1; x < src.width() - 1; ++x) dst_it[x] = (src_it[x-1] - src_it[x+1]) / 2; } } @@ -141,7 +144,7 @@ operator. The code to compute gradient in the vertical direction is very similar: -.. code-block: cpp +.. code-block:: cpp void y_gradient(gray8c_view_t const& src, gray8s_view_t const& dst) { @@ -159,8 +162,9 @@ Instead of looping over the rows, we loop over each column and create a ``y_iterator``, an iterator moving vertically. In this case a simple pointer cannot be used because the distance between two adjacent pixels equals the number of bytes in each row of the image. GIL uses here a special step -iterator class whose size is 8 bytes - it contains a raw C pointer and a step. -Its ``operator[]`` multiplies the index by its step. +iterator class whose size is twice the size of the iterator in horizontal +direction - it contains a raw C pointer and a step. Its ``operator[]`` multiplies +the index by its step. The above version of ``y_gradient``, however, is much slower (easily an order of magnitude slower) than ``x_gradient`` because of the memory access pattern; @@ -206,15 +210,15 @@ done with GIL locators: gray8c_view_t::xy_locator src_loc = src.xy_at(0,1); for (int y = 1; y < src.height() - 1; ++y) { - gray8s_view_t::x_iterator dst_it = dst.row_begin(y); + gray8s_view_t::x_iterator dst_it = dst.row_begin(y); for (int x = 0; x < src.width(); ++x) - { - (*dst_it) = (src_loc(0,-1) - src_loc(0,1)) / 2; + { + *dst_it = (src_loc(0,-1) - src_loc(0,1)) / 2; ++dst_it; ++src_loc.x(); // each dimension can be advanced separately } - src_loc+=point(-src.width(), 1); // carriage return + src_loc += point_t(-src.width(), 1); // carriage return } } @@ -229,17 +233,18 @@ simultaneously using its ``operator+=`` and ``operator-=``. Similar to image views, locators provide binary ``operator()`` which returns a reference to a pixel with a relative offset to the current locator position. For example, ``src_loc(0,1)`` returns a reference to the -neighbor below the current pixel. Locators are very lightweight -objects - in the above example the locator has a size of 8 bytes - it -consists of a raw pointer to the current pixel and an int indicating +neighbor below the current pixel. Locators are very lightweight +objects - they consist of a raw pointer to the current pixel and an int indicating the number of bytes from one row to the next (which is the step when moving vertically). The call to ``++src_loc.x()`` corresponds to a -single C pointer increment. However, the example above performs more +single C pointer increment. However, the example above performs more computations than necessary. The code ``src_loc(0,1)`` has to compute -the offset of the pixel in two dimensions, which is slow. Notice +the offset of the pixel in two dimensions, which is slow. Notice though that the offset of the two neighbors is the same, regardless of the pixel location. To improve the performance, GIL can cache and -reuse this offset:: +reuse this offset: + +.. code-block:: cpp void y_gradient(gray8c_view_t const& src, gray8s_view_t const& dst) { @@ -252,12 +257,12 @@ reuse this offset:: gray8s_view_t::x_iterator dst_it = dst.row_begin(y); for (int x = 0; x < src.width(); ++x) - { - (*dst_it) = (src_loc[above] - src_loc[below]) / 2; + { + *dst_it = (src_loc[above] - src_loc[below]) / 2; ++dst_it; ++src_loc.x(); } - src_loc+=point(-src.width(), 1); + src_loc += point_t(-src.width(), 1); } } @@ -276,10 +281,10 @@ Here is how the new interface looks like: .. code-block:: cpp template - void x_gradient(const SrcView& src, const DstView& dst) + void x_gradient(SrcView const& src, DstView const& dst) { - gil_function_requires >(); - gil_function_requires >(); + gil_function_requires>(); + gil_function_requires>(); gil_function_requires < ColorSpacesCompatibleConcept @@ -293,8 +298,8 @@ Here is how the new interface looks like: } The new algorithm now takes the types of the input and output image -views as template parameters. That allows using both built-in GIL -image views, as well as any user-defined image view classes. The +views as template parameters. That allows using both built-in GIL +image views, as well as any user-defined image view classes. The first three lines are optional; they use ``boost::concept_check`` to ensure that the two arguments are valid GIL image views, that the second one is mutable and that their color spaces are compatible @@ -302,20 +307,20 @@ second one is mutable and that their color spaces are compatible GIL does not require using its own built-in constructs. You are free to use your own channels, color spaces, iterators, locators, views and -images. However, to work with the rest of GIL they have to satisfy a +images. However, to work with the rest of GIL they have to satisfy a set of requirements; in other words, they have to \e model the -corresponding GIL _concept_. GIL's concepts are defined in the user +corresponding GIL *concept*. GIL's concepts are defined in the user guide. One of the biggest drawbacks of using templates and generic programming in C++ is that compile errors can be very difficult to -comprehend. This is a side-effect of the lack of early type +comprehend. This is a side-effect of the lack of early type checking - a generic argument may not satisfy the requirements of a function, but the incompatibility may be triggered deep into a nested -call, in code unfamiliar and hardly related to the problem. GIL uses +call, in code unfamiliar and hardly related to the problem. GIL uses ``boost::concept_check`` to mitigate this problem. The above three lines of code check whether the template parameters are valid models -of their corresponding concepts. If a model is incorrect, the compile +of their corresponding concepts. If a model is incorrect, the compile error will be inside ``gil_function_requires``, which is much closer to the problem and easier to track. Furthermore, such checks get compiled out and have zero performance overhead. The disadvantage of @@ -331,15 +336,15 @@ channels of the pixel and compute the gradient for each channel: .. code-block:: cpp template - void x_gradient(const SrcView& src, const DstView& dst) + void x_gradient(SrcView const& src, DstView const& dst) { - for (int y=0; y < src.height(); ++y) + for (int y = 0; y < src.height(); ++y) { typename SrcView::x_iterator src_it = src.row_begin(y); typename DstView::x_iterator dst_it = dst.row_begin(y); for (int x = 1; x < src.width() - 1; ++x) - for (int c = 0; c < num_channels::value; ++c) + for (std::size_t c = 0; c < num_channels::value; ++c) dst_it[x][c] = (src_it[x-1][c]- src_it[x+1][c]) / 2; } } @@ -352,23 +357,24 @@ GIL allows us to abstract out such per-channel operations: template struct halfdiff_cast_channels { - template Out operator()(T const& in1, T const& in2) const + template + auto operator()(T const& in1, T const& in2) const -> Out { return Out((in1 - in2) / 2); } }; template - void x_gradient(const SrcView& src, const DstView& dst) + void x_gradient(SrcView const& src, DstView const& dst) { - typedef typename channel_type::type dst_channel_t; + using dst_channel_t = typename channel_type::type; - for (int y=0; y < src.height(); ++y) + for (int y = 0; y < src.height(); ++y) { typename SrcView::x_iterator src_it = src.row_begin(y); typename DstView::x_iterator dst_it = dst.row_begin(y); - for (int x=1; x < src.width() - 1; ++x) + for (int x = 1; x < src.width() - 1; ++x) { static_transform(src_it[x-1], src_it[x+1], dst_it[x], halfdiff_cast_channels()); @@ -383,11 +389,11 @@ Other such algorithms are ``static_generate``, ``static_fill`` and GIL channel algorithms use static recursion to unroll the loops; they never loop over the channels explicitly. -Note that sometimes modern compilers (at least Visual Studio 8) already unroll -channel-level loops, such as the one above. However, another advantage of -using GIL's channel-level algorithms is that they pair the channels -semantically, not based on their order in memory. For example, the above -example will properly match an RGB source with a BGR destination. +Note that sometimes modern compilers already unroll channel-level loops +such as the one above. However, another advantage of using GIL's channel-level +algorithms is that they pair the channels semantically, not based on their +order in memory. For example, the above example will properly match an +RGB source with a BGR destination. Here is how we can use our generic version with images of different types: @@ -395,34 +401,34 @@ Here is how we can use our generic version with images of different types: // Calling with 16-bit grayscale data void XGradientGray16_Gray32( - unsigned short const* src_pixels, ptrdiff_t src_row_bytes, + unsigned short const* src_pixels, std::ptrdiff_t src_row_bytes, int w, int h, - signed int* dst_pixels, ptrdiff_t dst_row_bytes) + signed int* dst_pixels, std::ptrdiff_t dst_row_bytes) { - gray16c_view_t src=interleaved_view(w, h, (gray16_pixel_t const*)src_pixels, src_row_bytes); - gray32s_view_t dst=interleaved_view(w, h, (gray32s_pixel_t*)dst_pixels, dst_row_bytes); + gray16c_view_t src = interleaved_view(w, h, reinterpret_cast(src_pixels), src_row_bytes); + gray32s_view_t dst = interleaved_view(w, h, reinterpret_cast(dst_pixels), dst_row_bytes); x_gradient(src,dst); } // Calling with 8-bit RGB data into 16-bit BGR void XGradientRGB8_BGR16( - unsigned char const* src_pixels, ptrdiff_t src_row_bytes, + unsigned char const* src_pixels, std::ptrdiff_t src_row_bytes, int w, int h, - signed short* dst_pixels, ptrdiff_t dst_row_bytes) + signed short* dst_pixels, std::ptrdiff_t dst_row_bytes) { - rgb8c_view_t src = interleaved_view(w, h, (rgb8_pixel_t const*)src_pixels, src_row_bytes); - bgr16s_view_t dst = interleaved_view(w, h, (bgr16s_pixel_t*)dst_pixels, dst_row_bytes); + rgb8c_view_t src = interleaved_view(w, h, reinterpret_cast(src_pixels), src_row_bytes); + bgr16s_view_t dst = interleaved_view(w, h, reinterpret_cast(dst_pixels), dst_row_bytes); x_gradient(src, dst); } // Either or both the source and the destination could be planar - the gradient code does not change void XGradientPlanarRGB8_RGB32( unsigned short const* src_r, unsigned short const* src_g, unsigned short const* src_b, - ptrdiff_t src_row_bytes, int w, int h, - signed int* dst_pixels, ptrdiff_t dst_row_bytes) + std::ptrdiff_t src_row_bytes, int w, int h, + signed int* dst_pixels, std::ptrdiff_t dst_row_bytes) { rgb16c_planar_view_t src = planar_rgb_view (w, h, src_r, src_g, src_b, src_row_bytes); - rgb32s_view_t dst = interleaved_view(w, h,(rgb32s_pixel_t*)dst_pixels, dst_row_bytes); + rgb32s_view_t dst = interleaved_view(w, h, reinterpret_cast(dst_pixels), dst_row_bytes); x_gradient(src,dst); } @@ -445,7 +451,7 @@ Here is how to do this in GIL: .. code-block:: cpp template - void y_gradient(const SrcView& src, const DstView& dst) + void y_gradient(SrcView const& src, DstView const& dst) { x_gradient(rotated90ccw_view(src), rotated90ccw_view(dst)); } @@ -470,16 +476,16 @@ channel of a color image. Here is how to do that: .. code-block:: cpp template - void nth_channel_x_gradient(const SrcView& src, int n, const DstView& dst) + void nth_channel_x_gradient(SrcView const& src, int n, DstView const& dst) { x_gradient(nth_channel_view(src, n), dst); } ``nth_channel_view`` is a view transformation function that takes any view and returns a single-channel (grayscale) view of its N-th -channel. For interleaved RGB view, for example, the returned view is +channel. For interleaved RGB view, for example, the returned view is a step view - a view whose horizontal iterator skips over two channels -when incremented. If applied on a planar RGB view, the returned type +when incremented. If applied on a planar RGB view, the returned type is a simple grayscale view whose horizontal iterator is a C pointer. Image view transformation functions can be piped together. For example, to compute the y gradient of the second channel of the even @@ -487,7 +493,7 @@ pixels in the view, use: .. code-block:: cpp - y_gradient(subsampled_view(nth_channel_view(src, 1), 2,2), dst); + y_gradient(subsampled_view(nth_channel_view(src, 1), 2, 2), dst); GIL can sometimes simplify piped views. For example, two nested subsampled views (views that skip over pixels in X and in Y) can be @@ -497,7 +503,7 @@ the steps of the two views. 1D pixel iterators ------------------ -Let's go back to ``x_gradient`` one more time. Many image view +Let's go back to ``x_gradient`` one more time. Many image view algorithms apply the same operation for each pixel and GIL provides an abstraction to handle them. However, our algorithm has an unusual access pattern, as it skips the first and the last column. It would be @@ -510,7 +516,7 @@ and last column: void x_gradient_unguarded(gray8c_view_t const& src, gray8s_view_t const& dst) { - for (int y=0; y < src.height(); ++y) + for (int y = 0; y < src.height(); ++y) { gray8c_view_t::x_iterator src_it = src.row_begin(y); gray8s_view_t::x_iterator dst_it = dst.row_begin(y); @@ -522,7 +528,7 @@ and last column: void x_gradient(gray8c_view_t const& src, gray8s_view_t const& dst) { - assert(src.width()>=2); + assert(src.width() >= 2); x_gradient_unguarded(subimage_view(src, 1, 0, src.width()-2, src.height()), subimage_view(dst, 1, 0, src.width()-2, src.height())); } @@ -542,7 +548,7 @@ rewrite it more compactly: void x_gradient_unguarded(gray8c_view_t const& src, gray8s_view_t const& dst) { gray8c_view_t::iterator src_it = src.begin(); - for (gray8s_view_t::iterator dst_it = dst.begin(); dst_it!=dst.end(); ++dst_it, ++src_it) + for (gray8s_view_t::iterator dst_it = dst.begin(); dst_it != dst.end(); ++dst_it, ++src_it) *dst_it = (src_it.x()[-1] - src_it.x()[1]) / 2; } @@ -569,7 +575,7 @@ GIL provides STL equivalents of many algorithms. For example, destination range the result of a generic function taking the corresponding element of the source range. In our example, we want to assign to each destination pixel the value of the half-difference of -the horizontal neighbors of the corresponding source pixel. If we +the horizontal neighbors of the corresponding source pixel. If we abstract that operation in a function object, we can use GIL's ``transform_pixel_positions`` to do that: @@ -577,7 +583,7 @@ abstract that operation in a function object, we can use GIL's struct half_x_difference { - int operator()(const gray8c_loc_t& src_loc) const + auto operator()(gray8c_loc_t const& src_loc) const -> int { return (src_loc.x()[-1] - src_loc.x()[1]) / 2; } @@ -594,7 +600,7 @@ GIL provides the algorithms ``for_each_pixel`` and ``for_each_pixel_position`` and ``transform_pixel_positions``, which instead of references to pixels, pass to the generic function pixel locators. This allows for more powerful functions that can use the -pixel neighbors through the passed locators. GIL algorithms iterate +pixel neighbors through the passed locators. GIL algorithms iterate through the pixels using the more efficient two nested loops (as opposed to the single loop using 1-D iterators) @@ -647,7 +653,7 @@ Image The above example has a performance problem - ``x_gradient`` dereferences most source pixels twice, which will cause the above code -to perform color conversion twice. Sometimes it may be more efficient +to perform color conversion twice. Sometimes it may be more efficient to copy the color converted image into a temporary buffer and use it to compute the gradient - that way color conversion is invoked once per pixel. Using our non-generic version we can do it like this: @@ -664,7 +670,7 @@ per pixel. Using our non-generic version we can do it like this: First we construct an 8-bit grayscale image with the same dimensions as our source. Then we copy a color-converted view of the source into -the temporary image. Finally we use a read-only view of the temporary +the temporary image. Finally we use a read-only view of the temporary image in our ``x_gradient algorithm``. As the example shows, GIL provides global functions ``view`` and ``const_view`` that take an image and return a mutable or an immutable view of its pixels. @@ -674,10 +680,10 @@ Creating a generic version of the above is a bit trickier: .. code-block:: cpp template - void x_luminosity_gradient(const SrcView& src, const DstView& dst) + void x_luminosity_gradient(SrcView const& src, DstView const& dst) { using d_channel_t = typename channel_type::type; - using channel_t = typename channel_convert_to_unsigned::type; + using channel_t = typename detail::channel_convert_to_unsigned::type; using gray_pixel_t = pixel; using gray_image_t = image; @@ -706,7 +712,7 @@ metafunction to remove its sign (if it is a signed integral type) and then use it to generate the type of a grayscale pixel. From the pixel type we create the image type. GIL's image class is specialized over the pixel type and a boolean indicating whether the image should be -planar or interleaved. Single-channel (grayscale) images in GIL must +planar or interleaved. Single-channel (grayscale) images in GIL must always be interleaved. There are multiple ways of constructing types in GIL. Instead of instantiating the classes directly we could have used type factory metafunctions. The following code is equivalent: @@ -716,10 +722,9 @@ used type factory metafunctions. The following code is equivalent: template void x_luminosity_gradient(SrcView const& src, DstView const& dst) { - typedef typename channel_type::type d_channel_t; - typedef typename channel_convert_to_unsigned::type channel_t; - typedef typename image_type::type gray_image_t; - typedef typename gray_image_t::value_type gray_pixel_t; + using d_channel_t = typename channel_type::type; + using channel_t = typename detail::channel_convert_to_unsigned::type; + using gray_image_t = typename image_type::type; gray_image_t ccv_image(src.dimensions()); copy_and_convert_pixels(src, view(ccv_image)); @@ -751,77 +756,94 @@ Virtual Image Views So far we have been dealing with images that have pixels stored in memory. GIL allows you to create an image view of an arbitrary image, including a synthetic function. To demonstrate this, let us create a -view of the Mandelbrot set. First, we need to create a function +view of the Mandelbrot set. First, we need to create a function object that computes the value of the Mandelbrot set at a given location (x,y) in the image: .. code-block:: cpp // models PixelDereferenceAdaptorConcept + template // Models PixelValueConcept struct mandelbrot_fn { - typedef point point_t; - - typedef mandelbrot_fn const_t; - typedef gray8_pixel_t value_type; - typedef value_type reference; - typedef value_type const_reference; - typedef point_t argument_type; - typedef reference result_type; + using point_t = point; + using const_t = mandelbrot_fn; + using value_type = Pixel; + using reference = value_type; + using const_reference = value_type; + using argument_type = point_t; + using result_type = reference; static bool constexpr is_mutable = false; - mandelbrot_fn() {} - mandelbrot_fn(const point_t& sz) : _img_size(sz) {} + mandelbrot_fn() = default; + mandelbrot_fn(point_t const& sz, value_type const& in_color, value_type const& out_color) + : _in_color(in_color), _out_color(out_color), _img_size(sz) {} - result_type operator()(const point_t& p) const + auto operator()(point_t const& p) const -> result_type { // normalize the coords to (-2..1, -1.5..1.5) - double t=get_num_iter(point(p.x/(double)_img_size.x*3-2, p.y/(double)_img_size.y*3-1.5f)); - return value_type((bits8)(pow(t,0.2)*255)); // raise to power suitable for viewing + // (actually make y -1.0..2 so it is asymmetric, so we can verify some view factory methods) + double t = get_num_iter(point(p.x/(double)_img_size.x*3-2, p.y/(double)_img_size.y*3-1.0f));//1.5f)); + t = pow(t,0.2); + + result_type ret; + for (std::size_t k = 0; k::value; ++k) + ret[k] = typename channel_type::type(_in_color[k]*t + _out_color[k]*(1-t)); + return ret; } + private: + value_type _in_color,_out_color; point_t _img_size; + static constexpr int MAX_ITER = 100; - double get_num_iter(const point& p) const + auto get_num_iter(point const& p) const -> double { point Z(0,0); - for (int i=0; i<100; ++i) // 100 iterations - { + for (int i = 0; i < MAX_ITER; ++i) + { Z = point(Z.x*Z.x - Z.y*Z.y + p.x, 2*Z.x*Z.y + p.y); if (Z.x*Z.x + Z.y*Z.y > 4) - return i/(double)100; + return i/static_cast(MAX_ITER); } return 0; } }; We can now use GIL's ``virtual_2d_locator`` with this function object -to construct a Mandelbrot view of size 200x200 pixels: +to construct a rgb8 Mandelbrot view of size 200x200 pixels: .. code-block:: cpp - typedef mandelbrot_fn::point_t point_t; - typedef virtual_2d_locator locator_t; - typedef image_view my_virt_view_t; + using deref_t = mandelbrot_fn; + using point_t = deref_t::point_t; + using locator_t = virtual_2d_locator; + using my_virt_view_t = image_view; point_t dims(200,200); // Construct a Mandelbrot view with a locator, taking top-left corner (0,0) and step (1,1) - my_virt_view_t mandel(dims, locator_t(point_t(0,0), point_t(1,1), mandelbrot_fn(dims))); + my_virt_view_t mandel(dims, locator_t( + point_t(0,0), point_t(1,1), + deref_t(dims, rgb8_pixel_t(255, 0, 255), rgb8_pixel_t(0, 255, 0)))); + We can treat the synthetic view just like a real one. For example, -let's invoke our ``x_gradient`` algorithm to compute the gradient of +let's invoke our ``x_luminosity_gradient`` algorithm to compute the luminosity gradient of the 90-degree rotated view of the Mandelbrot set and save the original and the result: .. code-block:: cpp + #include + gray8s_image_t img(dims); - x_gradient(rotated90cw_view(mandel), view(img)); + x_luminosity_gradient(rotated90cw_view(mandel), view(img)); - // Save the Mandelbrot set and its 90-degree rotated gradient (jpeg cannot save signed char; must convert to unsigned char) - jpeg_write_view("mandel.jpg",mandel); - jpeg_write_view("mandel_grad.jpg",color_converted_view(const_view(img))); + // Save the Mandelbrot set and its 90-degree rotated gradient + // (jpeg cannot save signed char; must convert to unsigned char) + write_view("mandel.jpg", mandel, jpeg_tag{}); + write_view("mandel_grad.jpg", color_converted_view(const_view(img)), jpeg_tag{}); Here is what the two files look like: @@ -831,23 +853,23 @@ Run-Time Specified Images and Image Views ----------------------------------------- So far we have created a generic function that computes the image -gradient of an image view template specialization. Sometimes, +gradient of an image view template specialization. Sometimes, however, the properties of an image view, such as its color space and -channel depth, may not be available at compile time. GIL's +channel depth, may not be available at compile time. GIL's ``dynamic_image`` extension allows for working with GIL constructs -that are specified at run time, also called _variants_. GIL provides +that are specified at run time, also called *variants*. GIL provides models of a run-time instantiated image, ``any_image``, and a run-time instantiated image view, ``any_image_view``. The mechanisms are in place to create other variants, such as ``any_pixel``, -``any_pixel_iterator``, etc. Most of GIL's algorithms and all of the +``any_pixel_iterator``, etc. Most of GIL's algorithms and all of the view transformation functions also work with run-time instantiated image views and binary algorithms, such as ``copy_pixels`` can have either or both arguments be variants. Lets make our ``x_luminosity_gradient`` algorithm take a variant image view. For simplicity, let's assume that only the source view can be a -variant. (As an example of using multiple variants, see GIL's image -view algorithm overloads taking multiple variants.) +variant. As an example of using multiple variants, see GIL's image +view algorithm overloads taking multiple variants. First, we need to make a function object that contains the templated destination view and has an application operator taking a templated @@ -860,32 +882,35 @@ source view: template struct x_gradient_obj { - typedef void result_type; // required typedef + using result_type = void; // required typedef - const DstView& _dst; - x_gradient_obj(const DstView& dst) : _dst(dst) {} + DstView const& _dst; + x_gradient_obj(DstView const& dst) : _dst(dst) {} template - void operator()(const SrcView& src) const { x_luminosity_gradient(src, _dst); } + auto operator()(SrcView const& src) const -> result_type + { + x_luminosity_gradient(src, _dst); + } }; The second step is to provide an overload of ``x_luminosity_gradient`` that -takes image view variant and calls GIL's ``apply_operation`` passing it the +takes an image view variant and calls ``variant2::visit`` passing it the function object: .. code-block:: cpp - template - void x_luminosity_gradient(const any_image_view& src, const DstView& dst) + template + void x_luminosity_gradient(any_image_view const& src, DstView const& dst) { - apply_operation(src, x_gradient_obj(dst)); + variant2::visit(x_gradient_obj(dst), src); } -``any_image_view`` is the image view variant. It is -templated over ``SrcViews``, an enumeration of all possible view types +``any_image_view`` is the image view variant. It is +templated over ``SrcViews...``, an enumeration of all possible view types the variant can take. ``src`` contains inside an index of the currently instantiated type, as well as a block of memory containing -the instance. ``apply_operation`` goes through a switch statement +the instance. ``variant2::visit`` goes through a switch statement over the index, each case of which casts the memory to the correct view type and invokes the function object with it. Invoking an algorithm on a variant has the overhead of one switch @@ -897,39 +922,41 @@ Here is how we can construct a variant and invoke the algorithm: .. code-block:: cpp - #include - #include + #include - typedef mp11::mp_list my_img_types; - any_image runtime_image; - jpeg_read_image("input.jpg", runtime_image); + using my_img = any_image; + my_img runtime_image; + read_image("input.jpg", runtime_image, jpeg_tag{}); gray8s_image_t gradient(runtime_image.dimensions()); x_luminosity_gradient(const_view(runtime_image), view(gradient)); - jpeg_write_view("x_gradient.jpg", color_converted_view(const_view(gradient))); + write_view( + "x_gradient.jpg", + color_converted_view(const_view(gradient)), + jpeg_tag{}); In this example, we create an image variant that could be 8-bit or 16-bit RGB or grayscale image. We then use GIL's I/O extension to load the image from file in its native color space and channel depth. If none of the allowed image types matches the image on disk, an -exception will be thrown. We then construct a 8 bit signed -(i.e. ``char``) image to store the gradient and invoke ``x_gradient`` -on it. Finally we save the result into another file. We save the view +exception will be thrown. We then construct an 8 bit signed +(i.e. ``char``) image to store the gradient and invoke ``x_luminosity_gradient`` +on it. Finally we save the result into another file. We save the view converted to 8-bit unsigned, because JPEG I/O does not support signed char. -Note how free functions and methods such as ``jpeg_read_image``, +Note how free functions and methods such as ``read_image``, ``dimensions``, ``view`` and ``const_view`` work on both templated and -variant types. For templated images ``view(img)`` returns a templated -view, whereas for image variants it returns a view variant. For +variant types. For templated images ``view(img)`` returns a templated +view, whereas for image variants it returns a view variant. For example, the return type of ``view(runtime_image)`` is ``any_image_view`` where ``Views`` enumerates four views -corresponding to the four image types. ``const_view(runtime_image)`` +corresponding to the four image types. ``const_view(runtime_image)`` returns a ``any_image_view`` of the four read-only view types, etc. A warning about using variants: instantiating an algorithm with a variant effectively instantiates it with every possible type the -variant can take. For binary algorithms, the algorithm is +variant can take. For binary algorithms, the algorithm is instantiated with every possible combination of the two input types! This can take a toll on both the compile time and the executable size. @@ -937,7 +964,7 @@ Conclusion ---------- This tutorial provides a glimpse at the challenges associated with -writing generic and efficient image processing algorithms in GIL. We +writing generic and efficient image processing algorithms in GIL. We have taken a simple algorithm and shown how to make it work with image representations that vary in bit depth, color space, ordering of the channels, and planar/interleaved structure. We have demonstrated that @@ -953,8 +980,8 @@ work on homogeneous images, i.e. images whose pixels have channels that are all of the same type. There are examples of images, such as a packed 565 RGB format, which contain channels of different types. While GIL provides concepts and algorithms operating on -heterogeneous pixels, we leave the task of extending x_gradient as an -exercise for the reader. Second, after computing the value of the +heterogeneous pixels, we leave the task of extending ``x_gradient`` as an +exercise for the reader. Second, after computing the value of the gradient we are simply casting it to the destination channel type. This may not always be the desired operation. For example, if the source channel is a float with range [0..1] and the destination is @@ -962,7 +989,7 @@ unsigned char, casting the half-difference to unsigned char will result in either 0 or 1. Instead, what we might want to do is scale the result into the range of the destination channel. GIL's channel-level algorithms might be useful in such cases. For example, -\p channel_convert converts between channels by linearly scaling the +``channel_convert`` converts between channels by linearly scaling the source channel value into the range of the destination channel. There is a lot to be done in improving the performance as diff --git a/example/Jamfile b/example/Jamfile index fc33ce319c..ba4a6e81c2 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -1,38 +1,64 @@ # Boost.GIL (Generic Image Library) - examples # -# Copyright (c) 2018 Mateusz Loskot +# Copyright (c) 2018-2022 Mateusz Loskot # # Use, modification and distribution is subject to 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) -import ac ; +import ../../config/checks/config : requires ; import regex ; -import testing ; using libjpeg : : : : true ; # work around bug on master +using libpng : : : : true ; -project - : # requirements - ; +# The header providing std::experimental::filesystem +# is deprecated by Microsoft and will be REMOVED. It is superseded by the C++17 +# header providing std::filesystem. +# You can define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +# to acknowledge that you have received this warning. +local msvc-cxxs-with-experimental-fs = 14 ; -# TODO: Add missing examples +project + : + requirements + [ requires + cxx14_constexpr + cxx14_return_type_deduction + ] + . + # TODO: Enable concepts check for all, not just test/core + #BOOST_GIL_USE_CONCEPT_CHECK=1 + msvc,$(msvc-cxxs-with-experimental-fs):_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING=1 + msvc:/bigobj + ; local sources = + adaptive_histogram_equalization.cpp adaptive_threshold.cpp affine.cpp + anisotropic_diffusion.cpp convolution.cpp convolve2d.cpp dynamic_image.cpp harris.cpp hessian.cpp + histogram_equalization.cpp + histogram_matching.cpp histogram.cpp + hough_transform_circle.cpp + hough_transform_line.cpp interleaved_ptr.cpp mandelbrot.cpp + morphology.cpp packed_pixel.cpp + rasterizer_circle.cpp + rasterizer_ellipse.cpp + rasterizer_line.cpp resize.cpp sobel_scharr.cpp threshold.cpp + tutorial_histogram.cpp x_gradient.cpp ; @@ -40,11 +66,13 @@ local targets ; for local s in $(sources) { - targets += - [ compile $(s) : - [ ac.check-library /libjpeg//libjpeg : /libjpeg//libjpeg : no ] - ] - ; +targets += + [ exe [ regex.replace $(s) ".cpp" "" ] : + $(s) + /libjpeg//libjpeg + /libpng//libpng + ] + ; } alias examples : $(targets) ; diff --git a/example/README.md b/example/README.md index cee1770888..53a61209b4 100644 --- a/example/README.md +++ b/example/README.md @@ -3,6 +3,7 @@ This directory contains - examples of C++ programs using GIL +- a documentation file describing the synopsis, build and execution requirements for each example - configuration files for Boost.Build command line and CMake integration for popular IDEs. We provide Boost.Build (`Jamfile`) and CMake (`CMakeLists.txt`) @@ -10,36 +11,4 @@ configurations to build the examples. See the [CONTRIBUTING.md](../CONTRIBUTING.md) for details on how to run `b2` and `cmake` for Boost.GIL. -Each example is build as a separate executable. -Each executable generates its output as `out-.jpg`. -For example, the `resize.cpp` example generates the image `out-resize.jpg`. - -The following C++ examples are included: - -1. `resize.cpp` - Scales an image using bilinear or nearest-neighbour resampling. - -2. `affine.cpp` - Performs an arbitrary affine transformation on the image. - -3. `convolution.cpp` - Convolves the image with a Gaussian kernel. - -4. `mandelbrot.cpp` - Creates a synthetic image defining the Mandelbrot set. - -5. `interleaved_ptr.cpp` - Illustrates how to create a custom pixel reference and iterator. - Creates a GIL image view over user-supplied data without the need to cast to GIL pixel type. - -6. `x_gradient.cpp` - Horizontal gradient, from the tutorial - -7. `histogram.cpp` - Algorithm to compute the histogram of an image - -8. `packed_pixel.cpp` - Illustrates how to create a custom pixel model - a pixel whose channel size is not divisible by bytes. - -9. `dynamic_image.cpp` - Example of using images whose type is instantiated at run time. +Each example is built as a separate executable. diff --git a/example/adaptive_histogram_equalization.cpp b/example/adaptive_histogram_equalization.cpp new file mode 100644 index 0000000000..290e418fa0 --- /dev/null +++ b/example/adaptive_histogram_equalization.cpp @@ -0,0 +1,33 @@ +// +// Copyright 2020 Debabrata Mandal +// Copyright 2021 Pranam Lashkari +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include + +using namespace boost::gil; + +// Demonstrates Adaptive Histogram Equalization (AHE) + +// See also: +// histogram.cpp - General use of histograms in GIL +// histogram_equalization.cpp - Regular Histogram Equalization +// histogram_matching.cpp - Reference-based histogram computation + +int main() +{ + gray8_image_t img; + read_image("test_adaptive.png", img, png_tag{}); + gray8_image_t img_out(img.dimensions()); + + boost::gil::non_overlapping_interpolated_clahe(view(img), view(img_out)); + write_view("out-adaptive.png", view(img_out), png_tag{}); + return 0; +} diff --git a/example/adaptive_histogram_equalization.md b/example/adaptive_histogram_equalization.md new file mode 100644 index 0000000000..5737ea4c96 --- /dev/null +++ b/example/adaptive_histogram_equalization.md @@ -0,0 +1,23 @@ +# Adaptive Histogram Equalization + +Adaptive Histogram Equalization (AHE) capabilities in GIL are demonstrated by the program `adaptive_histogram_equalization`, compiled from the sources `example/adaptive_histogram_equalization.cpp`. + +## Synopsis + +`adaptive_histogram_equalization` + +The program doesn't take any argument on the command line. + +`adaptive_histogram_equalization` expects to find an image called `test_adaptive.png` in the current directory, and produces the image `out-adaptive.png` in return. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +- `adaptive_histogram_equalization` expects to find an image called `test_adaptive.png` in the current directory. + diff --git a/example/adaptive_threshold.cpp b/example/adaptive_threshold.cpp index 65f0f9ac14..1f8806f3f5 100644 --- a/example/adaptive_threshold.cpp +++ b/example/adaptive_threshold.cpp @@ -5,12 +5,22 @@ // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // + #include #include #include using namespace boost::gil; +// Demonstrates Adaptive Thresholding + +// threshold_adaptive works following either the mean or the gaussian method, and accepts also a direction +// The direction indicates whether the pixels are assigned the max value when their values are greater +// than the threshold (regular), or when they are less than the threshold (inverse) +// threshold_adaptive is defined in include/boost/gil/image_processing/threshold.hpp +// See also: +// threshold.cpp - Simple thresholding + int main() { gray8_image_t img; @@ -19,13 +29,13 @@ int main() boost::gil::threshold_adaptive(const_view(img), view(img_out), 11, threshold_adaptive_method::mean, threshold_direction::regular, 2); write_view("out-threshold-adaptive-mean.png", view(img_out), png_tag{}); - + boost::gil::threshold_adaptive(const_view(img), view(img_out), 11, threshold_adaptive_method::mean, threshold_direction::inverse, 2); write_view("out-threshold-adaptive-mean-inv.png", view(img_out), png_tag{}); boost::gil::threshold_adaptive(const_view(img), view(img_out), 7, threshold_adaptive_method::gaussian, threshold_direction::regular, 2); write_view("out-threshold-adaptive-gaussian.png", view(img_out), png_tag{}); - + boost::gil::threshold_adaptive(const_view(img), view(img_out), 11, threshold_adaptive_method::gaussian, threshold_direction::inverse, 2); write_view("out-threshold-adaptive-gaussian-inv.png", view(img_out), png_tag{}); diff --git a/example/adaptive_threshold.md b/example/adaptive_threshold.md new file mode 100644 index 0000000000..a21438c492 --- /dev/null +++ b/example/adaptive_threshold.md @@ -0,0 +1,26 @@ +# Adaptive Thresholding + +Adaptive Thresholding capabilities in GIL are demonstrated by the program `adaptive_threshold`, compiled from the sources `example/adaptive_threshold.cpp`. + +## Synopsis + +`adaptive_threshold` + +The program doesn't take any argument on the command line. + +`adaptive_threshold` expects to find an image called `test_adaptive.png` in the current directory, and produces one image for each thresholding method: +- `out-threshold-adaptive-mean.png` +- `out-threshold-adaptive-mean-inv.png` +- `out-threshold-adaptive-gaussian.png` +- `out-threshold-adaptive-gaussian-inv.png`. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +- `adaptive_threshold` expects to find an image called `test_adaptive.png` in the current directory. diff --git a/example/affine.cpp b/example/affine.cpp index a54cfaa1d1..a078d17a2d 100644 --- a/example/affine.cpp +++ b/example/affine.cpp @@ -5,12 +5,17 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #include #include #include #include -// Example for resample_pixels() in the numeric extension +// Performs an arbitrary affine transformation on the image. + +// This example relies on the matrices and functions available in GIL to define the operation, +// in include/boost/gil/extension/numeric/affine.hpp +// and calls resample_pixels(), avaiable in the numeric extension, to apply it int main() { diff --git a/example/affine.md b/example/affine.md new file mode 100644 index 0000000000..00c22494ad --- /dev/null +++ b/example/affine.md @@ -0,0 +1,22 @@ +# Affine transformation + +Affine transformation capabilities in GIL are demonstrated by the program `affine`, compiled from the sources `example/affine.cpp`. + +## Synopsis + +`affine` + +The program doesn't take any argument on the command line. + +`affine` expects to find an image called `test.jpg` in the current directory, and produces an image in return, where the transformations have been applied: `out-affine.jpg` + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +- `affine` expects to find an image called `test.jpg` in the current directory. diff --git a/example/anisotropic_diffusion.cpp b/example/anisotropic_diffusion.cpp new file mode 100644 index 0000000000..04c95406bd --- /dev/null +++ b/example/anisotropic_diffusion.cpp @@ -0,0 +1,135 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include "boost/gil/extension/image_processing/diffusion.hpp" +#include +#include +#include + +#include +#include +#include +#include + +namespace gil = boost::gil; + +// Demonstrates Anisotropic Diffusion + +// This example uses the Perona-Malik's diffusion algorithm, which is the default in GIL. +// In addition, Gaussian conductivity and two wide range conductivity functions are also available. +// see include/boost/gil/image_processing/diffusion.hpp + +void gray_version(std::string const& input_path, std::string const& output_path, + unsigned int iteration_count, float kappa) +{ + gil::gray8_image_t input; + gil::read_image(input_path, input, gil::png_tag{}); + auto input_view = gil::view(input); + + gil::gray32f_image_t gray(input.dimensions()); + auto gray_view = gil::view(gray); + + gil::transform_pixels(input_view, gray_view, [](const gil::gray8_pixel_t& p) { return p[0]; }); + double sum_before = 0; + gil::for_each_pixel(gray_view, [&sum_before](gil::gray32f_pixel_t p) { sum_before += p[0]; }); + gil::gray32f_image_t output(gray.dimensions()); + auto output_view = gil::view(output); + + // gil::anisotropic_diffusion(gray_view, output_view, iteration_count, {kappa, delta_t}); + gil::default_anisotropic_diffusion(gray_view, output_view, iteration_count, kappa); + double sum_after = 0; + gil::for_each_pixel(output_view, [&sum_after](gil::gray32f_pixel_t p) { sum_after += p[0]; }); + + gil::gray8_image_t true_output(output.dimensions()); + gil::transform_pixels(output_view, gil::view(true_output), + [](gil::gray32f_pixel_t p) { return static_cast(p[0]); }); + + gil::write_view(output_path, gil::view(true_output), gil::png_tag{}); + + std::cout << "sum of intensity before diffusion: " << sum_before << '\n' + << "sum of intensity after diffusion: " << sum_after << '\n' + << "difference: " << sum_after - sum_before << '\n'; +} + +void rgb_version(const std::string& input_path, const std::string& output_path, + unsigned int iteration_count, float kappa) +{ + gil::rgb8_image_t input; + gil::read_image(input_path, input, gil::png_tag{}); + auto input_view = gil::view(input); + + gil::rgb32f_image_t gray(input.dimensions()); + auto gray_view = gil::view(gray); + + gil::transform_pixels(input_view, gray_view, [](const gil::rgb8_pixel_t& p) { + return gil::rgb32f_pixel_t(p[0], p[1], p[2]); + }); + double sum_before[3] = {}; + gil::for_each_pixel(gray_view, [&sum_before](gil::rgb32f_pixel_t p) { + sum_before[0] += p[0]; + sum_before[1] += p[1]; + sum_before[2] += p[2]; + }); + gil::rgb32f_image_t output(gray.dimensions()); + auto output_view = gil::view(output); + + // gil::anisotropic_diffusion(gray_view, output_view, iteration_count, {kappa, delta_t}); + gil::default_anisotropic_diffusion(gray_view, output_view, iteration_count, kappa); + double sum_after[3] = {}; + gil::for_each_pixel(output_view, [&sum_after](gil::rgb32f_pixel_t p) { + sum_after[0] += p[0]; + sum_after[1] += p[1]; + sum_after[2] += p[2]; + }); + + gil::rgb8_image_t true_output(output.dimensions()); + gil::transform_pixels(output_view, gil::view(true_output), [](gil::rgb32f_pixel_t p) { + return gil::rgb8_pixel_t(static_cast(p[0]), static_cast(p[1]), + static_cast(p[2])); + }); + + gil::write_view(output_path, gil::view(true_output), gil::png_tag{}); + std::cout << "sum of intensity before diffusion: (" << sum_before[0] << ", " << sum_before[1] + << ", " << sum_before[2] << ")\n" + << "sum of intensity after diffusion: (" << sum_after[0] << ", " << sum_after[1] + << ", " << sum_after[2] << ")\n" + << "difference: (" << sum_after[0] - sum_before[0] << ", " + << sum_after[1] - sum_before[1] << ", " << sum_after[2] - sum_before[2] << ")\n"; +} + +int main(int argc, char* argv[]) +{ + if (argc != 6) + { + std::cerr << "usage: " << argv[0] + << " " + " \n"; + return -1; + } + std::string input_path = argv[1]; + std::string output_path = argv[2]; + std::string colorspace = argv[3]; + + unsigned int iteration_count = static_cast(std::stoul(argv[4])); + float kappa = std::stof(argv[5]); + if (colorspace == "gray") + { + gray_version(input_path, output_path, iteration_count, kappa); + } + else if (colorspace == "rgb") + { + rgb_version(input_path, output_path, iteration_count, kappa); + } + else + { + std::cerr << "unknown colorspace option passed (did you type gray with A?)\n"; + } +} diff --git a/example/anisotropic_diffusion.md b/example/anisotropic_diffusion.md new file mode 100644 index 0000000000..770fa452c7 --- /dev/null +++ b/example/anisotropic_diffusion.md @@ -0,0 +1,25 @@ +# Anisotropic diffusion + +Anisotropic diffusion capabilities in GIL are demonstrated by the program `anisotropic_diffusion`, compiled from the sources `example/anisotropic_diffusion.cpp`. + +## Synopsis + +`anisoptropic_diffusion input.png output.png gray|rgb iterations kappa` +- The first parameter must be the full path to an existing image in the JPEG format for `anisoptropic_diffusion` to process +- The second parameter is the full path to the output image of `anisotropic_diffusion`. The directory will *not* be created and must exist. +- The third parameter is the colour space: either `gray` or `rgb` +- The fourth parameter is the number of iterations, which *must* be a positive integer +- The fifth and last parameter is the value of the kappa constant + +Note that both the input and the ouput images must be in the PNG format. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +`anisotropic_diffusion` has no specific execution requirements. diff --git a/example/b2/README.md b/example/b2/README.md index e4f56aaf8a..c847a26015 100644 --- a/example/b2/README.md +++ b/example/b2/README.md @@ -8,7 +8,7 @@ users who wish to run complete build of Boost.GIL tests and examples. Copy any of the provided user configuration files to `%HOME%\user-config.jam` on Windows or `$HOME/user-config.jam` on Linux. -Refer to [Boost.Build User Manual](https://boostorg.github.io/build/) +Refer to [B2 User Manual](https://www.bfgroup.xyz/b2/manual/release/index.html) for more details about use of configuration files. ## Examples diff --git a/example/cmake/CMakeSettings.json b/example/cmake/CMakeSettings.json index 8aee0eeaa4..c28101f929 100644 --- a/example/cmake/CMakeSettings.json +++ b/example/cmake/CMakeSettings.json @@ -7,8 +7,8 @@ { "BuildDir": "${workspaceRoot}\\_build" }, { "InstallDir": "${workspaceRoot}\\_install" }, { "DEFAULT_BUILD_TESTING": "OFF" }, - { "DEFAULT_Boost_ADDITIONAL_VERSIONS": "1.74;1.73;1.72;1.71" }, - { "DEFAULT_Boost_COMPILER": "-vc142;-vc141" }, + { "DEFAULT_Boost_ADDITIONAL_VERSIONS": "1.80" }, + { "DEFAULT_Boost_COMPILER": "-vc143" }, { "DEFAULT_Boost_DEBUG": "ON" }, { "DEFAULT_GIL_BUILD_EXAMPLES": "ON" }, { "DEFAULT_GIL_BUILD_HEADER_TESTS": "OFF" }, diff --git a/example/convolution.cpp b/example/convolution.cpp index 4844f35aac..b7e1b6b2e3 100644 --- a/example/convolution.cpp +++ b/example/convolution.cpp @@ -1,15 +1,28 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // 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 + #include #include -#include -#include +#include +#include + +// Convolves the image with a Gaussian kernel. + +// Note that the kernel can be fixed or resizable: +// kernel_1d_fixed k(matrix, centre) produces a fixed kernel +// kernel_1d k(matrix, size, centre) produces a resizable kernel + +// Work can be done row by row and column by column, as in this example, +// using the functions convolve_rows and convolve_cols (or their _fixed counterpart) +// but the header boost/gil/image_processing/convolve.hpp also offers the function convolve_1d which combines the two. -// Example for convolve_rows() and convolve_cols() in the numeric extension +// See also: +// convolve2d.cpp - Convolution with 2d kernels int main() { using namespace boost::gil; diff --git a/example/convolution.md b/example/convolution.md new file mode 100644 index 0000000000..0e0b621fc1 --- /dev/null +++ b/example/convolution.md @@ -0,0 +1,22 @@ +# Convolution + +Convolution capabilities in GIL are demonstrated by the program `convolution`, compiled from the sources `example/convolution.cpp`. + +## Synopsis + +`convolution` + +The program doesn't take any argument on the command line. + +`convolution` expects to find an image called `test.jpg` in the current directory, and produces two images in return, where the filters have been applied: `out-convolution.jpg` and `out-convolution2.jpg` + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +- `convolution` expects to find an image called `test.jpg` in the current directory. diff --git a/example/convolve2d.cpp b/example/convolve2d.cpp index 338f49a3ab..caa61c55e5 100644 --- a/example/convolve2d.cpp +++ b/example/convolve2d.cpp @@ -1,18 +1,36 @@ +// +// Copyright 2019 Miral Shah +// Copyright 2019 Mateusz Loskot +// Copyright 2021 Pranam Lashkari +// +// Use, modification and distribution are subject to 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) + #include #include -#include -#include +#include +#include #include #include + using namespace boost::gil; using namespace std; + +// Convolves the image with a 2d kernel. + +// Note that the kernel can be fixed or resizable: +// kernel_2d_fixed k(elements, centre_y, centre_x) produces a fixed kernel +// kernel_2d k(elements, size, centre_y, centre_x) produces a resizable kernel +// The size of the kernel matrix is deduced as the square root of the number of the elements (9 elements yield a 3x3 matrix) + +// See also: +// convolution.cpp - Convolution with 2d kernels + + int main() { - //gray8_image_t img; - //read_image("test_adaptive.png", img, png_tag{}); - //gray8_image_t img_out(img.dimensions()); - gray8_image_t img; read_image("src_view.png", img, png_tag{}); gray8_image_t img_out(img.dimensions()), img_out1(img.dimensions()); @@ -21,21 +39,16 @@ int main() detail::kernel_2d kernel(v.begin(), v.size(), 1, 1); detail::convolve_2d(view(img), kernel, view(img_out1)); - //write_view("out-convolve2d.png", view(img_out), png_tag{}); - write_view("out-convolve2d.png", view(img_out1), jpeg_tag{}); + write_view("out-convolve2d.png", view(img_out1), png_tag{}); - - //------------------------------------// std::vector v1(3, 1.0f / 3.0f); kernel_1d kernel1(v1.begin(), v1.size(), 1); detail::convolve_1d(const_view(img), kernel1, view(img_out), boundary_option::extend_zero); write_view("out-convolve_option_extend_zero.png", view(img_out), png_tag{}); - if (equal_pixels(view(img_out1), view(img_out)))cout << "convolve_option_extend_zero" << endl; - - cout << "done\n"; - cin.get(); + if (equal_pixels(view(img_out1), view(img_out))) + cout << "convolve_option_extend_zero" << endl; return 0; } diff --git a/example/convolve2d.md b/example/convolve2d.md new file mode 100644 index 0000000000..86c2bf9351 --- /dev/null +++ b/example/convolve2d.md @@ -0,0 +1,25 @@ +# Convolution (2d kernel) + +2d kernel convolution capabilities in GIL are demonstrated by the program `convolve2d`, compiled from the sources `example/convolve2d.cpp`. + +## Synopsis + +`convolve2d` + +The program doesn't take any argument on the command line. + +`convolve2d` expects to find an image called `src_view.png` in the current directory, and produces two images in return, where the filters have been applied: `out-convolve2d.png` and `out-convolve_option_extend_zero.png` + +Note that the user is expected to press a key to end the program. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured +- The PNG library installed and configured. + +### Execution requirements + +- `convolve2d` expects to find an image called `src_view.png` in the current directory. diff --git a/example/dynamic_image.cpp b/example/dynamic_image.cpp index d7abdbf390..5226572414 100644 --- a/example/dynamic_image.cpp +++ b/example/dynamic_image.cpp @@ -5,18 +5,23 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #include #include #include #include +// Demonstrates how to use images whose type is instantiated at run time. + int main() { namespace gil = boost::gil; gil::any_image dynamic_image; gil::read_image("test.jpg", dynamic_image, gil::jpeg_tag()); + // Save the image upside down, preserving its native color space and channel depth auto view = gil::flipped_up_down_view(gil::const_view(dynamic_image)); + gil::write_view("out-dynamic_image.jpg", view, gil::jpeg_tag()); } diff --git a/example/dynamic_image.md b/example/dynamic_image.md new file mode 100644 index 0000000000..fb27cfbe46 --- /dev/null +++ b/example/dynamic_image.md @@ -0,0 +1,22 @@ +# Dynamic Image + +Dynamic image manipulation capabilities in GIL are demonstrated by the program `dynamic_image`, compiled from the sources `example/dynamic_image.cpp`. + +## Synopsis + +`dynamic_image` + +The program doesn't take any argument on the command line. + +`dynamic_image` expects to find an image called `test.jpg` in the current directory, and produces an image in return, where the a flip has been applied: `out-dynamic_image.jpg`. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured + +### Execution requirements + +- `dynamic_image` expects to find an image called `test.jpg` in the current directory. diff --git a/example/harris.cpp b/example/harris.cpp index f860105d2f..ea19c98ed0 100644 --- a/example/harris.cpp +++ b/example/harris.cpp @@ -1,24 +1,32 @@ // // Copyright 2019 Olzhas Zhumabek +// Copyright 2021 Pranam Lashkari // // Use, modification and distribution are subject to 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) // -#include -#include + #include -#include +#include +#include #include -#include -#include +#include +#include +#include + +#include "hvstack.hpp" + +#include #include -#include #include -#include +#include +#include namespace gil = boost::gil; +// Demonstrates Harris corner detection + // some images might produce artifacts // when converted to grayscale, // which was previously observed on @@ -29,24 +37,28 @@ gil::gray8_image_t to_grayscale(gil::rgb8_view_t original) gil::gray8_image_t output_image(original.dimensions()); auto output = gil::view(output_image); constexpr double max_channel_intensity = (std::numeric_limits::max)(); - for (long int y = 0; y < original.height(); ++y) { - for (long int x = 0; x < original.width(); ++x) { + for (long int y = 0; y < original.height(); ++y) + { + for (long int x = 0; x < original.width(); ++x) + { // scale the values into range [0, 1] and calculate linear intensity - double red_intensity = original(x, y).at(std::integral_constant{}) - / max_channel_intensity; - double green_intensity = original(x, y).at(std::integral_constant{}) - / max_channel_intensity; - double blue_intensity = original(x, y).at(std::integral_constant{}) - / max_channel_intensity; - auto linear_luminosity = 0.2126 * red_intensity - + 0.7152 * green_intensity - + 0.0722 * blue_intensity; + double red_intensity = + original(x, y).at(std::integral_constant{}) / max_channel_intensity; + double green_intensity = + original(x, y).at(std::integral_constant{}) / max_channel_intensity; + double blue_intensity = + original(x, y).at(std::integral_constant{}) / max_channel_intensity; + auto linear_luminosity = + 0.2126 * red_intensity + 0.7152 * green_intensity + 0.0722 * blue_intensity; // perform gamma adjustment double gamma_compressed_luminosity = 0; - if (linear_luminosity < 0.0031308) { + if (linear_luminosity < 0.0031308) + { gamma_compressed_luminosity = linear_luminosity * 12.92; - } else { + } + else + { gamma_compressed_luminosity = 1.055 * std::pow(linear_luminosity, 1 / 2.4) - 0.055; } @@ -62,45 +74,47 @@ void apply_gaussian_blur(gil::gray8_view_t input_view, gil::gray8_view_t output_ { constexpr static auto filterHeight = 5ull; constexpr static auto filterWidth = 5ull; - constexpr static double filter[filterHeight][filterWidth] = - { - 2, 4, 6, 4, 2, - 4, 9, 12, 9, 4, - 5, 12, 15, 12, 5, - 4, 9, 12, 9, 4, - 2, 4, 5, 4, 2, + constexpr static double filter[filterHeight][filterWidth] = { + { 2, 4, 6, 4, 2 }, + { 4, 9, 12, 9, 4 }, + { 5, 12, 15, 12, 5}, + { 4, 9, 12, 9, 4 }, + { 2, 4, 5, 4, 2 } }; constexpr double factor = 1.0 / 159; constexpr double bias = 0.0; const auto height = input_view.height(); const auto width = input_view.width(); - for (long x = 0; x < width; ++x) { - for (long y = 0; y < height; ++y) { + for (long x = 0; x < width; ++x) + { + for (long y = 0; y < height; ++y) + { double intensity = 0.0; - for (size_t filter_y = 0; filter_y < filterHeight; ++filter_y) { - for (size_t filter_x = 0; filter_x < filterWidth; ++filter_x) { + for (size_t filter_y = 0; filter_y < filterHeight; ++filter_y) + { + for (size_t filter_x = 0; filter_x < filterWidth; ++filter_x) + { int image_x = x - filterWidth / 2 + filter_x; int image_y = y - filterHeight / 2 + filter_y; - if (image_x >= input_view.width() || image_x < 0 - || image_y >= input_view.height() || image_y < 0) { + if (image_x >= input_view.width() || image_x < 0 || + image_y >= input_view.height() || image_y < 0) + { continue; } auto& pixel = input_view(image_x, image_y); - intensity += pixel.at(std::integral_constant{}) - * filter[filter_y][filter_x]; + intensity += + pixel.at(std::integral_constant{}) * filter[filter_y][filter_x]; } } auto& pixel = output_view(gil::point_t(x, y)); pixel = (std::min)((std::max)(int(factor * intensity + bias), 0), 255); } - } } -std::vector suppress( - gil::gray32f_view_t harris_response, - double harris_response_threshold) +std::vector suppress(gil::gray32f_view_t harris_response, + double harris_response_threshold) { std::vector corner_points; for (gil::gray32f_view_t::coord_t y = 1; y < harris_response.height() - 1; ++y) @@ -111,29 +125,17 @@ std::vector suppress( return pixel.at(std::integral_constant{}); }; double values[9] = { - value(harris_response(x - 1, y - 1)), - value(harris_response(x, y - 1)), - value(harris_response(x + 1, y - 1)), - value(harris_response(x - 1, y)), - value(harris_response(x, y)), - value(harris_response(x + 1, y)), - value(harris_response(x - 1, y + 1)), - value(harris_response(x, y + 1)), - value(harris_response(x + 1, y + 1)) - }; + value(harris_response(x - 1, y - 1)), value(harris_response(x, y - 1)), + value(harris_response(x + 1, y - 1)), value(harris_response(x - 1, y)), + value(harris_response(x, y)), value(harris_response(x + 1, y)), + value(harris_response(x - 1, y + 1)), value(harris_response(x, y + 1)), + value(harris_response(x + 1, y + 1))}; - auto maxima = *std::max_element( - values, - values + 9, - [](double lhs, double rhs) - { - return lhs < rhs; - } - ); + auto maxima = *std::max_element(values, values + 9, + [](double lhs, double rhs) { return lhs < rhs; }); - if (maxima == value(harris_response(x, y)) - && std::count(values, values + 9, maxima) == 1 - && maxima >= harris_response_threshold) + if (maxima == value(harris_response(x, y)) && + std::count(values, values + 9, maxima) == 1 && maxima >= harris_response_threshold) { corner_points.emplace_back(x, y); } @@ -147,8 +149,9 @@ int main(int argc, char* argv[]) { if (argc != 6) { - std::cout << "usage: " << argv[0] << " " - " \n"; + std::cout << "usage: " << argv[0] + << " " + " \n"; return -1; } @@ -159,7 +162,8 @@ int main(int argc, char* argv[]) gil::rgb8_image_t input_image; gil::read_image(argv[1], input_image, gil::png_tag{}); - + auto original_image = input_image; + auto original_view = gil::view(original_image); auto input_view = gil::view(input_image); auto grayscaled = to_grayscale(input_view); gil::gray8_image_t smoothed_image(grayscaled.dimensions()); @@ -178,30 +182,22 @@ int main(int argc, char* argv[]) gil::gray32f_image_t m11(x_gradient.dimensions()); gil::gray32f_image_t m12_21(x_gradient.dimensions()); gil::gray32f_image_t m22(x_gradient.dimensions()); - gil::compute_tensor_entries( - x_gradient, - y_gradient, - gil::view(m11), - gil::view(m12_21), - gil::view(m22) - ); + gil::compute_tensor_entries(x_gradient, y_gradient, gil::view(m11), gil::view(m12_21), + gil::view(m22)); gil::gray32f_image_t harris_response(x_gradient.dimensions()); auto gaussian_kernel = gil::generate_gaussian_kernel(window_size, 0.84089642); - gil::compute_harris_responses( - gil::view(m11), - gil::view(m12_21), - gil::view(m22), - gaussian_kernel, - discrimnation_constant, - gil::view(harris_response) - ); + gil::compute_harris_responses(gil::view(m11), gil::view(m12_21), gil::view(m22), + gaussian_kernel, discrimnation_constant, + gil::view(harris_response)); auto corner_points = suppress(gil::view(harris_response), harris_response_threshold); - for (auto point: corner_points) + for (auto point : corner_points) { input_view(point) = gil::rgb8_pixel_t(0, 0, 0); input_view(point).at(std::integral_constant{}) = 255; } - gil::write_view(argv[5], input_view, gil::png_tag{}); + auto stacked = gil::hstack(std::vector{original_view, input_view}); + + gil::write_view(argv[5], gil::view(stacked), gil::png_tag{}); } diff --git a/example/harris.md b/example/harris.md new file mode 100644 index 0000000000..21770da315 --- /dev/null +++ b/example/harris.md @@ -0,0 +1,24 @@ +# Harris Corner Detection + +Harris corner detection capabilities in GIL are demonstrated by the program `harris`, compiled from the sources `example/harris.cpp` and `hvstack.hpp`. + +## Synopsis + +`harris input.png window-size discriminant harris-threshold output.png` + +- The first parameter must be the full path to an existing image in the PNG format for `harris` to process +- The second parameter is the size of the window containing the pixels to analyse and must be an odd number (e.g. 9 for a 3x3 matrix) +- The third parameter is the empirically-defined discriminant constant, usually between 0.04 and 0.06 +- The fourth parameter is the harris threshold used to identify the optimal values +- The fifth and last parameter is the full path to the output image of `harris`. The directory will *not* be created and must exist. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured + +### Execution requirements + +- `harris` has no specific execution requirements. diff --git a/example/hessian.cpp b/example/hessian.cpp index d12f51ddb0..7f9db59484 100644 --- a/example/hessian.cpp +++ b/example/hessian.cpp @@ -1,3 +1,11 @@ +// +// Copyright 2019 Olzhas Zhumabek +// +// 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 +// + #include #include #include @@ -11,6 +19,8 @@ namespace gil = boost::gil; +// Demonstrates Hessian feature (blob) detection + // some images might produce artifacts // when converted to grayscale, // which was previously observed on @@ -60,15 +70,15 @@ gil::gray8_image_t to_grayscale(gil::rgb8_view_t original) void apply_gaussian_blur(gil::gray8_view_t input_view, gil::gray8_view_t output_view) { - constexpr static auto filter_height = 5ull; - constexpr static auto filter_width = 5ull; + constexpr static std::ptrdiff_t filter_height = 5ull; + constexpr static std::ptrdiff_t filter_width = 5ull; constexpr static double filter[filter_height][filter_width] = { - 2, 4, 6, 4, 2, - 4, 9, 12, 9, 4, - 5, 12, 15, 12, 5, - 4, 9, 12, 9, 4, - 2, 4, 5, 4, 2, + { 2, 4, 6, 4, 2 }, + { 4, 9, 12, 9, 4 }, + { 5, 12, 15, 12, 5 }, + { 4, 9, 12, 9, 4 }, + { 2, 4, 5, 4, 2 } }; constexpr double factor = 1.0 / 159; constexpr double bias = 0.0; diff --git a/example/hessian.md b/example/hessian.md new file mode 100644 index 0000000000..376cefdb8c --- /dev/null +++ b/example/hessian.md @@ -0,0 +1,20 @@ +# Hessian Feature Detection + +Hessian feature detection capabilities in GIL are demonstrated by the program `hessian`, compiled from the sources `example/hessian.cpp`. + +## Synopsis +`hessian input.png window-size hessian-threshold output.png` + +- The first parameter must be the full path to an existing image in the PNG format for `hessian` to process +- The second parameter is the size of the window containing the pixels to analyse and must be an odd number (e.g. 9 for a 3x3 matrix) +- The third parameter is the hessian threshold used to identify the optimal values +- The fourth and last parameter is the full path to the output image of `hessian`. The directory will *not* be created and must exist. + +## Specific requirements + +### Build requirements +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured + +### Execution requirements +- `hessian` has no specific execution requirements. diff --git a/example/histogram.cpp b/example/histogram.cpp index b1b4896429..5dcdf2d1ee 100644 --- a/example/histogram.cpp +++ b/example/histogram.cpp @@ -1,49 +1,55 @@ // -// Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2020 Debabrata Mandal // // 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 // -#include -#include -#include -#include +#include +#include +#include -// Example file to demonstrate a way to compute histogram +#include using namespace boost::gil; -template -void gray_image_hist(GrayView const& img_view, R& hist) -{ - for (auto it = img_view.begin(); it != img_view.end(); ++it) - ++hist[*it]; +// Explains how to use the histogram class and some of its features +// that can be applied for a variety of tasks. - // Alternatively, prefer the algorithm with lambda - // for_each_pixel(img_view, [&hist](gray8_pixel_t const& pixel) { - // ++hist[pixel]; - // }); -} +// See also: +// histogram_equalization.cpp - Regular Histogram Equalization +// adaptive_histogram_equalization.cpp - Adaptive Histogram Equalization +// histogram_matching.cpp - Reference-based histogram computation -template -void get_hist(const V& img_view, R& hist) { - gray_image_hist(color_converted_view(img_view), hist); -} - -int main() { - rgb8_image_t img; - read_image("test.jpg", img, jpeg_tag()); - - int histogram[256]; - std::fill(histogram,histogram + 256, 0); - get_hist(const_view(img), histogram); - - std::fstream histo_file("out-histogram.txt", std::ios::out); - for(std::size_t ii = 0; ii < 256; ++ii) - histo_file << histogram[ii] << std::endl; - histo_file.close(); +int main() +{ + // Create a histogram class. Use uint or unsigned short as the default axes type in most cases. + histogram h; + + // Fill histogram with GIL images (of any color space) + gray8_image_t g; + read_image("test_adaptive.png", g, png_tag{}); + + fill_histogram + ( + view(g), // Input image view + h, // Histogram to be filled + 1, // Histogram bin widths + false, // Specify whether to accumulate over the values already present in h (default = false) + true, // Specify whether to have a sparse (true) or continuous histogram (false) (default = true) + false, // Specify if image mask is to be specified + {{}}, // Mask as a 2D vector. Used only if prev argument specified + {0}, // Lower limit on the values in histogram (default numeric_limit::min() on axes) + {255}, // Upper limit on the values in histogram (default numeric_limit::max() on axes) + true // Use specified limits if this is true (default is false) + ); + + // Normalize the histogram + h.normalize(); + + // Get a cumulative histogram from the histogram + auto h2 = cumulative_histogram(h); return 0; } diff --git a/example/histogram.md b/example/histogram.md new file mode 100644 index 0000000000..e785c8741a --- /dev/null +++ b/example/histogram.md @@ -0,0 +1,23 @@ +# Histogram + +Histogram capabilities in GIL are demonstrated by the program `histogram`, compiled from the sources `example/histogram.cpp`. + +## Synopsis + +`histogram` + +The program doesn't take any argument on the command line. + +`histogram` expects to find an image called `test_adaptive.png` in the current directory. +The program doesn't produce any output. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +- `histogram` expects to find an image called `test_adaptive.png` in the current directory. diff --git a/example/histogram_equalization.cpp b/example/histogram_equalization.cpp new file mode 100644 index 0000000000..eec5c46a66 --- /dev/null +++ b/example/histogram_equalization.cpp @@ -0,0 +1,35 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include +#include +#include + +using namespace boost::gil; + +// Demonstrates Histogram Equalization + +// See also: +// histogram.cpp - General use of histograms in GIL +// adaptive_histogram_equalization.cpp - Adaptive Histogram Equalization +// histogram_matching.cpp - Reference-based histogram computation + +int main() +{ + gray8_image_t img; + + read_image("test_adaptive.png", img, png_tag{}); + gray8_image_t img_out(img.dimensions()); + + // Consider changing image to independent color space, e.g. cmyk + boost::gil::histogram_equalization(view(img),view(img_out)); + + write_view("histogram_gray_equalized.png", view(img_out), png_tag{}); + + return 0; +} diff --git a/example/histogram_equalization.md b/example/histogram_equalization.md new file mode 100644 index 0000000000..67a8ee1bec --- /dev/null +++ b/example/histogram_equalization.md @@ -0,0 +1,68 @@ +# Histogram Equalization + +Histogram equalization capabilities in GIL are demonstrated by the program `histogram_equalization`, compiled from the sources `example/histogram_equalization.cpp`. + +## Synopsis + +`histogram_equalization` + +The program doesn't take any argument on the command line. + +Hough line transform solves the equation of a line in reverse, but in *polar coordinates*! The implementation will make each pixel vote on all possible lines it could be part of, limited by input Hough parameters. + +A line in polar coordinates is represented by normal to it in polar coordinates, as shown in example here +https://docs.opencv.org/3.4/d9/db0/tutorial_hough_lines.html + +Since real line is the diagonal throughout the image, the angle of normal to it will 45 degrees. (1s represent real line, 5s represent normal to it). + +``` +1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 +0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 +0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 +0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 +0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 +0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 +0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 +5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 +``` + +It is obvious that the angle for the expected Hough transform is 45 degrees (angle between 5s and bottom 0s). Now we +need to find the length of 5s. Since 5s, 1s and bottom 0s form a right triangle, we know that bottom 0s are 32 in length +and is a hypotenuse! Using trivial trigonometry we know that the length we are searching for is 32 * cos(45). + +`histogram_equalization` expects to find an image called `test_adaptive.png` in the current directory, and produces an image in return, where the equalization have been applied: `histogram_gray_equalized.png`. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +- `histogram_equalization` expects to find an image called `test_adaptive.png` in the current directory. diff --git a/example/histogram_matching.cpp b/example/histogram_matching.cpp new file mode 100644 index 0000000000..e4b0e9ec9f --- /dev/null +++ b/example/histogram_matching.cpp @@ -0,0 +1,52 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include + +#include + +using namespace boost::gil; + +// Demonstrates Histogram Matching + +// See also: +// histogram_equalization.cpp - Regular Histogram Equalization +// adaptive_histogram_equalization.cpp - Adaptive Histogram Equalization +// histogram.cpp - General use of histograms in GIL + +std::vector> get_mask(gray8_view_t const& mask) +{ + std::vector> mask_vec(mask.height(), std::vector(mask.width(), 0)); + for (std::ptrdiff_t i = 0; i < mask.height(); i++) + { + for (std::ptrdiff_t j = 0; j < mask.width(); j++) + { + mask_vec[i][j] = mask(j, i); + } + } + return mask_vec; +} + +int main() +{ + gray8_image_t img; + read_image("test_adaptive.png", img, png_tag{}); + + gray8_image_t ref_img; + read_image("test_adaptive.png", ref_img, png_tag{}); + + gray8_image_t img_out(img.dimensions()); + + boost::gil::histogram_matching(view(img), view(ref_img), view(img_out)); + + write_view("histogram_gray_matching.png", view(img_out), png_tag{}); + + return 0; +} diff --git a/example/histogram_matching.md b/example/histogram_matching.md new file mode 100644 index 0000000000..9c1e904abb --- /dev/null +++ b/example/histogram_matching.md @@ -0,0 +1,19 @@ +# Histogram Matching + +Histogram matching capabilities in GIL are demonstrated by the program `histogram_matching`, compiled from the sources `example/histogram_matching.cpp`. + +## Synopsis +`histogram_matching` + +The program doesn't take any argument on the command line. + +`histogram_matching` expects to find an image called `test_adaptive.png` in the current directory, and produces an image in return, where the equalization have been applied: `histogram_gray_matching.png`. + +## Specific requirements + +### Build requirements +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements +- `histogram_matching` expects to find an image called `test_adaptive.png` in the current directory. diff --git a/example/hough_transform_circle.cpp b/example/hough_transform_circle.cpp new file mode 100644 index 0000000000..44a739d62d --- /dev/null +++ b/example/hough_transform_circle.cpp @@ -0,0 +1,61 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include + +#include +#include +#include + +namespace gil = boost::gil; + +// Demonstrates how to use a Hough transform to identify a circle + +// Note this relies on the brute force approach, which today is the only one available in GIL +// The function hough_circle_transform_brute, defined in include/boost/gil/image_processing/hough_transform.cpp, +// accepts a greyscale edge map, the three Hough parameters allowing to do the drawing and the voting, +// an accumulator in the form of an iterator of views of the parameter space and a utility rasterizer to produce the points. +// The example outputs the voting cell of the centre of a circle drawn programatically. +// See also: +// hough_transform_line.cpp - Hough transform to detect lines + +int main() +{ + const std::size_t size = 128; + gil::gray8_image_t input_image(size, size); + auto input = gil::view(input_image); + + const std::ptrdiff_t circle_radius = 16; + const gil::point_t circle_center = {64, 64}; + const auto rasterizer = gil::midpoint_circle_rasterizer{circle_center, circle_radius}; + gil::apply_rasterizer(input, rasterizer, gil::gray8_pixel_t{255}); + + const auto radius_parameter = + gil::hough_parameter::from_step_count(circle_radius, 3, 3); + const auto x_parameter = + gil::hough_parameter::from_step_count(circle_center.x, 3, 3); + const auto y_parameter = + gil::hough_parameter::from_step_count(circle_center.x, 3, 3); + + std::vector parameter_space_images( + radius_parameter.step_count, + gil::gray16_image_t(x_parameter.step_count, y_parameter.step_count)); + std::vector parameter_space_views(parameter_space_images.size()); + std::transform(parameter_space_images.begin(), parameter_space_images.end(), + parameter_space_views.begin(), + [](gil::gray16_image_t& img) + { + return gil::view(img); + }); + + gil::hough_circle_transform_brute(input, radius_parameter, x_parameter, y_parameter, + parameter_space_views.begin(), rasterizer); + std::cout << parameter_space_views[3](3, 3) << '\n'; +} diff --git a/example/hough_transform_circle.md b/example/hough_transform_circle.md new file mode 100644 index 0000000000..9bcfcd6ff6 --- /dev/null +++ b/example/hough_transform_circle.md @@ -0,0 +1,21 @@ +# Hough Circle Transform + +Hough circle transform capabilities in GIL are demonstrated by the program `hough_transform_circle`, compiled from the sources `example/hough_transform_circle.cpp`. + +## Synopsis + +`hough_transform_circle` + +The program doesn't take any argument on the command line. + +The program outputs the voting cell of the centre of a circle drawn programatically. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above. + +### Execution requirements + +`hough_transform_circle` doesn't have any specific execution requirements. diff --git a/example/hough_transform_line.cpp b/example/hough_transform_line.cpp new file mode 100644 index 0000000000..d67fff5b59 --- /dev/null +++ b/example/hough_transform_line.cpp @@ -0,0 +1,81 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include +#include + +namespace gil = boost::gil; + +// Demonstrates the Hough transform to detect lines + +// The algorithm itself is implemented in include/boost/gil/image_processing/hough_transform.hpp. +// It follows the regular algorithm, using Hesse notation, and steps around each point using the minimal visible angle +// defined as atan2(1, d), where d is whichever dimension in the input image is the longest. +// The function make_theta_parameter, defined in include/boost/gil/image_processing/hough_parameter.hpp, allows to generate the parameter accordingly. +// See also: +// hough_transform_circle.cpp - Hough transform to detect circles + +int main() +{ + const std::ptrdiff_t size = 32; + gil::gray16_image_t input_image(size, size); + auto input_view = gil::view(input_image); + + // fill secondary diagonal with ones + // do note that origin is located at upper left, + // not bottom left as in usual plots + for (std::ptrdiff_t i = 0; i < size; ++i) + { + input_view(i, size - i - 1) = 1; + } + + // print vertically flipped for better understanding of origin location + for (std::ptrdiff_t y = size - 1; y >= 0; --y) + { + for (std::ptrdiff_t x = 0; x < size; ++x) + { + std::cout << input_view(x, y)[0] << ' '; + } + std::cout << '\n'; + } + + // this is the expected theta + double _45_degrees = gil::detail::pi / 4; + double _5_degrees = gil::detail::pi / 36; + auto theta_parameter = + gil::make_theta_parameter(_45_degrees, _5_degrees, input_view.dimensions()); + auto expected_radius = static_cast(std::floor(std::cos(_45_degrees) * size)); + auto radius_parameter = + gil::hough_parameter::from_step_size(expected_radius, 7, 1); + gil::gray32_image_t accumulator_array_image(theta_parameter.step_count, + radius_parameter.step_count); + auto accumulator_array = gil::view(accumulator_array_image); + gil::hough_line_transform(input_view, accumulator_array, theta_parameter, radius_parameter); + std::cout << "expecting maximum at theta=" << _45_degrees << " and radius=" << expected_radius + << '\n'; + for (std::size_t theta_index = 0; theta_index < theta_parameter.step_count; ++theta_index) + { + for (std::size_t radius_index = 0; radius_index < radius_parameter.step_count; + ++radius_index) + { + double current_theta = + theta_parameter.start_point + theta_index * theta_parameter.step_size; + std::ptrdiff_t current_radius = + radius_parameter.start_point + radius_parameter.step_size * radius_index; + if (current_theta == _45_degrees && current_radius == expected_radius) { + std::cout << "* "; + } + std::cout << "theta: " << current_theta << " radius: " << current_radius + << " accumulated value: " << accumulator_array(theta_index, radius_index)[0] + << '\n'; + } + } +} diff --git a/example/hough_transform_line.md b/example/hough_transform_line.md new file mode 100644 index 0000000000..a49493ecf4 --- /dev/null +++ b/example/hough_transform_line.md @@ -0,0 +1,21 @@ +# Hough Line Transform + +Hough line transform capabilities in GIL are demonstrated by the program `hough_transform_line`, compiled from the sources `example/hough_transform_line.cpp`. + +## Synopsis + +`hough_transform_line` + +The program doesn't take any argument on the command line. + +The program outputs the expected theta and radius, followed by every step of the search. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above. + +### Execution requirements + +`hough_transform_line` doesn't have any specific execution requirements. diff --git a/example/hvstack.hpp b/example/hvstack.hpp new file mode 100644 index 0000000000..9110b6f2de --- /dev/null +++ b/example/hvstack.hpp @@ -0,0 +1,123 @@ +// +// // Copyright 2021 Olzhas Zhumabek +// +// 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 +// + +#include "boost/gil/image_view_factory.hpp" +#include +#include +#include +#include +#include +#include + +namespace boost { namespace gil { +template +void hstack(const std::vector& views, const View& output_view) +{ + if (views.size() == 0) + { + throw std::invalid_argument("empty views vector is passed - cannot create stacked image"); + } + + auto height = views.front().height(); + for (const auto& view : views) + { + if (view.height() != height) + { + throw std::invalid_argument("one or many views are not of the same height"); + } + } + + std::ptrdiff_t full_width = + std::accumulate(views.begin(), views.end(), 0, + [](std::ptrdiff_t old, const View& view) { return old + view.width(); }); + if (output_view.width() != full_width || output_view.height() != height) + { + throw std::invalid_argument("the destination view is not of the right dimensions"); + } + + std::ptrdiff_t current_x = 0; + for (std::size_t i = 0; i < views.size(); ++i) + { + auto subview = + subimage_view(output_view, current_x, 0, views[i].width(), views[i].height()); + copy_pixels(views[i], subview); + current_x += views[i].width(); + } +} + +template +image hstack(const std::vector& views) +{ + if (views.size() == 0) + { + throw std::invalid_argument("empty views vector is passed - cannot create stacked image"); + } + + std::ptrdiff_t full_width = + std::accumulate(views.begin(), views.end(), 0, + [](std::ptrdiff_t old, const View& view) { return old + view.width(); }); + std::ptrdiff_t height = views.front().height(); + image result_image(full_width, height); + hstack(views, view(result_image)); + return result_image; +} + +template +void vstack(const std::vector& views, const View& output_view) +{ + if (views.size() == 0) + { + throw std::invalid_argument("empty views vector is passed - cannot create stacked image"); + } + + auto full_height = + std::accumulate(views.begin(), views.end(), 0, + [](std::ptrdiff_t old, const View& view) { return old + view.height(); }); + std::ptrdiff_t width = views.front().height(); + + for (const auto& view : views) + { + if (view.width() != width) + { + throw std::invalid_argument("one or many views are not of the same width"); + } + } + + if (output_view != full_height || output_view.width() != width) + { + throw std::invalid_argument("the destination view is not of the right dimensions"); + } + + std::ptrdiff_t current_y = 0; + for (std::size_t i = 0; i < views.size(); ++i) + { + auto subview = + subimage_view(output_view, 0, current_y, views[i].width(), views[i].height()); + copy_pixels(views[i], subview); + current_y += views[i].height(); + } +} + +template +image vstack(const std::vector& views) +{ + if (views.size() == 0) + { + throw std::invalid_argument("empty views vector is passed - cannot create stacked image"); + } + + auto full_height = + std::accumulate(views.begin(), views.end(), 0, + [](std::ptrdiff_t old, const View& view) { return old + view.height(); }); + std::ptrdiff_t width = views.front().height(); + + image result_image(width, full_height); + hstack(views, view(result_image)); + return result_image; +} +}} // namespace boost::gil diff --git a/example/interleaved_ptr.cpp b/example/interleaved_ptr.cpp index df80003b8c..1befd8cb0a 100644 --- a/example/interleaved_ptr.cpp +++ b/example/interleaved_ptr.cpp @@ -5,13 +5,17 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #ifdef WIN32 #define _CRT_SECURE_NO_DEPRECATE 1 #pragma warning(disable : 4244) // #pragma warning(disable : 4996) // MSFT declared it deprecated #endif -// Example file to demonstrate how to create a model of a pixel iterator +// Illustrates how to create a custom pixel reference and iterator. +// Creates a GIL image view over user-supplied data without the need to cast to GIL pixel type. +// The pixel iterator itself is implemented in interleaved_ptr.hpp, with the reference defined in interleaved_ref.hpp. + // FIXME: Review and remove if possible: gcc doesn't compile unless we forward-declare at_c before we include gil... namespace boost { namespace gil { diff --git a/example/interleaved_ptr.hpp b/example/interleaved_ptr.hpp index a60fc9d433..643af90099 100644 --- a/example/interleaved_ptr.hpp +++ b/example/interleaved_ptr.hpp @@ -5,6 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #ifndef BOOST_GIL_EXAMPLE_INTERLEAVED_PTR_HPP #define BOOST_GIL_EXAMPLE_INTERLEAVED_PTR_HPP diff --git a/example/interleaved_ptr.md b/example/interleaved_ptr.md new file mode 100644 index 0000000000..c0e3727711 --- /dev/null +++ b/example/interleaved_ptr.md @@ -0,0 +1,22 @@ +# Custom pixel reference and iteraror: Interleaved Pointer + +Definition of custom pixel reference and iterator capabilities in GIL are demonstrated by the program `interleaved_ptr`, compiled from the sources `example/interleaved_ptr.cpp`, `interleaved_ptr.hpp` and `interleaved_ref.hpp`. + +## Synopsis + +`interleaved_ptr` + +The program doesn't take any argument on the command line. + +The program expects to find an image, `test.jpg` in the current directory, and produces the image `out-interleaved_ptr.jpg` in return, that has been generated using the custom pixel reference and iterator objects. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`interleaved_ptr` expects to find an image, `test.jpg` in the current directory. diff --git a/example/interleaved_ref.hpp b/example/interleaved_ref.hpp index 837e7f222c..b016d36c13 100644 --- a/example/interleaved_ref.hpp +++ b/example/interleaved_ref.hpp @@ -5,6 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #ifndef BOOST_GIL_EXAMPLE_INTERLEAVED_REF_HPP #define BOOST_GIL_EXAMPLE_INTERLEAVED_REF_HPP diff --git a/example/mandelbrot.cpp b/example/mandelbrot.cpp index 0bcfc2ea14..fd89975ec9 100644 --- a/example/mandelbrot.cpp +++ b/example/mandelbrot.cpp @@ -5,11 +5,16 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #include #include #include -// Example for convolve_rows() and convolve_cols() in the numeric extension +// Creates a synthetic image defining the Mandelbrot set. +// The example relies on a virtual_2d_locator to iterate over the pixels in the destination view. +// The pixels (of type rgb8_pixel_t) are generated programmatically, and the code shows how to access +// the colour channels and set them to arbitrary values. + using namespace boost::gil; @@ -40,7 +45,7 @@ struct mandelbrot_fn t=pow(t,0.2); value_type ret; - for (int k=0; k::value; ++k) + for (std::size_t k=0; k::value; ++k) ret[k]=(typename channel_type

::type)(_in_color[k]*t + _out_color[k]*(1-t)); return ret; } diff --git a/example/mandelbrot.md b/example/mandelbrot.md new file mode 100644 index 0000000000..05af4a46ad --- /dev/null +++ b/example/mandelbrot.md @@ -0,0 +1,22 @@ +# Mandelbrot + +Pixel iteration using `virtual_2d_locators` capabilities in GIL are demonstrated by the program `mandelbrot`, compiled from the sources `example/mandelbrot.cpp`. + +## Synopsis + +`mandelbrot` + +The program doesn't take any argument on the command line. + +The program produces the image `out-mandelbrot.jpg` that has been generated using the `virtual_2d_locator` object. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`mandelbrot` has no specific execution requirements. diff --git a/example/morphology.cpp b/example/morphology.cpp new file mode 100644 index 0000000000..33f244ae7d --- /dev/null +++ b/example/morphology.cpp @@ -0,0 +1,154 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include +#include +#include + + +// Demonstrates a number of morphological operations +// The structuring element is defined as an instance of a kernel_2d: +// Default structuring element is SE = [1,1,1] +// |1,1,1| +// [1,1,1] +// SE(1,1)(center pixel) is the one which coincides with the currently +// considered pixel of the image to be convolved. The structuring element can be +// easily changed by the user. +// The example demonstrates the following morphological operations: +// - black_hat +// - top_hat +// - morphological_gradient +// - dilation +// - erosion +// - opening +// - closing +// - binary +// These operations are defined in include/boost/gil/image_processing/morphology.hpp + +namespace gil = boost::gil; +int main(int argc, char** argv) +{ + std::map operations; + if (argc < 4 || argc > 11) + { + throw std::invalid_argument( + "Wrong format of command line arguments.\n" + "Correct format is " + " " + "" + " \n"); + // User has to enter atleast one operation and they can enter maximum 8 + // operations considering binary conversion to be an + // operation.Output_image_template argument is the common component which + // will be added in names of all output images followed by a hyphen and + // the operation name. + // Example : + // ./example_morphology morphology_original.png out black_hat top_hat + // morphological_gradient dilation erosion opening closing binary + // Order of arguments entered will not matter with the exception of binary + // operation used for binary morphological operations.If binary is entered + // through the command line, it will always be the first operation to be + // applied. + return -1; + } + else + { + for (int i = 3; i < argc; ++i) + operations[argv[i]] = true; + } + gil::gray8_image_t img; + gil::read_image(argv[1], img, gil::png_tag{}); + + // Image can be converted to a binary format with high value as 255 and low + // value as 0 by using the threshold operator . This can be used for binary + // morphological operations . Convenient threshold for binary conversion may + // be chosen by the user. + if (operations["binary"]) + { + threshold_binary(view(img), view(img), 170, 255); + std::string name = argv[2]; + name += "-binary.png"; + gil::write_view(name, view(img), gil::png_tag{}); + } + + std::vector ker_vec(9, 1.0f); // Structuring element + gil::detail::kernel_2d ker_mat(ker_vec.begin(), ker_vec.size(), 1, 1); + gil::gray8_image_t img_out_dilation(img.dimensions()), img_out_erosion(img.dimensions()), + img_out_opening(img.dimensions()); + gil::gray8_image_t img_out_closing(img.dimensions()), img_out_mg(img.dimensions()), + img_out_top_hat(img.dimensions()); + gil::gray8_image_t img_out_black_hat(img.dimensions()); + + // Do not pass empty input image views in functions defined below for + // morphological operations to avoid errors. + if (operations["dilation"]) + { + // dilate(input_image_view,output_image_view,structuring_element,iterations) + dilate(view(img), view(img_out_dilation), ker_mat, 1); + std::string name = argv[2]; + name += "-dilation.png"; + gil::write_view(name, view(img_out_dilation), gil::png_tag{}); + } + + if (operations["erosion"]) + { + // erode(input_image_view,output_image_view,structuring_element,iterations) + erode(view(img), view(img_out_erosion), ker_mat, 1); + std::string name = argv[2]; + name += "-erosion.png"; + gil::write_view(name, view(img_out_erosion), gil::png_tag{}); + } + + if (operations["opening"]) + { + // opening(input_image_view,output_image_view,structuring_element) + opening(view(img), view(img_out_opening), ker_mat); + std::string name = argv[2]; + name += "-opening.png"; + gil::write_view(name, view(img_out_opening), gil::png_tag{}); + } + + if (operations["closing"]) + { + // closing(input_image_view,output_image_view,structuring_element) + closing(view(img), view(img_out_closing), ker_mat); + std::string name = argv[2]; + name += "-closing.png"; + gil::write_view(name, view(img_out_closing), gil::png_tag{}); + } + + if (operations["morphological_gradient"]) + { + // morphological_gradient(input_image_view,output_image_view,structuring_element) + morphological_gradient(view(img), view(img_out_mg), ker_mat); + std::string name = argv[2]; + name += "-morphological_gradient.png"; + gil::write_view(name, view(img_out_mg), gil::png_tag{}); + } + + if (operations["top_hat"]) + { + // top_hat(input_image_view,output_image_view,structuring_element) + top_hat(view(img), view(img_out_top_hat), ker_mat); + std::string name = argv[2]; + name += "-top_hat.png"; + gil::write_view(name, view(img_out_top_hat), gil::png_tag{}); + } + + if (operations["black_hat"]) + { + // black_hat(input_image_view,output_image_view,structuring_element) + black_hat(view(img), view(img_out_black_hat), ker_mat); + std::string name = argv[2]; + name += "-black_hat.png"; + gil::write_view(name, view(img_out_black_hat), gil::png_tag{}); + } +} diff --git a/example/morphology.md b/example/morphology.md new file mode 100644 index 0000000000..b7cbfa5b8a --- /dev/null +++ b/example/morphology.md @@ -0,0 +1,34 @@ +# Morphology + +Morphological operations capabilities in GIL are demonstrated by the program `morphology`, compiled from the sources `example/morphology.cpp`. + +## Synopsis + +`morphology input.png output-image-template operation1 [operation2 ... operationN]` + +- The first parameter must be the full path to an existing image in the PNG format for `morphology` to process +- The second parameter is the pattern to use to name the output files. For example, a template of `out-` will generate files like `out-erosion.png`. Note that a full path can be given here, but that the directory must exist, as `morphology` will *not* create it. +- The rest of the parameters are operation names, separated by a space. Each operation triggers the output of a file, whose name follows the pattern: `_output-image-template_-_operation_-.png`. For example, the line `morphology input.png out- erosion` will produce the image `out-erosion.png`. + +The morphological operations available are the following: +- black_hat +- top_hat +- morphological_gradient +- dilation +- erosion +- opening +- closing +- binary + +The operations can be provided in any order, only note that if `binary` is supplied, it will be applied first. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +`morphology` has no specific execution requirements. diff --git a/example/morphology_original.png b/example/morphology_original.png new file mode 100644 index 0000000000..b63e99ed9d Binary files /dev/null and b/example/morphology_original.png differ diff --git a/example/packed_pixel.cpp b/example/packed_pixel.cpp index 573b668501..03aac5db8f 100644 --- a/example/packed_pixel.cpp +++ b/example/packed_pixel.cpp @@ -5,6 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #include #include diff --git a/example/packed_pixel.md b/example/packed_pixel.md new file mode 100644 index 0000000000..be8a759176 --- /dev/null +++ b/example/packed_pixel.md @@ -0,0 +1,22 @@ +# Packed Pixel + +Packed pixel formats capabilities in GIL are demonstrated by the program `packed_pixel`, compiled from the sources `example/packed_pixel.cpp`. + +## Synopsis + +`packed_pixel` + +The program doesn't take any argument on the command line. + +`packed_pixel` expects to find an image called `input.jpg` in the current directory in JPEG format, and produces two images in the current directory in return, `out-packed_pixel_bgr772.jpg` and `out-packed_pixel_gray1.jpg`. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`packed_pixel` expects to find an image called `input.jpg` in the current directory in JPEG format. diff --git a/example/rasterizer_circle.cpp b/example/rasterizer_circle.cpp new file mode 100644 index 0000000000..ae85e86447 --- /dev/null +++ b/example/rasterizer_circle.cpp @@ -0,0 +1,36 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include + +namespace gil = boost::gil; + +// Demonstrates the use of a rasterizer to generate an image of a circle +// The various rasterizers available are defined in include/boost/gil/extension/rasterization/circle.hpp, +// include/boost/gil/extension/rasterization/ellipse.hpp and include/boost/gil/extension/rasterization/line.hpp +// This example uses a trigonometric rasterizer; GIL also offers the rasterizer midpoint_circle_rasterizer, +// which implements the Midpoint algorithm. +// See also: +// rasterizer_ellipse.cpp - Demonstrates the use of a rasterizer to generate an image of an ellipse +// rasterizer_line.cpp - Demonstrates the use of a rasterizer to generate an image of a line + +int main() +{ + const std::ptrdiff_t size = 256; + gil::gray8_image_t buffer_image(size, size); + auto buffer = gil::view(buffer_image); + + const gil::point_t center = {128, 128}; + const std::ptrdiff_t radius = 64; + const auto rasterizer = gil::trigonometric_circle_rasterizer{center, radius}; + gil::apply_rasterizer(buffer, rasterizer, gil::gray8_pixel_t{255}); + + gil::write_view("circle.png", buffer, gil::png_tag{}); +} diff --git a/example/rasterizer_circle.md b/example/rasterizer_circle.md new file mode 100644 index 0000000000..9f2dafd562 --- /dev/null +++ b/example/rasterizer_circle.md @@ -0,0 +1,22 @@ +# Circle rasterizeration + +Circle rasterization capabilities in GIL are demonstrated by the program `rasterizer_circle`, compiled from the sources `example/rasterizer_circle.cpp`. + +## Synopsis + +`rasterizer_circle` + +The program doesn't take any argument on the command line. + +`rasterizer_circle` produces an image in the current directory called `circle.png`, in the PNG format. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +`rasterizer_circle` doesn't have any specific execution requirements. diff --git a/example/rasterizer_ellipse.cpp b/example/rasterizer_ellipse.cpp new file mode 100644 index 0000000000..91ff538930 --- /dev/null +++ b/example/rasterizer_ellipse.cpp @@ -0,0 +1,55 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// Use, modification and distribution are subject to 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) +// + +#include +#include + +namespace gil = boost::gil; + +// Demonstrates the use of a rasterizer to generate an image of an ellipse +// The various rasterizers available are defined in include/boost/gil/extension/rasterization/circle.hpp, +// include/boost/gil/extension/rasterization/ellipse.hpp and include/boost/gil/extension/rasterization/line.hpp +// The rasterizer used is a generalisation of the midpoint algorithm often used for drawing circle. +// This examples also shows how to create images with various pixel depth, as well as the behaviour +// in case of the rasterization of a curve that doesn't fit in a view. +// See also: +// rasterizer_circle.cpp - Demonstrates the use of a rasterizer to generate an image of a circle +// rasterizer_line.cpp - Demonstrates the use of a rasterizer to generate an image of a line + + +int main() +{ + // Syntax for usage :- + // auto rasterizer = gil::midpoint_ellipse_rasterizer{}; + // rasterizer(img_view, pixel, center, semi-axes_length); + // Where + // img_view : gil view of the image on which ellipse is to be drawn. + // pixel : Pixel value for the elliptical curve to be drawn. Pixel's type must be compatible to the + // pixel type of the image view. + // center : Point containing positive integer x co-ordinate and y co-ordinate of the center + // respectively. + // semi-axes_length : Point containing positive integer lengths of horizontal semi-axis + // and vertical semi-axis respectively. + + gil::gray8_image_t gray_buffer_image(256, 256); + auto gray_ellipse_rasterizer = gil::midpoint_ellipse_rasterizer{{128, 128}, {100, 50}}; + gil::apply_rasterizer(view(gray_buffer_image), gray_ellipse_rasterizer, gil::gray8_pixel_t{128}); + + gil::rgb8_image_t rgb_buffer_image(256, 256); + auto rgb_ellipse_rasterizer = gil::midpoint_ellipse_rasterizer{{128, 128}, {50, 100}}; + gil::apply_rasterizer(view(rgb_buffer_image), rgb_ellipse_rasterizer, gil::rgb8_pixel_t{0, 0, 255}); + + gil::rgb8_image_t rgb_buffer_image_out_of_bound(256, 256); + auto rgb_ellipse_rasterizer_out_of_bound = gil::midpoint_ellipse_rasterizer{{100, 100}, {160, 160}}; + apply_rasterizer(view(rgb_buffer_image_out_of_bound), rgb_ellipse_rasterizer_out_of_bound, gil::rgb8_pixel_t{255, 0, 0}); + + gil::write_view("rasterized_ellipse_gray.jpg", view(gray_buffer_image), gil::jpeg_tag{}); + gil::write_view("rasterized_ellipse_rgb.jpg", view(rgb_buffer_image), gil::jpeg_tag{}); + gil::write_view("rasterized_ellipse_rgb_out_of_bound.jpg", view(rgb_buffer_image_out_of_bound), + gil::jpeg_tag{}); +} diff --git a/example/rasterizer_ellipse.md b/example/rasterizer_ellipse.md new file mode 100644 index 0000000000..345219bc7c --- /dev/null +++ b/example/rasterizer_ellipse.md @@ -0,0 +1,25 @@ +# Ellipse rasterizeration + +Ellipse rasterization capabilities in GIL are demonstrated by the program `rasterizer_ellipse`, compiled from the sources `example/rasterizer_ellipse.cpp`. + +## Synopsis + +`rasterizer_ellipse` + +The program doesn't take any argument on the command line. + +`rasterizer_ellipse` produces three images in the current directory: +- `rasterized_ellipse_gray.jpg`, in the JPEG format, wich is a greyscale image of an ellipse +- `rasterized_ellipse_rgb.jpg`, in the JPEG format, wich is an RGB image of an ellipse +- `rasterized_ellipse_rgb_out_of_bound.jpg`, in the JPEG format, wich is an RGB image of an ellipse bigger than the containinig view. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`rasterizer_ellipse` doesn't have any specific execution requirements. diff --git a/example/rasterizer_line.cpp b/example/rasterizer_line.cpp new file mode 100644 index 0000000000..6c217023b5 --- /dev/null +++ b/example/rasterizer_line.cpp @@ -0,0 +1,43 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include + +namespace gil = boost::gil; + +// Demonstrates the use of a rasterizer to generate an image of a line +// The various rasterizers available are defined in include/boost/gil/extension/rasterization/circle.hpp, +// include/boost/gil/extension/rasterization/ellipse.hpp and include/boost/gil/extension/rasterization/line.hpp +// The rasterizer used implements the Bresenham's line algorithm. +// Multiple images are created, all of the same size, but with areas of different sizes passed to the rasterizer, resulting in different lines. +// See also: +// rasterizer_circle.cpp - Demonstrates the use of a rasterizer to generate an image of a circle +// rasterizer_ellipse.cpp - Demonstrates the use of a rasterizer to generate an image of an ellipse + + +const std::ptrdiff_t size = 256; + +void line_bresenham(std::ptrdiff_t width, std::ptrdiff_t height, const std::string& output_name) +{ + const auto rasterizer = gil::bresenham_line_rasterizer{{0, 0}, {width - 1, height - 1}}; + + gil::gray8_image_t image(size, size); + auto view = gil::view(image); + gil::apply_rasterizer(view, rasterizer, gil::gray8_pixel_t{255}); + gil::write_view(output_name, view, gil::png_tag{}); +} + +int main() +{ + line_bresenham(256, 256, "line-bresenham-256-256.png"); + line_bresenham(256, 128, "line-bresenham-256-128.png"); + line_bresenham(256, 1, "line-bresenham-256-1.png"); + line_bresenham(1, 256, "line-bresenham-1-256.png"); +} diff --git a/example/rasterizer_line.md b/example/rasterizer_line.md new file mode 100644 index 0000000000..849a297744 --- /dev/null +++ b/example/rasterizer_line.md @@ -0,0 +1,26 @@ +# Line rasterizeration + +Line rasterization capabilities in GIL are demonstrated by the program `rasterizer_line`, compiled from the sources `example/rasterizer_line.cpp`. + +## Synopsis + +`rasterizer_line` + +The program doesn't take any argument on the command line. + +`rasterizer_line` produces four images of different lines in the current directory: +- `line-bresenham-256-256.png`, in the PNG format +- `line-bresenham-256-128.png`, in the PNG format +- `line-bresenham-256-1.png`, in the PNG format +- `line-bresenham-1-256.png`, in the PNG format. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +`rasterizer_line` doesn't have any specific execution requirements. diff --git a/example/resize.cpp b/example/resize.cpp index 2dc0bf1993..663a3a6c64 100644 --- a/example/resize.cpp +++ b/example/resize.cpp @@ -5,12 +5,16 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #include #include #include #include -// Example for resize_view() in the numeric extension +// Demonstrates how to scale an image using bilinear sampling +// This example relies on the function resize_view(), available in include/boost/gil/extension/numeric/resample.hpp, +// to apply the resampling. +// This example demonstrates bilinear sampling; include/boost/gil/extension/numeric/sample.hpp also offers nearest-neighbour sampling. int main() { diff --git a/example/resize.md b/example/resize.md new file mode 100644 index 0000000000..dd6d787cb8 --- /dev/null +++ b/example/resize.md @@ -0,0 +1,22 @@ +# Resize + +Resizing capabilities in GIL are demonstrated by the program `resize`, compiled from the sources `example/resize.cpp`. + +## Synopsis + +`resize` + +This program expects to find an image called `test.jpg`, in JPEG format, in the current directory, and produces a scaled down version of this image, called `out-resize.jpg`, in JPEG format in the current directory. + +The program doesn't take any argument on the command line. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`resize` expects to find an image called `test.jpg`, in JPEG format, in the current directory. diff --git a/example/sobel_scharr.cpp b/example/sobel_scharr.cpp index 10e15c2230..c22480d799 100644 --- a/example/sobel_scharr.cpp +++ b/example/sobel_scharr.cpp @@ -1,12 +1,25 @@ +// +// Copyright 2019 Olzhas Zhumabek +// Copyright 2021 Pranam Lashkari +// +// 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 + #include #include #include -#include +#include #include #include namespace gil = boost::gil; +// Demonstrates the use of Sobel and Scharr filters to detect edges +// The example generates a couple of images representing respectively the result of +// applying the filter horizontally and vertically. +// The functions generating the filters (or kernels) are defined in include/boost/gil/image_processing/numeric.hpp. + int main(int argc, char* argv[]) { if (argc != 5) diff --git a/example/sobel_scharr.md b/example/sobel_scharr.md new file mode 100644 index 0000000000..416b67caba --- /dev/null +++ b/example/sobel_scharr.md @@ -0,0 +1,23 @@ +# Sobel and Scharr filters for edge detection + +Edge detection via Sobel and Scharr filters capabilities in GIL are demonstrated by the program `sobel_scharr`, compiled from the sources `example/sobel_scharr.cpp`. + +## Synopsis + +`sobel_scharr input.png sobel|scharr output-x.png output-y.png` + +- The first argument must be the full path to an existing image in PNG format +- The second argument is the type of the filter to apply, either `sobel` or `scharr` +- The third argument is the full path to an image of the result of applying the filter in the x direction. The directory will *not* be created and must exist +- The fourth and last argument is the full path to an image of the result of applying the filter in the y direction. The directory will *not* be created and must exist. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The PNG library installed and configured. + +### Execution requirements + +`sobek_scharr` doesn't have any specific execution requirements. diff --git a/example/threshold.cpp b/example/threshold.cpp index 565ff1ca10..34703d97a3 100644 --- a/example/threshold.cpp +++ b/example/threshold.cpp @@ -5,11 +5,26 @@ // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // + #include #include using namespace boost::gil; +// Demonstrates thresholding +// Thresholding can either attribute an arbitrary value to pixels whose values are greater than the threshold +// or can truncate the pixel values at an arbitrary maximum. +// In particular, the function threshold_truncate accepts a mode and a direction. +// Passing threshold_truncate_mode::threshold effectively truncates the values to the threshold, whereas +// threshold_truncate_mode::zero sets them to 0. +// The combination of mode and direction controls which pixels are modified: +// - threshold and regular: truncates the pixels whose values are greater than the threshold +// - threshold and inverse: truncates the pixels whose values are less than the threshold +// - zero and regular: zeroes the pixels whose values are less than the threshold +// - zero and inverse: zeroes the pixels whose values are greater than the threshold +// See also: +// adaptive_threshold.cpp - Adaptive thresholding + int main() { rgb8_image_t img; diff --git a/example/threshold.md b/example/threshold.md new file mode 100644 index 0000000000..3c09656194 --- /dev/null +++ b/example/threshold.md @@ -0,0 +1,22 @@ +# Thresholding + +Thresholding capabilities in GIL are demonstrated by the program `threshold`, compiled from the sources `example/threshold.cpp`. + +## Synopsis + +`threshold` + +The program expects to find an image in the JPEG format in the current directory named `input.jpg`, and produces two images in JPEG format in the current directory in return, named `out-threshold-binary.jpg` and `out-threshold-binary-inv.jpg`, where a thresholding and an inverse thresholding, respectively, has been applied. + +The program doesn't take any command line arguments. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`threshold` expects to find an image in the JPEG format in the current directory. diff --git a/example/tutorial_histogram.cpp b/example/tutorial_histogram.cpp new file mode 100644 index 0000000000..4986a9705a --- /dev/null +++ b/example/tutorial_histogram.cpp @@ -0,0 +1,51 @@ +// +// Copyright 2005-2007 Adobe Systems Incorporated +// +// 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 +// + +#include +#include + +#include +#include + +// Demonstrates a way to compute a histogram +// This example relies on a colour-converting view to work with a greyscale view + +using namespace boost::gil; + +template +void gray_image_hist(GrayView const& img_view, R& hist) +{ + for (auto it = img_view.begin(); it != img_view.end(); ++it) + ++hist[*it]; + + // Alternatively, prefer the algorithm with lambda + // for_each_pixel(img_view, [&hist](gray8_pixel_t const& pixel) { + // ++hist[pixel]; + // }); +} + +template +void get_hist(const V& img_view, R& hist) { + gray_image_hist(color_converted_view(img_view), hist); +} + +int main() { + rgb8_image_t img; + read_image("test.jpg", img, jpeg_tag()); + + int histogram[256]; + std::fill(histogram,histogram + 256, 0); + get_hist(const_view(img), histogram); + + std::fstream histo_file("out-histogram.txt", std::ios::out); + for(std::size_t ii = 0; ii < 256; ++ii) + histo_file << histogram[ii] << std::endl; + histo_file.close(); + + return 0; +} diff --git a/example/tutorial_histogram.md b/example/tutorial_histogram.md new file mode 100644 index 0000000000..5d5e44c73a --- /dev/null +++ b/example/tutorial_histogram.md @@ -0,0 +1,22 @@ +# Histogram computation + +Histogram computation capabilities in GIL are demonstrated by the program `tutorial_histogram`, compiled from the sources `example/tutorial_histogram.cpp`. + +## Synopsis + +`tutorial_histogram` + +The program expects to find an image in the JPEG format in the current directory named `input.jpg`, and produces a text file named `out-histogram.txt`, in the current direction, where the histogram has been calculated.. + +The program doesn't take any command line arguments. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`tutorial_histogram` expects to find an image in the JPEG format in the current directory. diff --git a/example/x_gradient.cpp b/example/x_gradient.cpp index 111d40b9e6..8d24d3a11b 100644 --- a/example/x_gradient.cpp +++ b/example/x_gradient.cpp @@ -5,9 +5,18 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // + #include -// Example to demonstrate a way to compute gradients along x-axis +// Demonstrates how to compute gradients along the x-axis +// This example converts the input image to a greyscale view via color_converted_view, +// and then relies on the function static_transform to apply the operation halfdiff_cast_channels. +// The result is captured in a view, initially blacked out via a call to fill_pixels (defined in +// include/boost/gil/algorithm.hpp) +// static_transform is defined in include/boost/gil/color_based_algorithm.hpp and applies an operation +// to either a single source or two sources and a destination (as is the case here). +// In this example, the gradient is calculated as half the difference between the two pixels surrounding x +// in the loop in x_gradient. using namespace boost::gil; diff --git a/example/x_gradient.md b/example/x_gradient.md new file mode 100644 index 0000000000..c1e4500df4 --- /dev/null +++ b/example/x_gradient.md @@ -0,0 +1,22 @@ +# Gradients calculations + +Gradients calculations capabilities in GIL are demonstrated by the program `x_gradient`, compiled from the sources `example/x_gradient.cpp`. + +## Synopsis + +`x_gradient` + +The program expects to find an image in the JPEG format in the current directory named `input.jpg`, and produces an image in JPEG format in the current directory in return, named `out-x_gradient.jpg`, where the gradients have been captured. + +The program doesn't take any command line arguments. + +## Specific requirements + +### Build requirements + +- A C++ compiler compliant with C++14 or above +- The JPEG library installed and configured. + +### Execution requirements + +`x_gradient` expects to find an image in the JPEG format in the current directory. diff --git a/include/boost/gil.hpp b/include/boost/gil.hpp index 5fc7d15819..9bfba80240 100644 --- a/include/boost/gil.hpp +++ b/include/boost/gil.hpp @@ -19,9 +19,10 @@ #include #include #include -#include #include +#include #include +#include #include #include #include @@ -37,12 +38,27 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include #include #include +#include +#include "boost/gil/extension/image_processing/diffusion.hpp" +#include +#include +#include +#include +#include +#include "boost/gil/extension/image_processing/hough_parameter.hpp" +#include "boost/gil/extension/image_processing/hough_transform.hpp" +#include +#include #include #include diff --git a/include/boost/gil/algorithm.hpp b/include/boost/gil/algorithm.hpp index b67d54a35c..2b61068830 100644 --- a/include/boost/gil/algorithm.hpp +++ b/include/boost/gil/algorithm.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -8,6 +9,10 @@ #ifndef BOOST_GIL_ALGORITHM_HPP #define BOOST_GIL_ALGORITHM_HPP +#include +#include +#include +#include #include #include #include @@ -26,6 +31,7 @@ #include #include #include +#include namespace boost { namespace gil { @@ -83,22 +89,22 @@ struct binary_operation_obj using result_type = Result; template BOOST_FORCEINLINE - result_type operator()(const std::pair& p) const { + auto operator()(const std::pair& p) const -> result_type { return apply(*p.first, *p.second, typename views_are_compatible::type()); } template BOOST_FORCEINLINE - result_type operator()(const V1& v1, const V2& v2) const { + auto operator()(const V1& v1, const V2& v2) const -> result_type { return apply(v1, v2, typename views_are_compatible::type()); } - result_type operator()(const error_t&) const { throw std::bad_cast(); } + auto operator()(const error_t&) const -> result_type { throw std::bad_cast(); } private: // dispatch from apply overload to a function with distinct name template BOOST_FORCEINLINE - result_type apply(V1 const& v1, V2 const& v2, std::false_type) const + auto apply(V1 const& v1, V2 const& v2, std::false_type) const -> result_type { return ((const Derived*)this)->apply_incompatible(v1, v2); } @@ -106,7 +112,7 @@ struct binary_operation_obj // dispatch from apply overload to a function with distinct name template BOOST_FORCEINLINE - result_type apply(V1 const& v1, V2 const& v2, std::true_type) const + auto apply(V1 const& v1, V2 const& v2, std::true_type) const -> result_type { return ((const Derived*)this)->apply_compatible(v1, v2); } @@ -114,7 +120,7 @@ struct binary_operation_obj // function with distinct name - it can be overloaded by subclasses template BOOST_FORCEINLINE - result_type apply_incompatible(V1 const& /*v1*/, V2 const& /*v2*/) const + auto apply_incompatible(V1 const& /*v1*/, V2 const& /*v2*/) const -> result_type { throw std::bad_cast(); } @@ -149,9 +155,10 @@ auto copy( /// \ingroup STLOptimizations /// \brief Copy when both src and dst are interleaved and of the same type can be just memmove template -BOOST_FORCEINLINE boost::gil::pixel* -copy(const boost::gil::pixel* first, const boost::gil::pixel* last, - boost::gil::pixel* dst) { +BOOST_FORCEINLINE +auto copy(const boost::gil::pixel* first, const boost::gil::pixel* last, + boost::gil::pixel* dst) -> boost::gil::pixel* +{ return (boost::gil::pixel*)std::copy((unsigned char*)first,(unsigned char*)last, (unsigned char*)dst); } } // namespace std @@ -168,7 +175,8 @@ namespace std { /// \ingroup STLOptimizations /// \brief Copy when both src and dst are planar pointers is copy for each channel template BOOST_FORCEINLINE -boost::gil::planar_pixel_iterator copy(boost::gil::planar_pixel_iterator first, boost::gil::planar_pixel_iterator last, boost::gil::planar_pixel_iterator dst) { +auto copy(boost::gil::planar_pixel_iterator first, boost::gil::planar_pixel_iterator last, boost::gil::planar_pixel_iterator dst) -> boost::gil::planar_pixel_iterator +{ boost::gil::gil_function_requires::value_type,typename std::iterator_traits::value_type>>(); static_for_each(first,last,dst,boost::gil::detail::copy_fn()); return dst+(last-first); @@ -244,7 +252,7 @@ struct copier_n,iterator_from_2d

    > { }; template -BOOST_FORCEINLINE DstIterator copy_with_2d_iterators(SrcIterator first, SrcIterator last, DstIterator dst) { +BOOST_FORCEINLINE auto copy_with_2d_iterators(SrcIterator first, SrcIterator last, DstIterator dst) -> DstIterator { using src_x_iterator = typename SrcIterator::x_iterator; using dst_x_iterator = typename DstIterator::x_iterator; @@ -270,9 +278,11 @@ namespace std { /// \ingroup STLOptimizations /// \brief std::copy(I1,I1,I2) with I1 and I2 being a iterator_from_2d template -BOOST_FORCEINLINE boost::gil::iterator_from_2d
      copy1(boost::gil::iterator_from_2d first, boost::gil::iterator_from_2d last, boost::gil::iterator_from_2d
        dst) { +BOOST_FORCEINLINE auto copy1(boost::gil::iterator_from_2d first, boost::gil::iterator_from_2d last, boost::gil::iterator_from_2d
          dst) -> boost::gil::iterator_from_2d
            +{ return boost::gil::detail::copy_with_2d_iterators(first,last,dst); } + } // namespace std namespace boost { namespace gil { @@ -307,13 +317,13 @@ class copy_and_convert_pixels_fn : public binary_operation_obj BOOST_FORCEINLINE - result_type apply_incompatible(const V1& src, const V2& dst) const { + auto apply_incompatible(const V1& src, const V2& dst) const -> result_type { copy_pixels(color_converted_view(src,_cc),dst); } // If the two color spaces are compatible, copy_and_convert is just copy template BOOST_FORCEINLINE - result_type apply_compatible(const V1& src, const V2& dst) const { + auto apply_compatible(const V1& src, const V2& dst) const -> result_type { copy_pixels(src,dst); } }; @@ -734,10 +744,33 @@ void default_construct_pixels(View const& view) namespace detail { +enum class copy_planarity_condition +{ + planar_to_planar, + interleaved_to_planar, + mixed_to_interleaved +}; + +using planar_to_planar_type = + std::integral_constant + < + copy_planarity_condition, copy_planarity_condition::planar_to_planar + >; +using interleaved_to_planar_type = + std::integral_constant + < + copy_planarity_condition, copy_planarity_condition::interleaved_to_planar + >; +using mixed_to_interleaved_type = + std::integral_constant + < + copy_planarity_condition, copy_planarity_condition::mixed_to_interleaved + >; + /// std::uninitialized_copy for pairs of planar iterators template BOOST_FORCEINLINE -void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, std::true_type) +void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, It2 last2, planar_to_planar_type) { std::size_t channel=0; try { @@ -761,13 +794,25 @@ void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, std::true_type) } } -/// std::uninitialized_copy for interleaved or mixed iterators +/// std::uninitialized_copy for interleaved or mixed(planar into interleaved) iterators template BOOST_FORCEINLINE -void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, std::false_type) +void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, It2, mixed_to_interleaved_type) { std::uninitialized_copy(first1, last1, first2); } + +/// std::uninitialized_copy for interleaved to planar iterators +template +BOOST_FORCEINLINE +void uninitialized_copy_aux(It1 first1, It1, It2 first2, It2 last2, +interleaved_to_planar_type) +{ + default_construct_aux(first2, last2, std::true_type()); + + typename It2::difference_type n = last2 - first2; + copier_n()(first1, n, first2); +} } // namespace detail /// \ingroup ImageViewSTLAlgorithmsUninitializedCopyPixels @@ -777,13 +822,24 @@ void uninitialized_copy_aux(It1 first1, It1 last1, It2 first2, std::false_type) template void uninitialized_copy_pixels(View1 const& view1, View2 const& view2) { - using is_planar = std::integral_constant::value && is_planar::value>; + using copy_planarity_condition = detail::copy_planarity_condition; + using copy_planarity_condition_type = + std::integral_constant + < + copy_planarity_condition, + !is_planar::value + ? copy_planarity_condition::mixed_to_interleaved + : (is_planar::value + ? copy_planarity_condition::planar_to_planar + : copy_planarity_condition::interleaved_to_planar) + >; BOOST_ASSERT(view1.dimensions() == view2.dimensions()); if (view1.is_1d_traversable() && view2.is_1d_traversable()) { detail::uninitialized_copy_aux( - view1.begin().x(), view1.end().x(), view2.begin().x(), is_planar()); + view1.begin().x(), view1.end().x(), view2.begin().x(), view2.end().x(), + copy_planarity_condition_type()); } else { @@ -792,12 +848,13 @@ void uninitialized_copy_pixels(View1 const& view1, View2 const& view2) { for (y = 0; y < view1.height(); ++y) detail::uninitialized_copy_aux( - view1.row_begin(y), view1.row_end(y), view2.row_begin(y), is_planar()); + view1.row_begin(y), view1.row_end(y), view2.row_begin(y), view2.row_end(y), + copy_planarity_condition_type()); } catch(...) { for (typename View1::y_coord_t y0 = 0; y0 < y; ++y0) - detail::destruct_aux(view2.row_begin(y0), view2.row_end(y0), is_planar()); + detail::destruct_aux(view2.row_begin(y0), view2.row_end(y0), is_planar()); throw; } } @@ -827,7 +884,8 @@ F for_each_pixel(View const& view, F fun) else { for (std::ptrdiff_t y = 0; y < view.height(); ++y) - std::for_each(view.row_begin(y), view.row_end(y), fun); + for (auto begin = view.row_begin(y), end = view.row_end(y); begin != end; ++begin) + fun(*begin); return fun; } } @@ -1127,6 +1185,423 @@ F transform_pixel_positions(const View1& src1,const View2& src2,const View3& dst } return fun; } + + +// Code below this line is moved here from + +/// \brief Reference proxy associated with a type that has a \p "reference" member type alias. +/// +/// The reference proxy is the reference type, but with stripped-out C++ reference. +/// Models PixelConcept. +template +struct pixel_proxy : std::remove_reference {}; + +/// \brief std::for_each for a pair of iterators +template +BinaryFunction for_each(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryFunction f) +{ + while (first1 != last1) + f(*first1++, *first2++); + return f; +} + +template +inline +auto assign_pixels(SrcIterator src, SrcIterator src_end, DstIterator dst) -> DstIterator +{ + for_each(src, src_end, dst, + pixel_assigns_t + < + typename pixel_proxy::value_type>::type, + typename pixel_proxy::value_type>::type + >()); + return dst + (src_end - src); +} + +namespace detail { + +template +struct inner_product_k_t +{ + template + < + class InputIterator1, + class InputIterator2, + class T, + class BinaryOperation1, + class BinaryOperation2 + > + static T apply( + InputIterator1 first1, + InputIterator2 first2, T init, + BinaryOperation1 binary_op1, + BinaryOperation2 binary_op2) + { + init = binary_op1(init, binary_op2(*first1, *first2)); + return inner_product_k_t::template apply( + first1 + 1, first2 + 1, init, binary_op1, binary_op2); + } +}; + +template <> +struct inner_product_k_t<0> +{ + template + < + class InputIterator1, + class InputIterator2, + class T, + class BinaryOperation1, + class BinaryOperation2 + > + static T apply( + InputIterator1 first1, + InputIterator2 first2, + T init, + BinaryOperation1 binary_op1, + BinaryOperation2 binary_op2) + { + return init; + } +}; + +} // namespace detail + +/// static version of std::inner_product +template +< + std::size_t Size, + class InputIterator1, + class InputIterator2, + class T, + class BinaryOperation1, + class BinaryOperation2 +> +BOOST_FORCEINLINE +T inner_product_k( + InputIterator1 first1, + InputIterator2 first2, + T init, + BinaryOperation1 binary_op1, + BinaryOperation2 binary_op2) +{ + return detail::inner_product_k_t::template apply( + first1, first2, init, binary_op1, binary_op2); +} + +/// \brief 1D un-guarded cross-correlation with a variable-size kernel +template +< + typename PixelAccum, + typename SrcIterator, + typename KernelIterator, + typename Size, + typename DstIterator +> +inline +auto correlate_pixels_n( + SrcIterator src_begin, + SrcIterator src_end, + KernelIterator kernel_begin, + Size kernel_size, + DstIterator dst_begin) + -> DstIterator +{ + using src_pixel_ref_t = typename pixel_proxy + < + typename std::iterator_traits::value_type + >::type; + using dst_pixel_ref_t = typename pixel_proxy + < + typename std::iterator_traits::value_type + >::type; + using kernel_value_t = typename std::iterator_traits::value_type; + + PixelAccum accum_zero; + pixel_zeros_t()(accum_zero); + while (src_begin != src_end) + { + pixel_assigns_t()( + std::inner_product( + src_begin, + src_begin + kernel_size, + kernel_begin, + accum_zero, + pixel_plus_t(), + pixel_multiplies_scalar_t()), + *dst_begin); + + ++src_begin; + ++dst_begin; + } + return dst_begin; +} + +/// \brief 1D un-guarded cross-correlation with a fixed-size kernel +template +< + std::size_t Size, + typename PixelAccum, + typename SrcIterator, + typename KernelIterator, + typename DstIterator +> +inline +auto correlate_pixels_k( + SrcIterator src_begin, + SrcIterator src_end, + KernelIterator kernel_begin, + DstIterator dst_begin) + -> DstIterator +{ + using src_pixel_ref_t = typename pixel_proxy + < + typename std::iterator_traits::value_type + >::type; + using dst_pixel_ref_t = typename pixel_proxy + < + typename std::iterator_traits::value_type + >::type; + using kernel_type = typename std::iterator_traits::value_type; + + PixelAccum accum_zero; + pixel_zeros_t()(accum_zero); + while (src_begin != src_end) + { + pixel_assigns_t()( + inner_product_k( + src_begin, + kernel_begin, + accum_zero, + pixel_plus_t(), + pixel_multiplies_scalar_t()), + *dst_begin); + + ++src_begin; + ++dst_begin; + } + return dst_begin; +} + +/// \brief destination is set to be product of the source and a scalar +/// \tparam PixelAccum - TODO +/// \tparam SrcView Models ImageViewConcept +/// \tparam DstView Models MutableImageViewConcept +template +inline +void view_multiplies_scalar(SrcView const& src_view, Scalar const& scalar, DstView const& dst_view) +{ + static_assert(std::is_scalar::value, "Scalar is not scalar"); + BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); + using src_pixel_ref_t = typename pixel_proxy::type; + using dst_pixel_ref_t = typename pixel_proxy::type; + using y_coord_t = typename SrcView::y_coord_t; + + y_coord_t const height = src_view.height(); + for (y_coord_t y = 0; y < height; ++y) + { + typename SrcView::x_iterator it_src = src_view.row_begin(y); + typename DstView::x_iterator it_dst = dst_view.row_begin(y); + typename SrcView::x_iterator it_src_end = src_view.row_end(y); + while (it_src != it_src_end) + { + pixel_assigns_t()( + pixel_multiplies_scalar_t()(*it_src, scalar), + *it_dst); + + ++it_src; + ++it_dst; + } + } +} + + +/// \ingroup ImageAlgorithms +/// \brief Boundary options for image boundary extension +enum class boundary_option +{ + output_ignore, /// do nothing to the output + output_zero, /// set the output to zero + extend_padded, /// assume the source boundaries to be padded already + extend_zero, /// assume the source boundaries to be zero + extend_constant /// assume the source boundaries to be the boundary value +}; + +namespace detail +{ + +template +void extend_row_impl( + SrcView const& src_view, + RltView result_view, + std::size_t extend_count, + boundary_option option) +{ + std::ptrdiff_t extend_count_ = static_cast(extend_count); + + if (option == boundary_option::extend_constant) + { + for (std::ptrdiff_t i = 0; i < result_view.height(); i++) + { + if(i >= extend_count_ && i < extend_count_ + src_view.height()) + { + assign_pixels( + src_view.row_begin(i - extend_count_), + src_view.row_end(i - extend_count_), + result_view.row_begin(i) + ); + } + else if(i < extend_count_) + { + assign_pixels(src_view.row_begin(0), src_view.row_end(0), result_view.row_begin(i)); + } + else + { + assign_pixels( + src_view.row_begin(src_view.height() - 1), + src_view.row_end(src_view.height() - 1), + result_view.row_begin(i) + ); + } + + } + } + else if (option == boundary_option::extend_zero) + { + typename SrcView::value_type acc_zero; + pixel_zeros_t()(acc_zero); + + for (std::ptrdiff_t i = 0; i < result_view.height(); i++) + { + if (i >= extend_count_ && i < extend_count_ + src_view.height()) + { + assign_pixels( + src_view.row_begin(i - extend_count_), + src_view.row_end(i - extend_count_), + result_view.row_begin(i) + ); + } + else + { + std::fill_n(result_view.row_begin(i), result_view.width(), acc_zero); + } + } + } + else if (option == boundary_option::extend_padded) + { + auto original_view = subimage_view( + src_view, + 0, + -extend_count, + src_view.width(), + src_view.height() + (2 * extend_count) + ); + for (std::ptrdiff_t i = 0; i < result_view.height(); i++) + { + assign_pixels( + original_view.row_begin(i), + original_view.row_end(i), + result_view.row_begin(i) + ); + } + } + else + { + BOOST_ASSERT_MSG(false, "Invalid boundary option"); + } +} + +} //namespace detail + + +/// \brief adds new row at top and bottom. +/// Image padding introduces new pixels around the edges of an image. +/// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. +/// \tparam SrcView Models ImageViewConcept +/// \tparam extend_count number of rows to be added each side +/// \tparam option - TODO +template +auto extend_row( + SrcView const& src_view, + std::size_t extend_count, + boundary_option option +) -> typename gil::image +{ + typename gil::image + result_img(src_view.width(), src_view.height() + (2 * extend_count)); + + auto result_view = view(result_img); + detail::extend_row_impl(src_view, result_view, extend_count, option); + return result_img; +} + + +/// \brief adds new column at left and right. +/// Image padding introduces new pixels around the edges of an image. +/// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. +/// \tparam SrcView Models ImageViewConcept +/// \tparam extend_count number of columns to be added each side +/// \tparam option - TODO +template +auto extend_col( + SrcView const& src_view, + std::size_t extend_count, + boundary_option option +) -> typename gil::image +{ + auto src_view_rotate = rotated90cw_view(src_view); + + typename gil::image + result_img(src_view.width() + (2 * extend_count), src_view.height()); + + auto result_view = rotated90cw_view(view(result_img)); + detail::extend_row_impl(src_view_rotate, result_view, extend_count, option); + return result_img; +} + +/// \brief adds new row and column at all sides. +/// Image padding introduces new pixels around the edges of an image. +/// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. +/// \tparam SrcView Models ImageViewConcept +/// \tparam extend_count number of rows/column to be added each side +/// \tparam option - TODO +template +auto extend_boundary( + SrcView const& src_view, + std::size_t extend_count, + boundary_option option +) -> typename gil::image +{ + if (option == boundary_option::extend_padded) + { + typename gil::image + result_img(src_view.width()+(2 * extend_count), src_view.height()+(2 * extend_count)); + typename gil::image::view_t result_view = view(result_img); + + auto original_view = subimage_view( + src_view, + -extend_count, + -extend_count, + src_view.width() + (2 * extend_count), + src_view.height() + (2 * extend_count) + ); + + for (std::ptrdiff_t i = 0; i < result_view.height(); i++) + { + assign_pixels( + original_view.row_begin(i), + original_view.row_end(i), + result_view.row_begin(i) + ); + } + + return result_img; + } + + auto auxilary_img = extend_col(src_view, extend_count, option); + return extend_row(view(auxilary_img), extend_count, option); +} + } } // namespace boost::gil #endif diff --git a/include/boost/gil/bit_aligned_pixel_iterator.hpp b/include/boost/gil/bit_aligned_pixel_iterator.hpp index e65a6a92ee..7d3059b66e 100644 --- a/include/boost/gil/bit_aligned_pixel_iterator.hpp +++ b/include/boost/gil/bit_aligned_pixel_iterator.hpp @@ -63,22 +63,22 @@ struct bit_aligned_pixel_iterator : public iterator_facade reference { bit_aligned_pixel_iterator it=*this; it.advance(d); return *it; } - reference operator->() const { return **this; } - const bit_range_t& bit_range() const { return _bit_range; } - bit_range_t& bit_range() { return _bit_range; } + auto operator->() const -> reference { return **this; } + auto bit_range() const -> bit_range_t const& { return _bit_range; } + auto bit_range() -> bit_range_t& { return _bit_range; } private: bit_range_t _bit_range; static constexpr int bit_size = NonAlignedPixelReference::bit_size; friend class boost::iterator_core_access; - reference dereference() const { return NonAlignedPixelReference(_bit_range); } + auto dereference() const -> reference { return NonAlignedPixelReference(_bit_range); } void increment() { ++_bit_range; } void decrement() { --_bit_range; } void advance(difference_type d) { _bit_range.bit_advance(d*bit_size); } - difference_type distance_to(const bit_aligned_pixel_iterator& it) const { return _bit_range.bit_distance_to(it._bit_range) / bit_size; } + auto distance_to(bit_aligned_pixel_iterator const& it) const -> difference_type { return _bit_range.bit_distance_to(it._bit_range) / bit_size; } bool equal(const bit_aligned_pixel_iterator& it) const { return _bit_range==it._bit_range; } }; @@ -122,12 +122,14 @@ struct byte_to_memunit> {}; template -inline std::ptrdiff_t memunit_step(const bit_aligned_pixel_iterator&) { +inline auto memunit_step(const bit_aligned_pixel_iterator&) -> std::ptrdiff_t +{ return NonAlignedPixelReference::bit_size; } template -inline std::ptrdiff_t memunit_distance(const bit_aligned_pixel_iterator& p1, const bit_aligned_pixel_iterator& p2) { +inline auto memunit_distance(bit_aligned_pixel_iterator const& p1, bit_aligned_pixel_iterator const& p2) -> std::ptrdiff_t +{ return (p2.bit_range().current_byte() - p1.bit_range().current_byte())*8 + p2.bit_range().bit_offset() - p1.bit_range().bit_offset(); } @@ -137,14 +139,15 @@ inline void memunit_advance(bit_aligned_pixel_iterator } template -inline bit_aligned_pixel_iterator memunit_advanced(const bit_aligned_pixel_iterator& p, std::ptrdiff_t diff) { +inline auto memunit_advanced(bit_aligned_pixel_iterator const& p, std::ptrdiff_t diff) -> bit_aligned_pixel_iterator { bit_aligned_pixel_iterator ret=p; memunit_advance(ret, diff); return ret; } template inline -NonAlignedPixelReference memunit_advanced_ref(bit_aligned_pixel_iterator it, std::ptrdiff_t diff) { +auto memunit_advanced_ref(bit_aligned_pixel_iterator it, std::ptrdiff_t diff) -> NonAlignedPixelReference +{ return *memunit_advanced(it,diff); } ///////////////////////////// @@ -183,11 +186,14 @@ namespace std { // It is important to provide an overload of uninitialized_copy for bit_aligned_pixel_iterator. The default STL implementation calls placement new, // which is not defined for bit_aligned_pixel_iterator. template -boost::gil::bit_aligned_pixel_iterator uninitialized_copy(boost::gil::bit_aligned_pixel_iterator first, - boost::gil::bit_aligned_pixel_iterator last, - boost::gil::bit_aligned_pixel_iterator dst) { +auto uninitialized_copy(boost::gil::bit_aligned_pixel_iterator first, + boost::gil::bit_aligned_pixel_iterator last, + boost::gil::bit_aligned_pixel_iterator dst) + -> boost::gil::bit_aligned_pixel_iterator +{ return std::copy(first,last,dst); } -} // namespace std +} // namespace std + #endif diff --git a/include/boost/gil/bit_aligned_pixel_reference.hpp b/include/boost/gil/bit_aligned_pixel_reference.hpp index 094ef4b951..3069de3d56 100644 --- a/include/boost/gil/bit_aligned_pixel_reference.hpp +++ b/include/boost/gil/bit_aligned_pixel_reference.hpp @@ -49,18 +49,18 @@ class bit_range { BOOST_ASSERT(bit_offset >= 0 && bit_offset < 8); } - bit_range(const bit_range& br) : _current_byte(br._current_byte), _bit_offset(br._bit_offset) {} + bit_range(bit_range const& br) : _current_byte(br._current_byte), _bit_offset(br._bit_offset) {} template bit_range(const bit_range& br) : _current_byte(br._current_byte), _bit_offset(br._bit_offset) {} - bit_range& operator=(const bit_range& br) { _current_byte = br._current_byte; _bit_offset=br._bit_offset; return *this; } - bool operator==(const bit_range& br) const { return _current_byte==br._current_byte && _bit_offset==br._bit_offset; } + auto operator=(bit_range const& br) -> bit_range& { _current_byte = br._current_byte; _bit_offset=br._bit_offset; return *this; } + bool operator==(bit_range const& br) const { return _current_byte==br._current_byte && _bit_offset==br._bit_offset; } - bit_range& operator++() { + auto operator++() -> bit_range& { _current_byte += (_bit_offset+RangeSize) / 8; _bit_offset = (_bit_offset+RangeSize) % 8; return *this; } - bit_range& operator--() { bit_advance(-RangeSize); return *this; } + auto operator--() -> bit_range& { bit_advance(-RangeSize); return *this; } void bit_advance(difference_type num_bits) { int new_offset = int(_bit_offset+num_bits); @@ -71,11 +71,13 @@ class bit_range { --_current_byte; } } - difference_type bit_distance_to(const bit_range& b) const { + + auto bit_distance_to(bit_range const& b) const -> difference_type + { return (b.current_byte() - current_byte())*8 + b.bit_offset()-bit_offset(); } - byte_t* current_byte() const { return _current_byte; } - int bit_offset() const { return _bit_offset; } + auto current_byte() const -> byte_t* { return _current_byte; } + auto bit_offset() const -> int { return _bit_offset; } }; /// \defgroup ColorBaseModelNonAlignedPixel bit_aligned_pixel_reference @@ -136,8 +138,10 @@ struct bit_aligned_pixel_reference bit_aligned_pixel_reference(){} bit_aligned_pixel_reference(data_ptr_t data_ptr, int bit_offset) : _bit_range(data_ptr, bit_offset) {} - explicit bit_aligned_pixel_reference(const bit_range_t& bit_range) : _bit_range(bit_range) {} - template bit_aligned_pixel_reference(const bit_aligned_pixel_reference& p) : _bit_range(p._bit_range) {} + explicit bit_aligned_pixel_reference(bit_range_t const& bit_range) : _bit_range(bit_range) {} + + template + bit_aligned_pixel_reference(bit_aligned_pixel_reference const& p) : _bit_range(p._bit_range) {} // Grayscale references can be constructed from the channel reference explicit bit_aligned_pixel_reference(typename kth_element_type::type const channel0) @@ -183,11 +187,12 @@ struct bit_aligned_pixel_reference auto operator->() const -> bit_aligned_pixel_reference const* { return this; } - bit_range_t const& bit_range() const { return _bit_range; } + auto bit_range() const -> bit_range_t const& { return _bit_range; } private: mutable bit_range_t _bit_range; - template friend struct bit_aligned_pixel_reference; + template + friend struct bit_aligned_pixel_reference; template static void check_compatible() { gil_function_requires >(); } @@ -369,7 +374,7 @@ namespace std { // Having three overloads allows us to swap between different (but compatible) models of PixelConcept template inline -void swap(const boost::gil::bit_aligned_pixel_reference x, R& y) { +void swap(boost::gil::bit_aligned_pixel_reference const x, R& y) { boost::gil::swap_proxy::value_type>(x,y); } @@ -381,7 +386,7 @@ void swap(typename boost::gil::bit_aligned_pixel_reference::value_ty template inline -void swap(const boost::gil::bit_aligned_pixel_reference x, const boost::gil::bit_aligned_pixel_reference y) { +void swap(boost::gil::bit_aligned_pixel_reference const x, const boost::gil::bit_aligned_pixel_reference y) { boost::gil::swap_proxy::value_type>(x,y); } diff --git a/include/boost/gil/channel.hpp b/include/boost/gil/channel.hpp index 39ea65eaca..eb2bde1f65 100644 --- a/include/boost/gil/channel.hpp +++ b/include/boost/gil/channel.hpp @@ -198,16 +198,32 @@ struct scoped_channel_value return *this; } - scoped_channel_value& operator++() { ++value_; return *this; } - scoped_channel_value& operator--() { --value_; return *this; } + auto operator++() -> scoped_channel_value& { ++value_; return *this; } + auto operator--() -> scoped_channel_value& { --value_; return *this; } - scoped_channel_value operator++(int) { scoped_channel_value tmp=*this; this->operator++(); return tmp; } - scoped_channel_value operator--(int) { scoped_channel_value tmp=*this; this->operator--(); return tmp; } + auto operator++(int) -> scoped_channel_value + { + scoped_channel_value tmp=*this; + this->operator++(); return tmp; + } + + auto operator--(int) -> scoped_channel_value + { + scoped_channel_value tmp=*this; + this->operator--(); return tmp; + } - template scoped_channel_value& operator+=(Scalar2 v) { value_+=v; return *this; } - template scoped_channel_value& operator-=(Scalar2 v) { value_-=v; return *this; } - template scoped_channel_value& operator*=(Scalar2 v) { value_*=v; return *this; } - template scoped_channel_value& operator/=(Scalar2 v) { value_/=v; return *this; } + template + auto operator+=(Scalar2 v) -> scoped_channel_value& { value_+=v; return *this; } + + template + auto operator-=(Scalar2 v) -> scoped_channel_value& { value_-=v; return *this; } + + template + auto operator*=(Scalar2 v) -> scoped_channel_value& { value_*=v; return *this; } + + template + auto operator/=(Scalar2 v) -> scoped_channel_value& { value_/=v; return *this; } operator BaseChannelValue() const { return value_; } private: @@ -313,7 +329,7 @@ class packed_channel_value value_ = packed_channel_value(static_cast(v)); } - static unsigned int num_bits() { return NumBits; } + static auto num_bits() -> unsigned int { return NumBits; } operator integer_t() const { return value_; } @@ -348,35 +364,69 @@ class packed_channel_reference_base data_ptr_t _data_ptr; // void* pointer to the first byte of the bit range using value_type = packed_channel_value; - using reference = const Derived; - using pointer = value_type *; - using const_pointer = const value_type *; + using reference = Derived const; + using pointer = value_type*; + using const_pointer = value_type const*; static constexpr int num_bits = NumBits; static constexpr bool is_mutable = IsMutable; - static value_type min_value() { return channel_traits::min_value(); } - static value_type max_value() { return channel_traits::max_value(); } + static auto min_value() -> value_type { return channel_traits::min_value(); } + static auto max_value() -> value_type { return channel_traits::max_value(); } using bitfield_t = BitField; using integer_t = typename value_type::integer_t; packed_channel_reference_base(data_ptr_t data_ptr) : _data_ptr(data_ptr) {} - packed_channel_reference_base(const packed_channel_reference_base& ref) : _data_ptr(ref._data_ptr) {} - const Derived& operator=(integer_t v) const { set(v); return derived(); } + packed_channel_reference_base(packed_channel_reference_base const& ref) : _data_ptr(ref._data_ptr) {} + + auto operator=(integer_t v) const -> Derived const& { set(v); return derived(); } - const Derived& operator++() const { set(get()+1); return derived(); } - const Derived& operator--() const { set(get()-1); return derived(); } + auto operator++() const -> Derived const& { set(get()+1); return derived(); } + auto operator--() const -> Derived const& { set(get()-1); return derived(); } - Derived operator++(int) const { Derived tmp=derived(); this->operator++(); return tmp; } - Derived operator--(int) const { Derived tmp=derived(); this->operator--(); return tmp; } + auto operator++(int) const -> Derived + { + Derived tmp=derived(); + this->operator++(); return tmp; + } + + auto operator--(int) const -> Derived + { + Derived tmp=derived(); + this->operator--(); + return tmp; + } - template const Derived& operator+=(Scalar2 v) const { set( static_cast( get() + v )); return derived(); } - template const Derived& operator-=(Scalar2 v) const { set( static_cast( get() - v )); return derived(); } - template const Derived& operator*=(Scalar2 v) const { set( static_cast( get() * v )); return derived(); } - template const Derived& operator/=(Scalar2 v) const { set( static_cast( get() / v )); return derived(); } + template + auto operator+=(Scalar2 v) const -> Derived const& + { + set( static_cast( get() + v )); + return derived(); + } + + template + auto operator-=(Scalar2 v) const -> Derived const& + { + set( static_cast( get() - v )); return derived(); + } + + template + auto operator*=(Scalar2 v) const -> Derived const& + { + set( static_cast( get() * v )); + return derived(); + } + + template + auto operator/=(Scalar2 v) const -> Derived const& + { + set( static_cast( get() / v )); + return derived(); + } operator integer_t() const { return get(); } - data_ptr_t operator &() const {return _data_ptr;} + auto operator&() const -> data_ptr_t {return _data_ptr;} + protected: using num_value_t = typename detail::num_value_fn::type; @@ -389,12 +439,15 @@ class packed_channel_reference_base const bitfield_t& get_data() const { return *static_cast(_data_ptr); } void set_data(const bitfield_t& val) const { *static_cast< bitfield_t*>(_data_ptr) = val; } #else - bitfield_t get_data() const { + auto get_data() const -> bitfield_t + { bitfield_t ret; static_copy_bytes()(gil_reinterpret_cast_c(_data_ptr),gil_reinterpret_cast(&ret)); return ret; } - void set_data(const bitfield_t& val) const { + + void set_data(bitfield_t const& val) const + { static_copy_bytes()(gil_reinterpret_cast_c(&val),gil_reinterpret_cast(_data_ptr)); } #endif @@ -403,8 +456,8 @@ class packed_channel_reference_base void set(integer_t value) const { // can this be done faster?? this->derived().set_unsafe(((value % num_values) + num_values) % num_values); } - integer_t get() const { return derived().get(); } - const Derived& derived() const { return static_cast(*this); } + auto get() const -> integer_t { return derived().get(); } + auto derived() const -> Derived const& { return static_cast(*this); } }; } // namespace detail @@ -468,9 +521,9 @@ class packed_channel_reference packed_channel_reference(const packed_channel_reference& ref) : parent_t(ref._data_ptr) {} packed_channel_reference(const mutable_reference& ref) : parent_t(ref._data_ptr) {} - unsigned first_bit() const { return FirstBit; } + auto first_bit() const -> unsigned int { return FirstBit; } - integer_t get() const { return integer_t((this->get_data()&channel_mask) >> FirstBit); } + auto get() const -> integer_t { return integer_t((this->get_data()&channel_mask) >> FirstBit); } }; /// \ingroup PackedChannelReferenceModel @@ -499,16 +552,17 @@ class packed_channel_reference return *this; } - const packed_channel_reference& operator=(const mutable_reference& ref) const { set_from_reference(ref.get_data()); return *this; } - const packed_channel_reference& operator=(const const_reference& ref) const { set_from_reference(ref.get_data()); return *this; } + auto operator=(mutable_reference const& ref) const -> packed_channel_reference const& { set_from_reference(ref.get_data()); return *this; } + auto operator=(const_reference const& ref) const -> packed_channel_reference const& { set_from_reference(ref.get_data()); return *this; } template - const packed_channel_reference& operator=(const packed_dynamic_channel_reference& ref) const { set_unsafe(ref.get()); return *this; } + auto operator=(packed_dynamic_channel_reference const& ref) const -> packed_channel_reference const& { set_unsafe(ref.get()); return *this; } - unsigned first_bit() const { return FirstBit; } + auto first_bit() const -> unsigned int { return FirstBit; } - integer_t get() const { return integer_t((this->get_data()&channel_mask) >> FirstBit); } + auto get() const -> integer_t { return integer_t((this->get_data()&channel_mask) >> FirstBit); } void set_unsafe(integer_t value) const { this->set_data((this->get_data() & ~channel_mask) | (( static_cast< BitField >( value )<set_data((this->get_data() & ~channel_mask) | (other_bits & channel_mask)); } }; @@ -599,13 +653,14 @@ class packed_dynamic_channel_reference using mutable_reference = packed_dynamic_channel_reference const; using integer_t = typename parent_t::integer_t; - packed_dynamic_channel_reference(const void* data_ptr, unsigned first_bit) : parent_t(data_ptr), _first_bit(first_bit) {} - packed_dynamic_channel_reference(const const_reference& ref) : parent_t(ref._data_ptr), _first_bit(ref._first_bit) {} - packed_dynamic_channel_reference(const mutable_reference& ref) : parent_t(ref._data_ptr), _first_bit(ref._first_bit) {} + packed_dynamic_channel_reference(void const* data_ptr, unsigned first_bit) : parent_t(data_ptr), _first_bit(first_bit) {} + packed_dynamic_channel_reference(const_reference const& ref) : parent_t(ref._data_ptr), _first_bit(ref._first_bit) {} + packed_dynamic_channel_reference(mutable_reference const& ref) : parent_t(ref._data_ptr), _first_bit(ref._first_bit) {} - unsigned first_bit() const { return _first_bit; } + auto first_bit() const -> unsigned int { return _first_bit; } - integer_t get() const { + auto get() const -> integer_t + { const BitField channel_mask = static_cast< integer_t >( parent_t::max_val ) <<_first_bit; return static_cast< integer_t >(( this->get_data()&channel_mask ) >> _first_bit ); } @@ -629,26 +684,30 @@ class packed_dynamic_channel_reference using integer_t = typename parent_t::integer_t; packed_dynamic_channel_reference(void* data_ptr, unsigned first_bit) : parent_t(data_ptr), _first_bit(first_bit) {} - packed_dynamic_channel_reference(const packed_dynamic_channel_reference& ref) : parent_t(ref._data_ptr), _first_bit(ref._first_bit) {} + packed_dynamic_channel_reference(packed_dynamic_channel_reference const& ref) : parent_t(ref._data_ptr), _first_bit(ref._first_bit) {} - packed_dynamic_channel_reference const& operator=(integer_t value) const + auto operator=(integer_t value) const -> packed_dynamic_channel_reference const& { BOOST_ASSERT(value <= parent_t::max_val); set_unsafe(value); return *this; } - const packed_dynamic_channel_reference& operator=(const mutable_reference& ref) const { set_unsafe(ref.get()); return *this; } - const packed_dynamic_channel_reference& operator=(const const_reference& ref) const { set_unsafe(ref.get()); return *this; } + auto operator=(mutable_reference const& ref) const -> packed_dynamic_channel_reference const& { set_unsafe(ref.get()); return *this; } + auto operator=(const_reference const& ref) const -> packed_dynamic_channel_reference const& { set_unsafe(ref.get()); return *this; } template - const packed_dynamic_channel_reference& operator=(const packed_channel_reference& ref) const - { set_unsafe(ref.get()); return *this; } + auto operator=(packed_channel_reference const& ref) const -> packed_dynamic_channel_reference const& + { + set_unsafe(ref.get()); + return *this; + } - unsigned first_bit() const { return _first_bit; } + auto first_bit() const -> unsigned int { return _first_bit; } - integer_t get() const { - const BitField channel_mask = static_cast< integer_t >( parent_t::max_val ) << _first_bit; + auto get() const -> integer_t + { + BitField const channel_mask = static_cast< integer_t >( parent_t::max_val ) << _first_bit; return static_cast< integer_t >(( this->get_data()&channel_mask ) >> _first_bit ); } diff --git a/include/boost/gil/channel_algorithm.hpp b/include/boost/gil/channel_algorithm.hpp index 12ecd01b0a..47496662fc 100644 --- a/include/boost/gil/channel_algorithm.hpp +++ b/include/boost/gil/channel_algorithm.hpp @@ -79,7 +79,7 @@ struct unsigned_integral_max_value> template struct unsigned_integral_num_bits - : std::integral_constant + : std::integral_constant(sizeof(UnsignedIntegralChannel) * 8)> {}; template @@ -148,13 +148,16 @@ template DstChannelV + { return DstChannelV(channel_traits::min_value() + (src - channel_traits::min_value()) / channel_range() * channel_range()); } + private: template - static double channel_range() { + static auto channel_range() -> double + { return double(channel_traits::max_value()) - double(channel_traits::min_value()); } }; @@ -198,7 +201,8 @@ struct channel_converter_unsigned_integral // and the dst max value is divisible by the src max value template struct channel_converter_unsigned_integral_impl { - DstChannelV operator()(SrcChannelV src) const { + auto operator()(SrcChannelV src) const -> DstChannelV + { using integer_t = typename unsigned_integral_max_value::value_type; static const integer_t mul = unsigned_integral_max_value::value / unsigned_integral_max_value::value; return DstChannelV(src * mul); @@ -210,7 +214,8 @@ struct channel_converter_unsigned_integral_impl struct channel_converter_unsigned_integral_impl { - DstChannelV operator()(SrcChannelV src) const { + auto operator()(SrcChannelV src) const -> DstChannelV + { using integer_t = typename unsigned_integral_max_value::value_type; static const integer_t div = unsigned_integral_max_value::value / unsigned_integral_max_value::value; static const integer_t div2 = div/2; @@ -221,7 +226,8 @@ struct channel_converter_unsigned_integral_impl struct channel_converter_unsigned_integral_impl { - DstChannelV operator()(uintmax_t src) const { + auto operator()(uintmax_t src) const -> DstChannelV + { static const uintmax_t div = unsigned_integral_max_value::value / unsigned_integral_max_value::value; static const uintmax_t div2 = div/2; if (src > unsigned_integral_max_value::value - div2) @@ -259,7 +265,7 @@ struct channel_converter_unsigned_integral_impl struct channel_converter_unsigned_integral_nondivisible { - DstChannelV operator()(SrcChannelV src) const + auto operator()(SrcChannelV src) const -> DstChannelV { using dest_t = typename base_channel_type::type; return DstChannelV( @@ -275,7 +281,7 @@ struct channel_converter_unsigned_integral_nondivisible struct channel_converter_unsigned_integral_nondivisible { - DstChannelV operator()(SrcChannelV src) const + auto operator()(SrcChannelV src) const -> DstChannelV { static const double mul = unsigned_integral_max_value::value @@ -288,9 +294,10 @@ struct channel_converter_unsigned_integral_nondivisible -struct channel_converter_unsigned_integral_nondivisible { - DstChannelV operator()(SrcChannelV src) const { - +struct channel_converter_unsigned_integral_nondivisible +{ + auto operator()(SrcChannelV src) const -> DstChannelV + { using src_integer_t = typename detail::unsigned_integral_max_value::value_type; using dst_integer_t = typename detail::unsigned_integral_max_value::value_type; @@ -312,7 +319,7 @@ struct channel_converter_unsigned_integral_nondivisible struct channel_converter_unsigned { using argument_type = float32_t; using result_type = DstChannelV; - DstChannelV operator()(float32_t x) const + auto operator()(float32_t x) const -> DstChannelV { using dst_integer_t = typename detail::unsigned_integral_max_value::value_type; return DstChannelV( static_cast< dst_integer_t >(x*channel_traits::max_value()+0.5f )); @@ -322,13 +329,13 @@ template struct channel_converter_unsigned struct channel_converter_unsigned { using argument_type = float32_t; using result_type = SrcChannelV; - float32_t operator()(SrcChannelV x) const { return float32_t(x/float(channel_traits::max_value())); } + auto operator()(SrcChannelV x) const -> float32_t { return float32_t(x/float(channel_traits::max_value())); } }; template <> struct channel_converter_unsigned { using argument_type = float32_t; using result_type = float32_t; - float32_t operator()(float32_t x) const { return x; } + auto operator()(float32_t x) const -> float32_t { return x; } }; @@ -336,7 +343,8 @@ template <> struct channel_converter_unsigned { template <> struct channel_converter_unsigned { using argument_type = uint32_t; using result_type = float32_t; - float32_t operator()(uint32_t x) const { + auto operator()(uint32_t x) const -> float32_t + { // unfortunately without an explicit check it is possible to get a round-off error. We must ensure that max_value of uint32_t matches max_value of float32_t if (x>=channel_traits::max_value()) return channel_traits::max_value(); return float(x) / float(channel_traits::max_value()); @@ -346,7 +354,8 @@ template <> struct channel_converter_unsigned { template <> struct channel_converter_unsigned { using argument_type = float32_t; using result_type = uint32_t; - uint32_t operator()(float32_t x) const { + auto operator()(float32_t x) const -> uint32_t + { // unfortunately without an explicit check it is possible to get a round-off error. We must ensure that max_value of uint32_t matches max_value of float32_t if (x>=channel_traits::max_value()) return channel_traits::max_value(); @@ -406,7 +415,7 @@ template <> struct channel_convert_from_unsigned { using argument_type = uint8_t; using result_type = int8_t; using type = int8_t; - type operator()(uint8_t val) const { + type operator()(uint8_t val) const { return static_cast(static_cast(val) - 128); } }; @@ -437,7 +446,8 @@ template // Model ChannelValueConce struct channel_converter { using argument_type = SrcChannelV; using result_type = DstChannelV; - DstChannelV operator()(const SrcChannelV& src) const { + auto operator()(SrcChannelV const& src) const -> DstChannelV + { using to_unsigned = detail::channel_convert_to_unsigned; using from_unsigned = detail::channel_convert_from_unsigned; using converter_unsigned = channel_converter_unsigned; @@ -448,7 +458,8 @@ struct channel_converter { /// \ingroup ChannelConvertAlgorithm /// \brief Converting from one channel type to another. template // Model ChannelConcept (could be channel references) -inline typename channel_traits::value_type channel_convert(const SrcChannel& src) { +inline auto channel_convert(SrcChannel const& src) -> typename channel_traits::value_type +{ return channel_converter::value_type, typename channel_traits::value_type>()(src); } @@ -459,17 +470,26 @@ inline typename channel_traits::value_type channel_convert(const Src /// on heterogeneous pixels. struct default_channel_converter { template - void operator()(const Ch1& src, Ch2& dst) const { + void operator()(Ch1 const& src, Ch2& dst) const + { dst=channel_convert(src); } }; -namespace detail { +namespace detail +{ // fast integer division by 255 - inline uint32_t div255(uint32_t in) { uint32_t tmp=in+128; return (tmp + (tmp>>8))>>8; } + inline auto div255(uint32_t in) -> uint32_t + { + uint32_t tmp = in + 128; + return (tmp + (tmp >> 8)) >> 8; + } // fast integer divison by 32768 - inline uint32_t div32768(uint32_t in) { return (in+16384)>>15; } + inline auto div32768(uint32_t in) -> uint32_t + { + return (in + 16384) >> 15; + } } /// \defgroup ChannelMultiplyAlgorithm channel_multiply @@ -491,7 +511,8 @@ struct channel_multiplier_unsigned { using first_argument_type = ChannelValue; using second_argument_type = ChannelValue; using result_type = ChannelValue; - ChannelValue operator()(ChannelValue a, ChannelValue b) const { + auto operator()(ChannelValue a, ChannelValue b) const -> ChannelValue + { return ChannelValue(static_cast::type>(a / double(channel_traits::max_value()) * b)); } }; @@ -501,7 +522,7 @@ template<> struct channel_multiplier_unsigned { using first_argument_type = uint8_t; using second_argument_type = uint8_t; using result_type = uint8_t; - uint8_t operator()(uint8_t a, uint8_t b) const { return uint8_t(detail::div255(uint32_t(a) * uint32_t(b))); } + auto operator()(uint8_t a, uint8_t b) const -> uint8_t { return uint8_t(detail::div255(uint32_t(a) * uint32_t(b))); } }; /// \brief Specialization of channel_multiply for 16-bit unsigned channels @@ -509,7 +530,7 @@ template<> struct channel_multiplier_unsigned { using first_argument_type = uint16_t; using second_argument_type = uint16_t; using result_type = uint16_t; - uint16_t operator()(uint16_t a, uint16_t b) const { return uint16_t((uint32_t(a) * uint32_t(b))/65535); } + auto operator()(uint16_t a, uint16_t b) const -> uint16_t { return uint16_t((uint32_t(a) * uint32_t(b))/65535); } }; /// \brief Specialization of channel_multiply for float 0..1 channels @@ -517,7 +538,7 @@ template<> struct channel_multiplier_unsigned { using first_argument_type = float32_t; using second_argument_type = float32_t; using result_type = float32_t; - float32_t operator()(float32_t a, float32_t b) const { return a*b; } + auto operator()(float32_t a, float32_t b) const -> float32_t { return a*b; } }; /// \brief A function object to multiply two channels. result = a * b / max_value @@ -526,7 +547,8 @@ struct channel_multiplier { using first_argument_type = ChannelValue; using second_argument_type = ChannelValue; using result_type = ChannelValue; - ChannelValue operator()(ChannelValue a, ChannelValue b) const { + auto operator()(ChannelValue a, ChannelValue b) const -> ChannelValue + { using to_unsigned = detail::channel_convert_to_unsigned; using from_unsigned = detail::channel_convert_from_unsigned; using multiplier_unsigned = channel_multiplier_unsigned; @@ -536,7 +558,8 @@ struct channel_multiplier { /// \brief A function multiplying two channels. result = a * b / max_value template // Models ChannelConcept (could be a channel reference) -inline typename channel_traits::value_type channel_multiply(Channel a, Channel b) { +inline auto channel_multiply(Channel a, Channel b) -> typename channel_traits::value_type +{ return channel_multiplier::value_type>()(a,b); } /// @} @@ -556,8 +579,8 @@ inline typename channel_traits::value_type channel_multiply(Channel a, /// \brief Default implementation. Provide overloads for performance /// \ingroup ChannelInvertAlgorithm channel_invert template // Models ChannelConcept (could be a channel reference) -inline typename channel_traits::value_type channel_invert(Channel x) { - +inline auto channel_invert(Channel x) -> typename channel_traits::value_type +{ using base_t = typename base_channel_type::type; using promoted_t = typename promote_integral::type; promoted_t const promoted_x = x; @@ -568,6 +591,6 @@ inline typename channel_traits::value_type channel_invert(Channel x) { return inverted_x; } -} } // namespace boost::gil +}} // namespace boost::gil #endif diff --git a/include/boost/gil/channel_numeric_operations.hpp b/include/boost/gil/channel_numeric_operations.hpp new file mode 100644 index 0000000000..703854710f --- /dev/null +++ b/include/boost/gil/channel_numeric_operations.hpp @@ -0,0 +1,253 @@ +// +// Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari +// +// 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 +// +#ifndef BOOST_GIL_CHANNEL_NUMERIC_OPERATIONS_HPP +#define BOOST_GIL_CHANNEL_NUMERIC_OPERATIONS_HPP + +#include + +namespace boost { namespace gil { + +// Function objects and utilities for channel-wise numeric operations. +// +// List of currently defined functors: +// channel_plus_t (+) +// channel_minus_t (-) +// channel_multiplies_t (*) +// channel_divides_t (/), +// channel_plus_scalar_t (+s) +// channel_minus_scalar_t (-s), +// channel_multiplies_scalar_t (*s) +// channel_divides_scalar_t (/s), +// channel_halves_t (/=2) +// channel_zeros_t (=0) +// channel_assigns_t (=) + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of addition of two channel values. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_plus_t +{ + using ChannelRef1 = typename channel_traits::const_reference; + using ChannelRef2 = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef1 not convertible to ChannelResult"); + static_assert(std::is_convertible::value, + "ChannelRef2 not convertible to ChannelResult"); + + /// \param ch1 - first of the two addends (augend). + /// \param ch2 - second of the two addends. + auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult + { + return ChannelResult(ch1) + ChannelResult(ch2); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of subtraction of two channel values. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_minus_t +{ + using ChannelRef1 = typename channel_traits::const_reference; + using ChannelRef2 = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef1 not convertible to ChannelResult"); + static_assert(std::is_convertible::value, + "ChannelRef2 not convertible to ChannelResult"); + + /// \param ch1 - minuend operand of the subtraction. + /// \param ch2 - subtrahend operand of the subtraction. + auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult + { + return ChannelResult(ch1) - ChannelResult(ch2); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of multiplication of two channel values. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_multiplies_t +{ + using ChannelRef1 = typename channel_traits::const_reference; + using ChannelRef2 = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef1 not convertible to ChannelResult"); + static_assert(std::is_convertible::value, + "ChannelRef2 not convertible to ChannelResult"); + + /// \param ch1 - first of the two factors (multiplicand). + /// \param ch2 - second of the two factors (multiplier). + auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult + { + return ChannelResult(ch1) * ChannelResult(ch2); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of division of two channel values. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_divides_t +{ + using ChannelRef1 = typename channel_traits::const_reference; + using ChannelRef2 = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef1 not convertible to ChannelResult"); + static_assert(std::is_convertible::value, + "ChannelRef2 not convertible to ChannelResult"); + + /// \param ch1 - dividend operand of the two division operation. + /// \param ch2 - divisor operand of the two division operation. + auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult + { + return ChannelResult(ch1) / ChannelResult(ch2); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of adding scalar to channel value. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_plus_scalar_t +{ + using ChannelRef = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef not convertible to ChannelResult"); + static_assert(std::is_scalar::value, "Scalar not a scalar"); + static_assert(std::is_convertible::value, + "Scalar not convertible to ChannelResult"); + + auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult + { + return ChannelResult(channel) + ChannelResult(scalar); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of subtracting scalar from channel value. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_minus_scalar_t +{ + using ChannelRef = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef not convertible to ChannelResult"); + static_assert(std::is_scalar::value, "Scalar not a scalar"); + static_assert(std::is_convertible::value, + "Scalar not convertible to ChannelResult"); + + /// \param channel - minuend operand of the subtraction. + /// \param scalar - subtrahend operand of the subtraction. + auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult + { + // TODO: Convertion after subtraction vs conversion of operands in channel_minus_t? + return ChannelResult(channel - scalar); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of channel value by a scalar. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_multiplies_scalar_t +{ + using ChannelRef = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef not convertible to ChannelResult"); + static_assert(std::is_scalar::value, "Scalar not a scalar"); + static_assert(std::is_convertible::value, + "Scalar not convertible to ChannelResult"); + + /// \param channel - first of the two factors (multiplicand). + /// \param scalar - second of the two factors (multiplier). + auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult + { + return ChannelResult(channel) * ChannelResult(scalar); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of dividing channel value by scalar. +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_divides_scalar_t +{ + using ChannelRef = typename channel_traits::const_reference; + static_assert(std::is_convertible::value, + "ChannelRef not convertible to ChannelResult"); + static_assert(std::is_scalar::value, "Scalar not a scalar"); + static_assert(std::is_convertible::value, + "Scalar not convertible to ChannelResult"); + + /// \param channel - dividend operand of the two division operation. + /// \param scalar - divisor operand of the two division operation. + auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult + { + return ChannelResult(channel) / ChannelResult(scalar); + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Arithmetic operation of dividing channel value by 2 +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_halves_t +{ + using ChannelRef = typename channel_traits::reference; + + auto operator()(ChannelRef channel) const -> ChannelRef + { + // TODO: Split into steps: extract with explicit conversion to double, divide and assign? + //double const v = ch; + //ch = static_cast(v / 2.0); + channel /= 2.0; + return channel; + } +}; + +/// \ingroup ChannelNumericOperations +/// \brief Operation of setting channel value to zero +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_zeros_t +{ + using ChannelRef = typename channel_traits::reference; + + auto operator()(ChannelRef channel) const -> ChannelRef + { + channel = Channel(0); + return channel; + } +}; + +/// \ingroup ChannelNumericOperations +/// structure for assigning one channel to another +/// \note This is a generic implementation; user should specialize it for better performance. +template +struct channel_assigns_t +{ + using ChannelRef1 = typename channel_traits::const_reference; + using ChannelRef2 = typename channel_traits::reference; + static_assert(std::is_convertible::value, + "ChannelRef1 not convertible to Channel2"); + + /// \param ch1 - assignor side (input) of the assignment operation + /// \param ch2 - assignee side (output) of the assignment operation + auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelRef2 + { + ch2 = Channel2(ch1); + return ch2; + } +}; + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/cmyk.hpp b/include/boost/gil/cmyk.hpp index 7b03c199f5..50c1abfa25 100644 --- a/include/boost/gil/cmyk.hpp +++ b/include/boost/gil/cmyk.hpp @@ -40,13 +40,13 @@ using cmyk_layout_t = layout; /// \ingroup ImageViewConstructors /// \brief from raw CMYK planar data template -inline typename type_from_x_iterator >::view_t -planar_cmyk_view(std::size_t width, std::size_t height, IC c, IC m, IC y, IC k, std::ptrdiff_t rowsize_in_bytes) +inline auto planar_cmyk_view(std::size_t width, std::size_t height, IC c, IC m, IC y, IC k, std::ptrdiff_t rowsize_in_bytes) + -> typename type_from_x_iterator>::view_t { using view_t = typename type_from_x_iterator >::view_t; return view_t(width, height, typename view_t::locator(planar_pixel_iterator(c,m,y,k), rowsize_in_bytes)); } -} } // namespace gil +}} // namespace gil #endif diff --git a/include/boost/gil/color_base.hpp b/include/boost/gil/color_base.hpp index 16609429a0..022238f71c 100644 --- a/include/boost/gil/color_base.hpp +++ b/include/boost/gil/color_base.hpp @@ -34,7 +34,7 @@ auto semantic_at_c(ColorBase& p) template -typename kth_semantic_element_const_reference_type::type semantic_at_c(const ColorBase& p); +auto semantic_at_c(const ColorBase& p) -> typename kth_semantic_element_const_reference_type::type; // Forward declare element_reference_type template struct element_reference_type; @@ -79,8 +79,14 @@ struct homogeneous_color_base { using layout_t = Layout; - homogeneous_color_base() = default; - homogeneous_color_base(Element v) : v0_(v) {} + template + < + typename U = Element, + typename = typename std::enable_if::value>::type + > + homogeneous_color_base() : v0_{} {} + + explicit homogeneous_color_base(Element v) : v0_(v) {} template homogeneous_color_base(homogeneous_color_base const& c) @@ -100,7 +106,7 @@ struct homogeneous_color_base operator Element() const { return v0_; } private: - Element v0_{}; + Element v0_; }; /// \brief A homogeneous color base holding two color elements @@ -111,8 +117,15 @@ struct homogeneous_color_base { using layout_t = Layout; - homogeneous_color_base() = default; + template + < + typename U = Element, + typename = typename std::enable_if::value>::type + > + homogeneous_color_base() : v0_{}, v1_{} {} + explicit homogeneous_color_base(Element v) : v0_(v), v1_(v) {} + homogeneous_color_base(Element v0, Element v1) : v0_(v0), v1_(v1) {} template @@ -165,7 +178,7 @@ struct homogeneous_color_base { return v1_; } // Support for planar_pixel_reference operator[] - Element at_c_dynamic(std::size_t i) const + auto at_c_dynamic(std::size_t i) const -> Element { if (i == 0) return v0_; @@ -174,8 +187,8 @@ struct homogeneous_color_base } private: - Element v0_{}; - Element v1_{}; + Element v0_; + Element v1_; }; /// \brief A homogeneous color base holding three color elements. @@ -186,8 +199,15 @@ struct homogeneous_color_base { using layout_t = Layout; - homogeneous_color_base() = default; + template + < + typename U = Element, + typename = typename std::enable_if::value>::type + > + homogeneous_color_base() : v0_{}, v1_{}, v2_{} {} + explicit homogeneous_color_base(Element v) : v0_(v), v1_(v), v2_(v) {} + homogeneous_color_base(Element v0, Element v1, Element v2) : v0_(v0), v1_(v1), v2_(v2) {} @@ -257,7 +277,7 @@ struct homogeneous_color_base { return v2_; } // Support for planar_pixel_reference operator[] - Element at_c_dynamic(std::size_t i) const + auto at_c_dynamic(std::size_t i) const -> Element { switch (i) { @@ -268,9 +288,9 @@ struct homogeneous_color_base } private: - Element v0_{}; - Element v1_{}; - Element v2_{}; + Element v0_; + Element v1_; + Element v2_; }; /// \brief A homogeneous color base holding four color elements. @@ -281,8 +301,15 @@ struct homogeneous_color_base { using layout_t = Layout; - homogeneous_color_base() = default; + template + < + typename U = Element, + typename = typename std::enable_if::value>::type + > + homogeneous_color_base() : v0_{}, v1_{}, v2_{}, v3_{} {} + explicit homogeneous_color_base(Element v) : v0_(v), v1_(v), v2_(v), v3_(v) {} + homogeneous_color_base(Element v0, Element v1, Element v2, Element v3) : v0_(v0), v1_(v1), v2_(v2), v3_(v3) {} @@ -365,7 +392,7 @@ struct homogeneous_color_base { return v3_; } // Support for planar_pixel_reference operator[] - Element at_c_dynamic(std::size_t i) const + auto at_c_dynamic(std::size_t i) const -> Element { switch (i) { @@ -377,10 +404,10 @@ struct homogeneous_color_base } private: - Element v0_{}; - Element v1_{}; - Element v2_{}; - Element v3_{}; + Element v0_; + Element v1_; + Element v2_; + Element v3_; }; /// \brief A homogeneous color base holding five color elements. @@ -391,7 +418,15 @@ struct homogeneous_color_base { using layout_t = Layout; - homogeneous_color_base() = default; + template + < + typename U = Element, + typename = typename std::enable_if::value>::type + > + homogeneous_color_base() + : v0_{}, v1_{}, v2_{}, v3_{}, v4_{} + {} + explicit homogeneous_color_base(Element v) : v0_(v), v1_(v), v2_(v), v3_(v), v4_(v) {} @@ -492,7 +527,7 @@ struct homogeneous_color_base } // Support for planar_pixel_reference operator[] - Element at_c_dynamic(std::size_t i) const + auto at_c_dynamic(std::size_t i) const -> Element { switch (i) { @@ -505,11 +540,11 @@ struct homogeneous_color_base } private: - Element v0_{}; - Element v1_{}; - Element v2_{}; - Element v3_{}; - Element v4_{}; + Element v0_; + Element v1_; + Element v2_; + Element v3_; + Element v4_; }; #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) diff --git a/include/boost/gil/color_base_algorithm.hpp b/include/boost/gil/color_base_algorithm.hpp index 6746f83002..233009827b 100644 --- a/include/boost/gil/color_base_algorithm.hpp +++ b/include/boost/gil/color_base_algorithm.hpp @@ -187,14 +187,18 @@ struct color_element_const_reference_type : public kth_semantic_element_const_re /// \brief Mutable accessor to the element associated with a given color name /// \ingroup ColorBaseAlgorithmColor template -typename color_element_reference_type::type get_color(ColorBase& cb, Color=Color()) { +auto get_color(ColorBase& cb, Color=Color()) + -> typename color_element_reference_type::type +{ return color_element_reference_type::get(cb); } /// \brief Constant accessor to the element associated with a given color name /// \ingroup ColorBaseAlgorithmColor template -typename color_element_const_reference_type::type get_color(const ColorBase& cb, Color=Color()) { +auto get_color(const ColorBase& cb, Color=Color()) + -> typename color_element_const_reference_type::type +{ return color_element_const_reference_type::get(cb); } @@ -435,36 +439,63 @@ template<> struct element_recursion<0> { }; // std::min and std::max don't have the mutable overloads... -template inline const Q& mutable_min(const Q& x, const Q& y) { return x inline Q& mutable_min( Q& x, Q& y) { return x inline const Q& mutable_max(const Q& x, const Q& y) { return x inline Q& mutable_max( Q& x, Q& y) { return x +inline auto mutable_min(Q const& x, Q const& y) -> Q const& { return x +inline auto mutable_min(Q& x, Q& y) -> Q& { return x +inline auto mutable_max(Q const& x, Q const& y) -> Q const& { return x +inline auto mutable_max(Q& x, Q& y) -> Q& { return x -struct min_max_recur { - template static typename element_const_reference_type

            ::type max_(const P& p) { +struct min_max_recur +{ + template + static auto max_(P const& p) -> typename element_const_reference_type

            ::type + { return mutable_max(min_max_recur::max_(p),semantic_at_c(p)); } - template static typename element_reference_type

            ::type max_( P& p) { + + template + static auto max_(P& p) -> typename element_reference_type

            ::type + { return mutable_max(min_max_recur::max_(p),semantic_at_c(p)); } - template static typename element_const_reference_type

            ::type min_(const P& p) { + + template + static auto min_(P const& p) -> typename element_const_reference_type

            ::type + { return mutable_min(min_max_recur::min_(p),semantic_at_c(p)); } - template static typename element_reference_type

            ::type min_( P& p) { + + template + static auto min_(P& p) -> typename element_reference_type

            ::type + { return mutable_min(min_max_recur::min_(p),semantic_at_c(p)); } }; // termination condition of the compile-time recursion for min/max element template <> -struct min_max_recur<1> { - template static typename element_const_reference_type

            ::type max_(const P& p) { return semantic_at_c<0>(p); } - template static typename element_reference_type

            ::type max_( P& p) { return semantic_at_c<0>(p); } - template static typename element_const_reference_type

            ::type min_(const P& p) { return semantic_at_c<0>(p); } - template static typename element_reference_type

            ::type min_( P& p) { return semantic_at_c<0>(p); } +struct min_max_recur<1> +{ + template + static auto max_(P const& p) -> typename element_const_reference_type

            ::type { return semantic_at_c<0>(p); } + + template + static auto max_(P& p) -> typename element_reference_type

            ::type { return semantic_at_c<0>(p); } + + template + static auto min_(P const& p) -> typename element_const_reference_type

            ::type { return semantic_at_c<0>(p); } + + template + static auto min_(P& p) -> typename element_reference_type

            ::type { return semantic_at_c<0>(p); } }; } // namespace detail @@ -483,19 +514,19 @@ struct min_max_recur<1> { template BOOST_FORCEINLINE -typename element_const_reference_type

            ::type static_max(const P& p) { return detail::min_max_recur::value>::max_(p); } +auto static_max(P const& p) -> typename element_const_reference_type

            ::type { return detail::min_max_recur::value>::max_(p); } template BOOST_FORCEINLINE -typename element_reference_type

            ::type static_max( P& p) { return detail::min_max_recur::value>::max_(p); } +auto static_max(P& p) -> typename element_reference_type

            ::type { return detail::min_max_recur::value>::max_(p); } template BOOST_FORCEINLINE -typename element_const_reference_type

            ::type static_min(const P& p) { return detail::min_max_recur::value>::min_(p); } +auto static_min(P const& p) -> typename element_const_reference_type

            ::type { return detail::min_max_recur::value>::min_(p); } template BOOST_FORCEINLINE -typename element_reference_type

            ::type static_min( P& p) { return detail::min_max_recur::value>::min_(p); } +auto static_min(P& p) -> typename element_reference_type

            ::type { return detail::min_max_recur::value>::min_(p); } /// \} /// \defgroup ColorBaseAlgorithmEqual static_equal @@ -515,7 +546,7 @@ typename element_reference_type

            ::type static_min( P& p) { return d template BOOST_FORCEINLINE -bool static_equal(const P1& p1, const P2& p2) { return detail::element_recursion::value>::static_equal(p1,p2); } +bool static_equal(P1 const& p1, const P2& p2) { return detail::element_recursion::value>::static_equal(p1,p2); } /// \} @@ -708,6 +739,6 @@ BOOST_FORCEINLINE Op static_for_each(const P1& p1,const P2& p2,const P3& p3,Op op) { return detail::element_recursion::value>::static_for_each(p1,p2,p3,op); } ///\} -} } // namespace boost::gil +}} // namespace boost::gil #endif diff --git a/include/boost/gil/color_convert.hpp b/include/boost/gil/color_convert.hpp index 080d758ae9..cd125e4bd3 100644 --- a/include/boost/gil/color_convert.hpp +++ b/include/boost/gil/color_convert.hpp @@ -63,7 +63,8 @@ namespace detail { // The default implementation of to_luminance uses float0..1 as the intermediate channel type template struct rgb_to_luminance_fn { - GrayChannelValue operator()(const RedChannel& red, const GreenChannel& green, const BlueChannel& blue) const { + auto operator()(const RedChannel& red, const GreenChannel& green, const BlueChannel& blue) const -> GrayChannelValue + { return channel_convert(float32_t( channel_convert(red )*0.30f + channel_convert(green)*0.59f + @@ -74,14 +75,16 @@ struct rgb_to_luminance_fn { // performance specialization for unsigned char template struct rgb_to_luminance_fn { - GrayChannelValue operator()(uint8_t red, uint8_t green, uint8_t blue) const { + auto operator()(uint8_t red, uint8_t green, uint8_t blue) const -> GrayChannelValue + { return channel_convert(uint8_t( ((uint32_t(red )*4915 + uint32_t(green)*9667 + uint32_t(blue )*1802) + 8192) >> 14)); } }; template -typename channel_traits::value_type rgb_to_luminance(const RedChannel& red, const GreenChannel& green, const BlueChannel& blue) { +auto rgb_to_luminance(const RedChannel& red, const GreenChannel& green, const BlueChannel& blue) -> typename channel_traits::value_type +{ return rgb_to_luminance_fn::value_type>()(red,green,blue); } @@ -170,13 +173,13 @@ struct default_color_converter_impl // Apply color correction, strengthening, reducing non-zero components by // s = 1 / (1 - k) for k < 1, where 1 denotes dst_t max, otherwise s = 1 (literal). uint_t const dst_max = channel_traits::max_value(); - uint_t const s_div = dst_max - k; + uint_t const s_div = static_cast(dst_max - k); if (s_div != 0) { double const s = dst_max / static_cast(s_div); - c = (c - k) * s; - m = (m - k) * s; - y = (y - k) * s; + c = static_cast((c - k) * s); + m = static_cast((m - k) * s); + y = static_cast((y - k) * s); } else { diff --git a/include/boost/gil/concepts/image.hpp b/include/boost/gil/concepts/image.hpp index 99a7d5f53f..15b1f1a149 100644 --- a/include/boost/gil/concepts/image.hpp +++ b/include/boost/gil/concepts/image.hpp @@ -48,7 +48,7 @@ namespace boost { namespace gil { /// void Image::recreate(point_t new_dims, std::size_t alignment=1); /// void Image::recreate(point_t new_dims, value_type fill_value, std::size_t alignment); /// -/// const point_t& Image::dimensions() const; +/// point_t const& Image::dimensions() const; /// const const_view_t& const_view(const Image&); /// const view_t& view(Image&); /// }; diff --git a/include/boost/gil/concepts/image_view.hpp b/include/boost/gil/concepts/image_view.hpp index 0818b9cd35..ffcd41258e 100644 --- a/include/boost/gil/concepts/image_view.hpp +++ b/include/boost/gil/concepts/image_view.hpp @@ -89,14 +89,14 @@ namespace boost { namespace gil { /// iterator View::end() const; /// reverse_iterator View::rbegin() const; /// reverse_iterator View::rend() const; -/// iterator View::at(const point_t&); +/// iterator View::at(point_t const&); /// point_t View::dimensions() const; // number of elements along each dimension /// bool View::is_1d_traversable() const; // can an iterator over the first dimension visit each value? I.e. are there gaps between values? /// /// // iterator along a given dimension starting at a given point -/// template View::axis::iterator View::axis_iterator(const point_t&) const; +/// template View::axis::iterator View::axis_iterator(point_t const&) const; /// -/// reference operator()(View,const point_t&) const; +/// reference operator()(View,point_t const&) const; /// }; /// \endcode template @@ -196,17 +196,17 @@ struct RandomAccessNDImageViewConcept /// y_coord_t View::height() const; /// /// // X-navigation -/// x_iterator View::x_at(const point_t&) const; +/// x_iterator View::x_at(point_t const&) const; /// x_iterator View::row_begin(y_coord_t) const; /// x_iterator View::row_end (y_coord_t) const; /// /// // Y-navigation -/// y_iterator View::y_at(const point_t&) const; +/// y_iterator View::y_at(point_t const&) const; /// y_iterator View::col_begin(x_coord_t) const; /// y_iterator View::col_end (x_coord_t) const; /// /// // navigating in 2D -/// xy_locator View::xy_at(const point_t&) const; +/// xy_locator View::xy_at(point_t const&) const; /// /// // (x,y) versions of all methods taking point_t /// View::View(x_coord_t,y_coord_t,const locator&); diff --git a/include/boost/gil/deprecated.hpp b/include/boost/gil/deprecated.hpp deleted file mode 100644 index 77459d2c72..0000000000 --- a/include/boost/gil/deprecated.hpp +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright 2005-2007 Adobe Systems Incorporated -// -// 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 -// -#ifndef BOOST_GIL_DEPRECATED_HPP -#define BOOST_GIL_DEPRECATED_HPP - -#include - -/// This file is provided as a courtesy to ease upgrading GIL client code. -/// Please make sure your code compiles when this file is not included. - -#define planar_ptr planar_pixel_iterator -#define planar_ref planar_pixel_reference -#define membased_2d_locator memory_based_2d_locator -#define pixel_step_iterator memory_based_step_iterator -#define pixel_image_iterator iterator_from_2d - -#define equal_channels static_equal -#define copy_channels static_copy -#define fill_channels static_fill -#define generate_channels static_generate -#define for_each_channel static_for_each -#define transform_channels static_transform -#define max_channel static_max -#define min_channel static_min - -#define semantic_channel semantic_at_c - -template -void resize_clobber_image(Img& img, const typename Img::point_t& new_dims) { - img.recreate(new_dims); -} - -template -void resize_clobber_image(Img& img, const typename Img::x_coord_t& width, const typename Img::y_coord_t& height) { - img.recreate(width,height); -} - -template typename T::x_coord_t get_width(const T& a) { return a.width(); } -template typename T::y_coord_t get_height(const T& a) { return a.height(); } -template typename T::point_t get_dimensions(const T& a) { return a.dimensions(); } -template std::size_t get_num_channels(const T& a) { return a.num_channels(); } - -#define GIL boost::gil -#define ADOBE_GIL_NAMESPACE_BEGIN namespace boost { namespace gil { -#define ADOBE_GIL_NAMESPACE_END } } - -#define ByteAdvancableIteratorConcept MemoryBasedIteratorConcept -#define byte_advance memunit_advance -#define byte_advanced memunit_advanced -#define byte_step memunit_step -#define byte_distance memunit_distance - -#define byte_addressable_step_iterator memory_based_step_iterator -#define byte_addressable_2d_locator memory_based_2d_locator - -// These are members of memory-based locators -//#define row_bytes row_size // commented out because row_bytes is commonly used -#define pix_bytestep pixel_size - -#endif diff --git a/include/boost/gil/detail/math.hpp b/include/boost/gil/detail/math.hpp index 934f11ac82..5515adb2e8 100644 --- a/include/boost/gil/detail/math.hpp +++ b/include/boost/gil/detail/math.hpp @@ -9,19 +9,19 @@ #define BOOST_GIL_IMAGE_PROCESSING_DETAIL_MATH_HPP #include -#include +#include namespace boost { namespace gil { namespace detail { static constexpr double pi = 3.14159265358979323846; -static constexpr std::array dx_sobel = {{-1, 0, 1, -2, 0, 2, -1, 0, 1}}; +static constexpr std::array dx_sobel = {{1, 0, -1, 2, 0, -2, 1, 0, -1}}; static constexpr std::array dx_scharr = {{-1, 0, 1, -1, 0, 1, -1, 0, 1}}; static constexpr std::array dy_sobel = {{1, 2, 1, 0, 0, 0, -1, -2, -1}}; static constexpr std::array dy_scharr = {{1, 1, 1, 0, 0, 0, -1, -1, -1}}; template -inline detail::kernel_2d get_identity_kernel() +inline auto get_identity_kernel() -> detail::kernel_2d { detail::kernel_2d kernel(1, 0, 0); kernel[0] = 1; diff --git a/include/boost/gil/detail/mp11.hpp b/include/boost/gil/detail/mp11.hpp index 2778978faf..8c4eec0305 100644 --- a/include/boost/gil/detail/mp11.hpp +++ b/include/boost/gil/detail/mp11.hpp @@ -10,7 +10,6 @@ #define BOOST_GIL_DETAIL_MP11_HPP #include -#include // required by dynamic_image and boost::variant (?) namespace boost { namespace gil { namespace detail { diff --git a/include/boost/gil/device_n.hpp b/include/boost/gil/device_n.hpp index 6868d66466..b025d87cdf 100644 --- a/include/boost/gil/device_n.hpp +++ b/include/boost/gil/device_n.hpp @@ -32,8 +32,8 @@ struct devicen_color_t {}; template struct devicen_t; -/// \brief Unnamed color space of 1, 3, 4, or 5 channels -/// \tparam N Number of color components (1, 3, 4 or 5). +/// \brief Unnamed color space of 1, 2, 3, 4, or 5 channels +/// \tparam N Number of color components (1, 2, 3, 4 or 5). /// \ingroup ColorSpaceModel template struct devicen_t @@ -43,7 +43,7 @@ struct devicen_t using color_t = devicen_color_t; static_assert( - N == 1 || (3 <= N && N <= 5), + (1 <= N && N <= 5), "invalid number of DeviceN color components"); public: @@ -58,8 +58,9 @@ struct devicen_layout_t : layout::type> {}; /// \ingroup ImageViewConstructors /// \brief from 2-channel planar data template -inline typename type_from_x_iterator>>::view_t -planar_devicen_view(std::size_t width, std::size_t height, IC c0, IC c1, std::ptrdiff_t rowsize_in_bytes) +inline +auto planar_devicen_view(std::size_t width, std::size_t height, IC c0, IC c1, std::ptrdiff_t rowsize_in_bytes) + -> typename type_from_x_iterator>>::view_t { using view_t = typename type_from_x_iterator>>::view_t; return view_t(width, height, typename view_t::locator(typename view_t::x_iterator(c0,c1), rowsize_in_bytes)); diff --git a/include/boost/gil/extension/dynamic_image/algorithm.hpp b/include/boost/gil/extension/dynamic_image/algorithm.hpp index e63e1f9825..174be8f31e 100644 --- a/include/boost/gil/extension/dynamic_image/algorithm.hpp +++ b/include/boost/gil/extension/dynamic_image/algorithm.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2022 Marco Langer // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -12,7 +13,10 @@ #include +#include + #include +#include //////////////////////////////////////////////////////////////////////////////////////// /// \file @@ -43,31 +47,31 @@ struct equal_pixels_fn : binary_operation_obj /// \tparam Types Model Boost.MP11-compatible list of models of ImageViewConcept /// \tparam View Model MutableImageViewConcept template -bool equal_pixels(any_image_view const& src, View const& dst) +auto equal_pixels(any_image_view const& src, View const& dst) -> bool { - return apply_operation( - src, - std::bind(detail::equal_pixels_fn(), std::placeholders::_1, dst)); + return variant2::visit( + std::bind(detail::equal_pixels_fn(), std::placeholders::_1, dst), + src); } /// \ingroup ImageViewSTLAlgorithmsEqualPixels /// \tparam View Model ImageViewConcept /// \tparam Types Model Boost.MP11-compatible list of models of MutableImageViewConcept template -bool equal_pixels(View const& src, any_image_view const& dst) +auto equal_pixels(View const& src, any_image_view const& dst) -> bool { - return apply_operation( - dst, - std::bind(detail::equal_pixels_fn(), src, std::placeholders::_1)); + return variant2::visit( + std::bind(detail::equal_pixels_fn(), src, std::placeholders::_1), + dst); } /// \ingroup ImageViewSTLAlgorithmsEqualPixels /// \tparam Types1 Model Boost.MP11-compatible list of models of ImageViewConcept /// \tparam Types2 Model Boost.MP11-compatible list of models of MutableImageViewConcept template -bool equal_pixels(any_image_view const& src, any_image_view const& dst) +auto equal_pixels(any_image_view const& src, any_image_view const& dst) -> bool { - return apply_operation(src, dst, detail::equal_pixels_fn()); + return variant2::visit(detail::equal_pixels_fn(), src, dst); } namespace detail { @@ -90,7 +94,7 @@ struct copy_pixels_fn : public binary_operation_obj template void copy_pixels(any_image_view const& src, View const& dst) { - apply_operation(src, std::bind(detail::copy_pixels_fn(), std::placeholders::_1, dst)); + variant2::visit(std::bind(detail::copy_pixels_fn(), std::placeholders::_1, dst), src); } /// \ingroup ImageViewSTLAlgorithmsCopyPixels @@ -99,7 +103,7 @@ void copy_pixels(any_image_view const& src, View const& dst) template void copy_pixels(View const& src, any_image_view const& dst) { - apply_operation(dst, std::bind(detail::copy_pixels_fn(), src, std::placeholders::_1)); + variant2::visit(std::bind(detail::copy_pixels_fn(), src, std::placeholders::_1), dst); } /// \ingroup ImageViewSTLAlgorithmsCopyPixels @@ -108,7 +112,7 @@ void copy_pixels(View const& src, any_image_view const& dst) template void copy_pixels(any_image_view const& src, any_image_view const& dst) { - apply_operation(src, dst, detail::copy_pixels_fn()); + variant2::visit(detail::copy_pixels_fn(), src, dst); } //forward declaration for default_color_converter (see full definition in color_convert.hpp) @@ -122,7 +126,7 @@ template void copy_and_convert_pixels(any_image_view const& src, View const& dst, CC cc) { using cc_fn = detail::copy_and_convert_pixels_fn; - apply_operation(src, std::bind(cc_fn{cc}, std::placeholders::_1, dst)); + variant2::visit(std::bind(cc_fn{cc}, std::placeholders::_1, dst), src); } /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels @@ -132,7 +136,7 @@ template void copy_and_convert_pixels(any_image_view const& src, View const& dst) { using cc_fn = detail::copy_and_convert_pixels_fn; - apply_operation(src, std::bind(cc_fn{}, std::placeholders::_1, dst)); + variant2::visit(std::bind(cc_fn{}, std::placeholders::_1, dst), src); } /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels @@ -143,7 +147,7 @@ template void copy_and_convert_pixels(View const& src, any_image_view const& dst, CC cc) { using cc_fn = detail::copy_and_convert_pixels_fn; - apply_operation(dst, std::bind(cc_fn{cc}, src, std::placeholders::_1)); + variant2::visit(std::bind(cc_fn{cc}, src, std::placeholders::_1), dst); } /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels @@ -153,7 +157,7 @@ template void copy_and_convert_pixels(View const& src, any_image_view const& dst) { using cc_fn = detail::copy_and_convert_pixels_fn; - apply_operation(dst, std::bind(cc_fn{}, src, std::placeholders::_1)); + variant2::visit(std::bind(cc_fn{}, src, std::placeholders::_1), dst); } /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels @@ -165,7 +169,7 @@ void copy_and_convert_pixels( any_image_view const& src, any_image_view const& dst, CC cc) { - apply_operation(src, dst, detail::copy_and_convert_pixels_fn(cc)); + variant2::visit(detail::copy_and_convert_pixels_fn(cc), src, dst); } /// \ingroup ImageViewSTLAlgorithmsCopyAndConvertPixels @@ -176,8 +180,8 @@ void copy_and_convert_pixels( any_image_view const& src, any_image_view const& dst) { - apply_operation(src, dst, - detail::copy_and_convert_pixels_fn()); + variant2::visit( + detail::copy_and_convert_pixels_fn(), src, dst); } namespace detail { @@ -186,7 +190,7 @@ template struct fill_pixels_fn1 { template - static void apply(V const &src, Value const &val) { fill_pixels(src, val); } + static void apply(V const& src, Value const& val) { fill_pixels(src, val); } }; // copy_pixels invoked on incompatible images @@ -194,7 +198,7 @@ template <> struct fill_pixels_fn1 { template - static void apply(V const &, Value const &) { throw std::bad_cast();} + static void apply(V const&, Value const&) { throw std::bad_cast();} }; template @@ -227,7 +231,36 @@ struct fill_pixels_fn template void fill_pixels(any_image_view const& view, Value const& val) { - apply_operation(view, detail::fill_pixels_fn(val)); + variant2::visit(detail::fill_pixels_fn(val), view); +} + +namespace detail { + +template +struct for_each_pixel_fn +{ + for_each_pixel_fn(F&& fun) : fun_(std::move(fun)) {} + + template + auto operator()(View const& view) -> F + { + return for_each_pixel(view, fun_); + } + + F fun_; +}; + +} // namespace detail + +/// \defgroup ImageViewSTLAlgorithmsForEachPixel for_each_pixel +/// \ingroup ImageViewSTLAlgorithms +/// \brief std::for_each for any image views +/// +/// \ingroup ImageViewSTLAlgorithmsForEachPixel +template +auto for_each_pixel(any_image_view const& view, F fun) -> F +{ + return variant2::visit(detail::for_each_pixel_fn(std::move(fun)), view); } }} // namespace boost::gil diff --git a/include/boost/gil/extension/dynamic_image/any_image.hpp b/include/boost/gil/extension/dynamic_image/any_image.hpp index 701fece697..2af5164082 100644 --- a/include/boost/gil/extension/dynamic_image/any_image.hpp +++ b/include/boost/gil/extension/dynamic_image/any_image.hpp @@ -10,7 +10,6 @@ #define BOOST_GIL_EXTENSION_DYNAMIC_IMAGE_ANY_IMAGE_HPP #include -#include #include #include @@ -89,29 +88,15 @@ template class any_image : public variant2::variant { using parent_t = variant2::variant; -public: + +public: using view_t = mp11::mp_rename, any_image_view>; using const_view_t = mp11::mp_rename, any_image_view>; using x_coord_t = std::ptrdiff_t; using y_coord_t = std::ptrdiff_t; using point_t = point; - any_image() = default; - any_image(any_image const& img) : parent_t((parent_t const&)img) {} - - template - explicit any_image(Image const& img) : parent_t(img) {} - - template - any_image(Image&& img) : parent_t(std::move(img)) {} - - template - explicit any_image(Image& img, bool do_swap) : parent_t(img, do_swap) {} - - template - any_image(any_image const& img) - : parent_t((variant2::variant const&)img) - {} + using parent_t::parent_t; any_image& operator=(any_image const& img) { @@ -133,9 +118,9 @@ class any_image : public variant2::variant return *this; } - void recreate(const point_t& dims, unsigned alignment=1) + void recreate(point_t const& dims, unsigned alignment=1) { - apply_operation(*this, detail::recreate_image_fnobj(dims, alignment)); + variant2::visit(detail::recreate_image_fnobj(dims, alignment), *this); } void recreate(x_coord_t width, y_coord_t height, unsigned alignment=1) @@ -145,12 +130,12 @@ class any_image : public variant2::variant std::size_t num_channels() const { - return apply_operation(*this, detail::any_type_get_num_channels()); + return variant2::visit(detail::any_type_get_num_channels(), *this); } point_t dimensions() const { - return apply_operation(*this, detail::any_type_get_dimensions()); + return variant2::visit(detail::any_type_get_dimensions(), *this); } x_coord_t width() const { return dimensions().x; } @@ -170,7 +155,7 @@ BOOST_FORCEINLINE auto view(any_image& img) -> typename any_image::view_t { using view_t = typename any_image::view_t; - return apply_operation(img, detail::any_image_get_view()); + return variant2::visit(detail::any_image_get_view(), img); } /// \brief Returns the constant-pixel view of any image. The returned view is any view. @@ -180,7 +165,7 @@ BOOST_FORCEINLINE auto const_view(any_image const& img) -> typename any_image::const_view_t { using view_t = typename any_image::const_view_t; - return apply_operation(img, detail::any_image_get_const_view()); + return variant2::visit(detail::any_image_get_const_view(), img); } ///@} diff --git a/include/boost/gil/extension/dynamic_image/any_image_view.hpp b/include/boost/gil/extension/dynamic_image/any_image_view.hpp index 901fb6fdbe..5b9c94fbe1 100644 --- a/include/boost/gil/extension/dynamic_image/any_image_view.hpp +++ b/include/boost/gil/extension/dynamic_image/any_image_view.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2020 Samuel Debionne // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -24,10 +25,10 @@ struct dynamic_xy_step_transposed_type; namespace detail { template -struct get_const_t { using type = typename View::const_t; }; +using get_const_t = typename View::const_t; template -struct views_get_const_t : mp11::mp_transform {}; +using views_get_const_t = mp11::mp_transform; // works for both image_view and image struct any_type_get_num_channels @@ -67,7 +68,7 @@ struct any_type_get_size /// Other requirements, such as access to the pixels, would be inefficient to provide. Thus \p any_image_view does not fully model ImageViewConcept. /// However, many algorithms provide overloads taking runtime specified views and thus in many cases \p any_image_view can be used in places taking a view. /// -/// To perform an algorithm on any_image_view, put the algorithm in a function object and invoke it by calling \p apply_operation(runtime_view, algorithm_fn); +/// To perform an algorithm on any_image_view, put the algorithm in a function object and invoke it by calling \p variant2::visit(algorithm_fn, runtime_view); //////////////////////////////////////////////////////////////////////////////////////// template @@ -75,23 +76,14 @@ class any_image_view : public variant2::variant { using parent_t = variant2::variant; -public: +public: using const_t = detail::views_get_const_t; using x_coord_t = std::ptrdiff_t; using y_coord_t = std::ptrdiff_t; using point_t = point; using size_type = std::size_t; - any_image_view() = default; - any_image_view(any_image_view const& view) : parent_t((parent_t const&)view) {} - - template - explicit any_image_view(View const& view) : parent_t(view) {} - - template - any_image_view(any_image_view const& view) - : parent_t((variant2::variant const&)view) - {} + using parent_t::parent_t; any_image_view& operator=(any_image_view const& view) { @@ -113,9 +105,9 @@ class any_image_view : public variant2::variant return *this; } - std::size_t num_channels() const { return apply_operation(*this, detail::any_type_get_num_channels()); } - point_t dimensions() const { return apply_operation(*this, detail::any_type_get_dimensions()); } - size_type size() const { return apply_operation(*this, detail::any_type_get_size()); } + std::size_t num_channels() const { return variant2::visit(detail::any_type_get_num_channels(), *this); } + point_t dimensions() const { return variant2::visit(detail::any_type_get_dimensions(), *this); } + size_type size() const { return variant2::visit(detail::any_type_get_size(), *this); } x_coord_t width() const { return dimensions().x; } y_coord_t height() const { return dimensions().y; } }; diff --git a/include/boost/gil/extension/dynamic_image/apply_operation.hpp b/include/boost/gil/extension/dynamic_image/apply_operation.hpp index 2a5b542f5b..48ff1ac3b1 100644 --- a/include/boost/gil/extension/dynamic_image/apply_operation.hpp +++ b/include/boost/gil/extension/dynamic_image/apply_operation.hpp @@ -15,6 +15,7 @@ namespace boost { namespace gil { /// \ingroup Variant /// \brief Applies the visitor op to the variants template +[[deprecated("Use variant2::visit instead.")]] BOOST_FORCEINLINE auto apply_operation(Variant1&& arg1, Visitor&& op) #if defined(BOOST_NO_CXX14_DECLTYPE_AUTO) || defined(BOOST_NO_CXX11_DECLTYPE_N3276) @@ -27,6 +28,7 @@ auto apply_operation(Variant1&& arg1, Visitor&& op) /// \ingroup Variant /// \brief Applies the visitor op to the variants template +[[deprecated("Use variant2::visit instead.")]] BOOST_FORCEINLINE auto apply_operation(Variant1&& arg1, Variant2&& arg2, Visitor&& op) #if defined(BOOST_NO_CXX14_DECLTYPE_AUTO) || defined(BOOST_NO_CXX11_DECLTYPE_N3276) diff --git a/include/boost/gil/extension/dynamic_image/image_view_factory.hpp b/include/boost/gil/extension/dynamic_image/image_view_factory.hpp index fe88222a14..a41dc32c54 100644 --- a/include/boost/gil/extension/dynamic_image/image_view_factory.hpp +++ b/include/boost/gil/extension/dynamic_image/image_view_factory.hpp @@ -174,7 +174,7 @@ auto flipped_up_down_view(any_image_view const& src) -> typename dynamic_y_step_type>::type { using result_view_t = typename dynamic_y_step_type>::type; - return apply_operation(src, detail::flipped_up_down_view_fn()); + return variant2::visit(detail::flipped_up_down_view_fn(), src); } /// \ingroup ImageViewTransformationsFlipLR @@ -185,39 +185,40 @@ auto flipped_left_right_view(any_image_view const& src) -> typename dynamic_x_step_type>::type { using result_view_t = typename dynamic_x_step_type>::type; - return apply_operation(src, detail::flipped_left_right_view_fn()); + return variant2::visit(detail::flipped_left_right_view_fn(), src); } /// \ingroup ImageViewTransformationsTransposed /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template inline -auto transposed_view(const any_image_view& src) +auto transposed_view(any_image_view const& src) -> typename dynamic_xy_step_transposed_type>::type { using result_view_t = typename dynamic_xy_step_transposed_type>::type; - return apply_operation(src, detail::tranposed_view_fn()); + return variant2::visit(detail::tranposed_view_fn(), src); } /// \ingroup ImageViewTransformations90CW /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template inline -auto rotated90cw_view(const any_image_view& src) +auto rotated90cw_view(any_image_view const& src) -> typename dynamic_xy_step_transposed_type>::type { using result_view_t = typename dynamic_xy_step_transposed_type>::type; - return apply_operation(src,detail::rotated90cw_view_fn()); + return variant2::visit(detail::rotated90cw_view_fn(), src); } /// \ingroup ImageViewTransformations90CCW /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template inline -auto rotated90ccw_view(const any_image_view& src) +auto rotated90ccw_view(any_image_view const& src) -> typename dynamic_xy_step_transposed_type>::type { - return apply_operation(src,detail::rotated90ccw_view_fn>::type>()); + using result_view_t = typename dynamic_xy_step_transposed_type>::type; + return variant2::visit(detail::rotated90ccw_view_fn(), src); } /// \ingroup ImageViewTransformations180 @@ -227,8 +228,8 @@ inline auto rotated180_view(any_image_view const& src) -> typename dynamic_xy_step_type>::type { - using step_type = typename dynamic_xy_step_type>::type; - return apply_operation(src, detail::rotated180_view_fn()); + using result_view_t = typename dynamic_xy_step_type>::type; + return variant2::visit(detail::rotated180_view_fn(), src); } /// \ingroup ImageViewTransformationsSubimage @@ -242,7 +243,7 @@ auto subimage_view( -> any_image_view { using subimage_view_fn = detail::subimage_view_fn>; - return apply_operation(src, subimage_view_fn(topleft, dimensions)); + return variant2::visit(subimage_view_fn(topleft, dimensions), src); } /// \ingroup ImageViewTransformationsSubimage @@ -255,7 +256,7 @@ auto subimage_view( -> any_image_view { using subimage_view_fn = detail::subimage_view_fn>; - return apply_operation(src, subimage_view_fn(point_t(x_min, y_min),point_t(width, height))); + return variant2::visit(subimage_view_fn(point_t(x_min, y_min),point_t(width, height)), src); } /// \ingroup ImageViewTransformationsSubsampled @@ -267,7 +268,7 @@ auto subsampled_view(any_image_view const& src, point_t const& step) { using step_type = typename dynamic_xy_step_type>::type; using subsampled_view = detail::subsampled_view_fn; - return apply_operation(src, subsampled_view(step)); + return variant2::visit(subsampled_view(step), src); } /// \ingroup ImageViewTransformationsSubsampled @@ -279,7 +280,7 @@ auto subsampled_view(any_image_view const& src, std::ptrdiff_t x_step, { using step_type = typename dynamic_xy_step_type>::type; using subsampled_view_fn = detail::subsampled_view_fn; - return apply_operation(src, subsampled_view_fn(point_t(x_step, y_step))); + return variant2::visit(subsampled_view_fn(point_t(x_step, y_step)), src); } namespace detail { @@ -304,29 +305,21 @@ struct nth_channel_view_type> /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template inline -auto nth_channel_view(const any_image_view& src, int n) +auto nth_channel_view(any_image_view const& src, int n) -> typename nth_channel_view_type>::type { using result_view_t = typename nth_channel_view_type>::type; - return apply_operation(src,detail::nth_channel_view_fn(n)); + return variant2::visit(detail::nth_channel_view_fn(n), src); } namespace detail { -template -struct get_ccv_type : color_converted_view_type {}; - template struct views_get_ccv_type { private: - // FIXME: Remove class name injection with detail:: qualification - // Required as workaround for MP11 issue that treats unqualified metafunction - // in the class definition of the same name as the specialization (Peter Dimov): - // invalid template argument for template parameter 'F', expected a class template - template - using ccvt = detail::get_ccv_type; - + template + using ccvt = typename color_converted_view_type::type; public: using type = mp11::mp_transform; }; @@ -339,7 +332,7 @@ template struct color_converted_view_type,DstP,CC> { //using type = any_image_view::type>; - using type = detail::views_get_ccv_type, DstP, CC>; + using type = typename detail::views_get_ccv_type, DstP, CC>::type; }; /// \ingroup ImageViewTransformationsColorConvert @@ -347,11 +340,11 @@ struct color_converted_view_type,DstP,CC> /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template inline -auto color_converted_view(const any_image_view& src, CC) +auto color_converted_view(any_image_view const& src, CC cc) -> typename color_converted_view_type, DstP, CC>::type { using cc_view_t = typename color_converted_view_type, DstP, CC>::type; - return apply_operation(src, detail::color_converted_view_fn()); + return variant2::visit(detail::color_converted_view_fn(cc), src); } /// \ingroup ImageViewTransformationsColorConvert @@ -359,7 +352,7 @@ auto color_converted_view(const any_image_view& src, CC) template struct color_converted_view_type,DstP> { - using type = detail::views_get_ccv_type, DstP, default_color_converter>; + using type = typename detail::views_get_ccv_type, DstP, default_color_converter>::type; }; /// \ingroup ImageViewTransformationsColorConvert @@ -371,7 +364,7 @@ auto color_converted_view(any_image_view const& src) -> typename color_converted_view_type, DstP>::type { using cc_view_t = typename color_converted_view_type, DstP>::type; - return apply_operation(src, detail::color_converted_view_fn()); + return variant2::visit(detail::color_converted_view_fn(), src); } /// \ingroup ImageViewTransformationsColorConvert @@ -379,12 +372,12 @@ auto color_converted_view(any_image_view const& src) /// These are workarounds for GCC 3.4, which thinks color_converted_view is ambiguous with the same method for templated views (in gil/image_view_factory.hpp) /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template +[[deprecated("Use color_converted_view(const any_image_view& src, CC) instead.")]] inline -auto any_color_converted_view(const any_image_view& src, CC) +auto any_color_converted_view(const any_image_view& src, CC cc) -> typename color_converted_view_type, DstP, CC>::type { - using cc_view_t = typename color_converted_view_type, DstP, CC>::type; - return apply_operation(src, detail::color_converted_view_fn()); + return color_converted_view(src, cc); } /// \ingroup ImageViewTransformationsColorConvert @@ -392,12 +385,12 @@ auto any_color_converted_view(const any_image_view& src, CC) /// These are workarounds for GCC 3.4, which thinks color_converted_view is ambiguous with the same method for templated views (in gil/image_view_factory.hpp) /// \tparam Views Models Boost.MP11-compatible list of models of ImageViewConcept template +[[deprecated("Use color_converted_view(any_image_view const& src) instead.")]] inline auto any_color_converted_view(const any_image_view& src) -> typename color_converted_view_type, DstP>::type { - using cc_view_t = typename color_converted_view_type, DstP>::type; - return apply_operation(src, detail::color_converted_view_fn()); + return color_converted_view(src); } /// \} diff --git a/include/boost/gil/extension/histogram/std.hpp b/include/boost/gil/extension/histogram/std.hpp new file mode 100644 index 0000000000..a015078212 --- /dev/null +++ b/include/boost/gil/extension/histogram/std.hpp @@ -0,0 +1,172 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#ifndef BOOST_GIL_EXTENSION_HISTOGRAM_STL_HISTOGRAM_HPP +#define BOOST_GIL_EXTENSION_HISTOGRAM_STL_HISTOGRAM_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace boost { namespace gil { + +////////////////////////////////////////////////////////// +/// Histogram extension for STL container +////////////////////////////////////////////////////////// +/// \defgroup Histogram - STL Containers +/// \brief Collection of functions to provide histogram support in GIL using Standard +/// Template Library Containers +/// The conversion from Boost.GIL images to compatible histograms are provided. The supported +/// container types would be std::vector, std::array, std::map. +/// +/// Some general constraints on STL extension:- +/// 1. Supports only 1D histogram. +/// 2. Cannot use signed images with compatible random access containers. +/// 3. Automatic resize of std::array in case of shortage of bins, to ensure +/// correctness comes before performance. +/// 4. Container key type (if exists) has to be one of std::integral types to be +/// GIL compatible. +/// 5. Container value type has to be of std::arithmetic types. +/// + +/// +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::vector of fill_histogram +/// +template +void fill_histogram(SrcView const& srcview, std::vector& histogram, bool accumulate = false) +{ + gil_function_requires>(); + static_assert(std::is_arithmetic::value, "Improper container type for images."); + static_assert( + std::is_unsigned::type>::value, + "Improper container type for signed images."); + + using channel_t = typename channel_type::type; + using pixel_t = pixel; + + if (!accumulate) + histogram.clear(); + histogram.resize(std::numeric_limits::max() + 1); + + for_each_pixel(color_converted_view(srcview), [&](pixel_t const& p) { + ++histogram[static_cast(p)]; + }); +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::array of fill_histogram +/// +template +void fill_histogram(SrcView const& srcview, std::array& histogram, bool accumulate = false) +{ + gil_function_requires>(); + static_assert(std::is_arithmetic::value && N > 0, "Improper container type for images."); + static_assert( + std::is_unsigned::type>::value, + "Improper container type for signed images."); + + using channel_t = typename channel_type::type; + using pixel_t = pixel; + + const size_t pixel_max = std::numeric_limits::max(); + const float scale = (histogram.size() - 1.0f) / pixel_max; + + if (!accumulate) + std::fill(std::begin(histogram), std::end(histogram), 0); + + for_each_pixel(color_converted_view(srcview), [&](pixel_t const& p) { + ++histogram[static_cast(p * scale)]; + }); +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::map of fill_histogram +/// +template +void fill_histogram(SrcView const& srcview, std::map& histogram, bool accumulate = false) +{ + gil_function_requires>(); + static_assert( + std::is_arithmetic::value && std::is_integral::value, + "Improper container type for images."); + + using channel_t = typename channel_type::type; + using pixel_t = pixel; + + if (!accumulate) + histogram.clear(); + + for_each_pixel(color_converted_view(srcview), [&](pixel_t const& p) { + ++histogram[static_cast(p)]; + }); +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::vector of cumulative_histogram +/// +template +std::vector cumulative_histogram(std::vector& hist) +{ + std::vector cumulative_hist(hist.size()); + static_assert(std::is_arithmetic::value, "Improper container type for images."); + T cumulative_counter = 0; + for (std::size_t i = 0; i < hist.size(); i++) + { + cumulative_counter += hist[i]; + cumulative_hist[i] = cumulative_counter; + } + return cumulative_hist; +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::array of cumulative_histogram +/// +template +std::array cumulative_histogram(std::array& histogram) +{ + std::array cumulative_hist; + static_assert(std::is_arithmetic::value && N > 0, "Improper container type for images."); + T cumulative_counter = 0; + for (std::size_t i = 0; i < N; i++) + { + cumulative_counter += histogram[i]; + cumulative_hist[i] = cumulative_counter; + } + return cumulative_hist; +} + +/// \ingroup Histogram - STL Containers +/// \brief Overload for std::map of cumulative_histogram +/// +template +std::map cumulative_histogram(std::map& histogram) +{ + std::map cumulative_hist; + static_assert( + std::is_arithmetic::value && std::is_integral::value, + "Improper container type for images."); + T2 cumulative_counter = 0; + for (auto const& it : histogram) + { + cumulative_counter += it.second; + cumulative_hist[it.first] = cumulative_counter; + } + return cumulative_hist; +} + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/image_processing/diffusion.hpp b/include/boost/gil/extension/image_processing/diffusion.hpp new file mode 100644 index 0000000000..b8333c6291 --- /dev/null +++ b/include/boost/gil/extension/image_processing/diffusion.hpp @@ -0,0 +1,426 @@ +// +// Copyright 2020 Olzhas Zhumabek +// Copyright 2021 Pranam Lashkari +// +// Use, modification and distribution are subject to 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) +// + +#ifndef BOOST_GIL_EXTENSION_IMAGE_PROCESSING_DIFFUSION_HPP +#define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_DIFFUSION_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost { namespace gil { +namespace conductivity { +struct perona_malik_conductivity +{ + double kappa; + template + Pixel operator()(Pixel input) + { + using channel_type = typename channel_type::type; + // C++11 doesn't seem to capture members + static_transform(input, input, [this](channel_type value) { + value /= kappa; + return std::exp(-std::abs(value)); + }); + + return input; + } +}; + +struct gaussian_conductivity +{ + double kappa; + template + Pixel operator()(Pixel input) + { + using channel_type = typename channel_type::type; + // C++11 doesn't seem to capture members + static_transform(input, input, [this](channel_type value) { + value /= kappa; + return std::exp(-value * value); + }); + + return input; + } +}; + +struct wide_regions_conductivity +{ + double kappa; + template + Pixel operator()(Pixel input) + { + using channel_type = typename channel_type::type; + // C++11 doesn't seem to capture members + static_transform(input, input, [this](channel_type value) { + value /= kappa; + return 1.0 / (1.0 + value * value); + }); + + return input; + } +}; + +struct more_wide_regions_conductivity +{ + double kappa; + template + Pixel operator()(Pixel input) + { + using channel_type = typename channel_type::type; + // C++11 doesn't seem to capture members + static_transform(input, input, [this](channel_type value) { + value /= kappa; + return 1.0 / std::sqrt((1.0 + value * value)); + }); + + return input; + } +}; +} // namespace diffusion + +/** + \brief contains discrete approximations of 2D Laplacian operator +*/ +namespace laplace_function { +// The functions assume clockwise enumeration of stencil points, as such +// NW North NE 0 1 2 (-1, -1) (0, -1) (+1, -1) +// West East ===> 7 3 ===> (-1, 0) (+1, 0) +// SW South SE 6 5 4 (-1, +1) (0, +1) (+1, +1) + +/** + \brief This function makes sure all Laplace functions enumerate + values in the same order and direction. + + The first element is difference North West direction, second in North, + and so on in clockwise manner. Leave element as zero if it is not + to be computed. +*/ +inline std::array get_directed_offsets() +{ + return {point_t{-1, -1}, point_t{0, -1}, point_t{+1, -1}, point_t{+1, 0}, + point_t{+1, +1}, point_t{0, +1}, point_t{-1, +1}, point_t{-1, 0}}; +} + +template +using stencil_type = std::array; + +/** + \brief 5 point stencil approximation of Laplacian + + Only main 4 directions are non-zero, the rest are zero +*/ +struct stencil_5points +{ + double delta_t = 0.25; + + template + stencil_type compute_laplace(SubImageView view, + point_t origin) + { + auto current = view(origin); + stencil_type stencil; + using channel_type = typename channel_type::type; + std::array offsets(get_directed_offsets()); + typename SubImageView::value_type zero_pixel; + static_fill(zero_pixel, 0); + for (std::size_t index = 0; index < offsets.size(); ++index) + { + if (index % 2 != 0) + { + static_transform(view(origin.x + offsets[index].x, origin.y + offsets[index].y), + current, stencil[index], std::minus{}); + } + else + { + stencil[index] = zero_pixel; + } + } + return stencil; + } + + template + Pixel reduce(const stencil_type& stencil) + { + using channel_type = typename channel_type::type; + auto result = []() { + Pixel zero_pixel; + static_fill(zero_pixel, channel_type(0)); + return zero_pixel; + }(); + + for (std::size_t index : {1u, 3u, 5u, 7u}) + { + static_transform(result, stencil[index], result, std::plus{}); + } + Pixel delta_t_pixel; + static_fill(delta_t_pixel, delta_t); + static_transform(result, delta_t_pixel, result, std::multiplies{}); + + return result; + } +}; + +/** + \brief 9 point stencil approximation of Laplacian + + This is full 8 way approximation, though diagonal + elements are halved during reduction. +*/ +struct stencil_9points_standard +{ + double delta_t = 0.125; + + template + stencil_type compute_laplace(SubImageView view, + point_t origin) + { + stencil_type stencil; + auto out = stencil.begin(); + auto current = view(origin); + using channel_type = typename channel_type::type; + std::array offsets(get_directed_offsets()); + for (auto offset : offsets) + { + static_transform(view(origin.x + offset.x, origin.y + offset.y), current, *out++, + std::minus{}); + } + + return stencil; + } + + template + Pixel reduce(const stencil_type& stencil) + { + using channel_type = typename channel_type::type; + auto result = []() { + Pixel zero_pixel; + static_fill(zero_pixel, channel_type(0)); + return zero_pixel; + }(); + for (std::size_t index : {1u, 3u, 5u, 7u}) + { + static_transform(result, stencil[index], result, std::plus{}); + } + + for (std::size_t index : {0u, 2u, 4u, 6u}) + { + Pixel half_pixel; + static_fill(half_pixel, channel_type(1 / 2.0)); + static_transform(stencil[index], half_pixel, half_pixel, + std::multiplies{}); + static_transform(result, half_pixel, result, std::plus{}); + } + + Pixel delta_t_pixel; + static_fill(delta_t_pixel, delta_t); + static_transform(result, delta_t_pixel, result, std::multiplies{}); + + return result; + } +}; +} // namespace laplace_function + +namespace brightness_function { +using laplace_function::stencil_type; +struct identity +{ + template + stencil_type operator()(const stencil_type& stencil) + { + return stencil; + } +}; + +// TODO: Figure out how to implement color gradient brightness, as it +// seems to need dx and dy using sobel or scharr kernels + +struct rgb_luminance +{ + using pixel_type = rgb32f_pixel_t; + stencil_type operator()(const stencil_type& stencil) + { + stencil_type output; + std::transform(stencil.begin(), stencil.end(), output.begin(), [](const pixel_type& pixel) { + float32_t luminance = 0.2126f * pixel[0] + 0.7152f * pixel[1] + 0.0722f * pixel[2]; + pixel_type result_pixel; + static_fill(result_pixel, luminance); + return result_pixel; + }); + return output; + } +}; + +} // namespace brightness_function + +enum class matlab_connectivity +{ + minimal, + maximal +}; + +enum class matlab_conduction_method +{ + exponential, + quadratic +}; + +template +void classic_anisotropic_diffusion(const InputView& input, const OutputView& output, + unsigned int num_iter, double kappa) +{ + anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{}, + brightness_function::identity{}, + conductivity::perona_malik_conductivity{kappa}); +} + +template +void matlab_anisotropic_diffusion(const InputView& input, const OutputView& output, + unsigned int num_iter, double kappa, + matlab_connectivity connectivity, + matlab_conduction_method conduction_method) +{ + if (connectivity == matlab_connectivity::minimal) + { + if (conduction_method == matlab_conduction_method::exponential) + { + anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{}, + brightness_function::identity{}, + conductivity::gaussian_conductivity{kappa}); + } + else if (conduction_method == matlab_conduction_method::quadratic) + { + anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{}, + brightness_function::identity{}, + conductivity::gaussian_conductivity{kappa}); + } + else + { + throw std::logic_error("unhandled conduction method found"); + } + } + else if (connectivity == matlab_connectivity::maximal) + { + if (conduction_method == matlab_conduction_method::exponential) + { + anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{}, + brightness_function::identity{}, + conductivity::gaussian_conductivity{kappa}); + } + else if (conduction_method == matlab_conduction_method::quadratic) + { + anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{}, + brightness_function::identity{}, + conductivity::gaussian_conductivity{kappa}); + } + else + { + throw std::logic_error("unhandled conduction method found"); + } + } + else + { + throw std::logic_error("unhandled connectivity found"); + } +} + +template +void default_anisotropic_diffusion(const InputView& input, const OutputView& output, + unsigned int num_iter, double kappa) +{ + anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_9points_standard{}, + brightness_function::identity{}, conductivity::gaussian_conductivity{kappa}); +} + +/// \brief Performs diffusion according to Perona-Malik equation +/// +/// WARNING: Output channel type must be floating point, +/// otherwise there will be loss in accuracy which most +/// probably will lead to incorrect results (input will be unchanged). +/// Anisotropic diffusion is a smoothing algorithm that respects +/// edge boundaries and can work as an edge detector if suitable +/// iteration count is set and grayscale image view is used +/// as an input +template +void anisotropic_diffusion(const InputView& input, const OutputView& output, unsigned int num_iter, + LaplaceStrategy laplace, BrightnessFunction brightness, + DiffusivityFunction diffusivity) +{ + using input_pixel_type = typename InputView::value_type; + using pixel_type = typename OutputView::value_type; + using channel_type = typename channel_type::type; + using computation_image = image; + const auto width = input.width(); + const auto height = input.height(); + const auto zero_pixel = []() { + pixel_type pixel; + static_fill(pixel, static_cast(0)); + + return pixel; + }(); + computation_image result_image(width + 2, height + 2, zero_pixel); + auto result = view(result_image); + computation_image scratch_result_image(width + 2, height + 2, zero_pixel); + auto scratch_result = view(scratch_result_image); + transform_pixels(input, subimage_view(result, 1, 1, width, height), + [](const input_pixel_type& pixel) { + pixel_type converted; + for (std::size_t i = 0; i < num_channels{}; ++i) + { + converted[i] = pixel[i]; + } + return converted; + }); + + for (unsigned int iteration = 0; iteration < num_iter; ++iteration) + { + for (std::ptrdiff_t relative_y = 0; relative_y < height; ++relative_y) + { + for (std::ptrdiff_t relative_x = 0; relative_x < width; ++relative_x) + { + auto x = relative_x + 1; + auto y = relative_y + 1; + auto stencil = laplace.compute_laplace(result, point_t(x, y)); + auto brightness_stencil = brightness(stencil); + laplace_function::stencil_type diffusivity_stencil; + std::transform(brightness_stencil.begin(), brightness_stencil.end(), + diffusivity_stencil.begin(), diffusivity); + laplace_function::stencil_type product_stencil; + std::transform(stencil.begin(), stencil.end(), diffusivity_stencil.begin(), + product_stencil.begin(), [](pixel_type lhs, pixel_type rhs) { + static_transform(lhs, rhs, lhs, std::multiplies{}); + return lhs; + }); + static_transform(result(x, y), laplace.reduce(product_stencil), + scratch_result(x, y), std::plus{}); + } + } + using std::swap; + swap(result, scratch_result); + } + + copy_pixels(subimage_view(result, 1, 1, width, height), output); +} + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/image_processing/hough_parameter.hpp b/include/boost/gil/extension/image_processing/hough_parameter.hpp new file mode 100644 index 0000000000..91521d1a9c --- /dev/null +++ b/include/boost/gil/extension/image_processing/hough_parameter.hpp @@ -0,0 +1,113 @@ +// Boost.GIL (Generic Image Library) - tests +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#ifndef BOOST_GIL_EXTENSION_IMAGE_PROCESSING_HOUGH_PARAMETER_HPP +#define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_HOUGH_PARAMETER_HPP + +#include "boost/gil/point.hpp" + +#include +#include + +namespace boost +{ +namespace gil +{ +/// \ingroup HoughTransform +/// \brief A type to encapsulate Hough transform parameter range +/// +/// This type provides a way to express value range for a parameter +/// as well as some factory functions to simplify initialization +template +struct hough_parameter +{ + T start_point; + T step_size; + std::size_t step_count; + + /// \ingroup HoughTransform + /// \brief Create Hough parameter from value neighborhood and step count + /// + /// This function will take start_point as middle point, and in both + /// directions will try to walk half_step_count times until distance of + /// neighborhood is reached + static hough_parameter from_step_count(T start_point, T neighborhood, + std::size_t half_step_count) + { + T step_size = neighborhood / half_step_count; + std::size_t step_count = half_step_count * 2 + 1; + // explicitly fill out members, as aggregate init will error out with narrowing + hough_parameter parameter; + parameter.start_point = start_point - neighborhood; + parameter.step_size = step_size; + parameter.step_count = step_count; + return parameter; + } + + /// \ingroup HoughTransform + /// \brief Create Hough parameter from value neighborhood and step size + /// + /// This function will take start_point as middle point, and in both + /// directions will try to walk step_size at a time until distance of + /// neighborhood is reached + static hough_parameter from_step_size(T start_point, T neighborhood, T step_size) + { + std::size_t step_count = + 2 * static_cast(std::floor(neighborhood / step_size)) + 1; + // do not use step_size - neighborhood, as step_size might not allow + // landing exactly on that value when starting from start_point + // also use parentheses on step_count / 2 because flooring is exactly + // what we want + + // explicitly fill out members, as aggregate init will error out with narrowing + hough_parameter parameter; + parameter.start_point = start_point - step_size * (step_count / 2); + parameter.step_size = step_size; + parameter.step_count = step_count; + return parameter; + } +}; + +/// \ingroup HoughTransform +/// \brief Calculate minimum angle which would be observable if walked on a circle +/// +/// When drawing a circle or moving around a point in circular motion, it is +/// important to not do too many steps, but also to not have disconnected +/// trajectory. This function will calculate the minimum angle that is observable +/// when walking on a circle or tilting a line. +/// WARNING: do keep in mind IEEE 754 quirks, e.g. no-associativity, +/// no-commutativity and precision. Do not expect expressions that are +/// mathematically the same to produce the same values +inline double minimum_angle_step(point_t dimensions) +{ + auto longer_dimension = dimensions.x > dimensions.y ? dimensions.x : dimensions.y; + return std::atan2(1, longer_dimension); +} + +/// \ingroup HoughTransform +/// \brief Create a Hough transform parameter with optimal angle step +/// +/// Due to computational intensity and noise sensitivity of Hough transform, +/// having any candidates missed or computed again is problematic. This function +/// will properly encapsulate optimal value range around approx_angle with amplitude of +/// neighborhood in each direction. +/// WARNING: do keep in mind IEEE 754 quirks, e.g. no-associativity, +/// no-commutativity and precision. Do not expect expressions that are +/// mathematically the same to produce the same values +inline auto make_theta_parameter(double approx_angle, double neighborhood, point_t dimensions) + -> hough_parameter +{ + auto angle_step = minimum_angle_step(dimensions); + + // std::size_t step_count = + // 2 * static_cast(std::floor(neighborhood / angle_step)) + 1; + // return {approx_angle - angle_step * (step_count / 2), angle_step, step_count}; + return hough_parameter::from_step_size(approx_angle, neighborhood, angle_step); +} +}} // namespace boost::gil +#endif diff --git a/include/boost/gil/extension/image_processing/hough_transform.hpp b/include/boost/gil/extension/image_processing/hough_transform.hpp new file mode 100644 index 0000000000..7331ed9260 --- /dev/null +++ b/include/boost/gil/extension/image_processing/hough_transform.hpp @@ -0,0 +1,138 @@ +// Boost.GIL (Generic Image Library) - tests +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#ifndef BOOST_GIL_EXTENSION_IMAGE_PROCESSING_HOUGH_TRANSFORM_HPP +#define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_HOUGH_TRANSFORM_HPP + +#include +#include + +#include +#include +#include +#include +#include + +namespace boost { namespace gil { +/// \defgroup HoughTransform +/// \brief A family of shape detectors that are specified by equation +/// +/// Hough transform is a method of mapping (voting) an object which can be described by +/// equation to single point in accumulator array (also called parameter space). +/// Each set pixel in edge map votes for every shape it can be part of. +/// Circle and ellipse transforms are very costly to brute force, while +/// non-brute-forcing algorithms tend to gamble on probabilities. + +/// \ingroup HoughTransform +/// \brief Vote for best fit of a line in parameter space +/// +/// The input must be an edge map with grayscale pixels. Be aware of overflow inside +/// accumulator array. The theta parameter is best computed through factory function +/// provided in hough_parameter.hpp +template +void hough_line_transform(InputView const& input_view, OutputView const& accumulator_array, + hough_parameter const& theta, + hough_parameter const& radius) +{ + std::ptrdiff_t r_lower_bound = radius.start_point; + std::ptrdiff_t r_upper_bound = r_lower_bound + radius.step_size * (radius.step_count - 1); + + for (std::ptrdiff_t y = 0; y < input_view.height(); ++y) + { + for (std::ptrdiff_t x = 0; x < input_view.width(); ++x) + { + if (!input_view(x, y)[0]) + { + continue; + } + + for (std::size_t theta_index = 0; theta_index < theta.step_count; ++theta_index) + { + double theta_current = + theta.start_point + theta.step_size * static_cast(theta_index); + std::ptrdiff_t current_r = + std::llround(static_cast(x) * std::cos(theta_current) + + static_cast(y) * std::sin(theta_current)); + if (current_r < r_lower_bound || current_r > r_upper_bound) + { + continue; + } + std::size_t r_index = static_cast( + std::llround((current_r - radius.start_point) / radius.step_size)); + // one more safety guard to not get out of bounds + if (r_index < radius.step_count) + { + accumulator_array(theta_index, r_index)[0] += 1; + } + } + } + } +} + +/// \ingroup HoughTransform +/// \brief Vote for best fit of a circle in parameter space according to rasterizer +/// +/// The input must be an edge map with grayscale pixels. Be aware of overflow inside +/// accumulator array. Rasterizer is used to rasterize a circle for voting. The circle +/// then is translated for every origin (x, y) in x y parameter space. For available +/// circle rasterizers, please look at rasterization/circle.hpp +template +void hough_circle_transform_brute(ImageView const& input, + hough_parameter const& radius_parameter, + hough_parameter const& x_parameter, + hough_parameter const& y_parameter, + ForwardIterator d_first, Rasterizer rasterizer) +{ + for (std::size_t radius_index = 0; radius_index < radius_parameter.step_count; ++radius_index) + { + const auto radius = radius_parameter.start_point + + radius_parameter.step_size * static_cast(radius_index); + Rasterizer rasterizer{point_t{}, radius}; + std::vector circle_points(rasterizer.point_count()); + rasterizer(circle_points.begin()); + // sort by scanline to improve cache coherence for row major images + std::sort(circle_points.begin(), circle_points.end(), + [](point_t const& lhs, point_t const& rhs) { return lhs.y < rhs.y; }); + const auto translate = [](std::vector& points, point_t offset) { + std::transform(points.begin(), points.end(), points.begin(), [offset](point_t point) { + return point_t(point.x + offset.x, point.y + offset.y); + }); + }; + + // in case somebody passes iterator to likes of std::vector + typename std::iterator_traits::reference current_image = *d_first; + + // the algorithm has to traverse over parameter space and look at input, instead + // of vice versa, as otherwise it will call translate too many times, as input + // is usually bigger than the coordinate portion of parameter space. + // This might cause extensive cache misses + for (std::size_t x_index = 0; x_index < x_parameter.step_count; ++x_index) + { + for (std::size_t y_index = 0; y_index < y_parameter.step_count; ++y_index) + { + const std::ptrdiff_t x = x_parameter.start_point + x_index * x_parameter.step_size; + const std::ptrdiff_t y = y_parameter.start_point + y_index * y_parameter.step_size; + + auto translated_circle = circle_points; + translate(translated_circle, {x, y}); + for (const auto& point : translated_circle) + { + if (input(point)) + { + ++current_image(x_index, y_index)[0]; + } + } + } + } + ++d_first; + } +} + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/io/bmp/detail/read.hpp b/include/boost/gil/extension/io/bmp/detail/read.hpp index 136bd802b1..fa6ab2db98 100644 --- a/include/boost/gil/extension/io/bmp/detail/read.hpp +++ b/include/boost/gil/extension/io/bmp/detail/read.hpp @@ -11,11 +11,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include @@ -709,7 +709,7 @@ class dynamic_image_reader< Device { detail::bmp_type_format_checker format_checker( this->_info._bits_per_pixel ); - if( !construct_matched( images + if( !detail::construct_matched( images , format_checker )) { @@ -725,8 +725,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + ,view( images ) ); } } diff --git a/include/boost/gil/extension/io/bmp/detail/reader_backend.hpp b/include/boost/gil/extension/io/bmp/detail/reader_backend.hpp index 5cf45f7edf..246d06ee6b 100644 --- a/include/boost/gil/extension/io/bmp/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/bmp/detail/reader_backend.hpp @@ -200,7 +200,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/bmp/detail/write.hpp b/include/boost/gil/extension/io/bmp/detail/write.hpp index fbea9ce05e..d75a6bdbbe 100644 --- a/include/boost/gil/extension/io/bmp/detail/write.hpp +++ b/include/boost/gil/extension/io/bmp/detail/write.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include @@ -201,8 +201,8 @@ class dynamic_image_writer< Device , parent_t > op( this ); - apply_operation( views - , op + variant2::visit( op + ,views ); } }; diff --git a/include/boost/gil/extension/io/bmp/tags.hpp b/include/boost/gil/extension/io/bmp/tags.hpp index 37921e1922..c3ba18fb8e 100644 --- a/include/boost/gil/extension/io/bmp/tags.hpp +++ b/include/boost/gil/extension/io/bmp/tags.hpp @@ -73,7 +73,7 @@ template<> struct image_read_info< bmp_tag > { /// Default contructor. - image_read_info< bmp_tag >() + image_read_info() : _top_down(false) , _valid( false ) {} @@ -136,8 +136,8 @@ struct image_read_settings< bmp_tag > : public image_read_settings_base /// Constructor /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim ) : image_read_settings_base( top_left , dim diff --git a/include/boost/gil/extension/io/jpeg/detail/read.hpp b/include/boost/gil/extension/io/jpeg/detail/read.hpp index def89fd58e..57992c07e6 100644 --- a/include/boost/gil/extension/io/jpeg/detail/read.hpp +++ b/include/boost/gil/extension/io/jpeg/detail/read.hpp @@ -12,10 +12,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -284,7 +284,7 @@ class dynamic_image_reader< Device : JCS_RGB ); - if( !construct_matched( images + if( !detail::construct_matched( images , format_checker )) { @@ -300,8 +300,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + , view( images ) ); } } diff --git a/include/boost/gil/extension/io/jpeg/detail/reader_backend.hpp b/include/boost/gil/extension/io/jpeg/detail/reader_backend.hpp index 90e6075958..022d219f6b 100644 --- a/include/boost/gil/extension/io/jpeg/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/jpeg/detail/reader_backend.hpp @@ -189,7 +189,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/jpeg/detail/write.hpp b/include/boost/gil/extension/io/jpeg/detail/write.hpp index a41a7e6544..e4534b3d58 100644 --- a/include/boost/gil/extension/io/jpeg/detail/write.hpp +++ b/include/boost/gil/extension/io/jpeg/detail/write.hpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include @@ -167,7 +167,7 @@ class dynamic_image_writer< Device , parent_t > op( this ); - apply_operation( views, op ); + variant2::visit( op, views ); } }; diff --git a/include/boost/gil/extension/io/jpeg/tags.hpp b/include/boost/gil/extension/io/jpeg/tags.hpp index a12c237df6..0ebbe7cfca 100644 --- a/include/boost/gil/extension/io/jpeg/tags.hpp +++ b/include/boost/gil/extension/io/jpeg/tags.hpp @@ -141,7 +141,7 @@ template<> struct image_read_settings< jpeg_tag > : public image_read_settings_base { /// Default constructor - image_read_settings() + image_read_settings() : image_read_settings_base() , _dct_method( jpeg_dct_method::default_value ) {} @@ -150,8 +150,8 @@ struct image_read_settings< jpeg_tag > : public image_read_settings_base /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. /// \param dct_method Specifies dct method. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim , jpeg_dct_method::type dct_method = jpeg_dct_method::default_value ) : image_read_settings_base( top_left diff --git a/include/boost/gil/extension/io/png/detail/read.hpp b/include/boost/gil/extension/io/png/detail/read.hpp index 06837c7bd9..089caf9fe6 100644 --- a/include/boost/gil/extension/io/png/detail/read.hpp +++ b/include/boost/gil/extension/io/png/detail/read.hpp @@ -13,10 +13,10 @@ #include #include // FIXME: Include what you use! +#include #include #include #include -#include #include #include #include @@ -416,7 +416,7 @@ class dynamic_image_reader< Device , this->_info._color_type ); - if( !construct_matched( images + if( !detail::construct_matched( images , format_checker )) { @@ -432,8 +432,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + , view( images ) ); } } diff --git a/include/boost/gil/extension/io/png/detail/reader_backend.hpp b/include/boost/gil/extension/io/png/detail/reader_backend.hpp index aaf5843ef0..3f49070f3b 100644 --- a/include/boost/gil/extension/io/png/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/png/detail/reader_backend.hpp @@ -601,7 +601,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/png/detail/write.hpp b/include/boost/gil/extension/io/png/detail/write.hpp index eee5b9ed1c..5dcb6b698b 100644 --- a/include/boost/gil/extension/io/png/detail/write.hpp +++ b/include/boost/gil/extension/io/png/detail/write.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -171,10 +171,24 @@ class writer< Device {}; template - struct is_equal_to_sixteen : mp11::mp_less + struct is_equal_to_sixteen : mp11::mp_and < - std::integral_constant, - std::integral_constant + mp11::mp_not + < + mp11::mp_less + < + std::integral_constant, + std::integral_constant + > + >, + mp11::mp_not + < + mp11::mp_less + < + std::integral_constant, + std::integral_constant + > + > > {}; @@ -234,7 +248,7 @@ class dynamic_image_writer< Device , parent_t > op( this ); - apply_operation( views, op ); + variant2::visit( op, views ); } }; diff --git a/include/boost/gil/extension/io/png/tags.hpp b/include/boost/gil/extension/io/png/tags.hpp index d2fa4ddeda..77ad0366fb 100644 --- a/include/boost/gil/extension/io/png/tags.hpp +++ b/include/boost/gil/extension/io/png/tags.hpp @@ -573,7 +573,7 @@ template<> struct image_read_info< png_tag > : public png_info_base { /// Default constructor. - image_read_info< png_tag >() + image_read_info() : png_info_base() {} }; @@ -669,7 +669,7 @@ struct image_read_settings< png_tag > : public image_read_settings_base , public png_read_settings_base { /// Default Constructor - image_read_settings< png_tag >() + image_read_settings() : image_read_settings_base() , png_read_settings_base() , _screen_gamma( 1.0 ) @@ -679,8 +679,8 @@ struct image_read_settings< png_tag > : public image_read_settings_base /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. /// \param gamma Screen gamma value. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim , const bool apply_screen_gamma = false , const png_gamma::type& screen_gamma = 1.0 ) @@ -708,15 +708,15 @@ struct image_read_settings< png_tag > : public image_read_settings_base , public png_read_settings_base { /// Default Constructor. - image_read_settings< png_tag >() + image_read_settings() : image_read_settings_base() , png_read_settings_base() , _apply_screen_gamma( false ) , _screen_gamma ( 2 ) {} - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim ) : image_read_settings_base( top_left , dim diff --git a/include/boost/gil/extension/io/pnm/detail/read.hpp b/include/boost/gil/extension/io/pnm/detail/read.hpp index fa925cfc30..d25866bc86 100644 --- a/include/boost/gil/extension/io/pnm/detail/read.hpp +++ b/include/boost/gil/extension/io/pnm/detail/read.hpp @@ -13,11 +13,11 @@ #include #include // FIXME: Include what you use! +#include #include #include #include #include -#include #include #include #include @@ -421,7 +421,7 @@ class dynamic_image_reader< Device { detail::pnm_type_format_checker format_checker( this->_info._type ); - if( !construct_matched( images + if( !detail::construct_matched( images , format_checker )) { @@ -437,8 +437,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + ,view( images ) ); } } diff --git a/include/boost/gil/extension/io/pnm/detail/reader_backend.hpp b/include/boost/gil/extension/io/pnm/detail/reader_backend.hpp index 907cbd012a..76cd86f262 100644 --- a/include/boost/gil/extension/io/pnm/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/pnm/detail/reader_backend.hpp @@ -82,7 +82,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/pnm/detail/write.hpp b/include/boost/gil/extension/io/pnm/detail/write.hpp index b32c042b8c..c10d1f7955 100644 --- a/include/boost/gil/extension/io/pnm/detail/write.hpp +++ b/include/boost/gil/extension/io/pnm/detail/write.hpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include @@ -233,7 +233,7 @@ class dynamic_image_writer< Device , parent_t > op( this ); - apply_operation( views, op ); + variant2::visit( op, views ); } }; diff --git a/include/boost/gil/extension/io/pnm/tags.hpp b/include/boost/gil/extension/io/pnm/tags.hpp index 8623e014fc..b2a29e72c8 100644 --- a/include/boost/gil/extension/io/pnm/tags.hpp +++ b/include/boost/gil/extension/io/pnm/tags.hpp @@ -65,15 +65,15 @@ template<> struct image_read_settings< pnm_tag > : public image_read_settings_base { /// Default constructor - image_read_settings< pnm_tag >() + image_read_settings() : image_read_settings_base() {} /// Constructor /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim ) : image_read_settings_base( top_left , dim diff --git a/include/boost/gil/extension/io/raw/detail/read.hpp b/include/boost/gil/extension/io/raw/detail/read.hpp index d0d9aefc27..c6fdb134f4 100644 --- a/include/boost/gil/extension/io/raw/detail/read.hpp +++ b/include/boost/gil/extension/io/raw/detail/read.hpp @@ -13,11 +13,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include @@ -205,7 +205,7 @@ class dynamic_image_reader< Device { detail::raw_type_format_checker format_checker( this->_info ); - if( !construct_matched( images + if( !detail::construct_matched( images , format_checker )) { @@ -224,8 +224,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + , view( images ) ); } } diff --git a/include/boost/gil/extension/io/raw/detail/reader_backend.hpp b/include/boost/gil/extension/io/raw/detail/reader_backend.hpp index 8fb45fe57a..cd2616dfdc 100644 --- a/include/boost/gil/extension/io/raw/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/raw/detail/reader_backend.hpp @@ -98,7 +98,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/raw/tags.hpp b/include/boost/gil/extension/io/raw/tags.hpp index f8e18a5040..f9365a7814 100644 --- a/include/boost/gil/extension/io/raw/tags.hpp +++ b/include/boost/gil/extension/io/raw/tags.hpp @@ -123,7 +123,7 @@ template<> struct image_read_info< raw_tag > { /// Default contructor. - image_read_info< raw_tag >() + image_read_info() : _valid( false ) {} @@ -182,8 +182,8 @@ struct image_read_settings< raw_tag > : public image_read_settings_base /// Constructor /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim ) : image_read_settings_base( top_left , dim diff --git a/include/boost/gil/extension/io/targa/detail/read.hpp b/include/boost/gil/extension/io/targa/detail/read.hpp index 40d42561c1..035d2d0b61 100644 --- a/include/boost/gil/extension/io/targa/detail/read.hpp +++ b/include/boost/gil/extension/io/targa/detail/read.hpp @@ -12,11 +12,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include @@ -367,7 +367,7 @@ class dynamic_image_reader< Device { detail::targa_type_format_checker format_checker( this->_info._bits_per_pixel ); - if( !construct_matched( images + if( !detail::construct_matched( images , format_checker )) { @@ -383,8 +383,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + ,view( images ) ); } } diff --git a/include/boost/gil/extension/io/targa/detail/reader_backend.hpp b/include/boost/gil/extension/io/targa/detail/reader_backend.hpp index 7990dc0d9f..3841a9a64c 100644 --- a/include/boost/gil/extension/io/targa/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/targa/detail/reader_backend.hpp @@ -122,7 +122,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/targa/detail/write.hpp b/include/boost/gil/extension/io/targa/detail/write.hpp index 2f2f5cc556..cff5fb337e 100644 --- a/include/boost/gil/extension/io/targa/detail/write.hpp +++ b/include/boost/gil/extension/io/targa/detail/write.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include @@ -170,7 +170,7 @@ class dynamic_image_writer< Device , parent_t > op( this ); - apply_operation( views, op ); + variant2::visit( op, views ); } }; diff --git a/include/boost/gil/extension/io/targa/tags.hpp b/include/boost/gil/extension/io/targa/tags.hpp index f56a1e20b4..faee40461a 100644 --- a/include/boost/gil/extension/io/targa/tags.hpp +++ b/include/boost/gil/extension/io/targa/tags.hpp @@ -77,7 +77,7 @@ template<> struct image_read_info< targa_tag > { /// Default contructor. - image_read_info< targa_tag >() + image_read_info() : _screen_origin_bit(false) , _valid( false ) {} @@ -143,8 +143,8 @@ struct image_read_settings< targa_tag > : public image_read_settings_base /// Constructor /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim ) : image_read_settings_base( top_left , dim diff --git a/include/boost/gil/extension/io/tiff/detail/device.hpp b/include/boost/gil/extension/io/tiff/detail/device.hpp index 26eb9cdbcf..a765a4ca60 100644 --- a/include/boost/gil/extension/io/tiff/detail/device.hpp +++ b/include/boost/gil/extension/io/tiff/detail/device.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -192,7 +193,7 @@ class tiff_device_base { io_error_if( TIFFReadScanline( _tiff_file.get() , reinterpret_cast< tdata_t >( &buffer.front() ) - , (uint32) row + , static_cast( row ) , plane ) == -1 , "Read error." ); @@ -205,7 +206,7 @@ class tiff_device_base { io_error_if( TIFFReadScanline( _tiff_file.get() , reinterpret_cast< tdata_t >( buffer ) - , (uint32) row + , static_cast( row ) , plane ) == -1 , "Read error." ); @@ -221,9 +222,9 @@ class tiff_device_base { if( TIFFReadTile( _tiff_file.get() , reinterpret_cast< tdata_t >( &buffer.front() ) - , (uint32) x - , (uint32) y - , (uint32) z + , static_cast< std::uint32_t >( x ) + , static_cast< std::uint32_t >( y ) + , static_cast< std::uint32_t >( z ) , plane ) == -1 ) { @@ -234,9 +235,9 @@ class tiff_device_base } template< typename Buffer > - void write_scaline( Buffer& buffer - , uint32 row - , tsample_t plane + void write_scaline( Buffer& buffer + , std::uint32_t row + , tsample_t plane ) { io_error_if( TIFFWriteScanline( _tiff_file.get() @@ -248,9 +249,9 @@ class tiff_device_base ); } - void write_scaline( byte_t* buffer - , uint32 row - , tsample_t plane + void write_scaline( byte_t* buffer + , std::uint32_t row + , tsample_t plane ) { io_error_if( TIFFWriteScanline( _tiff_file.get() @@ -263,11 +264,11 @@ class tiff_device_base } template< typename Buffer > - void write_tile( Buffer& buffer - , uint32 x - , uint32 y - , uint32 z - , tsample_t plane + void write_tile( Buffer& buffer + , std::uint32_t x + , std::uint32_t y + , std::uint32_t z + , tsample_t plane ) { if( TIFFWriteTile( _tiff_file.get() @@ -300,8 +301,8 @@ class tiff_device_base ) { bool result = true; - uint32 tw = static_cast< uint32 >( width ); - uint32 th = static_cast< uint32 >( height ); + std::uint32_t tw = static_cast< std::uint32_t >( width ); + std::uint32_t th = static_cast< std::uint32_t >( height ); TIFFDefaultTileSize( _tiff_file.get() , &tw diff --git a/include/boost/gil/extension/io/tiff/detail/read.hpp b/include/boost/gil/extension/io/tiff/detail/read.hpp index 9ce3a072f1..e0f40f412c 100644 --- a/include/boost/gil/extension/io/tiff/detail/read.hpp +++ b/include/boost/gil/extension/io/tiff/detail/read.hpp @@ -12,11 +12,11 @@ #include #include +#include #include #include #include #include -#include #include #include @@ -766,8 +766,8 @@ class dynamic_image_reader< Device , parent_t > op( this ); - apply_operation( view( images ) - , op + variant2::visit( op + , view( images ) ); } } diff --git a/include/boost/gil/extension/io/tiff/detail/reader_backend.hpp b/include/boost/gil/extension/io/tiff/detail/reader_backend.hpp index 6c876a0c1f..42dc363159 100644 --- a/include/boost/gil/extension/io/tiff/detail/reader_backend.hpp +++ b/include/boost/gil/extension/io/tiff/detail/reader_backend.hpp @@ -103,7 +103,7 @@ struct reader_backend< Device } /// Check if image is large enough. - void check_image_size( const point_t& img_dim ) + void check_image_size( point_t const& img_dim ) { if( _settings._dim.x > 0 ) { diff --git a/include/boost/gil/extension/io/tiff/detail/write.hpp b/include/boost/gil/extension/io/tiff/detail/write.hpp index 040a0d76f6..cb10c1e9ee 100644 --- a/include/boost/gil/extension/io/tiff/detail/write.hpp +++ b/include/boost/gil/extension/io/tiff/detail/write.hpp @@ -15,9 +15,10 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -183,7 +184,7 @@ class writer< Device this->_io_dev.write_scaline( row - , (uint32) y + , static_cast( y ) , 0 ); @@ -211,7 +212,7 @@ class writer< Device this->_io_dev.write_scaline( row - , (uint32) y + , static_cast( y ) , 0 ); @@ -273,7 +274,7 @@ class writer< Device ); this->_io_dev.write_scaline( row_addr - , (uint32) y + , static_cast( y ) , 0 ); @@ -393,8 +394,8 @@ class writer< Device } this->_io_dev.write_tile( row - , static_cast< uint32 >( j ) - , static_cast< uint32 >( i ) + , static_cast< std::uint32_t >( j ) + , static_cast< std::uint32_t >( i ) , 0 , 0 ); @@ -437,7 +438,7 @@ class dynamic_image_writer< Device , parent_t > op( this ); - apply_operation( views, op ); + variant2::visit( op, views ); } }; diff --git a/include/boost/gil/extension/io/tiff/tags.hpp b/include/boost/gil/extension/io/tiff/tags.hpp index 4bee8bb978..e4a3ebfa29 100644 --- a/include/boost/gil/extension/io/tiff/tags.hpp +++ b/include/boost/gil/extension/io/tiff/tags.hpp @@ -270,7 +270,7 @@ template<> struct image_read_settings< tiff_tag > : public image_read_settings_base { /// Default constructor - image_read_settings< tiff_tag >() + image_read_settings() : image_read_settings_base() , _directory( tiff_directory::default_value::value ) {} @@ -279,8 +279,8 @@ struct image_read_settings< tiff_tag > : public image_read_settings_base /// \param top_left Top left coordinate for reading partial image. /// \param dim Dimensions for reading partial image. /// \param directory Defines the page to read in a multipage tiff file. - image_read_settings( const point_t& top_left - , const point_t& dim + image_read_settings( point_t const& top_left + , point_t const& dim , const tiff_directory::type& directory = tiff_directory::default_value::value ) : image_read_settings_base( top_left diff --git a/include/boost/gil/extension/numeric/affine.hpp b/include/boost/gil/extension/numeric/affine.hpp index ebef93afca..204a2877bb 100644 --- a/include/boost/gil/extension/numeric/affine.hpp +++ b/include/boost/gil/extension/numeric/affine.hpp @@ -89,6 +89,85 @@ point transform(matrix3x2 const& mat, point const& src) return src * mat; } +/// Returns the inverse of the given affine transformation matrix +/// +/// \warning Floating point arithmetic, use Boost.Rational if precision maters +template +boost::gil::matrix3x2 inverse(boost::gil::matrix3x2 m) +{ + T const determinant = m.a * m.d - m.b * m.c; + + boost::gil::matrix3x2 res; + res.a = m.d / determinant; + res.b = -m.b / determinant; + res.c = -m.c / determinant; + res.d = m.a / determinant; + res.e = (m.c * m.f - m.d * m.e) / determinant; + res.f = (m.b * m.e - m.a * m.f) / determinant; + + return res; +} + +/// \fn gil::matrix3x2 center_rotate +/// \tparam T Data type for source image dimensions +/// \tparam F Data type for angle through which image is to be rotated +/// @param dims dimensions of source image +/// @param rads angle through which image is to be rotated +/// @return A transformation matrix for rotating the source image about its center +/// \brief rotates an image from its center point +/// using consecutive affine transformations. +template +boost::gil::matrix3x2 center_rotate(boost::gil::point dims,F rads) +{ + const F PI = F(3.141592653589793238); + const F c_theta = std::abs(std::cos(rads)); + const F s_theta = std::abs(std::sin(rads)); + + // Bound checks for angle rads + while(rads + PI < 0) + { + rads = rads + PI; + } + + while(rads > PI) + { + rads = rads - PI; + } + + // Basic Rotation Matrix + boost::gil::matrix3x2 rotate = boost::gil::matrix3x2::get_rotate(rads); + + // Find distance for translating the image into view + boost::gil::matrix3x2 translation(0,0,0,0,0,0); + if(rads > 0) + { + translation.b = s_theta; + } + else + { + translation.c = s_theta; + } + + if(std::abs(rads) > PI/2) + { + translation.a = c_theta; + translation.d = c_theta; + } + + // To bring the complete image into view + boost::gil::matrix3x2 translate = + boost::gil::matrix3x2::get_translate(-1 * dims * translation); + + // To fit inside the source dimensions + boost::gil::matrix3x2 scale = + boost::gil::matrix3x2::get_scale( + s_theta * dims.y / dims.x + c_theta , + s_theta * dims.x / dims.y + c_theta + ); + + return scale * translate * rotate; +} + }} // namespace boost::gil -#endif +#endif \ No newline at end of file diff --git a/include/boost/gil/extension/numeric/algorithm.hpp b/include/boost/gil/extension/numeric/algorithm.hpp index 2fbd4b0245..821263a80f 100644 --- a/include/boost/gil/extension/numeric/algorithm.hpp +++ b/include/boost/gil/extension/numeric/algorithm.hpp @@ -1,6 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated -// Copyright 2019 Pranam Lashkari +// Copyright 2019-2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -9,434 +9,8 @@ #ifndef BOOST_GIL_EXTENSION_NUMERIC_ALGORITHM_HPP #define BOOST_GIL_EXTENSION_NUMERIC_ALGORITHM_HPP -#include +#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -namespace boost { namespace gil { - -/// \brief Reference proxy associated with a type that has a \p "reference" member type alias. -/// -/// The reference proxy is the reference type, but with stripped-out C++ reference. -/// Models PixelConcept. -template -struct pixel_proxy : std::remove_reference {}; - -/// \brief std::for_each for a pair of iterators -template -BinaryFunction for_each(Iterator1 first1, Iterator1 last1, Iterator2 first2, BinaryFunction f) -{ - while (first1 != last1) - f(*first1++, *first2++); - return f; -} - -template -inline -auto assign_pixels(SrcIterator src, SrcIterator src_end, DstIterator dst) -> DstIterator -{ - for_each(src, src_end, dst, - pixel_assigns_t - < - typename pixel_proxy::value_type>::type, - typename pixel_proxy::value_type>::type - >()); - return dst + (src_end - src); -} - -namespace detail { - -template -struct inner_product_k_t -{ - template - < - class InputIterator1, - class InputIterator2, - class T, - class BinaryOperation1, - class BinaryOperation2 - > - static T apply( - InputIterator1 first1, - InputIterator2 first2, T init, - BinaryOperation1 binary_op1, - BinaryOperation2 binary_op2) - { - init = binary_op1(init, binary_op2(*first1, *first2)); - return inner_product_k_t::template apply( - first1 + 1, first2 + 1, init, binary_op1, binary_op2); - } -}; - -template <> -struct inner_product_k_t<0> -{ - template - < - class InputIterator1, - class InputIterator2, - class T, - class BinaryOperation1, - class BinaryOperation2 - > - static T apply( - InputIterator1 first1, - InputIterator2 first2, - T init, - BinaryOperation1 binary_op1, - BinaryOperation2 binary_op2) - { - return init; - } -}; - -} // namespace detail - -/// static version of std::inner_product -template -< - std::size_t Size, - class InputIterator1, - class InputIterator2, - class T, - class BinaryOperation1, - class BinaryOperation2 -> -BOOST_FORCEINLINE -T inner_product_k( - InputIterator1 first1, - InputIterator2 first2, - T init, - BinaryOperation1 binary_op1, - BinaryOperation2 binary_op2) -{ - return detail::inner_product_k_t::template apply( - first1, first2, init, binary_op1, binary_op2); -} - -/// \brief 1D un-guarded cross-correlation with a variable-size kernel -template -< - typename PixelAccum, - typename SrcIterator, - typename KernelIterator, - typename Size, - typename DstIterator -> -inline -auto correlate_pixels_n( - SrcIterator src_begin, - SrcIterator src_end, - KernelIterator kernel_begin, - Size kernel_size, - DstIterator dst_begin) - -> DstIterator -{ - using src_pixel_ref_t = typename pixel_proxy - < - typename std::iterator_traits::value_type - >::type; - using dst_pixel_ref_t = typename pixel_proxy - < - typename std::iterator_traits::value_type - >::type; - using kernel_value_t = typename std::iterator_traits::value_type; - - PixelAccum accum_zero; - pixel_zeros_t()(accum_zero); - while (src_begin != src_end) - { - pixel_assigns_t()( - std::inner_product( - src_begin, - src_begin + kernel_size, - kernel_begin, - accum_zero, - pixel_plus_t(), - pixel_multiplies_scalar_t()), - *dst_begin); - - ++src_begin; - ++dst_begin; - } - return dst_begin; -} - -/// \brief 1D un-guarded cross-correlation with a fixed-size kernel -template -< - std::size_t Size, - typename PixelAccum, - typename SrcIterator, - typename KernelIterator, - typename DstIterator -> -inline -auto correlate_pixels_k( - SrcIterator src_begin, - SrcIterator src_end, - KernelIterator kernel_begin, - DstIterator dst_begin) - -> DstIterator -{ - using src_pixel_ref_t = typename pixel_proxy - < - typename std::iterator_traits::value_type - >::type; - using dst_pixel_ref_t = typename pixel_proxy - < - typename std::iterator_traits::value_type - >::type; - using kernel_type = typename std::iterator_traits::value_type; - - PixelAccum accum_zero; - pixel_zeros_t()(accum_zero); - while (src_begin != src_end) - { - pixel_assigns_t()( - inner_product_k( - src_begin, - kernel_begin, - accum_zero, - pixel_plus_t(), - pixel_multiplies_scalar_t()), - *dst_begin); - - ++src_begin; - ++dst_begin; - } - return dst_begin; -} - -/// \brief destination is set to be product of the source and a scalar -/// \tparam PixelAccum - TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam DstView Models MutableImageViewConcept -template -inline -void view_multiplies_scalar(SrcView const& src_view, Scalar const& scalar, DstView const& dst_view) -{ - static_assert(std::is_scalar::value, "Scalar is not scalar"); - BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); - using src_pixel_ref_t = typename pixel_proxy::type; - using dst_pixel_ref_t = typename pixel_proxy::type; - using y_coord_t = typename SrcView::y_coord_t; - - y_coord_t const height = src_view.height(); - for (y_coord_t y = 0; y < height; ++y) - { - typename SrcView::x_iterator it_src = src_view.row_begin(y); - typename DstView::x_iterator it_dst = dst_view.row_begin(y); - typename SrcView::x_iterator it_src_end = src_view.row_end(y); - while (it_src != it_src_end) - { - pixel_assigns_t()( - pixel_multiplies_scalar_t()(*it_src, scalar), - *it_dst); - - ++it_src; - ++it_dst; - } - } -} - - -/// \ingroup ImageAlgorithms -/// \brief Boundary options for image boundary extension -enum class boundary_option -{ - output_ignore, /// do nothing to the output - output_zero, /// set the output to zero - extend_padded, /// assume the source boundaries to be padded already - extend_zero, /// assume the source boundaries to be zero - extend_constant /// assume the source boundaries to be the boundary value -}; - -namespace detail -{ - -template -void extend_row_impl( - SrcView const& src_view, - RltView result_view, - std::size_t extend_count, - boundary_option option) -{ - std::ptrdiff_t extend_count_ = static_cast(extend_count); - - if (option == boundary_option::extend_constant) - { - for (std::ptrdiff_t i = 0; i < result_view.height(); i++) - { - if(i >= extend_count_ && i < extend_count_ + src_view.height()) - { - assign_pixels( - src_view.row_begin(i - extend_count_), - src_view.row_end(i - extend_count_), - result_view.row_begin(i) - ); - } - else if(i < extend_count_) - { - assign_pixels(src_view.row_begin(0), src_view.row_end(0), result_view.row_begin(i)); - } - else - { - assign_pixels( - src_view.row_begin(src_view.height() - 1), - src_view.row_end(src_view.height() - 1), - result_view.row_begin(i) - ); - } - - } - } - else if (option == boundary_option::extend_zero) - { - typename SrcView::value_type acc_zero; - pixel_zeros_t()(acc_zero); - - for (std::ptrdiff_t i = 0; i < result_view.height(); i++) - { - if (i >= extend_count_ && i < extend_count_ + src_view.height()) - { - assign_pixels( - src_view.row_begin(i - extend_count_), - src_view.row_end(i - extend_count_), - result_view.row_begin(i) - ); - } - else - { - std::fill_n(result_view.row_begin(i), result_view.width(), acc_zero); - } - } - } - else if (option == boundary_option::extend_padded) - { - auto original_view = subimage_view( - src_view, - 0, - -extend_count, - src_view.width(), - src_view.height() + (2 * extend_count) - ); - for (std::ptrdiff_t i = 0; i < result_view.height(); i++) - { - assign_pixels( - original_view.row_begin(i), - original_view.row_end(i), - result_view.row_begin(i) - ); - } - } - else - { - BOOST_ASSERT_MSG(false, "Invalid boundary option"); - } -} - -} //namespace detail - - -/// \brief adds new row at top and bottom. -/// Image padding introduces new pixels around the edges of an image. -/// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. -/// \tparam SrcView Models ImageViewConcept -/// \tparam extend_count number of rows to be added each side -/// \tparam option - TODO -template -auto extend_row( - SrcView const& src_view, - std::size_t extend_count, - boundary_option option -) -> typename gil::image -{ - typename gil::image - result_img(src_view.width(), src_view.height() + (2 * extend_count)); - - auto result_view = view(result_img); - detail::extend_row_impl(src_view, result_view, extend_count, option); - return result_img; -} - - -/// \brief adds new column at left and right. -/// Image padding introduces new pixels around the edges of an image. -/// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. -/// \tparam SrcView Models ImageViewConcept -/// \tparam extend_count number of columns to be added each side -/// \tparam option - TODO -template -auto extend_col( - SrcView const& src_view, - std::size_t extend_count, - boundary_option option -) -> typename gil::image -{ - auto src_view_rotate = rotated90cw_view(src_view); - - typename gil::image - result_img(src_view.width() + (2 * extend_count), src_view.height()); - - auto result_view = rotated90cw_view(view(result_img)); - detail::extend_row_impl(src_view_rotate, result_view, extend_count, option); - return result_img; -} - -/// \brief adds new row and column at all sides. -/// Image padding introduces new pixels around the edges of an image. -/// The border provides space for annotations or acts as a boundary when using advanced filtering techniques. -/// \tparam SrcView Models ImageViewConcept -/// \tparam extend_count number of rows/column to be added each side -/// \tparam option - TODO -template -auto extend_boundary( - SrcView const& src_view, - std::size_t extend_count, - boundary_option option -) -> typename gil::image -{ - if (option == boundary_option::extend_padded) - { - typename gil::image - result_img(src_view.width()+(2 * extend_count), src_view.height()+(2 * extend_count)); - typename gil::image::view_t result_view = view(result_img); - - auto original_view = subimage_view( - src_view, - -extend_count, - -extend_count, - src_view.width() + (2 * extend_count), - src_view.height() + (2 * extend_count) - ); - - for (std::ptrdiff_t i = 0; i < result_view.height(); i++) - { - assign_pixels( - original_view.row_begin(i), - original_view.row_end(i), - result_view.row_begin(i) - ); - } - - return result_img; - } - - auto auxilary_img = extend_col(src_view, extend_count, option); - return extend_row(view(auxilary_img), extend_count, option); -} - -}} // namespace boost::gil +BOOST_HEADER_DEPRECATED("") #endif diff --git a/include/boost/gil/extension/numeric/channel_numeric_operations.hpp b/include/boost/gil/extension/numeric/channel_numeric_operations.hpp index 264ffdd782..6cea3adfeb 100644 --- a/include/boost/gil/extension/numeric/channel_numeric_operations.hpp +++ b/include/boost/gil/extension/numeric/channel_numeric_operations.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -8,245 +9,8 @@ #ifndef BOOST_GIL_EXTENSION_NUMERIC_CHANNEL_NUMERIC_OPERATIONS_HPP #define BOOST_GIL_EXTENSION_NUMERIC_CHANNEL_NUMERIC_OPERATIONS_HPP -#include +#include -namespace boost { namespace gil { - -// Function objects and utilities for channel-wise numeric operations. -// -// List of currently defined functors: -// channel_plus_t (+) -// channel_minus_t (-) -// channel_multiplies_t (*) -// channel_divides_t (/), -// channel_plus_scalar_t (+s) -// channel_minus_scalar_t (-s), -// channel_multiplies_scalar_t (*s) -// channel_divides_scalar_t (/s), -// channel_halves_t (/=2) -// channel_zeros_t (=0) -// channel_assigns_t (=) - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of addition of two channel values. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_plus_t -{ - using ChannelRef1 = typename channel_traits::const_reference; - using ChannelRef2 = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef1 not convertible to ChannelResult"); - static_assert(std::is_convertible::value, - "ChannelRef2 not convertible to ChannelResult"); - - /// \param ch1 - first of the two addends (augend). - /// \param ch2 - second of the two addends. - auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult - { - return ChannelResult(ch1) + ChannelResult(ch2); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of subtraction of two channel values. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_minus_t -{ - using ChannelRef1 = typename channel_traits::const_reference; - using ChannelRef2 = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef1 not convertible to ChannelResult"); - static_assert(std::is_convertible::value, - "ChannelRef2 not convertible to ChannelResult"); - - /// \param ch1 - minuend operand of the subtraction. - /// \param ch2 - subtrahend operand of the subtraction. - auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult - { - return ChannelResult(ch1) - ChannelResult(ch2); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of multiplication of two channel values. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_multiplies_t -{ - using ChannelRef1 = typename channel_traits::const_reference; - using ChannelRef2 = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef1 not convertible to ChannelResult"); - static_assert(std::is_convertible::value, - "ChannelRef2 not convertible to ChannelResult"); - - /// \param ch1 - first of the two factors (multiplicand). - /// \param ch2 - second of the two factors (multiplier). - auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult - { - return ChannelResult(ch1) * ChannelResult(ch2); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of division of two channel values. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_divides_t -{ - using ChannelRef1 = typename channel_traits::const_reference; - using ChannelRef2 = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef1 not convertible to ChannelResult"); - static_assert(std::is_convertible::value, - "ChannelRef2 not convertible to ChannelResult"); - - /// \param ch1 - dividend operand of the two division operation. - /// \param ch2 - divisor operand of the two division operation. - auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelResult - { - return ChannelResult(ch1) / ChannelResult(ch2); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of adding scalar to channel value. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_plus_scalar_t -{ - using ChannelRef = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef not convertible to ChannelResult"); - static_assert(std::is_scalar::value, "Scalar not a scalar"); - static_assert(std::is_convertible::value, - "Scalar not convertible to ChannelResult"); - - auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult - { - return ChannelResult(channel) + ChannelResult(scalar); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of subtracting scalar from channel value. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_minus_scalar_t -{ - using ChannelRef = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef not convertible to ChannelResult"); - static_assert(std::is_scalar::value, "Scalar not a scalar"); - static_assert(std::is_convertible::value, - "Scalar not convertible to ChannelResult"); - - /// \param channel - minuend operand of the subtraction. - /// \param scalar - subtrahend operand of the subtraction. - auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult - { - // TODO: Convertion after subtraction vs conversion of operands in channel_minus_t? - return ChannelResult(channel - scalar); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of channel value by a scalar. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_multiplies_scalar_t -{ - using ChannelRef = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef not convertible to ChannelResult"); - static_assert(std::is_scalar::value, "Scalar not a scalar"); - static_assert(std::is_convertible::value, - "Scalar not convertible to ChannelResult"); - - /// \param channel - first of the two factors (multiplicand). - /// \param scalar - second of the two factors (multiplier). - auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult - { - return ChannelResult(channel) * ChannelResult(scalar); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of dividing channel value by scalar. -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_divides_scalar_t -{ - using ChannelRef = typename channel_traits::const_reference; - static_assert(std::is_convertible::value, - "ChannelRef not convertible to ChannelResult"); - static_assert(std::is_scalar::value, "Scalar not a scalar"); - static_assert(std::is_convertible::value, - "Scalar not convertible to ChannelResult"); - - /// \param channel - dividend operand of the two division operation. - /// \param scalar - divisor operand of the two division operation. - auto operator()(ChannelRef channel, Scalar const& scalar) const -> ChannelResult - { - return ChannelResult(channel) / ChannelResult(scalar); - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Arithmetic operation of dividing channel value by 2 -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_halves_t -{ - using ChannelRef = typename channel_traits::reference; - - auto operator()(ChannelRef channel) const -> ChannelRef - { - // TODO: Split into steps: extract with explicit conversion to double, divide and assign? - //double const v = ch; - //ch = static_cast(v / 2.0); - channel /= 2.0; - return channel; - } -}; - -/// \ingroup ChannelNumericOperations -/// \brief Operation of setting channel value to zero -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_zeros_t -{ - using ChannelRef = typename channel_traits::reference; - - auto operator()(ChannelRef channel) const -> ChannelRef - { - channel = Channel(0); - return channel; - } -}; - -/// \ingroup ChannelNumericOperations -/// structure for assigning one channel to another -/// \note This is a generic implementation; user should specialize it for better performance. -template -struct channel_assigns_t -{ - using ChannelRef1 = typename channel_traits::const_reference; - using ChannelRef2 = typename channel_traits::reference; - static_assert(std::is_convertible::value, - "ChannelRef1 not convertible to Channel2"); - - /// \param ch1 - assignor side (input) of the assignment operation - /// \param ch2 - assignee side (output) of the assignment operation - auto operator()(ChannelRef1 ch1, ChannelRef2 ch2) const -> ChannelRef2 - { - ch2 = Channel2(ch1); - return ch2; - } -}; - -}} // namespace boost::gil +BOOST_HEADER_DEPRECATED("") #endif diff --git a/include/boost/gil/extension/numeric/convolve.hpp b/include/boost/gil/extension/numeric/convolve.hpp index a401b0028e..ba8a6f08fd 100644 --- a/include/boost/gil/extension/numeric/convolve.hpp +++ b/include/boost/gil/extension/numeric/convolve.hpp @@ -1,6 +1,7 @@ // // Copyright 2005-2007 Adobe Systems Incorporated // Copyright 2019 Miral Shah +// Copyright 2019-2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -9,411 +10,8 @@ #ifndef BOOST_GIL_EXTENSION_NUMERIC_CONVOLVE_HPP #define BOOST_GIL_EXTENSION_NUMERIC_CONVOLVE_HPP -#include -#include -#include +#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace boost { namespace gil { - -// 2D spatial seperable convolutions and cross-correlations - -namespace detail { - -/// \brief Compute the cross-correlation of 1D kernel with the rows of an image -/// \tparam PixelAccum - TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel - TODO -/// \tparam DstView Models MutableImageViewConcept -/// \tparam Correlator - TODO -/// \param src_view -/// \param kernel - TODO -/// \param dst_view Destination where new computed values of pixels are assigned to -/// \param option - TODO -/// \param correlator - TODO -template -< - typename PixelAccum, - typename SrcView, - typename Kernel, - typename DstView, - typename Correlator -> -void correlate_rows_impl( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option, - Correlator correlator) -{ - BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); - BOOST_ASSERT(kernel.size() != 0); - - if(kernel.size() == 1) - { - // Reduces to a multiplication - view_multiplies_scalar(src_view, *kernel.begin(), dst_view); - return; - } - - using src_pixel_ref_t = typename pixel_proxy::type; - using dst_pixel_ref_t = typename pixel_proxy::type; - using x_coord_t = typename SrcView::x_coord_t; - using y_coord_t = typename SrcView::y_coord_t; - - x_coord_t const width = src_view.width(); - y_coord_t const height = src_view.height(); - if (width == 0) - return; - - PixelAccum acc_zero; - pixel_zeros_t()(acc_zero); - if (option == boundary_option::output_ignore || option == boundary_option::output_zero) - { - typename DstView::value_type dst_zero; - pixel_assigns_t()(acc_zero, dst_zero); - if (width < static_cast(kernel.size())) - { - if (option == boundary_option::output_zero) - fill_pixels(dst_view, dst_zero); - } - else - { - std::vector buffer(width); - for (y_coord_t y = 0; y < height; ++y) - { - assign_pixels(src_view.row_begin(y), src_view.row_end(y), &buffer.front()); - typename DstView::x_iterator it_dst = dst_view.row_begin(y); - if (option == boundary_option::output_zero) - std::fill_n(it_dst, kernel.left_size(), dst_zero); - it_dst += kernel.left_size(); - correlator(&buffer.front(), &buffer.front() + width + 1 - kernel.size(), - kernel.begin(), it_dst); - it_dst += width + 1 - kernel.size(); - if (option == boundary_option::output_zero) - std::fill_n(it_dst, kernel.right_size(), dst_zero); - } - } - } - else - { - std::vector buffer(width + kernel.size() - 1); - for (y_coord_t y = 0; y < height; ++y) - { - PixelAccum *it_buffer = &buffer.front(); - if (option == boundary_option::extend_padded) - { - assign_pixels( - src_view.row_begin(y) - kernel.left_size(), - src_view.row_end(y) + kernel.right_size(), - it_buffer); - } - else if (option == boundary_option::extend_zero) - { - std::fill_n(it_buffer, kernel.left_size(), acc_zero); - it_buffer += kernel.left_size(); - assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer); - it_buffer += width; - std::fill_n(it_buffer, kernel.right_size(), acc_zero); - } - else if (option == boundary_option::extend_constant) - { - PixelAccum filler; - pixel_assigns_t()(*src_view.row_begin(y), filler); - std::fill_n(it_buffer, kernel.left_size(), filler); - it_buffer += kernel.left_size(); - assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer); - it_buffer += width; - pixel_assigns_t()(src_view.row_end(y)[-1], filler); - std::fill_n(it_buffer, kernel.right_size(), filler); - } - - correlator( - &buffer.front(), &buffer.front() + width, - kernel.begin(), - dst_view.row_begin(y)); - } - } -} - -template -class correlator_n -{ -public: - correlator_n(std::size_t size) : size_(size) {} - - template - void operator()( - SrcIterator src_begin, - SrcIterator src_end, - KernelIterator kernel_begin, - DstIterator dst_begin) - { - correlate_pixels_n(src_begin, src_end, kernel_begin, size_, dst_begin); - } - -private: - std::size_t size_{0}; -}; - -template -struct correlator_k -{ - template - void operator()( - SrcIterator src_begin, - SrcIterator src_end, - KernelIterator kernel_begin, - DstIterator dst_begin) - { - correlate_pixels_k(src_begin, src_end, kernel_begin, dst_begin); - } -}; - -} // namespace detail - -/// \ingroup ImageAlgorithms -/// \brief Correlate 1D variable-size kernel along the rows of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void correlate_rows( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - detail::correlate_rows_impl( - src_view, kernel, dst_view, option, detail::correlator_n(kernel.size())); -} - -/// \ingroup ImageAlgorithms -/// \brief Correlate 1D variable-size kernel along the columns of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void correlate_cols( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - correlate_rows( - transposed_view(src_view), kernel, transposed_view(dst_view), option); -} - -/// \ingroup ImageAlgorithms -/// \brief Convolve 1D variable-size kernel along the rows of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void convolve_rows( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - correlate_rows(src_view, reverse_kernel(kernel), dst_view, option); -} - -/// \ingroup ImageAlgorithms -/// \brief Convolve 1D variable-size kernel along the columns of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void convolve_cols( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - convolve_rows( - transposed_view(src_view), kernel, transposed_view(dst_view), option); -} - -/// \ingroup ImageAlgorithms -/// \brief Correlate 1D fixed-size kernel along the rows of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void correlate_rows_fixed( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - using correlator = detail::correlator_k; - detail::correlate_rows_impl(src_view, kernel, dst_view, option, correlator{}); -} - -/// \ingroup ImageAlgorithms -/// \brief Correlate 1D fixed-size kernel along the columns of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void correlate_cols_fixed( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - correlate_rows_fixed( - transposed_view(src_view), kernel, transposed_view(dst_view), option); -} - -/// \ingroup ImageAlgorithms -/// \brief Convolve 1D fixed-size kernel along the rows of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void convolve_rows_fixed( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - correlate_rows_fixed(src_view, reverse_kernel(kernel), dst_view, option); -} - -/// \ingroup ImageAlgorithms -/// \brief Convolve 1D fixed-size kernel along the columns of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void convolve_cols_fixed( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - convolve_rows_fixed( - transposed_view(src_view), kernel, transposed_view(dst_view), option); -} - -namespace detail -{ - -/// \ingroup ImageAlgorithms -/// \brief Convolve 1D variable-size kernel along both rows and columns of image -/// \tparam PixelAccum TODO -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -BOOST_FORCEINLINE -void convolve_1d( - SrcView const& src_view, - Kernel const& kernel, - DstView const& dst_view, - boundary_option option = boundary_option::extend_zero) -{ - convolve_rows(src_view, kernel, dst_view, option); - convolve_cols(dst_view, kernel, dst_view, option); -} - -template -void convolve_2d_impl(SrcView const& src_view, DstView const& dst_view, Kernel const& kernel) -{ - int flip_ker_row, flip_ker_col, row_boundary, col_boundary; - float aux_total; - for (std::ptrdiff_t view_row = 0; view_row < src_view.height(); ++view_row) - { - for (std::ptrdiff_t view_col = 0; view_col < src_view.width(); ++view_col) - { - aux_total = 0.0f; - for (std::size_t kernel_row = 0; kernel_row < kernel.size(); ++kernel_row) - { - flip_ker_row = kernel.size() - 1 - kernel_row; // row index of flipped kernel - - for (std::size_t kernel_col = 0; kernel_col < kernel.size(); ++kernel_col) - { - flip_ker_col = kernel.size() - 1 - kernel_col; // column index of flipped kernel - - // index of input signal, used for checking boundary - row_boundary = view_row + (kernel.center_y() - flip_ker_row); - col_boundary = view_col + (kernel.center_x() - flip_ker_col); - - // ignore input samples which are out of bound - if (row_boundary >= 0 && row_boundary < src_view.height() && - col_boundary >= 0 && col_boundary < src_view.width()) - { - aux_total += - src_view(col_boundary, row_boundary) * - kernel.at(flip_ker_row, flip_ker_col); - } - } - } - dst_view(view_col, view_row) = aux_total; - } - } -} - -/// \ingroup ImageAlgorithms -/// \brief convolve_2d can only use convolve_option_extend_zero as convolve_boundary_option -/// this is the default option and cannot be changed for now -/// (In future there are plans to improve the algorithm and allow user to use other options as well) -/// \tparam SrcView Models ImageViewConcept -/// \tparam Kernel TODO -/// \tparam DstView Models MutableImageViewConcept -template -void convolve_2d(SrcView const& src_view, Kernel const& kernel, DstView const& dst_view) -{ - BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); - BOOST_ASSERT(kernel.size() != 0); - - gil_function_requires>(); - gil_function_requires>(); - static_assert(color_spaces_are_compatible - < - typename color_space_type::type, - typename color_space_type::type - >::value, "Source and destination views must have pixels with the same color space"); - - for (std::size_t i = 0; i < src_view.num_channels(); i++) - { - detail::convolve_2d_impl( - nth_channel_view(src_view, i), - nth_channel_view(dst_view, i), - kernel - ); - } -} - -}}} // namespace boost::gil::detail +BOOST_HEADER_DEPRECATED("") #endif diff --git a/include/boost/gil/extension/numeric/kernel.hpp b/include/boost/gil/extension/numeric/kernel.hpp index 5fc6419f0b..1f0299baae 100644 --- a/include/boost/gil/extension/numeric/kernel.hpp +++ b/include/boost/gil/extension/numeric/kernel.hpp @@ -1,6 +1,7 @@ // // Copyright 2005-2007 Adobe Systems Incorporated // Copyright 2019 Miral Shah +// Copyright 2022 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -10,352 +11,8 @@ #ifndef BOOST_GIL_EXTENSION_NUMERIC_KERNEL_HPP #define BOOST_GIL_EXTENSION_NUMERIC_KERNEL_HPP -#include -#include +#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace boost { namespace gil { - -// Definitions of 1D fixed-size and variable-size kernels and related operations - -namespace detail { - -/// \brief kernel adaptor for one-dimensional cores -/// Core needs to provide size(),begin(),end(),operator[], -/// value_type,iterator,const_iterator,reference,const_reference -template -class kernel_1d_adaptor : public Core -{ -public: - kernel_1d_adaptor() = default; - - explicit kernel_1d_adaptor(std::size_t center) - : center_(center) - { - BOOST_ASSERT(center_ < this->size()); - } - - kernel_1d_adaptor(std::size_t size, std::size_t center) - : Core(size) , center_(center) - { - BOOST_ASSERT(this->size() > 0); - BOOST_ASSERT(center_ < this->size()); // also implies `size() > 0` - } - - kernel_1d_adaptor(kernel_1d_adaptor const& other) - : Core(other), center_(other.center_) - { - BOOST_ASSERT(this->size() > 0); - BOOST_ASSERT(center_ < this->size()); // also implies `size() > 0` - } - - kernel_1d_adaptor& operator=(kernel_1d_adaptor const& other) - { - Core::operator=(other); - center_ = other.center_; - return *this; - } - - std::size_t left_size() const - { - BOOST_ASSERT(center_ < this->size()); - return center_; - } - - std::size_t right_size() const - { - BOOST_ASSERT(center_ < this->size()); - return this->size() - center_ - 1; - } - - auto center() -> std::size_t& - { - BOOST_ASSERT(center_ < this->size()); - return center_; - } - - auto center() const -> std::size_t const& - { - BOOST_ASSERT(center_ < this->size()); - return center_; - } - -private: - std::size_t center_{0}; -}; - -} // namespace detail - -/// \brief variable-size kernel -template > -class kernel_1d : public detail::kernel_1d_adaptor> -{ - using parent_t = detail::kernel_1d_adaptor>; -public: - - kernel_1d() = default; - kernel_1d(std::size_t size, std::size_t center) : parent_t(size, center) {} - - template - kernel_1d(FwdIterator elements, std::size_t size, std::size_t center) - : parent_t(size, center) - { - detail::copy_n(elements, size, this->begin()); - } - - kernel_1d(kernel_1d const& other) : parent_t(other) {} - kernel_1d& operator=(kernel_1d const& other) = default; -}; - -/// \brief static-size kernel -template -class kernel_1d_fixed : public detail::kernel_1d_adaptor> -{ - using parent_t = detail::kernel_1d_adaptor>; -public: - static constexpr std::size_t static_size = Size; - static_assert(static_size > 0, "kernel must have size greater than 0"); - static_assert(static_size % 2 == 1, "kernel size must be odd to ensure validity at the center"); - - kernel_1d_fixed() = default; - explicit kernel_1d_fixed(std::size_t center) : parent_t(center) {} - - template - explicit kernel_1d_fixed(FwdIterator elements, std::size_t center) - : parent_t(center) - { - detail::copy_n(elements, Size, this->begin()); - } - - kernel_1d_fixed(kernel_1d_fixed const& other) : parent_t(other) {} - kernel_1d_fixed& operator=(kernel_1d_fixed const& other) = default; -}; - -// TODO: This data member is odr-used and definition at namespace scope -// is required by C++11. Redundant and deprecated in C++17. -template -constexpr std::size_t kernel_1d_fixed::static_size; - -/// \brief reverse a kernel -template -inline Kernel reverse_kernel(Kernel const& kernel) -{ - Kernel result(kernel); - result.center() = kernel.right_size(); - std::reverse(result.begin(), result.end()); - return result; -} - - -namespace detail { - -template -class kernel_2d_adaptor : public Core -{ -public: - kernel_2d_adaptor() = default; - - explicit kernel_2d_adaptor(std::size_t center_y, std::size_t center_x) - : center_(center_x, center_y) - { - BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); - } - - kernel_2d_adaptor(std::size_t size, std::size_t center_y, std::size_t center_x) - : Core(size * size), square_size(size), center_(center_x, center_y) - { - BOOST_ASSERT(this->size() > 0); - BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); // implies `size() > 0` - } - - kernel_2d_adaptor(kernel_2d_adaptor const& other) - : Core(other), square_size(other.square_size), center_(other.center_.x, other.center_.y) - { - BOOST_ASSERT(this->size() > 0); - BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); // implies `size() > 0` - } - - kernel_2d_adaptor& operator=(kernel_2d_adaptor const& other) - { - Core::operator=(other); - center_.y = other.center_.y; - center_.x = other.center_.x; - square_size = other.square_size; - return *this; - } - - std::size_t upper_size() const - { - BOOST_ASSERT(center_.y < this->size()); - return center_.y; - } - - std::size_t lower_size() const - { - BOOST_ASSERT(center_.y < this->size()); - return this->size() - center_.y - 1; - } - - std::size_t left_size() const - { - BOOST_ASSERT(center_.x < this->size()); - return center_.x; - } - - std::size_t right_size() const - { - BOOST_ASSERT(center_.x < this->size()); - return this->size() - center_.x - 1; - } - - auto center_y() -> std::size_t& - { - BOOST_ASSERT(center_.y < this->size()); - return center_.y; - } - - auto center_y() const -> std::size_t const& - { - BOOST_ASSERT(center_.y < this->size()); - return center_.y; - } - - auto center_x() -> std::size_t& - { - BOOST_ASSERT(center_.x < this->size()); - return center_.x; - } - - auto center_x() const -> std::size_t const& - { - BOOST_ASSERT(center_.x < this->size()); - return center_.x; - } - - std::size_t size() const - { - return square_size; - } - - typename Core::value_type at(std::size_t x, std::size_t y) const - { - if (x >= this->size() || y >= this->size()) - { - throw std::out_of_range("Index out of range"); - } - return this->begin()[y * this->size() + x]; - } - -protected: - std::size_t square_size{0}; - -private: - point center_{0, 0}; -}; - -/// \brief variable-size kernel -template -< - typename T, - typename Allocator = std::allocator -> -class kernel_2d : public detail::kernel_2d_adaptor> -{ - using parent_t = detail::kernel_2d_adaptor>; - -public: - - kernel_2d() = default; - kernel_2d(std::size_t size,std::size_t center_y, std::size_t center_x) - : parent_t(size, center_y, center_x) - {} - - template - kernel_2d(FwdIterator elements, std::size_t size, std::size_t center_y, std::size_t center_x) - : parent_t(static_cast(std::sqrt(size)), center_y, center_x) - { - detail::copy_n(elements, size, this->begin()); - } - - kernel_2d(kernel_2d const& other) : parent_t(other) {} - kernel_2d& operator=(kernel_2d const& other) = default; -}; - -/// \brief static-size kernel -template -class kernel_2d_fixed : - public detail::kernel_2d_adaptor> -{ - using parent_t = detail::kernel_2d_adaptor>; -public: - static constexpr std::size_t static_size = Size; - static_assert(static_size > 0, "kernel must have size greater than 0"); - static_assert(static_size % 2 == 1, "kernel size must be odd to ensure validity at the center"); - - kernel_2d_fixed() - { - this->square_size = Size; - } - - explicit kernel_2d_fixed(std::size_t center_y, std::size_t center_x) : - parent_t(center_y, center_x) - { - this->square_size = Size; - } - - template - explicit kernel_2d_fixed(FwdIterator elements, std::size_t center_y, std::size_t center_x) - : parent_t(center_y, center_x) - { - this->square_size = Size; - detail::copy_n(elements, Size * Size, this->begin()); - } - - kernel_2d_fixed(kernel_2d_fixed const& other) : parent_t(other) {} - kernel_2d_fixed& operator=(kernel_2d_fixed const& other) = default; -}; - -// TODO: This data member is odr-used and definition at namespace scope -// is required by C++11. Redundant and deprecated in C++17. -template -constexpr std::size_t kernel_2d_fixed::static_size; - -template -inline Kernel reverse_kernel_2d(Kernel const& kernel) -{ - Kernel result(kernel); - result.center_x() = kernel.lower_size(); - result.center_y() = kernel.right_size(); - std::reverse(result.begin(), result.end()); - return result; -} - - -/// \brief reverse a kernel_2d -template -inline kernel_2d reverse_kernel(kernel_2d const& kernel) -{ - return reverse_kernel_2d(kernel); -} - -/// \brief reverse a kernel_2d -template -inline kernel_2d_fixed reverse_kernel(kernel_2d_fixed const& kernel) -{ - return reverse_kernel_2d(kernel); -} - -} //namespace detail - -}} // namespace boost::gil +BOOST_HEADER_DEPRECATED("") #endif diff --git a/include/boost/gil/extension/numeric/pixel_numeric_operations.hpp b/include/boost/gil/extension/numeric/pixel_numeric_operations.hpp index 24c983411b..d3a1e3d88e 100644 --- a/include/boost/gil/extension/numeric/pixel_numeric_operations.hpp +++ b/include/boost/gil/extension/numeric/pixel_numeric_operations.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -8,210 +9,8 @@ #ifndef BOOST_GIL_EXTENSION_NUMERIC_PIXEL_NUMERIC_OPERATIONS_HPP #define BOOST_GIL_EXTENSION_NUMERIC_PIXEL_NUMERIC_OPERATIONS_HPP -#include +#include -#include -#include - -namespace boost { namespace gil { - -// Function objects and utilities for pixel-wise numeric operations. -// -// List of currently defined functors: -// pixel_plus_t (+) -// pixel_minus_t (-) -// pixel_multiplies_scalar_t (*) -// pixel_divides_scalar_t (/) -// pixel_halves_t (/=2), -// pixel_zeros_t (=0) -// pixel_assigns_t (=) - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise addition of two pixels. -/// \tparam PixelRef1 - models PixelConcept -/// \tparam PixelRef2 - models PixelConcept -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_plus_t -{ - auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult - { - PixelResult result; - static_transform(p1, p2, result, - channel_plus_t - < - typename channel_type::type, - typename channel_type::type, - typename channel_type::type - >()); - return result; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise subtraction of two pixels. -/// \tparam PixelRef1 - models PixelConcept -/// \tparam PixelRef2 - models PixelConcept -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_minus_t -{ - auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult - { - PixelResult result; - static_transform(p1, p2, result, - channel_minus_t - < - typename channel_type::type, - typename channel_type::type, - typename channel_type::type - >()); - return result; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise multiplication of pixel elements by scalar. -/// \tparam PixelRef - models PixelConcept -/// \tparam Scalar - models a scalar type -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_multiplies_scalar_t -{ - auto operator()(PixelRef const& p, Scalar const& s) const -> PixelResult - { - PixelResult result; - static_transform(p, result, - std::bind( - channel_multiplies_scalar_t::type, - Scalar, - typename channel_type::type>(), - std::placeholders::_1, s)); - return result; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise multiplication of two pixels. -/// \tparam PixelRef1 - models PixelConcept -/// \tparam PixelRef1 - models PixelConcept -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_multiply_t -{ - auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult - { - PixelResult result; - static_transform(p1, p2, result, - channel_multiplies_t - < - typename channel_type::type, - typename channel_type::type, - typename channel_type::type - >()); - return result; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise division of pixel elements by scalar. -/// \tparam PixelRef - models PixelConcept -/// \tparam Scalar - models a scalar type -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_divides_scalar_t -{ - auto operator()(PixelRef const& p, Scalar const& s) const -> PixelResult - { - PixelResult result; - static_transform(p, result, - std::bind(channel_divides_scalar_t::type, - Scalar, - typename channel_type::type>(), - std::placeholders::_1, s)); - return result; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise division of two pixels. -/// \tparam PixelRef1 - models PixelConcept -/// \tparam PixelRef1 - models PixelConcept -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_divide_t -{ - auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult - { - PixelResult result; - static_transform(p1, p2, result, - channel_divides_t - < - typename channel_type::type, - typename channel_type::type, - typename channel_type::type - >()); - return result; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Performs channel-wise division by 2 -/// \tparam PixelRef - models PixelConcept -template -struct pixel_halves_t -{ - auto operator()(PixelRef& p) const -> PixelRef& - { - static_for_each(p, channel_halves_t::type>()); - return p; - } -}; - -/// \ingroup PixelNumericOperations -/// \brief Sets pixel elements to zero (for whatever zero means) -/// \tparam PixelRef - models PixelConcept -template -struct pixel_zeros_t -{ - auto operator()(PixelRef& p) const -> PixelRef& - { - static_for_each(p, channel_zeros_t::type>()); - return p; - } -}; - -/// \brief Sets pixel elements to zero (for whatever zero means) -/// \tparam Pixel - models PixelConcept -template -void zero_channels(Pixel& p) -{ - static_for_each(p, channel_zeros_t::type>()); -} - -/// \ingroup PixelNumericOperations -/// \brief Casts and assigns a pixel to another -/// -/// A generic implementation for casting and assigning a pixel to another. -/// User should specialize it for better performance. -/// -/// \tparam PixelRef - models PixelConcept -/// \tparam PixelResult - models PixelValueConcept -template -struct pixel_assigns_t -{ - auto operator()(PixelRef const& src, PixelResult& dst) const -> PixelResult - { - static_for_each(src, dst, - channel_assigns_t - < - typename channel_type::type, - typename channel_type::type - >()); - return dst; - } -}; - -}} // namespace boost::gil +BOOST_HEADER_DEPRECATED("") #endif diff --git a/include/boost/gil/extension/numeric/resample.hpp b/include/boost/gil/extension/numeric/resample.hpp index 16f859113f..aafcf2bd1d 100644 --- a/include/boost/gil/extension/numeric/resample.hpp +++ b/include/boost/gil/extension/numeric/resample.hpp @@ -73,10 +73,10 @@ namespace detail { template void resample_pixels(const any_image_view& src, const V2& dst, const MapFn& dst_to_src, Sampler sampler=Sampler()) { - apply_operation(src, std::bind( + variant2::visit(std::bind( detail::resample_pixels_fn(dst_to_src, sampler), std::placeholders::_1, - dst)); + dst), src); } /// \brief resample_pixels when the destination is run-time specified @@ -86,10 +86,10 @@ template void resample_pixels(const V1& src, const any_image_view& dst, const MapFn& dst_to_src, Sampler sampler=Sampler()) { using namespace std::placeholders; - apply_operation(dst, std::bind( + variant2::visit(std::bind( detail::resample_pixels_fn(dst_to_src, sampler), src, - std::placeholders::_1)); + std::placeholders::_1), dst); } /// \brief resample_pixels when both the source and the destination are run-time specified @@ -97,7 +97,7 @@ void resample_pixels(const V1& src, const any_image_view& dst, const /// \ingroup ImageAlgorithms template void resample_pixels(const any_image_view& src, const any_image_view& dst, const MapFn& dst_to_src, Sampler sampler=Sampler()) { - apply_operation(src,dst,detail::resample_pixels_fn(dst_to_src,sampler)); + variant2::visit(detail::resample_pixels_fn(dst_to_src,sampler), src, dst); } /////////////////////////////////////////////////////////////////////////// diff --git a/include/boost/gil/extension/numeric/sampler.hpp b/include/boost/gil/extension/numeric/sampler.hpp index 0f71a986d7..bc7fa351a6 100644 --- a/include/boost/gil/extension/numeric/sampler.hpp +++ b/include/boost/gil/extension/numeric/sampler.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -8,8 +9,8 @@ #ifndef BOOST_GIL_EXTENSION_NUMERIC_SAMPLER_HPP #define BOOST_GIL_EXTENSION_NUMERIC_SAMPLER_HPP -#include #include +#include namespace boost { namespace gil { diff --git a/include/boost/gil/extension/rasterization/apply_rasterizer.hpp b/include/boost/gil/extension/rasterization/apply_rasterizer.hpp new file mode 100644 index 0000000000..4eaffc35c7 --- /dev/null +++ b/include/boost/gil/extension/rasterization/apply_rasterizer.hpp @@ -0,0 +1,28 @@ +#ifndef BOOST_GIL_EXTENSION_RASTERIZATION_APPLY_RASTERIZER +#define BOOST_GIL_EXTENSION_RASTERIZATION_APPLY_RASTERIZER + +namespace boost { namespace gil { + +namespace detail { + +template +struct apply_rasterizer_op +{ + void operator()( + View const& view, Rasterizer const& rasterizer, Pixel const& pixel); +}; + +} // namespace detail + +template +void apply_rasterizer( + View const& view, Rasterizer const& rasterizer, Pixel const& pixel) +{ + using tag_t = typename Rasterizer::type; + detail::apply_rasterizer_op{}( + view, rasterizer, pixel); +} + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/rasterization/circle.hpp b/include/boost/gil/extension/rasterization/circle.hpp new file mode 100644 index 0000000000..2fe9a35c21 --- /dev/null +++ b/include/boost/gil/extension/rasterization/circle.hpp @@ -0,0 +1,179 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#ifndef BOOST_GIL_EXTENSION_RASTERIZATION_CIRCLE_HPP +#define BOOST_GIL_EXTENSION_RASTERIZATION_CIRCLE_HPP + +#include +#include +#include + +#include +#include +#include + +namespace boost { namespace gil { + +struct circle_rasterizer_t{}; + +/// \defgroup CircleRasterization +/// \ingroup Rasterization +/// \brief Circle rasterization algorithms +/// +/// The main problems are connectivity and equation following. Circle can be easily moved +/// to new offset, and rotation has no effect on it (not recommended to do rotation). + +/// \ingroup CircleRasterization +/// \brief Rasterize trigonometric circle according to radius by sine and radius by cosine +/// +/// This rasterizer is the one used that is used in standard Hough circle transform in +/// the books. It is also quite expensive to compute. +/// WARNING: the product of this rasterizer does not follow circle equation, even though it +/// produces quite round like shapes. +struct trigonometric_circle_rasterizer +{ + using type = circle_rasterizer_t; + + /// \brief Creates a trigonometric circle rasterizer + /// \param center_point - Point containing positive integer x co-ordinate and y co-ordinate of the + /// center respectively. + /// \param circle_radius - Radius of the circle + trigonometric_circle_rasterizer(point_t center_point, std::ptrdiff_t circle_radius) + : center(center_point), radius(circle_radius) + {} + + /// \brief Calculates minimum angle step that is distinguishable when walking on circle + /// + /// It is important to not have disconnected circle and to not compute unnecessarily, + /// thus the result of this function is used when rendering. + double minimum_angle_step() const noexcept + { + const auto diameter = radius * 2 - 1; + return std::atan2(1.0, diameter); + } + + /// \brief Calculate the amount of points that rasterizer will output + std::ptrdiff_t point_count() const noexcept + { + return 8 * static_cast( + std::round(detail::pi / 4 / minimum_angle_step()) + 1); + } + + /// \brief perform rasterization and output into d_first + template + void operator()(OutputIterator d_first) const + { + const double minimum_angle_step = std::atan2(1.0, radius); + auto translate_mirror_points = [this, &d_first](point_t p) { + *d_first++ = point_t{center.x + p.x, center.y + p.y}; + *d_first++ = point_t{center.x + p.x, center.y - p.y}; + *d_first++ = point_t{center.x - p.x, center.y + p.y}; + *d_first++ = point_t{center.x - p.x, center.y - p.y}; + *d_first++ = point_t{center.x + p.y, center.y + p.x}; + *d_first++ = point_t{center.x + p.y, center.y - p.x}; + *d_first++ = point_t{center.x - p.y, center.y + p.x}; + *d_first++ = point_t{center.x - p.y, center.y - p.x}; + }; + const std::ptrdiff_t iteration_count = point_count() / 8; + double angle = 0; + // do note that + 1 was done inside count estimation, thus <= is not needed, only < + for (std::ptrdiff_t i = 0; i < iteration_count; ++i, angle += minimum_angle_step) + { + std::ptrdiff_t x = static_cast(std::round(radius * std::cos(angle))); + std::ptrdiff_t y = static_cast(std::round(radius * std::sin(angle))); + translate_mirror_points({x, y}); + } + } + + point_t center; + std::ptrdiff_t radius; +}; + +/// \ingroup CircleRasterization +/// \brief Perform circle rasterization according to Midpoint algorithm +/// +/// This algorithm givess reasonable output and is cheap to compute. +/// reference: +/// https://en.wikipedia.org/wiki/Midpoint_circle_algorithm +struct midpoint_circle_rasterizer +{ + using type = circle_rasterizer_t; + + /// \brief Creates a midpoint circle rasterizer + /// \param center_point - Point containing positive integer x co-ordinate and y co-ordinate of the + /// center respectively. + /// \param circle_radius - Radius of the circle + midpoint_circle_rasterizer(point_t center_point, std::ptrdiff_t circle_radius) + : center(center_point), radius(circle_radius) + {} + + /// \brief Calculate the amount of points that rasterizer will output + std::ptrdiff_t point_count() const noexcept + { + // the reason for pulling 8 out is so that when the expression radius * cos(45 degrees) + // is used, it would yield the same result as here + // + 1 at the end is because the point at radius itself is computed as well + return 8 * static_cast( + std::round(radius * std::cos(boost::gil::detail::pi / 4)) + 1); + } + + /// \brief perform rasterization and output into d_first + template + void operator()(OutputIterator d_first) const + { + auto translate_mirror_points = [this, &d_first](point_t p) { + *d_first++ = point_t{center.x + p.x, center.y + p.y}; + *d_first++ = point_t{center.x + p.x, center.y - p.y}; + *d_first++ = point_t{center.x - p.x, center.y + p.y}; + *d_first++ = point_t{center.x - p.x, center.y - p.y}; + *d_first++ = point_t{center.x + p.y, center.y + p.x}; + *d_first++ = point_t{center.x + p.y, center.y - p.x}; + *d_first++ = point_t{center.x - p.y, center.y + p.x}; + *d_first++ = point_t{center.x - p.y, center.y - p.x}; + }; + std::ptrdiff_t iteration_distance = point_count() / 8; + std::ptrdiff_t y_current = radius; + std::ptrdiff_t r_squared = radius * radius; + translate_mirror_points({0, y_current}); + for (std::ptrdiff_t x = 1; x < iteration_distance; ++x) + { + std::ptrdiff_t midpoint = x * x + y_current * y_current - y_current - r_squared; + if (midpoint > 0) + { + --y_current; + } + translate_mirror_points({x, y_current}); + } + } + + point_t center; + std::ptrdiff_t radius; +}; + +namespace detail { + +template +struct apply_rasterizer_op +{ + void operator()( + View const& view, Rasterizer const& rasterizer, Pixel const& pixel) + { + std::vector trajectory(rasterizer.point_count()); + rasterizer(std::begin(trajectory)); + + for (auto const& point : trajectory) + { + view(point) = pixel; + } + } +}; + +} //namespace detail + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/rasterization/ellipse.hpp b/include/boost/gil/extension/rasterization/ellipse.hpp new file mode 100644 index 0000000000..27ca5de747 --- /dev/null +++ b/include/boost/gil/extension/rasterization/ellipse.hpp @@ -0,0 +1,209 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// Use, modification and distribution are subject to 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) +// +#ifndef BOOST_GIL_EXTENSION_RASTERIZATION_ELLIPSE_HPP +#define BOOST_GIL_EXTENSION_RASTERIZATION_ELLIPSE_HPP + +#include +#include +#include + +#include +#include +#include + +namespace boost { namespace gil { + +struct ellipse_rasterizer_t{}; + +/// \defgroup EllipseRasterization +/// \ingroup Rasterization +/// \brief Ellipse rasterization algorithms. + +/// \ingroup EllipseRasterization +/// \brief Performs ellipse rasterization using midpoint algorithm. Initially, program considers +/// origin as center of ellipse and obtains first quadrant trajectory points. After that, +/// it shifts origin to provided co-ordinates of center and then draws the curve. +struct midpoint_ellipse_rasterizer +{ + using type = ellipse_rasterizer_t; + + /// \brief Creates a midpoint ellipse rasterizer + /// \param center - Point containing positive integer x co-ordinate and y co-ordinate of the + /// center respectively. + /// \param semi_axes - Point containing positive integer lengths of horizontal semi-axis + /// and vertical semi-axis respectively. + midpoint_ellipse_rasterizer(point center_point, + point semi_axes_values) + : center(center_point) + , semi_axes(semi_axes_values) + {} + + /// \brief Returns a vector containing co-ordinates of first quadrant points which lie on + /// rasterizer trajectory of the ellipse. + auto obtain_trajectory() const + -> std::vector + { + // Citation : J. Van Aken, "An Efficient Ellipse-Drawing Algorithm" in IEEE Computer + // Graphics and Applications, vol. 4, no. 09, pp. 24-35, 1984. + // doi: 10.1109/MCG.1984.275994 + // keywords: {null} + // url: https://doi.ieeecomputersociety.org/10.1109/MCG.1984.275994 + std::vector trajectory_points; + std::ptrdiff_t x = semi_axes[0], y = 0; + + // Variables declared on following lines are temporary variables used for improving + // performance since they help in converting all multiplicative operations inside the while + // loop into additive/subtractive operations. + long long int const t1 = semi_axes[0] * semi_axes[0]; + long long int const t4 = semi_axes[1] * semi_axes[1]; + long long int t2, t3, t5, t6, t8, t9; + t2 = 2 * t1, t3 = 2 * t2; + t5 = 2 * t4, t6 = 2 * t5; + long long int const t7 = semi_axes[0] * t5; + t8 = 2 * t7, t9 = 0; + + // Following variables serve as decision parameters and help in choosing the right point + // to be included in rasterizer trajectory. + long long int d1, d2; + d1 = t2 - t7 + t4 / 2, d2 = t1 / 2 - t8 + t5; + + while (d2 < 0) + { + trajectory_points.push_back({x, y}); + y += 1; + t9 += t3; + if (d1 < 0) + { + d1 += t9 + t2; + d2 += t9; + } + else + { + x -= 1; + t8 -= t6; + d1 += t9 + t2 - t8; + d2 += t5 + t9 - t8; + } + } + while (x >= 0) + { + trajectory_points.push_back({x, y}); + x -= 1; + t8 -= t6; + if (d2 < 0) + { + y += 1; + t9 += t3; + d2 += t5 + t9 - t8; + } + else + { + d2 += t5 - t8; + } + } + return trajectory_points; + } + + /// \brief Fills pixels returned by function 'obtain_trajectory' as well as pixels + /// obtained from their reflection along major axis, minor axis and line passing through + /// center with slope -1 using colours provided by user. + /// \param view - Gil view of image on which the elliptical curve is to be drawn. + /// \param pixel - Pixel value for the elliptical curve to be drawn. + /// \param trajectory_points - Constant vector specifying pixel co-ordinates of points lying + /// on rasterizer trajectory. + /// \tparam View - Type of input image view. + /// \tparam Pixel - Type of pixel. Must be compatible to the pixel type of the image view + template + void draw_curve(View& view, Pixel const& pixel, + std::vector const& trajectory_points) const + { + using pixel_t = typename View::value_type; + if (!pixels_are_compatible()) + { + throw std::runtime_error("Pixel type of the given image is not compatible to the " + "type of the provided pixel."); + } + + // mutable center copy + point center2(center); + --center2[0], --center2[1]; // For converting center co-ordinate values to zero based indexing. + for (point_t pnt : trajectory_points) + { + std::array co_ords = {center2[0] + pnt[0], + center2[0] - pnt[0], center2[1] + pnt[1], center2[1] - pnt[1] + }; + bool validity[4]{}; + if (co_ords[0] < view.width()) + { + validity[0] = true; + } + if (co_ords[1] >= 0 && co_ords[1] < view.width()) + { + validity[1] = true; + } + if (co_ords[2] < view.height()) + { + validity[2] = true; + } + if (co_ords[3] >= 0 && co_ords[3] < view.height()) + { + validity[3] = true; + } + + if (validity[0] && validity[2]) + { + view(co_ords[0], co_ords[2]) = pixel; + } + if (validity[1] && validity[2]) + { + view(co_ords[1], co_ords[2]) = pixel; + } + if (validity[1] && validity[3]) + { + view(co_ords[1], co_ords[3]) = pixel; + } + if (validity[0] && validity[3]) + { + view(co_ords[0], co_ords[3]) = pixel; + } + } + } + + /// \brief Calls the function 'obtain_trajectory' and then passes obtained trajectory points + /// in the function 'draw_curve' for drawing the desired ellipse. + /// \param view - Gil view of image on which the elliptical curve is to be drawn. + /// \param pixel - Pixel value for the elliptical curve to be drawn. + /// \tparam View - Type of input image view. + /// \tparam Pixel - Type of pixel. Must be compatible to the pixel type of the image view + template + void operator()(View& view, Pixel const& pixel) const + { + draw_curve(view, pixel, obtain_trajectory()); + } + + point center; + point semi_axes; +}; + +namespace detail { + +template +struct apply_rasterizer_op +{ + void operator()( + View const& view, Rasterizer const& rasterizer, Pixel const& pixel) + { + rasterizer(view, pixel); + } +}; + +} //namespace detail + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/rasterization/line.hpp b/include/boost/gil/extension/rasterization/line.hpp new file mode 100644 index 0000000000..3014cabcc7 --- /dev/null +++ b/include/boost/gil/extension/rasterization/line.hpp @@ -0,0 +1,132 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#ifndef BOOST_GIL_EXTENSION_RASTERIZATION_LINE_HPP +#define BOOST_GIL_EXTENSION_RASTERIZATION_LINE_HPP + +#include +#include + +#include +#include +#include +#include + +namespace boost { namespace gil { + +struct line_rasterizer_t{}; + +/// \defgroup Rasterization +/// \brief A set of functions to rasterize shapes +/// +/// Due to images being discrete, most shapes require specialized algorithms to +/// handle rasterization efficiently and solve problem of connectivity and being +/// close to the original shape. + +/// \defgroup LineRasterization +/// \ingroup Rasterization +/// \brief A set of rasterizers for lines +/// +/// The main problem with line rasterization is to do it efficiently, e.g. less +/// floating point operations. There are multiple algorithms that on paper +/// should reach the same result, but due to quirks of IEEE-754 they don't. +/// Please select one and stick to it if possible. At the moment only Bresenham +/// rasterizer is implemented. + +/// \ingroup LineRasterization +/// \brief Rasterize a line according to Bresenham algorithm +/// +/// Do note that if either width or height is 1, slope is set to zero. +/// reference: +/// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#:~:text=Bresenham's%20line%20algorithm%20is%20a,straight%20line%20between%20two%20points. +struct bresenham_line_rasterizer +{ + using type = line_rasterizer_t; + + bresenham_line_rasterizer(point_t start, point_t end) + : start_point(start), end_point(end) + {} + + std::ptrdiff_t point_count() const noexcept + { + const auto abs_width = std::abs(end_point.x - start_point.x) + 1; + const auto abs_height = std::abs(end_point.y - start_point.y) + 1; + return abs_width > abs_height ? abs_width : abs_height; + } + + template + void operator()(OutputIterator d_first) const + { + // mutable stack copies + point_t start = start_point; + point_t end = end_point; + + if (start == end) + { + // put the point and immediately exit, as later on division by zero will + // occur + *d_first = start; + return; + } + + auto width = std::abs(end.x - start.x) + 1; + auto height = std::abs(end.y - start.y) + 1; + bool const needs_flip = width < height; + if (needs_flip) + { + // transpose the coordinate system if uncomfortable angle detected + std::swap(width, height); + std::swap(start.x, start.y); + std::swap(end.x, end.y); + } + std::ptrdiff_t const x_increment = end.x >= start.x ? 1 : -1; + std::ptrdiff_t const y_increment = end.y >= start.y ? 1 : -1; + double const slope = + height == 1 ? 0 : static_cast(height) / static_cast(width); + std::ptrdiff_t y = start.y; + double error_term = 0; + for (std::ptrdiff_t x = start.x; x != end.x; x += x_increment) + { + // transpose coordinate system back to proper form if needed + *d_first++ = needs_flip ? point_t{y, x} : point_t{x, y}; + error_term += slope; + if (error_term >= 0.5) + { + --error_term; + y += y_increment; + } + } + *d_first++ = needs_flip ? point_t{end.y, end.x} : end; + } + + point_t start_point; + point_t end_point; +}; + +namespace detail { + +template +struct apply_rasterizer_op +{ + void operator()( + View const& view, Rasterizer const& rasterizer, Pixel const& pixel) + { + std::vector trajectory(rasterizer.point_count()); + rasterizer(std::begin(trajectory)); + + for (auto const& point : trajectory) + { + view(point) = pixel; + } + } +}; + +} //namespace detail + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/extension/toolbox/color_spaces/hsl.hpp b/include/boost/gil/extension/toolbox/color_spaces/hsl.hpp index 61e9b50052..c07ba19b8a 100644 --- a/include/boost/gil/extension/toolbox/color_spaces/hsl.hpp +++ b/include/boost/gil/extension/toolbox/color_spaces/hsl.hpp @@ -75,6 +75,7 @@ struct default_color_converter_impl< rgb_t, hsl_t > { float32_t diff = max_color - min_color; + float32_t sum = max_color + min_color; // lightness calculation @@ -84,13 +85,11 @@ struct default_color_converter_impl< rgb_t, hsl_t > if( lightness < 0.5f ) { - saturation = diff - / ( min_color + max_color ); + saturation = diff / ( sum ); } else { - saturation = ( max_color - min_color ) - / ( 2.f - diff ); + saturation = diff / ( 2.f - sum ); } @@ -115,7 +114,7 @@ struct default_color_converter_impl< rgb_t, hsl_t > { // max_color is blue hue = 4.f - + ( temp_red - temp_blue ) + + ( temp_red - temp_green ) / diff; } diff --git a/include/boost/gil/extension/toolbox/color_spaces/hsv.hpp b/include/boost/gil/extension/toolbox/color_spaces/hsv.hpp index a9b33dba51..2c79360e87 100644 --- a/include/boost/gil/extension/toolbox/color_spaces/hsv.hpp +++ b/include/boost/gil/extension/toolbox/color_spaces/hsv.hpp @@ -8,8 +8,6 @@ #ifndef BOOST_GIL_EXTENSION_TOOLBOX_COLOR_SPACES_HSV_HPP #define BOOST_GIL_EXTENSION_TOOLBOX_COLOR_SPACES_HSV_HPP -#include - #include #include #include @@ -86,7 +84,7 @@ struct default_color_converter_impl< rgb_t, hsv_t > } else { - if( (std::abs)( boost::numeric_cast(temp_red - max_color) ) < 0.0001f ) + if( (std::abs)( temp_red - max_color ) < 0.0001f ) { hue = ( temp_green - temp_blue ) / diff; diff --git a/include/boost/gil/extension/toolbox/image_types/indexed_image.hpp b/include/boost/gil/extension/toolbox/image_types/indexed_image.hpp index f740d93084..cb49377b8b 100644 --- a/include/boost/gil/extension/toolbox/image_types/indexed_image.hpp +++ b/include/boost/gil/extension/toolbox/image_types/indexed_image.hpp @@ -100,7 +100,7 @@ struct indexed_image_deref_fn : indexed_image_deref_fn_base< IndicesLoc ) {} - typename base_t::result_type operator()( const point_t& p ) const + typename base_t::result_type operator()( point_t const& p ) const { return * this->_palette_loc.xy_at( at_c<0>( *this->_indices_loc.xy_at( p )), 0 ); } @@ -166,7 +166,7 @@ class indexed_image_view : public image_view< Locator > , _num_colors( 0 ) {} - indexed_image_view( const point_t& dimensions + indexed_image_view( point_t const& dimensions , std::size_t num_colors , const Locator& locator ) @@ -280,7 +280,7 @@ class indexed_image init( point_t( width, height ), num_colors ); } - indexed_image( const point_t& dimensions + indexed_image( point_t const& dimensions , const std::size_t num_colors = 1 , const std::size_t indices_alignment = 0 , const std::size_t palette_alignment = 0 @@ -323,7 +323,7 @@ class indexed_image private: - void init( const point_t& dimensions + void init( point_t const& dimensions , const std::size_t num_colors ) { diff --git a/include/boost/gil/extension/toolbox/image_types/subchroma_image.hpp b/include/boost/gil/extension/toolbox/image_types/subchroma_image.hpp index f49d69e286..493467c139 100644 --- a/include/boost/gil/extension/toolbox/image_types/subchroma_image.hpp +++ b/include/boost/gil/extension/toolbox/image_types/subchroma_image.hpp @@ -96,7 +96,7 @@ struct subchroma_image_deref_fn {} /// operator() - result_type operator()( const point_t& p ) const + result_type operator()( point_t const& p ) const { using scaling_factors_t = detail::scaling_factors < @@ -228,9 +228,9 @@ class subchroma_image_view : public image_view {} /// constructor - subchroma_image_view( const point_t& y_dimensions - , const point_t& v_dimensions - , const point_t& u_dimensions + subchroma_image_view( point_t const& y_dimensions + , point_t const& v_dimensions + , point_t const& u_dimensions , const Locator& locator ) : image_view< Locator >( y_dimensions, locator ) @@ -245,20 +245,20 @@ class subchroma_image_view : public image_view : image_view< locator >( v ) {} - const point_t& v_ssfactors() const { return point_t( get_deref_fn().vx_ssfactor(), get_deref_fn().vx_ssfactor() ); } - const point_t& u_ssfactors() const { return point_t( get_deref_fn().ux_ssfactor(), get_deref_fn().ux_ssfactor() ); } + point_t v_ssfactors() const { return point_t( get_deref_fn().vx_ssfactor(), get_deref_fn().vx_ssfactor() ); } + point_t u_ssfactors() const { return point_t( get_deref_fn().ux_ssfactor(), get_deref_fn().ux_ssfactor() ); } - const point_t& y_dimension() const { return _y_dimensions; } - const point_t& v_dimension() const { return _v_dimensions; } - const point_t& u_dimension() const { return _u_dimensions; } + point_t const& y_dimension() const { return _y_dimensions; } + point_t const& v_dimension() const { return _v_dimensions; } + point_t const& u_dimension() const { return _u_dimensions; } const plane_locator_t& y_plane() const { return get_deref_fn().y_locator(); } const plane_locator_t& v_plane() const { return get_deref_fn().v_locator(); } const plane_locator_t& u_plane() const { return get_deref_fn().u_locator(); } - const plane_view_t y_plane_view() const { return plane_view_t( _y_dimensions, y_plane() ); } - const plane_view_t v_plane_view() const { return plane_view_t( _v_dimensions, v_plane() ); } - const plane_view_t u_plane_view() const { return plane_view_t( _u_dimensions, u_plane() ); } + plane_view_t y_plane_view() const { return plane_view_t( _y_dimensions, y_plane() ); } + plane_view_t v_plane_view() const { return plane_view_t( _v_dimensions, v_plane() ); } + plane_view_t u_plane_view() const { return plane_view_t( _u_dimensions, u_plane() ); } private: diff --git a/include/boost/gil/histogram.hpp b/include/boost/gil/histogram.hpp new file mode 100644 index 0000000000..9bf66ab90b --- /dev/null +++ b/include/boost/gil/histogram.hpp @@ -0,0 +1,719 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#ifndef BOOST_GIL_HISTOGRAM_HPP +#define BOOST_GIL_HISTOGRAM_HPP + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace gil { + +////////////////////////////////////////////////////////// +/// Histogram +////////////////////////////////////////////////////////// +/// \defgroup Histogram Histogram +/// \brief Contains description of the boost.gil.histogram class, extensions provided in place +/// of the default class, algorithms over the histogram class (both extensions and the +/// default class) +/// + +namespace detail { + +/// \defgroup Histogram-Helpers Histogram-Helpers +/// \brief Helper implementations supporting the histogram class. + +/// \ingroup Histogram-Helpers +/// +template +inline auto hash_tuple_impl(std::size_t&, std::tuple const&) + -> typename std::enable_if::type +{ + // terminating case +} + +/// \ingroup Histogram-Helpers +/// +template +inline auto hash_tuple_impl(std::size_t& seed, std::tuple const& t) + -> typename std::enable_if::type +{ + boost::hash_combine(seed, std::get(t)); + hash_tuple_impl(seed, t); +} + +/// \ingroup Histogram-Helpers +/// \brief Functor provided for the hashing of tuples. +/// The following approach makes use hash_combine from +/// boost::container_hash. Although there is a direct hashing +/// available for tuples, this approach will ease adopting in +/// future to a std::hash_combine. In case std::hash extends +/// support to tuples this functor as well as the helper +/// implementation hash_tuple_impl can be removed. +/// +template +struct hash_tuple +{ + auto operator()(std::tuple const& t) const -> std::size_t + { + std::size_t seed = 0; + hash_tuple_impl<0>(seed, t); + return seed; + } +}; + +/// \ingroup Histogram-Helpers +/// \todo With C++14 and using auto we don't need the decltype anymore +/// +template +auto pixel_to_tuple(Pixel const& p, boost::mp11::index_sequence) + -> decltype(std::make_tuple(p[I]...)) +{ + return std::make_tuple(p[I]...); +} + +/// \ingroup Histogram-Helpers +/// \todo With C++14 and using auto we don't need the decltype anymore +/// +template +auto tuple_to_tuple(Tuple const& t, boost::mp11::index_sequence) + -> decltype(std::make_tuple(std::get(t)...)) +{ + return std::make_tuple(std::get(t)...); +} + +/// \ingroup Histogram-Helpers +/// +template +bool tuple_compare(Tuple const& t1, Tuple const& t2, boost::mp11::index_sequence) +{ + std::array::value> comp_list; + comp_list = {std::get(t1) <= std::get(t2)...}; + bool comp = true; + for (std::size_t i = 0; i < comp_list.size(); i++) + { + comp = comp & comp_list[i]; + } + return comp; +} + +/// \ingroup Histogram-Helpers +/// \brief Compares 2 tuples and outputs t1 <= t2 +/// Comparison is not in a lexicographic manner but on every element of the tuple hence +/// (2, 2) > (1, 3) evaluates to false +/// +template +bool tuple_compare(Tuple const& t1, Tuple const& t2) +{ + std::size_t const tuple_size = std::tuple_size::value; + auto index_list = boost::mp11::make_index_sequence{}; + return tuple_compare(t1, t2, index_list); +} + +/// \ingroup Histogram-Helpers +/// \brief Provides equivalent of std::numeric_limits for type std::tuple +/// tuple_limit gets called with only tuples having integral elements +/// +template +struct tuple_limit +{ + static constexpr Tuple min() + { + return min_impl(boost::mp11::make_index_sequence::value>{}); + } + static constexpr Tuple max() + { + return max_impl(boost::mp11::make_index_sequence::value>{}); + } + +private: + template + static constexpr Tuple min_impl(boost::mp11::index_sequence) + { + return std::make_tuple( + std::numeric_limits::type>::min()...); + } + + template + static constexpr Tuple max_impl(boost::mp11::index_sequence) + { + return std::make_tuple( + std::numeric_limits::type>::max()...); + } +}; + +/// \ingroup Histogram-Helpers +/// \brief Filler is used to fill the histogram class with all values between a specified range +/// This functor is used when sparsefill is false, since all the keys need to be present +/// in that case. +/// Currently on 1D implementation is available, extend by adding specialization for 2D +/// and higher dimensional cases. +/// +template +struct filler +{ + template + void operator()(Container&, Tuple&, Tuple&, std::size_t) + { + } +}; + +/// \ingroup Histogram-Helpers +/// \brief Specialisation for 1D histogram. +template <> +struct filler<1> +{ + template + void operator()(Container& hist, Tuple& lower, Tuple& upper, std::size_t bin_width = 1) + { + for (auto i = std::get<0>(lower); static_cast(std::get<0>(upper) - i) >= bin_width; i += bin_width) + { + hist(i / bin_width) = 0; + } + hist(std::get<0>(upper) / bin_width) = 0; + } +}; + +} //namespace detail + +/// +/// \class boost::gil::histogram +/// \ingroup Histogram +/// \brief Default histogram class provided by boost::gil. +/// +/// The class inherits over the std::unordered_map provided by STL. A complete example/tutorial +/// of how to use the class resides in the docs. +/// Simple calling syntax for a 3D dimensional histogram : +/// \code +/// histogram h; +/// h(1, 1, 1) = 0; +/// \endcode +/// This is just a starter to what all can be achieved with it, refer to the docs for the +/// full demo. +/// +template +class histogram : public std::unordered_map, double, detail::hash_tuple> +{ + using base_t = std::unordered_map, double, detail::hash_tuple>; + using bin_t = boost::mp11::mp_list; + using key_t = typename base_t::key_type; + using mapped_t = typename base_t::mapped_type; + using value_t = typename base_t::value_type; + +public: + histogram() = default; + + /// \brief Returns the number of dimensions(axes) the class supports. + static constexpr std::size_t dimension() + { + return std::tuple_size::value; + } + + /// \brief Returns bin value corresponding to specified tuple + mapped_t& operator()(T... indices) + { + auto key = std::make_tuple(indices...); + std::size_t const index_dimension = std::tuple_size>::value; + std::size_t const histogram_dimension = dimension(); + static_assert(histogram_dimension == index_dimension, "Dimensions do not match."); + + return base_t::operator[](key); + } + + /// \brief Checks if 2 histograms are equal. Ignores type, and checks if + /// the keys (after type casting) match. + template + bool equals(OtherType const& otherhist) const + { + bool check = (dimension() == otherhist.dimension()); + + using other_value_t = typename OtherType::value_type; + std::for_each(otherhist.begin(), otherhist.end(), [&](other_value_t const& v) { + key_t key = key_from_tuple(v.first); + if (base_t::find(key) != base_t::end()) + { + check = check & (base_t::at(key) == otherhist.at(v.first)); + } + else + { + check = false; + } + }); + return check; + } + + /// \brief Checks if the histogram class is compatible to be used with + /// a GIL image type + static constexpr bool is_pixel_compatible() + { + using bin_types = boost::mp11::mp_list; + return boost::mp11::mp_all_of::value; + } + + /// \brief Checks if the histogram class is compatible to be used with + /// the specified tuple type + template + bool is_tuple_compatible(Tuple const&) + { + std::size_t const tuple_size = std::tuple_size::value; + std::size_t const histogram_size = dimension(); + // TODO : Explore consequence of using if-constexpr + using sequence_type = typename std::conditional + < + tuple_size >= histogram_size, + boost::mp11::make_index_sequence, + boost::mp11::make_index_sequence + >::type; + + if (is_tuple_size_compatible()) + return is_tuple_type_compatible(sequence_type{}); + else + return false; + } + + /// \brief Returns a key compatible to be used as the histogram key + /// from the input tuple + template + key_t key_from_tuple(Tuple const& t) const + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const tuple_size = std::tuple_size::value; + std::size_t const histogram_dimension = dimension(); + + static_assert( + ((index_list_size != 0 && index_list_size == histogram_dimension) || + (tuple_size == histogram_dimension)), + "Tuple and histogram key of different sizes"); + + using new_index_list = typename std::conditional + < + index_list_size == 0, + boost::mp11::mp_list_c, + index_list + >::type; + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert((0 <= min && max < tuple_size) || index_list_size == 0, "Index out of Range"); + + using seq1 = boost::mp11::make_index_sequence; + using seq2 = boost::mp11::index_sequence; + // TODO : Explore consequence of using if-constexpr + using sequence_type = typename std::conditional::type; + + auto key = detail::tuple_to_tuple(t, sequence_type{}); + static_assert( + is_tuple_type_compatible(seq1{}), + "Tuple type and histogram type not compatible."); + + return make_histogram_key(key, seq1{}); + } + + /// \brief Returns a histogram compatible key from the input pixel which + /// can be directly used + template + key_t key_from_pixel(Pixel const& p) const + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const pixel_dimension = num_channels::value; + std::size_t const histogram_dimension = dimension(); + + static_assert( + ((index_list_size != 0 && index_list_size == histogram_dimension) || + (index_list_size == 0 && pixel_dimension == histogram_dimension)) && + is_pixel_compatible(), + "Pixels and histogram key are not compatible."); + + using new_index_list = typename std::conditional + < + index_list_size == 0, + boost::mp11::mp_list_c, + index_list + >::type; + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert( + (0 <= min && max < pixel_dimension) || index_list_size == 0, "Index out of Range"); + + using seq1 = boost::mp11::make_index_sequence; + using seq2 = boost::mp11::index_sequence; + using sequence_type = typename std::conditional::type; + + auto key = detail::pixel_to_tuple(p, sequence_type{}); + return make_histogram_key(key, seq1{}); + } + + /// \brief Return nearest smaller key to specified histogram key + key_t nearest_key(key_t const& k) const + { + using check_list = boost::mp11::mp_list...>; + static_assert( + boost::mp11::mp_all_of::value, + "Keys are not comparable."); + auto nearest_k = k; + if (base_t::find(k) != base_t::end()) + { + return nearest_k; + } + else + { + bool once = true; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + if (v.first <= k) + { + if (once) + { + once = !once; + nearest_k = v.first; + } + else if (nearest_k < v.first) + nearest_k = v.first; + } + }); + return nearest_k; + } + } + + /// \brief Fills the histogram with the input image view + template + void fill( + SrcView const& srcview, + std::size_t bin_width = 1, + bool applymask = false, + std::vector> mask = {}, + key_t lower = key_t(), + key_t upper = key_t(), + bool setlimits = false) + { + gil_function_requires>(); + using channel_t = typename channel_type::type; + + for (std::ptrdiff_t src_y = 0; src_y < srcview.height(); ++src_y) + { + auto src_it = srcview.row_begin(src_y); + for (std::ptrdiff_t src_x = 0; src_x < srcview.width(); ++src_x) + { + if (applymask && !mask[src_y][src_x]) + continue; + auto scaled_px = src_it[src_x]; + static_for_each(scaled_px, [&](channel_t& ch) { + ch = ch / bin_width; + }); + auto key = key_from_pixel(scaled_px); + if (!setlimits || + (detail::tuple_compare(lower, key) && detail::tuple_compare(key, upper))) + base_t::operator[](key)++; + } + } + } + + /// \brief Can return a subset or a mask over the current histogram + template + histogram sub_histogram(Tuple const& t1, Tuple const& t2) + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const histogram_dimension = dimension(); + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert( + (0 <= min && max < histogram_dimension) && index_list_size < histogram_dimension, + "Index out of Range"); + + using seq1 = boost::mp11::make_index_sequence; + using seq2 = boost::mp11::index_sequence; + + static_assert( + is_tuple_type_compatible(seq1{}), + "Tuple type and histogram type not compatible."); + + auto low = make_histogram_key(t1, seq1{}); + auto low_key = detail::tuple_to_tuple(low, seq2{}); + auto high = make_histogram_key(t2, seq1{}); + auto high_key = detail::tuple_to_tuple(high, seq2{}); + + histogram sub_h; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& k) { + auto tmp_key = detail::tuple_to_tuple(k.first, seq2{}); + if (low_key <= tmp_key && tmp_key <= high_key) + sub_h[k.first] += base_t::operator[](k.first); + }); + return sub_h; + } + + /// \brief Returns a sub-histogram over specified axes + template + histogram>...> sub_histogram() + { + using index_list = boost::mp11::mp_list_c; + std::size_t const index_list_size = boost::mp11::mp_size::value; + std::size_t const histogram_dimension = dimension(); + + std::size_t const min = + boost::mp11::mp_min_element::value; + + std::size_t const max = + boost::mp11::mp_max_element::value; + + static_assert( + (0 <= min && max < histogram_dimension) && index_list_size < histogram_dimension, + "Index out of Range"); + + histogram>...> sub_h; + + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + auto sub_key = + detail::tuple_to_tuple(v.first, boost::mp11::index_sequence{}); + sub_h[sub_key] += base_t::operator[](v.first); + }); + return sub_h; + } + + /// \brief Normalize this histogram class + void normalize() + { + double sum = 0.0; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + sum += v.second; + }); + // std::cout<<(long int)sum<<"asfe"; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + base_t::operator[](v.first) = v.second / sum; + }); + } + + /// \brief Return the sum count of all bins + double sum() const + { + double sum = 0.0; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + sum += v.second; + }); + return sum; + } + + /// \brief Return the minimum key in histogram + key_t min_key() const + { + key_t min_key = base_t::begin()->first; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + if (v.first < min_key) + min_key = v.first; + }); + return min_key; + } + + /// \brief Return the maximum key in histogram + key_t max_key() const + { + key_t max_key = base_t::begin()->first; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + if (v.first > max_key) + max_key = v.first; + }); + return max_key; + } + + /// \brief Return sorted keys in a vector + std::vector sorted_keys() const + { + std::vector sorted_keys; + std::for_each(base_t::begin(), base_t::end(), [&](value_t const& v) { + sorted_keys.push_back(v.first); + }); + std::sort(sorted_keys.begin(), sorted_keys.end()); + return sorted_keys; + } + +private: + template + key_t make_histogram_key(Tuple const& t, boost::mp11::index_sequence) const + { + return std::make_tuple( + static_cast>>( + std::get(t))...); + } + + template + static constexpr bool is_tuple_type_compatible(boost::mp11::index_sequence) + { + using tp = boost::mp11::mp_list + < + typename std::is_convertible + < + boost::mp11::mp_at>, + typename std::tuple_element::type + >::type... + >; + return boost::mp11::mp_all_of::value; + } + + template + static constexpr bool is_tuple_size_compatible() + { + return (std::tuple_size::value == dimension()); + } +}; + +/// +/// \fn void fill_histogram +/// \ingroup Histogram Algorithms +/// \tparam SrcView Input image view +/// \tparam Container Input histogram container +/// \brief Overload this function to provide support for boost::gil::histogram or +/// any other external histogram +/// +/// Example : +/// \code +/// histogram h; +/// fill_histogram(view(img), h); +/// \endcode +/// +template +void fill_histogram(SrcView const&, Container&); + +/// +/// \fn void fill_histogram +/// \ingroup Histogram Algorithms +/// @param srcview Input Input image view +/// @param hist Output Histogram to be filled +/// @param bin_width Input Specify the bin widths for the histogram. +/// @param accumulate Input Specify whether to accumulate over the values already present in h (default = false) +/// @param sparsaefill Input Specify whether to have a sparse or continuous histogram (default = true) +/// @param applymask Input Specify if image mask is to be specified +/// @param mask Input Mask as a 2D vector. Used only if prev argument specified +/// @param lower Input Lower limit on the values in histogram (default numeric_limit::min() on axes) +/// @param upper Input Upper limit on the values in histogram (default numeric_limit::max() on axes) +/// @param setlimits Input Use specified limits if this is true (default is false) +/// \brief Overload version of fill_histogram +/// +/// Takes a third argument to determine whether to clear container before filling. +/// For eg, when there is a need to accumulate the histograms do +/// \code +/// fill_histogram(view(img), hist, true); +/// \endcode +/// +template +void fill_histogram( + SrcView const& srcview, + histogram& hist, + std::size_t bin_width = 1, + bool accumulate = false, + bool sparsefill = true, + bool applymask = false, + std::vector> mask = {}, + typename histogram::key_type lower = + detail::tuple_limit::key_type>::min(), + typename histogram::key_type upper = + detail::tuple_limit::key_type>::max(), + bool setlimits = false) +{ + if (!accumulate) + hist.clear(); + + detail::filler::dimension()> f; + if (!sparsefill) + f(hist, lower, upper, bin_width); + + hist.template fill(srcview, bin_width, applymask, mask, lower, upper, setlimits); +} + +/// +/// \fn void cumulative_histogram(Container&) +/// \ingroup Histogram Algorithms +/// \tparam Container Input histogram container +/// \brief Optionally overload this function with any external histogram class +/// +/// Cumulative histogram is calculated over any arbitrary dimensional +/// histogram. The only tradeoff could be the runtime complexity which in +/// the worst case would be max( #pixel_values , #bins ) * #dimensions. +/// For single dimensional histograms the complexity has been brought down to +/// #bins * log( #bins ) by sorting the keys and then calculating the cumulative version. +/// +template +auto cumulative_histogram(Container const&) -> Container; + +template +auto cumulative_histogram(histogram const& hist) -> histogram +{ + using check_list = boost::mp11::mp_list...>; + static_assert( + boost::mp11::mp_all_of::value, + "Cumulative histogram not possible of this type"); + + using histogram_t = histogram; + using pair_t = std::pair; + using value_t = typename histogram_t::value_type; + + histogram_t cumulative_hist; + std::size_t const dims = histogram_t::dimension(); + if (dims == 1) + { + std::vector sorted_keys(hist.size()); + std::size_t counter = 0; + std::for_each(hist.begin(), hist.end(), [&](value_t const& v) { + sorted_keys[counter++] = std::make_pair(v.first, v.second); + }); + std::sort(sorted_keys.begin(), sorted_keys.end()); + auto cumulative_counter = static_cast(0); + for (std::size_t i = 0; i < sorted_keys.size(); ++i) + { + cumulative_counter += sorted_keys[i].second; + cumulative_hist[(sorted_keys[i].first)] = cumulative_counter; + } + } + else + { + std::for_each(hist.begin(), hist.end(), [&](value_t const& v1) { + auto cumulative_counter = static_cast(0); + std::for_each(hist.begin(), hist.end(), [&](value_t const& v2) { + bool comp = detail::tuple_compare( + v2.first, v1.first, + boost::mp11::make_index_sequence{}); + if (comp) + cumulative_counter += hist.at(v2.first); + }); + cumulative_hist[v1.first] = cumulative_counter; + }); + } + return cumulative_hist; +} + +}} //namespace boost::gil + +#endif diff --git a/include/boost/gil/image.hpp b/include/boost/gil/image.hpp index c3cc6818b7..86675b97d5 100644 --- a/include/boost/gil/image.hpp +++ b/include/boost/gil/image.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -37,7 +38,7 @@ namespace boost { namespace gil { /// //////////////////////////////////////////////////////////////////////////////////////// -template< typename Pixel, bool IsPlanar = false, typename Alloc=std::allocator > +template< typename Pixel, bool IsPlanar, typename Alloc> class image { public: @@ -54,7 +55,7 @@ class image using x_coord_t = coord_t; using y_coord_t = coord_t; - const point_t& dimensions() const { return _view.dimensions(); } + point_t const& dimensions() const { return _view.dimensions(); } x_coord_t width() const { return _view.width(); } y_coord_t height() const { return _view.height(); } @@ -63,7 +64,7 @@ class image _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in), _allocated_bytes( 0 ) {} // Create with dimensions and optional initial value and alignment - image(const point_t& dimensions, + image(point_t const& dimensions, std::size_t alignment=0, const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in) , _allocated_bytes( 0 ) @@ -79,7 +80,7 @@ class image allocate_and_default_construct(point_t(width,height)); } - image(const point_t& dimensions, + image(point_t const& dimensions, const Pixel& p_in, std::size_t alignment = 0, const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in) @@ -237,7 +238,14 @@ class image swap(_align_in_bytes, img._align_in_bytes); swap(_memory, img._memory); swap(_view, img._view); +#ifdef BOOST_NO_CXX17_HDR_MEMORY_RESOURCE swap(_alloc, img._alloc); +#else + if constexpr (std::allocator_traits::propagate_on_container_swap::value) + swap(_alloc, img._alloc); + else + BOOST_ASSERT(_alloc == img._alloc); +#endif swap(_allocated_bytes, img._allocated_bytes ); } @@ -246,7 +254,7 @@ class image ///////////////////// // without Allocator - void recreate(const point_t& dims, std::size_t alignment = 0) + void recreate(point_t const& dims, std::size_t alignment = 0) { if (dims == _view.dimensions() && _align_in_bytes == alignment) return; @@ -271,7 +279,7 @@ class image recreate(point_t(width, height), alignment); } - void recreate(const point_t& dims, const Pixel& p_in, std::size_t alignment = 0) + void recreate(point_t const& dims, const Pixel& p_in, std::size_t alignment = 0) { if (dims == _view.dimensions() && _align_in_bytes == alignment) return; @@ -297,7 +305,7 @@ class image } // with Allocator - void recreate(const point_t& dims, std::size_t alignment, const Alloc alloc_in) + void recreate(point_t const& dims, std::size_t alignment, const Alloc alloc_in) { if (dims == _view.dimensions() && _align_in_bytes == alignment && alloc_in == _alloc) return; @@ -322,7 +330,7 @@ class image recreate(point_t(width, height), alignment, alloc_in); } - void recreate(const point_t& dims, const Pixel& p_in, std::size_t alignment, const Alloc alloc_in) + void recreate(point_t const& dims, const Pixel& p_in, std::size_t alignment, const Alloc alloc_in) { if (dims == _view.dimensions() && _align_in_bytes == alignment && alloc_in == _alloc) return; @@ -348,7 +356,7 @@ class image } view_t _view; // contains pointer to the pixels, the image size and ways to navigate pixels - + // for construction from other type template friend class image; private: @@ -368,7 +376,7 @@ class image catch (...) { deallocate(); throw; } } - void allocate_and_fill(const point_t& dimensions, Pixel const& p_in) + void allocate_and_fill(point_t const& dimensions, Pixel const& p_in) { try { @@ -379,7 +387,7 @@ class image } template - void allocate_and_copy(const point_t& dimensions, View const& v) + void allocate_and_copy(point_t const& dimensions, View const& v) { try { @@ -448,6 +456,11 @@ class image { // if it throws and _memory!=0 the client must deallocate _memory _allocated_bytes = total_allocated_size_in_bytes(dimensions); + if (_allocated_bytes == 0) + { + return; + } + _memory=_alloc.allocate( _allocated_bytes ); unsigned char* tmp=(_align_in_bytes>0) ? (unsigned char*)align((std::size_t)_memory,_align_in_bytes) : _memory; @@ -464,6 +477,10 @@ class image std::size_t plane_size=row_size*dimensions.y; _allocated_bytes = total_allocated_size_in_bytes( dimensions ); + if (_allocated_bytes == 0) + { + return; + } _memory = _alloc.allocate( _allocated_bytes ); @@ -544,12 +561,17 @@ bool operator!=(const image& im1,const image inline -const typename image::view_t& view(image& img) { return img._view; } +template +inline auto view(image& img) + -> typename image::view_t const& +{ + return img._view; +} /// \brief Returns the constant-pixel view of an image -template inline -const typename image::const_view_t const_view(const image& img) +template +inline auto const_view(const image& img) + -> typename image::const_view_t const { return static_cast::const_view_t>(img._view); } diff --git a/include/boost/gil/image_processing/adaptive_histogram_equalization.hpp b/include/boost/gil/image_processing/adaptive_histogram_equalization.hpp new file mode 100644 index 0000000000..1b5de48682 --- /dev/null +++ b/include/boost/gil/image_processing/adaptive_histogram_equalization.hpp @@ -0,0 +1,302 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// + +#ifndef BOOST_GIL_IMAGE_PROCESSING_ADAPTIVE_HISTOGRAM_EQUALIZATION_HPP +#define BOOST_GIL_IMAGE_PROCESSING_ADAPTIVE_HISTOGRAM_EQUALIZATION_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost { namespace gil { + +///////////////////////////////////////// +/// Adaptive Histogram Equalization(AHE) +///////////////////////////////////////// +/// \defgroup AHE AHE +/// \brief Contains implementation and description of the algorithm used to compute +/// adaptive histogram equalization of input images. Naming for the AHE functions +/// are done in the following way +/// __.._ahe +/// For example, for AHE done using local (non-overlapping) tiles/blocks and +/// final output interpolated among tiles , it is called +/// non_overlapping_interpolated_clahe +/// + +namespace detail { + +/// \defgroup AHE-helpers AHE-helpers +/// \brief AHE helper functions + +/// \fn double actual_clip_limit +/// \ingroup AHE-helpers +/// \brief Computes the actual clip limit given a clip limit value using binary search. +/// Reference - Adaptive Histogram Equalization and Its Variations +/// (http://www.cs.unc.edu/techreports/86-013.pdf, Pg - 15) +/// +template +double actual_clip_limit(SrcHist const& src_hist, double cliplimit = 0.03) +{ + double epsilon = 1.0; + using value_t = typename SrcHist::value_type; + double sum = src_hist.sum(); + std::size_t num_bins = src_hist.size(); + + cliplimit = sum * cliplimit; + long low = 0, high = cliplimit, middle = low; + while (high - low >= 1) + { + middle = (low + high + 1) >> 1; + long excess = 0; + std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) { + if (v.second > middle) + excess += v.second - middle; + }); + if (std::abs(excess - (cliplimit - middle) * num_bins) < epsilon) + break; + else if (excess > (cliplimit - middle) * num_bins) + high = middle - 1; + else + low = middle + 1; + } + return middle / sum; +} + +/// \fn void clip_and_redistribute +/// \ingroup AHE-helpers +/// \brief Clips and redistributes excess pixels based on the actual clip limit value +/// obtained from the other helper function actual_clip_limit +/// Reference - Graphic Gems 4, Pg. 474 +/// (http://cas.xav.free.fr/Graphics%20Gems%204%20-%20Paul%20S.%20Heckbert.pdf) +/// +template +void clip_and_redistribute(SrcHist const& src_hist, DstHist& dst_hist, double clip_limit = 0.03) +{ + using value_t = typename SrcHist::value_type; + double sum = src_hist.sum(); + double actual_clip_value = detail::actual_clip_limit(src_hist, clip_limit); + // double actual_clip_value = clip_limit; + long actual_clip_limit = actual_clip_value * sum; + double excess = 0; + std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) { + if (v.second > actual_clip_limit) + excess += v.second - actual_clip_limit; + }); + std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) { + if (v.second >= actual_clip_limit) + dst_hist[dst_hist.key_from_tuple(v.first)] = clip_limit * sum; + else + dst_hist[dst_hist.key_from_tuple(v.first)] = v.second + excess / src_hist.size(); + }); + long rem = long(excess) % src_hist.size(); + if (rem == 0) + return; + long period = round(src_hist.size() / rem); + std::size_t index = 0; + while (rem) + { + if (dst_hist(index) >= clip_limit * sum) + { + index = (index + 1) % src_hist.size(); + } + dst_hist(index)++; + rem--; + index = (index + period) % src_hist.size(); + } +} + +} // namespace detail + +/// \fn void non_overlapping_interpolated_clahe +/// \ingroup AHE +/// @param src_view Input Source image view +/// @param dst_view Output Output image view +/// @param tile_width_x Input Tile width along x-axis to apply HE +/// @param tile_width_y Input Tile width along x-axis to apply HE +/// @param clip_limit Input Clipping limit to be applied +/// @param bin_width Input Bin widths for histogram +/// @param mask Input Specify if mask is to be used +/// @param src_mask Input Mask on input image to ignore specified pixels +/// \brief Performs local histogram equalization on tiles of size (tile_width_x, tile_width_y) +/// Then uses the clip limit to redistribute excess pixels above the limit uniformly to +/// other bins. The clip limit is specified as a fraction i.e. a bin's value is clipped +/// if bin_value >= clip_limit * (Total number of pixels in the tile) +/// +template +void non_overlapping_interpolated_clahe( + SrcView const& src_view, + DstView const& dst_view, + std::ptrdiff_t tile_width_x = 20, + std::ptrdiff_t tile_width_y = 20, + double clip_limit = 0.03, + std::size_t bin_width = 1.0, + bool mask = false, + std::vector> src_mask = {}) +{ + gil_function_requires>(); + gil_function_requires>(); + + static_assert( + color_spaces_are_compatible< + typename color_space_type::type, + typename color_space_type::type>::value, + "Source and destination views must have same color space"); + + using source_channel_t = typename channel_type::type; + using dst_channel_t = typename channel_type::type; + using coord_t = typename SrcView::x_coord_t; + + std::size_t const channels = num_channels::value; + coord_t const width = src_view.width(); + coord_t const height = src_view.height(); + + // Find control points + + std::vector sample_x; + coord_t sample_x1 = tile_width_x / 2; + coord_t sample_y1 = tile_width_y / 2; + + auto extend_left = tile_width_x; + auto extend_top = tile_width_y; + auto extend_right = (tile_width_x - width % tile_width_x) % tile_width_x + tile_width_x; + auto extend_bottom = (tile_width_y - height % tile_width_y) % tile_width_y + tile_width_y; + + auto new_width = width + extend_left + extend_right; + auto new_height = height + extend_top + extend_bottom; + + image padded_img(new_width, new_height); + + auto top_left_x = tile_width_x; + auto top_left_y = tile_width_y; + auto bottom_right_x = tile_width_x + width; + auto bottom_right_y = tile_width_y + height; + + copy_pixels(src_view, subimage_view(view(padded_img), top_left_x, top_left_y, width, height)); + + for (std::size_t k = 0; k < channels; k++) + { + std::vector> prev_row(new_width / tile_width_x), + next_row((new_width / tile_width_x)); + std::vector> prev_map( + new_width / tile_width_x), + next_map((new_width / tile_width_x)); + + coord_t prev = 0, next = 1; + auto channel_view = nth_channel_view(view(padded_img), k); + + for (std::ptrdiff_t i = top_left_y; i < bottom_right_y; ++i) + { + if ((i - sample_y1) / tile_width_y >= next || i == top_left_y) + { + if (i != top_left_y) + { + prev = next; + next++; + } + prev_row = next_row; + prev_map = next_map; + for (std::ptrdiff_t j = sample_x1; j < new_width; j += tile_width_x) + { + auto img_view = subimage_view( + channel_view, j - sample_x1, next * tile_width_y, + std::max( + std::min(tile_width_x + j - sample_x1, bottom_right_x) - + (j - sample_x1), + 0), + std::max( + std::min((next + 1) * tile_width_y, bottom_right_y) - + next * tile_width_y, + 0)); + + fill_histogram( + img_view, next_row[(j - sample_x1) / tile_width_x], bin_width, false, + false); + + detail::clip_and_redistribute( + next_row[(j - sample_x1) / tile_width_x], + next_row[(j - sample_x1) / tile_width_x], clip_limit); + + next_map[(j - sample_x1) / tile_width_x] = + histogram_equalization(next_row[(j - sample_x1) / tile_width_x]); + } + } + bool prev_row_mask = 1, next_row_mask = 1; + if (prev == 0) + prev_row_mask = false; + else if (next + 1 == new_height / tile_width_y) + next_row_mask = false; + for (std::ptrdiff_t j = top_left_x; j < bottom_right_x; ++j) + { + bool prev_col_mask = true, next_col_mask = true; + if ((j - sample_x1) / tile_width_x == 0) + prev_col_mask = false; + else if ((j - sample_x1) / tile_width_x + 1 == new_width / tile_width_x - 1) + next_col_mask = false; + + // Bilinear interpolation + point_t top_left( + (j - sample_x1) / tile_width_x * tile_width_x + sample_x1, + prev * tile_width_y + sample_y1); + point_t top_right(top_left.x + tile_width_x, top_left.y); + point_t bottom_left(top_left.x, top_left.y + tile_width_y); + point_t bottom_right(top_left.x + tile_width_x, top_left.y + tile_width_y); + + long double x_diff = top_right.x - top_left.x; + long double y_diff = bottom_left.y - top_left.y; + + long double x1 = (j - top_left.x) / x_diff; + long double x2 = (top_right.x - j) / x_diff; + long double y1 = (i - top_left.y) / y_diff; + long double y2 = (bottom_left.y - i) / y_diff; + + if (prev_row_mask == 0) + y1 = 1; + else if (next_row_mask == 0) + y2 = 1; + if (prev_col_mask == 0) + x1 = 1; + else if (next_col_mask == 0) + x2 = 1; + + long double numerator = + ((prev_row_mask & prev_col_mask) * x2 * + prev_map[(top_left.x - sample_x1) / tile_width_x][channel_view(j, i)] + + (prev_row_mask & next_col_mask) * x1 * + prev_map[(top_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) * + y2 + + ((next_row_mask & prev_col_mask) * x2 * + next_map[(bottom_left.x - sample_x1) / tile_width_x][channel_view(j, i)] + + (next_row_mask & next_col_mask) * x1 * + next_map[(bottom_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) * + y1; + + if (mask && !src_mask[i - top_left_y][j - top_left_x]) + { + dst_view(j - top_left_x, i - top_left_y) = + channel_convert( + static_cast(channel_view(i, j))); + } + else + { + dst_view(j - top_left_x, i - top_left_y) = + channel_convert(static_cast(numerator)); + } + } + } + } +} + +}} //namespace boost::gil + +#endif diff --git a/include/boost/gil/image_processing/convolve.hpp b/include/boost/gil/image_processing/convolve.hpp new file mode 100644 index 0000000000..91384f1626 --- /dev/null +++ b/include/boost/gil/image_processing/convolve.hpp @@ -0,0 +1,445 @@ +// +// Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2019 Miral Shah +// Copyright 2019-2021 Pranam Lashkari +// +// 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 +// +#ifndef BOOST_GIL_IMAGE_PROCESSING_CONVOLVE_HPP +#define BOOST_GIL_IMAGE_PROCESSING_CONVOLVE_HPP + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace boost { namespace gil { + +// 2D spatial seperable convolutions and cross-correlations + +namespace detail { + +/// \brief Computes the cross-correlation of 1D kernel with rows of an image. +/// \tparam PixelAccum - Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView - Specifies the type of gil view of source image which is to be row correlated +/// with the kernel. +/// \tparam Kernel - Specifies the type of 1D kernel which will be row correlated with source image. +/// \tparam DstView - Specifies the type of gil view which will store the result of row +/// correlation between source image and kernel. +/// \tparam Correlator - Specifies the type of correlator which should be used for performing +/// correlation. +/// \param src_view - Gil view of source image used in correlation. +/// \param kernel - 1D kernel which will be correlated with source image. +/// \param dst_view - Gil view which will store the result of row correlation between "src_view" +/// and "kernel". +/// \param option - Specifies the manner in which boundary pixels of "dst_view" should be computed. +/// \param correlator - Correlator which will be used for performing correlation. +template +< + typename PixelAccum, + typename SrcView, + typename Kernel, + typename DstView, + typename Correlator +> +void correlate_rows_impl( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option, + Correlator correlator) +{ + BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); + BOOST_ASSERT(kernel.size() != 0); + + if(kernel.size() == 1) + { + // Reduces to a multiplication + view_multiplies_scalar(src_view, *kernel.begin(), dst_view); + return; + } + + using src_pixel_ref_t = typename pixel_proxy::type; + using dst_pixel_ref_t = typename pixel_proxy::type; + using x_coord_t = typename SrcView::x_coord_t; + using y_coord_t = typename SrcView::y_coord_t; + + x_coord_t const width = src_view.width(); + y_coord_t const height = src_view.height(); + if (width == 0) + return; + + PixelAccum acc_zero; + pixel_zeros_t()(acc_zero); + if (option == boundary_option::output_ignore || option == boundary_option::output_zero) + { + typename DstView::value_type dst_zero; + pixel_assigns_t()(acc_zero, dst_zero); + if (width < static_cast(kernel.size())) + { + if (option == boundary_option::output_zero) + fill_pixels(dst_view, dst_zero); + } + else + { + std::vector buffer(width); + for (y_coord_t y = 0; y < height; ++y) + { + assign_pixels(src_view.row_begin(y), src_view.row_end(y), &buffer.front()); + typename DstView::x_iterator it_dst = dst_view.row_begin(y); + if (option == boundary_option::output_zero) + std::fill_n(it_dst, kernel.left_size(), dst_zero); + it_dst += kernel.left_size(); + correlator(&buffer.front(), &buffer.front() + width + 1 - kernel.size(), + kernel.begin(), it_dst); + it_dst += width + 1 - kernel.size(); + if (option == boundary_option::output_zero) + std::fill_n(it_dst, kernel.right_size(), dst_zero); + } + } + } + else + { + std::vector buffer(width + kernel.size() - 1); + for (y_coord_t y = 0; y < height; ++y) + { + PixelAccum *it_buffer = &buffer.front(); + if (option == boundary_option::extend_padded) + { + assign_pixels( + src_view.row_begin(y) - kernel.left_size(), + src_view.row_end(y) + kernel.right_size(), + it_buffer); + } + else if (option == boundary_option::extend_zero) + { + std::fill_n(it_buffer, kernel.left_size(), acc_zero); + it_buffer += kernel.left_size(); + assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer); + it_buffer += width; + std::fill_n(it_buffer, kernel.right_size(), acc_zero); + } + else if (option == boundary_option::extend_constant) + { + PixelAccum filler; + pixel_assigns_t()(*src_view.row_begin(y), filler); + std::fill_n(it_buffer, kernel.left_size(), filler); + it_buffer += kernel.left_size(); + assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer); + it_buffer += width; + pixel_assigns_t()(src_view.row_end(y)[-1], filler); + std::fill_n(it_buffer, kernel.right_size(), filler); + } + + correlator( + &buffer.front(), &buffer.front() + width, + kernel.begin(), + dst_view.row_begin(y)); + } + } +} + +/// \brief Provides functionality for performing 1D correlation between the kernel and a buffer +/// storing row pixels of source image. Kernel size is to be provided through constructor for all +/// instances. +template +class correlator_n +{ +public: + correlator_n(std::size_t size) : size_(size) {} + + template + void operator()( + SrcIterator src_begin, + SrcIterator src_end, + KernelIterator kernel_begin, + DstIterator dst_begin) + { + correlate_pixels_n(src_begin, src_end, kernel_begin, size_, dst_begin); + } + +private: + std::size_t size_{0}; +}; + +/// \brief Provides functionality for performing 1D correlation between the kernel and a buffer +/// storing row pixels of source image. Kernel size is a template parameter and must be +/// compulsorily specified while using. +template +struct correlator_k +{ + template + void operator()( + SrcIterator src_begin, + SrcIterator src_end, + KernelIterator kernel_begin, + DstIterator dst_begin) + { + correlate_pixels_k(src_begin, src_end, kernel_begin, dst_begin); + } +}; + +} // namespace detail + +/// \ingroup ImageAlgorithms +/// \brief Correlate 1D variable-size kernel along the rows of image. +/// \tparam PixelAccum Specifies tha data type which will be used while creating buffer container +/// which is utilized for holding source image pixels after applying appropriate boundary +/// manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be row correlated with source image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void correlate_rows( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + detail::correlate_rows_impl( + src_view, kernel, dst_view, option, detail::correlator_n(kernel.size())); +} + +/// \ingroup ImageAlgorithms +/// \brief Correlates 1D variable-size kernel along the columns of image. +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be column correlated with source +/// image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void correlate_cols( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + correlate_rows( + transposed_view(src_view), kernel, transposed_view(dst_view), option); +} + +/// \ingroup ImageAlgorithms +/// \brief Convolves 1D variable-size kernel along the rows of image. +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be row convoluted with source image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void convolve_rows( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + correlate_rows(src_view, reverse_kernel(kernel), dst_view, option); +} + +/// \ingroup ImageAlgorithms +/// \brief Convolves 1D variable-size kernel along the columns of image. +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be column convoluted with source +/// image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void convolve_cols( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + convolve_rows( + transposed_view(src_view), kernel, transposed_view(dst_view), option); +} + +/// \ingroup ImageAlgorithms +/// \brief Correlate 1D fixed-size kernel along the rows of image. +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be row correlated with source image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void correlate_rows_fixed( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + using correlator = detail::correlator_k; + detail::correlate_rows_impl(src_view, kernel, dst_view, option, correlator{}); +} + +/// \ingroup ImageAlgorithms +/// \brief Correlate 1D fixed-size kernel along the columns of image +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be column correlated with source +/// image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void correlate_cols_fixed( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + correlate_rows_fixed( + transposed_view(src_view), kernel, transposed_view(dst_view), option); +} + +/// \ingroup ImageAlgorithms +/// \brief Convolve 1D fixed-size kernel along the rows of image +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be row convolved with source image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void convolve_rows_fixed( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + correlate_rows_fixed(src_view, reverse_kernel(kernel), dst_view, option); +} + +/// \ingroup ImageAlgorithms +/// \brief Convolve 1D fixed-size kernel along the columns of image +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be column convolved with source +/// image. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void convolve_cols_fixed( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + convolve_rows_fixed( + transposed_view(src_view), kernel, transposed_view(dst_view), option); +} + +namespace detail +{ + +/// \ingroup ImageAlgorithms +/// \brief Convolve 1D variable-size kernel along both rows and columns of image +/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container +/// utilized for holding source image pixels after applying appropriate boundary manipulations. +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 1D kernel which will be used for 1D row and column +/// convolution. +/// \tparam DstView Models MutableImageViewConcept +template +BOOST_FORCEINLINE +void convolve_1d( + SrcView const& src_view, + Kernel const& kernel, + DstView const& dst_view, + boundary_option option = boundary_option::extend_zero) +{ + convolve_rows(src_view, kernel, dst_view, option); + convolve_cols(dst_view, kernel, dst_view, option); +} + +template +void convolve_2d_impl(SrcView const& src_view, DstView const& dst_view, Kernel const& kernel) +{ + int flip_ker_row, flip_ker_col, row_boundary, col_boundary; + float aux_total; + for (std::ptrdiff_t view_row = 0; view_row < src_view.height(); ++view_row) + { + for (std::ptrdiff_t view_col = 0; view_col < src_view.width(); ++view_col) + { + aux_total = 0.0f; + for (std::size_t kernel_row = 0; kernel_row < kernel.size(); ++kernel_row) + { + flip_ker_row = kernel.size() - 1 - kernel_row; // row index of flipped kernel + + for (std::size_t kernel_col = 0; kernel_col < kernel.size(); ++kernel_col) + { + flip_ker_col = kernel.size() - 1 - kernel_col; // column index of flipped kernel + + // index of input signal, used for checking boundary + row_boundary = view_row + (kernel.center_y() - flip_ker_row); + col_boundary = view_col + (kernel.center_x() - flip_ker_col); + + // ignore input samples which are out of bound + if (row_boundary >= 0 && row_boundary < src_view.height() && + col_boundary >= 0 && col_boundary < src_view.width()) + { + aux_total += + src_view(col_boundary, row_boundary)[0] * + kernel.at(flip_ker_col, flip_ker_row); + } + } + } + dst_view(view_col, view_row) = aux_total; + } + } +} + +/// \ingroup ImageAlgorithms +/// \brief convolve_2d can only use convolve_option_extend_zero as convolve_boundary_option +/// this is the default option and cannot be changed for now +/// (In future there are plans to improve the algorithm and allow user to use other options as well) +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel Specifies the type of 2D kernel which will be used while convolution. +/// \tparam DstView Models MutableImageViewConcept +template +void convolve_2d(SrcView const& src_view, Kernel const& kernel, DstView const& dst_view) +{ + BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); + BOOST_ASSERT(kernel.size() != 0); + + gil_function_requires>(); + gil_function_requires>(); + static_assert(color_spaces_are_compatible + < + typename color_space_type::type, + typename color_space_type::type + >::value, "Source and destination views must have pixels with the same color space"); + + for (std::size_t i = 0; i < src_view.num_channels(); i++) + { + detail::convolve_2d_impl( + nth_channel_view(src_view, i), + nth_channel_view(dst_view, i), + kernel + ); + } +} + +}}} // namespace boost::gil::detail + +#endif diff --git a/include/boost/gil/image_processing/filter.hpp b/include/boost/gil/image_processing/filter.hpp index 7b960731a9..1fc4902323 100644 --- a/include/boost/gil/image_processing/filter.hpp +++ b/include/boost/gil/image_processing/filter.hpp @@ -1,5 +1,6 @@ // // Copyright 2019 Miral Shah +// Copyright 2021 Pranam Lashkari // // Use, modification and distribution are subject to the Boost Software License, // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -9,19 +10,17 @@ #ifndef BOOST_GIL_IMAGE_PROCESSING_FILTER_HPP #define BOOST_GIL_IMAGE_PROCESSING_FILTER_HPP -#include -#include -#include +#include + +#include #include #include +#include #include #include - - - namespace boost { namespace gil { template diff --git a/include/boost/gil/image_processing/harris.hpp b/include/boost/gil/image_processing/harris.hpp index c5deedda99..56e0cdb30e 100644 --- a/include/boost/gil/image_processing/harris.hpp +++ b/include/boost/gil/image_processing/harris.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include namespace boost { namespace gil { /// \defgroup CornerDetectionAlgorithms @@ -44,7 +44,7 @@ void compute_harris_responses( " tensor from the same image"); } - auto const window_length = weights.size(); + std::ptrdiff_t const window_length = weights.size(); auto const width = m11.width(); auto const height = m11.height(); auto const half_length = window_length / 2; diff --git a/include/boost/gil/image_processing/hessian.hpp b/include/boost/gil/image_processing/hessian.hpp index 2c8c3adeb1..4d037e26a0 100644 --- a/include/boost/gil/image_processing/hessian.hpp +++ b/include/boost/gil/image_processing/hessian.hpp @@ -1,9 +1,16 @@ +// +// Copyright 2019 Olzhas Zhumabek +// Copyright 2021 Scramjet911 <36035352+Scramjet911@users.noreply.github.com> +// Use, modification and distribution are subject to 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) + #ifndef BOOST_GIL_IMAGE_PROCESSING_HESSIAN_HPP #define BOOST_GIL_IMAGE_PROCESSING_HESSIAN_HPP #include #include -#include +#include #include namespace boost { namespace gil { @@ -50,9 +57,9 @@ inline void compute_hessian_responses( auto ddxx_i = channel_t(); auto ddyy_i = channel_t(); auto dxdy_i = channel_t(); - for (typename OutputView::coord_t w_y = 0; w_y < weights.size(); ++w_y) + for (typename OutputView::coord_t w_y = 0; w_y < static_cast(weights.size()); ++w_y) { - for (typename OutputView::coord_t w_x = 0; w_x < weights.size(); ++w_x) + for (typename OutputView::coord_t w_x = 0; w_x < static_cast(weights.size()); ++w_x) { ddxx_i += ddxx(x + w_x - center, y + w_y - center) .at(std::integral_constant{}) * weights.at(w_x, w_y); diff --git a/include/boost/gil/image_processing/histogram_equalization.hpp b/include/boost/gil/image_processing/histogram_equalization.hpp new file mode 100644 index 0000000000..6ce3c5ef34 --- /dev/null +++ b/include/boost/gil/image_processing/histogram_equalization.hpp @@ -0,0 +1,150 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// +#ifndef BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_EQUALIZATION_HPP +#define BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_EQUALIZATION_HPP + +#include +#include + +#include +#include +#include + +namespace boost { namespace gil { + +///////////////////////////////////////// +/// Histogram Equalization(HE) +///////////////////////////////////////// +/// \defgroup HE HE +/// \brief Contains implementation and description of the algorithm used to compute +/// global histogram equalization of input images. +/// +/// Algorithm :- +/// 1. If histogram A is to be equalized compute the cumulative histogram of A. +/// 2. Let CFD(A) refer to the cumulative histogram of A +/// 3. For a uniform histogram A', CDF(A') = A' +/// 4. We need to transfrom A to A' such that +/// 5. CDF(A') = CDF(A) => A' = CDF(A) +/// 6. Hence the pixel transform , px => histogram_of_ith_channel[px]. +/// + +/// \fn histogram_equalization +/// \ingroup HE +/// \tparam SrcKeyType Key Type of input histogram +/// @param src_hist INPUT Input source histogram +/// \brief Overload for histogram equalization algorithm, takes in a single source histogram +/// and returns the color map used for histogram equalization. +/// +template +auto histogram_equalization(histogram const& src_hist) + -> std::map +{ + histogram dst_hist; + return histogram_equalization(src_hist, dst_hist); +} + +/// \overload histogram_equalization +/// \ingroup HE +/// \tparam SrcKeyType Key Type of input histogram +/// \tparam DstKeyType Key Type of output histogram +/// @param src_hist INPUT source histogram +/// @param dst_hist OUTPUT Output histogram +/// \brief Overload for histogram equalization algorithm, takes in both source histogram & +/// destination histogram and returns the color map used for histogram equalization +/// as well as transforming the destination histogram. +/// +template +auto histogram_equalization(histogram const& src_hist, histogram& dst_hist) + -> std::map +{ + static_assert( + std::is_integral::value && + std::is_integral::value, + "Source and destination histogram types are not appropriate"); + + using value_t = typename histogram::value_type; + dst_hist.clear(); + double sum = src_hist.sum(); + SrcKeyType min_key = std::numeric_limits::min(); + SrcKeyType max_key = std::numeric_limits::max(); + auto cumltv_srchist = cumulative_histogram(src_hist); + std::map color_map; + std::for_each(cumltv_srchist.begin(), cumltv_srchist.end(), [&](value_t const& v) { + DstKeyType trnsfrmd_key = + static_cast((v.second * (max_key - min_key)) / sum + min_key); + color_map[std::get<0>(v.first)] = trnsfrmd_key; + }); + std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) { + dst_hist[color_map[std::get<0>(v.first)]] += v.second; + }); + return color_map; +} + +/// \overload histogram_equalization +/// \ingroup HE +/// @param src_view INPUT source image view +/// @param dst_view OUTPUT Output image view +/// @param bin_width INPUT Histogram bin width +/// @param mask INPUT Specify is mask is to be used +/// @param src_mask INPUT Mask vector over input image +/// \brief Overload for histogram equalization algorithm, takes in both source & destination +/// image views and histogram equalizes the input image. +/// +template +void histogram_equalization( + SrcView const& src_view, + DstView const& dst_view, + std::size_t bin_width = 1, + bool mask = false, + std::vector> src_mask = {}) +{ + gil_function_requires>(); + gil_function_requires>(); + + static_assert( + color_spaces_are_compatible< + typename color_space_type::type, + typename color_space_type::type>::value, + "Source and destination views must have same color space"); + + // Defining channel type + using source_channel_t = typename channel_type::type; + using dst_channel_t = typename channel_type::type; + using coord_t = typename SrcView::x_coord_t; + + std::size_t const channels = num_channels::value; + coord_t const width = src_view.width(); + coord_t const height = src_view.height(); + std::size_t pixel_max = std::numeric_limits::max(); + std::size_t pixel_min = std::numeric_limits::min(); + + for (std::size_t i = 0; i < channels; i++) + { + histogram h; + fill_histogram(nth_channel_view(src_view, i), h, bin_width, false, false, mask, src_mask); + h.normalize(); + auto h2 = cumulative_histogram(h); + for (std::ptrdiff_t src_y = 0; src_y < height; ++src_y) + { + auto src_it = nth_channel_view(src_view, i).row_begin(src_y); + auto dst_it = nth_channel_view(dst_view, i).row_begin(src_y); + for (std::ptrdiff_t src_x = 0; src_x < width; ++src_x) + { + if (mask && !src_mask[src_y][src_x]) + dst_it[src_x][0] = channel_convert(src_it[src_x][0]); + else + dst_it[src_x][0] = static_cast( + h2[src_it[src_x][0]] * (pixel_max - pixel_min) + pixel_min); + } + } + } +} + +}} //namespace boost::gil + +#endif diff --git a/include/boost/gil/image_processing/histogram_matching.hpp b/include/boost/gil/image_processing/histogram_matching.hpp new file mode 100644 index 0000000000..25107a7de6 --- /dev/null +++ b/include/boost/gil/image_processing/histogram_matching.hpp @@ -0,0 +1,205 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// + +#ifndef BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_MATCHING_HPP +#define BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_MATCHING_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace boost { namespace gil { + +///////////////////////////////////////// +/// Histogram Matching(HM) +///////////////////////////////////////// +/// \defgroup HM HM +/// \brief Contains implementation and description of the algorithm used to compute +/// global histogram matching of input images. +/// +/// Algorithm :- +/// 1. Calculate histogram A(pixel) of input image and G(pixel) of reference image. +/// 2. Compute the normalized cumulative(CDF) histograms of A and G. +/// 3. Match the histograms using transofrmation => CDF(A(px)) = CDF(G(px')) +/// => px' = Inv-CDF (CDF(px)) +/// + +/// \fn histogram_matching +/// \ingroup HM +/// \tparam SrcKeyType Key Type of input histogram +/// @param src_hist INPUT Input source histogram +/// @param ref_hist INPUT Input reference histogram +/// \brief Overload for histogram matching algorithm, takes in a single source histogram & +/// reference histogram and returns the color map used for histogram matching. +/// +template +auto histogram_matching(histogram const& src_hist, histogram const& ref_hist) + -> std::map +{ + histogram dst_hist; + return histogram_matching(src_hist, ref_hist, dst_hist); +} + +/// \overload histogram_matching +/// \ingroup HM +/// \tparam SrcKeyType Key Type of input histogram +/// \tparam RefKeyType Key Type of reference histogram +/// \tparam DstKeyType Key Type of output histogram +/// @param src_hist INPUT source histogram +/// @param ref_hist INPUT reference histogram +/// @param dst_hist OUTPUT Output histogram +/// \brief Overload for histogram matching algorithm, takes in source histogram, reference +/// histogram & destination histogram and returns the color map used for histogram +/// matching as well as transforming the destination histogram. +/// +template +auto histogram_matching( + histogram const& src_hist, + histogram const& ref_hist, + histogram& dst_hist) + -> std::map +{ + static_assert( + std::is_integral::value && + std::is_integral::value && + std::is_integral::value, + "Source, Refernce or Destination histogram type is not appropriate."); + + using value_t = typename histogram::value_type; + dst_hist.clear(); + double src_sum = src_hist.sum(); + double ref_sum = ref_hist.sum(); + auto cumltv_srchist = cumulative_histogram(src_hist); + auto cumltv_refhist = cumulative_histogram(ref_hist); + std::map inverse_mapping; + + std::vector::key_type> src_keys, ref_keys; + src_keys = src_hist.sorted_keys(); + ref_keys = ref_hist.sorted_keys(); + std::ptrdiff_t start = ref_keys.size() - 1; + RefKeyType ref_max; + if (start >= 0) + ref_max = std::get<0>(ref_keys[start]); + + for (std::ptrdiff_t j = src_keys.size() - 1; j >= 0; --j) + { + double src_val = (cumltv_srchist[src_keys[j]] * ref_sum) / src_sum; + while (cumltv_refhist[ref_keys[start]] > src_val && start > 0) + { + start--; + } + if (std::abs(cumltv_refhist[ref_keys[start]] - src_val) > + std::abs(cumltv_refhist(std::min(ref_max, std::get<0>(ref_keys[start + 1]))) - + src_val)) + { + inverse_mapping[std::get<0>(src_keys[j])] = + std::min(ref_max, std::get<0>(ref_keys[start + 1])); + } + else + { + inverse_mapping[std::get<0>(src_keys[j])] = std::get<0>(ref_keys[start]); + } + if (j == 0) + break; + } + std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) { + dst_hist[inverse_mapping[std::get<0>(v.first)]] += v.second; + }); + return inverse_mapping; +} + +/// \overload histogram_matching +/// \ingroup HM +/// @param src_view INPUT source image view +/// @param ref_view INPUT Reference image view +/// @param dst_view OUTPUT Output image view +/// @param bin_width INPUT Histogram bin width +/// @param mask INPUT Specify is mask is to be used +/// @param src_mask INPUT Mask vector over input image +/// @param ref_mask INPUT Mask vector over reference image +/// \brief Overload for histogram matching algorithm, takes in both source, reference & +/// destination image views and histogram matches the input image using the +/// reference image. +/// +template +void histogram_matching( + SrcView const& src_view, + ReferenceView const& ref_view, + DstView const& dst_view, + std::size_t bin_width = 1, + bool mask = false, + std::vector> src_mask = {}, + std::vector> ref_mask = {}) +{ + gil_function_requires>(); + gil_function_requires>(); + gil_function_requires>(); + + static_assert( + color_spaces_are_compatible< + typename color_space_type::type, + typename color_space_type::type>::value, + "Source and reference view must have same color space"); + + static_assert( + color_spaces_are_compatible< + typename color_space_type::type, + typename color_space_type::type>::value, + "Source and destination view must have same color space"); + + // Defining channel type + using source_channel_t = typename channel_type::type; + using ref_channel_t = typename channel_type::type; + using dst_channel_t = typename channel_type::type; + using coord_t = typename SrcView::x_coord_t; + + std::size_t const channels = num_channels::value; + coord_t const width = src_view.width(); + coord_t const height = src_view.height(); + source_channel_t src_pixel_min = std::numeric_limits::min(); + source_channel_t src_pixel_max = std::numeric_limits::max(); + ref_channel_t ref_pixel_min = std::numeric_limits::min(); + ref_channel_t ref_pixel_max = std::numeric_limits::max(); + + for (std::size_t i = 0; i < channels; i++) + { + histogram src_histogram; + histogram ref_histogram; + fill_histogram( + nth_channel_view(src_view, i), src_histogram, bin_width, false, false, mask, src_mask, + std::tuple(src_pixel_min), + std::tuple(src_pixel_max), true); + fill_histogram( + nth_channel_view(ref_view, i), ref_histogram, bin_width, false, false, mask, ref_mask, + std::tuple(ref_pixel_min), std::tuple(ref_pixel_max), + true); + auto inverse_mapping = histogram_matching(src_histogram, ref_histogram); + for (std::ptrdiff_t src_y = 0; src_y < height; ++src_y) + { + auto src_it = nth_channel_view(src_view, i).row_begin(src_y); + auto dst_it = nth_channel_view(dst_view, i).row_begin(src_y); + for (std::ptrdiff_t src_x = 0; src_x < width; ++src_x) + { + if (mask && !src_mask[src_y][src_x]) + dst_it[src_x][0] = src_it[src_x][0]; + else + dst_it[src_x][0] = + static_cast(inverse_mapping[src_it[src_x][0]]); + } + } + } +} + +}} //namespace boost::gil + +#endif diff --git a/include/boost/gil/image_processing/kernel.hpp b/include/boost/gil/image_processing/kernel.hpp new file mode 100644 index 0000000000..98682d2bb3 --- /dev/null +++ b/include/boost/gil/image_processing/kernel.hpp @@ -0,0 +1,362 @@ +// +// Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2019 Miral Shah +// Copyright 2022 Pranam Lashkari +// +// 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 +// + +#ifndef BOOST_GIL_IMAGE_PROCESSING_KERNEL_HPP +#define BOOST_GIL_IMAGE_PROCESSING_KERNEL_HPP + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { namespace gil { + +// Definitions of 1D fixed-size and variable-size kernels and related operations + +namespace detail { + +/// \brief kernel adaptor for one-dimensional cores +/// Core needs to provide size(),begin(),end(),operator[], +/// value_type,iterator,const_iterator,reference,const_reference +template +class kernel_1d_adaptor : public Core +{ +public: + kernel_1d_adaptor() = default; + + explicit kernel_1d_adaptor(std::size_t center) + : center_(center) + { + BOOST_ASSERT(center_ < this->size()); + } + + kernel_1d_adaptor(std::size_t size, std::size_t center) + : Core(size) , center_(center) + { + BOOST_ASSERT(this->size() > 0); + BOOST_ASSERT(center_ < this->size()); // also implies `size() > 0` + } + + kernel_1d_adaptor(kernel_1d_adaptor const& other) + : Core(other), center_(other.center_) + { + BOOST_ASSERT(this->size() > 0); + BOOST_ASSERT(center_ < this->size()); // also implies `size() > 0` + } + + kernel_1d_adaptor& operator=(kernel_1d_adaptor const& other) + { + Core::operator=(other); + center_ = other.center_; + return *this; + } + + std::size_t left_size() const + { + BOOST_ASSERT(center_ < this->size()); + return center_; + } + + std::size_t right_size() const + { + BOOST_ASSERT(center_ < this->size()); + return this->size() - center_ - 1; + } + + auto center() -> std::size_t& + { + BOOST_ASSERT(center_ < this->size()); + return center_; + } + + auto center() const -> std::size_t const& + { + BOOST_ASSERT(center_ < this->size()); + return center_; + } + +private: + std::size_t center_{0}; +}; + +} // namespace detail + +/// \brief variable-size kernel +template > +class kernel_1d : public detail::kernel_1d_adaptor> +{ + using parent_t = detail::kernel_1d_adaptor>; +public: + + kernel_1d() = default; + kernel_1d(std::size_t size, std::size_t center) : parent_t(size, center) {} + + template + kernel_1d(FwdIterator elements, std::size_t size, std::size_t center) + : parent_t(size, center) + { + detail::copy_n(elements, size, this->begin()); + } + + kernel_1d(kernel_1d const& other) : parent_t(other) {} + kernel_1d& operator=(kernel_1d const& other) = default; +}; + +/// \brief static-size kernel +template +class kernel_1d_fixed : public detail::kernel_1d_adaptor> +{ + using parent_t = detail::kernel_1d_adaptor>; +public: + static constexpr std::size_t static_size = Size; + static_assert(static_size > 0, "kernel must have size greater than 0"); + static_assert(static_size % 2 == 1, "kernel size must be odd to ensure validity at the center"); + + kernel_1d_fixed() = default; + explicit kernel_1d_fixed(std::size_t center) : parent_t(center) {} + + template + explicit kernel_1d_fixed(FwdIterator elements, std::size_t center) + : parent_t(center) + { + detail::copy_n(elements, Size, this->begin()); + } + + kernel_1d_fixed(kernel_1d_fixed const& other) : parent_t(other) {} + kernel_1d_fixed& operator=(kernel_1d_fixed const& other) = default; +}; + +// TODO: This data member is odr-used and definition at namespace scope +// is required by C++11. Redundant and deprecated in C++17. +template +constexpr std::size_t kernel_1d_fixed::static_size; + +/// \brief reverse a kernel +template +inline Kernel reverse_kernel(Kernel const& kernel) +{ + Kernel result(kernel); + result.center() = kernel.right_size(); + std::reverse(result.begin(), result.end()); + return result; +} + + +namespace detail { + +template +class kernel_2d_adaptor : public Core +{ +public: + kernel_2d_adaptor() = default; + + explicit kernel_2d_adaptor(std::size_t center_y, std::size_t center_x) + : center_(center_x, center_y) + { + BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); + } + + kernel_2d_adaptor(std::size_t size, std::size_t center_y, std::size_t center_x) + : Core(size * size), square_size(size), center_(center_x, center_y) + { + BOOST_ASSERT(this->size() > 0); + BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); // implies `size() > 0` + } + + kernel_2d_adaptor(kernel_2d_adaptor const& other) + : Core(other), square_size(other.square_size), center_(other.center_.x, other.center_.y) + { + BOOST_ASSERT(this->size() > 0); + BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); // implies `size() > 0` + } + + kernel_2d_adaptor& operator=(kernel_2d_adaptor const& other) + { + Core::operator=(other); + center_.y = other.center_.y; + center_.x = other.center_.x; + square_size = other.square_size; + return *this; + } + + std::size_t upper_size() const + { + BOOST_ASSERT(center_.y < this->size()); + return center_.y; + } + + std::size_t lower_size() const + { + BOOST_ASSERT(center_.y < this->size()); + return this->size() - center_.y - 1; + } + + std::size_t left_size() const + { + BOOST_ASSERT(center_.x < this->size()); + return center_.x; + } + + std::size_t right_size() const + { + BOOST_ASSERT(center_.x < this->size()); + return this->size() - center_.x - 1; + } + + auto center_y() -> std::size_t& + { + BOOST_ASSERT(center_.y < this->size()); + return center_.y; + } + + auto center_y() const -> std::size_t const& + { + BOOST_ASSERT(center_.y < this->size()); + return center_.y; + } + + auto center_x() -> std::size_t& + { + BOOST_ASSERT(center_.x < this->size()); + return center_.x; + } + + auto center_x() const -> std::size_t const& + { + BOOST_ASSERT(center_.x < this->size()); + return center_.x; + } + + std::size_t size() const + { + return square_size; + } + + typename Core::value_type at(std::size_t x, std::size_t y) const + { + if (x >= this->size() || y >= this->size()) + { + throw std::out_of_range("Index out of range"); + } + return this->begin()[y * this->size() + x]; + } + +protected: + std::size_t square_size{0}; + +private: + point center_{0, 0}; +}; + +/// \brief variable-size kernel +template +< + typename T, + typename Allocator = std::allocator +> +class kernel_2d : public detail::kernel_2d_adaptor> +{ + using parent_t = detail::kernel_2d_adaptor>; + +public: + + kernel_2d() = default; + kernel_2d(std::size_t size,std::size_t center_y, std::size_t center_x) + : parent_t(size, center_y, center_x) + {} + + template + kernel_2d(FwdIterator elements, std::size_t size, std::size_t center_y, std::size_t center_x) + : parent_t(static_cast(std::sqrt(size)), center_y, center_x) + { + detail::copy_n(elements, size, this->begin()); + } + + kernel_2d(kernel_2d const& other) : parent_t(other) {} + kernel_2d& operator=(kernel_2d const& other) = default; +}; + +/// \brief static-size kernel +template +class kernel_2d_fixed : + public detail::kernel_2d_adaptor> +{ + using parent_t = detail::kernel_2d_adaptor>; +public: + static constexpr std::size_t static_size = Size; + static_assert(static_size > 0, "kernel must have size greater than 0"); + static_assert(static_size % 2 == 1, "kernel size must be odd to ensure validity at the center"); + + kernel_2d_fixed() + { + this->square_size = Size; + } + + explicit kernel_2d_fixed(std::size_t center_y, std::size_t center_x) : + parent_t(center_y, center_x) + { + this->square_size = Size; + } + + template + explicit kernel_2d_fixed(FwdIterator elements, std::size_t center_y, std::size_t center_x) + : parent_t(center_y, center_x) + { + this->square_size = Size; + detail::copy_n(elements, Size * Size, this->begin()); + } + + kernel_2d_fixed(kernel_2d_fixed const& other) : parent_t(other) {} + kernel_2d_fixed& operator=(kernel_2d_fixed const& other) = default; +}; + +// TODO: This data member is odr-used and definition at namespace scope +// is required by C++11. Redundant and deprecated in C++17. +template +constexpr std::size_t kernel_2d_fixed::static_size; + +template +inline Kernel reverse_kernel_2d(Kernel const& kernel) +{ + Kernel result(kernel); + result.center_x() = kernel.lower_size(); + result.center_y() = kernel.right_size(); + std::reverse(result.begin(), result.end()); + return result; +} + + +/// \brief reverse a kernel_2d +template +inline kernel_2d reverse_kernel(kernel_2d const& kernel) +{ + return reverse_kernel_2d(kernel); +} + +/// \brief reverse a kernel_2d +template +inline kernel_2d_fixed reverse_kernel(kernel_2d_fixed const& kernel) +{ + return reverse_kernel_2d(kernel); +} + +} //namespace detail + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/image_processing/morphology.hpp b/include/boost/gil/image_processing/morphology.hpp new file mode 100644 index 0000000000..babe1acb89 --- /dev/null +++ b/include/boost/gil/image_processing/morphology.hpp @@ -0,0 +1,298 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// Use, modification and distribution are subject to 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) +// + +#ifndef BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP +#define BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP + +#include +#include +#include + +namespace boost { namespace gil { namespace detail { + +enum class morphological_operation +{ + dilation, + erosion, +}; + +/// \addtogroup ImageProcessing +/// @{ + +/// \brief Implements morphological operations at pixel level.This function +/// compares neighbouring pixel values according to the kernel and choose +/// minimum/mamximum neighbouring pixel value and assigns it to the pixel under +/// consideration. +/// \param src_view - Source/Input image view. +/// \param dst_view - View which stores the final result of operations performed by this function. +/// \param kernel - Kernel matrix/structuring element containing 0's and 1's +/// which will be used for applying the required morphological operation. +/// \param identifier - Indicates the type of morphological operation to be applied. +/// \tparam SrcView type of source image. +/// \tparam DstView type of output image. +/// \tparam Kernel type of structuring element. +template +void morph_impl(SrcView const& src_view, DstView const& dst_view, Kernel const& kernel, + morphological_operation identifier) +{ + std::ptrdiff_t flip_ker_row, flip_ker_col, row_boundary, col_boundary; + typename channel_type::type target_element; + for (std::ptrdiff_t view_row = 0; view_row < src_view.height(); ++view_row) + { + for (std::ptrdiff_t view_col = 0; view_col < src_view.width(); ++view_col) + { + target_element = src_view(view_col, view_row); + for (std::size_t kernel_row = 0; kernel_row < kernel.size(); ++kernel_row) + { + flip_ker_row = kernel.size() - 1 - kernel_row; // row index of flipped kernel + + for (std::size_t kernel_col = 0; kernel_col < kernel.size(); ++kernel_col) + { + flip_ker_col = kernel.size() - 1 - kernel_col; // column index of flipped kernel + + // We ensure that we consider only those pixels which are overlapped + // on a non-zero kernel_element as + if (kernel.at(flip_ker_row, flip_ker_col) == 0) + { + continue; + } + // index of input signal, used for checking boundary + row_boundary = view_row + (kernel.center_y() - flip_ker_row); + col_boundary = view_col + (kernel.center_x() - flip_ker_col); + + // ignore input samples which are out of bound + if (row_boundary >= 0 && row_boundary < src_view.height() && + col_boundary >= 0 && col_boundary < src_view.width()) + { + + if (identifier == morphological_operation::dilation) + { + target_element = + (std::max)(src_view(col_boundary, row_boundary)[0], target_element); + } + else if (identifier == morphological_operation::erosion) + { + target_element = + (std::min)(src_view(col_boundary, row_boundary)[0], target_element); + } + } + } + } + dst_view(view_col, view_row) = target_element; + } + } +} + +/// \brief Checks feasibility of the desired operation and passes parameter +/// values to the function morph_impl alongwith individual channel views of the +/// input image. +/// \param src_view - Source/Input image view. +/// \param dst_view - View which stores the final result of operations performed by this function. +/// \param kernel - Kernel matrix/structuring element containing 0's and 1's +/// which will be used for applying the required morphological operation. +/// \param identifier - Indicates the type of morphological operation to be applied. +/// \tparam SrcView type of source image. +/// \tparam DstView type of output image. +/// \tparam Kernel type of structuring element. +template +void morph(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat, + morphological_operation identifier) +{ + BOOST_ASSERT(ker_mat.size() != 0 && src_view.dimensions() == dst_view.dimensions()); + gil_function_requires>(); + gil_function_requires>(); + + gil_function_requires::type, + typename color_space_type::type>>(); + + gil::image intermediate_img(src_view.dimensions()); + + for (std::size_t i = 0; i < src_view.num_channels(); i++) + { + morph_impl(nth_channel_view(src_view, i), nth_channel_view(view(intermediate_img), i), + ker_mat, identifier); + } + copy_pixels(view(intermediate_img), dst_view); +} + +/// \brief Calculates the difference between pixel values of first image_view +/// and second image_view. +/// \param src_view1 - First parameter for subtraction of views. +/// \param src_view2 - Second parameter for subtraction of views. +/// \param diff_view - View containing result of the subtraction of second view from +/// the first view. +/// \tparam SrcView type of source/Input images used for subtraction. +/// \tparam DiffView type of image view containing the result of subtraction. +template +void difference_impl(SrcView const& src_view1, SrcView const& src_view2, DiffView const& diff_view) +{ + for (std::ptrdiff_t view_row = 0; view_row < src_view1.height(); ++view_row) + for (std::ptrdiff_t view_col = 0; view_col < src_view1.width(); ++view_col) + diff_view(view_col, view_row) = + src_view1(view_col, view_row) - src_view2(view_col, view_row); +} + +/// \brief Passes parameter values to the function 'difference_impl' alongwith +/// individual channel views of input images. +/// \param src_view1 - First parameter for subtraction of views. +/// \param src_view2 - Second parameter for subtraction of views. +/// \param diff_view - View containing result of the subtraction of second view from the first view. +/// \tparam SrcView type of source/Input images used for subtraction. +/// \tparam DiffView type of image view containing the result of subtraction. +template +void difference(SrcView const& src_view1, SrcView const& src_view2, DiffView const& diff_view) +{ + gil_function_requires>(); + gil_function_requires>(); + + gil_function_requires::type, typename color_space_type::type>>(); + + for (std::size_t i = 0; i < src_view1.num_channels(); i++) + { + difference_impl(nth_channel_view(src_view1, i), nth_channel_view(src_view2, i), + nth_channel_view(diff_view, i)); + } +} +} // namespace detail + +/// \brief Applies morphological dilation on the input image view using given +/// structuring element. It gives the maximum overlapped value to the pixel +/// overlapping with the center element of structuring element. \param src_view +/// - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying dilation. +/// \param iterations - Specifies the number of times dilation is to be applied on the input image +/// view. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void dilate(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat, + int iterations) +{ + copy_pixels(src_view, int_op_view); + for (int i = 0; i < iterations; ++i) + morph(int_op_view, int_op_view, ker_mat, detail::morphological_operation::dilation); +} + +/// \brief Applies morphological erosion on the input image view using given +/// structuring element. It gives the minimum overlapped value to the pixel +/// overlapping with the center element of structuring element. +/// \param src_view - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying erosion. +/// \param iterations - Specifies the number of times erosion is to be applied on the input +/// image view. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void erode(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat, + int iterations) +{ + copy_pixels(src_view, int_op_view); + for (int i = 0; i < iterations; ++i) + morph(int_op_view, int_op_view, ker_mat, detail::morphological_operation::erosion); +} + +/// \brief Performs erosion and then dilation on the input image view . This +/// operation is utilized for removing noise from images. +/// \param src_view - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying the opening operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void opening(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat) +{ + erode(src_view, int_op_view, ker_mat, 1); + dilate(int_op_view, int_op_view, ker_mat, 1); +} + +/// \brief Performs dilation and then erosion on the input image view which is +/// exactly opposite to the opening operation . Closing operation can be +/// utilized for closing small holes inside foreground objects. +/// \param src_view - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying the closing operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void closing(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat) +{ + dilate(src_view, int_op_view, ker_mat, 1); + erode(int_op_view, int_op_view, ker_mat, 1); +} + +/// \brief Calculates the difference between image views generated after +/// applying dilation dilation and erosion on an image . The resultant image +/// will look like the outline of the object(s) present in the image. +/// \param src_view - Source/input image view. +/// \param dst_view - Destination view which will store the final result of morphological +/// gradient operation. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which +/// will be used for applying the morphological gradient operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam DstView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void morphological_gradient(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat) +{ + using namespace boost::gil; + gil::image int_dilate(src_view.dimensions()), + int_erode(src_view.dimensions()); + dilate(src_view, view(int_dilate), ker_mat, 1); + erode(src_view, view(int_erode), ker_mat, 1); + difference(view(int_dilate), view(int_erode), dst_view); +} + +/// \brief Calculates the difference between input image view and the view +/// generated by opening operation on the input image view. +/// \param src_view - Source/input image view. +/// \param dst_view - Destination view which will store the final result of top hat operation. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying the top hat operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam DstView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void top_hat(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat) +{ + using namespace boost::gil; + gil::image int_opening(src_view.dimensions()); + opening(src_view, view(int_opening), ker_mat); + difference(src_view, view(int_opening), dst_view); +} + +/// \brief Calculates the difference between closing of the input image and +/// input image. +/// \param src_view - Source/input image view. +/// \param dst_view - Destination view which will store the final result of black hat operation. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's +/// which will be used for applying the black hat operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam DstView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void black_hat(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat) +{ + using namespace boost::gil; + gil::image int_closing(src_view.dimensions()); + closing(src_view, view(int_closing), ker_mat); + difference(view(int_closing), src_view, dst_view); +} +/// @} +}} // namespace boost::gil +#endif // BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP diff --git a/include/boost/gil/image_processing/numeric.hpp b/include/boost/gil/image_processing/numeric.hpp index 2109c555f4..5500c7a96e 100644 --- a/include/boost/gil/image_processing/numeric.hpp +++ b/include/boost/gil/image_processing/numeric.hpp @@ -1,5 +1,6 @@ // // Copyright 2019 Olzhas Zhumabek +// Copyright 2021 Pranam Lashkari // // Use, modification and distribution are subject to the Boost Software License, // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -8,8 +9,8 @@ #ifndef BOOST_GIL_IMAGE_PROCESSING_NUMERIC_HPP #define BOOST_GIL_IMAGE_PROCESSING_NUMERIC_HPP -#include -#include +#include +#include #include #include #include @@ -48,7 +49,7 @@ inline double lanczos(double x, std::ptrdiff_t a) if (0 <= x && x <= 0) return 1; - if (-a < x && x < a) + if (static_cast(-a) < x && x < static_cast(a)) return normalized_sinc(x) / normalized_sinc(x / static_cast(a)); return 0; @@ -88,7 +89,8 @@ inline void compute_tensor_entries( /// in which all entries will be equal to /// \code 1 / (dst.size()) \endcode template > -inline detail::kernel_2d generate_normalized_mean(std::size_t side_length) +inline auto generate_normalized_mean(std::size_t side_length) + -> detail::kernel_2d { if (side_length % 2 != 1) throw std::invalid_argument("kernel dimensions should be odd and equal"); @@ -107,7 +109,8 @@ inline detail::kernel_2d generate_normalized_mean(std::size_t side /// /// Fills supplied view with 1s (ones) template > -inline detail::kernel_2d generate_unnormalized_mean(std::size_t side_length) +inline auto generate_unnormalized_mean(std::size_t side_length) + -> detail::kernel_2d { if (side_length % 2 != 1) throw std::invalid_argument("kernel dimensions should be odd and equal"); @@ -126,28 +129,33 @@ inline detail::kernel_2d generate_unnormalized_mean(std::size_t si /// Fills supplied view with values taken from Gaussian distribution. See /// https://en.wikipedia.org/wiki/Gaussian_blur template > -inline detail::kernel_2d generate_gaussian_kernel(std::size_t side_length, double sigma) +inline auto generate_gaussian_kernel(std::size_t side_length, double sigma) + -> detail::kernel_2d { if (side_length % 2 != 1) throw std::invalid_argument("kernel dimensions should be odd and equal"); - const double denominator = 2 * boost::gil::detail::pi * sigma * sigma; - auto middle = side_length / 2; + auto const middle = side_length / 2; std::vector values(side_length * side_length); + T sum{0}; for (std::size_t y = 0; y < side_length; ++y) { for (std::size_t x = 0; x < side_length; ++x) { - const auto delta_x = middle > x ? middle - x : x - middle; - const auto delta_y = middle > y ? middle - y : y - middle; - const double power = (delta_x * delta_x + delta_y * delta_y) / (2 * sigma * sigma); + const auto delta_x = x - middle; + const auto delta_y = y - middle; + const auto power = static_cast(delta_x * delta_x + delta_y * delta_y) / (2 * sigma * sigma); const double nominator = std::exp(-power); - const float value = static_cast(nominator / denominator); + const auto value = static_cast(nominator / denominator); values[y * side_length + x] = value; + sum += value; } } + // normalize so that Gaussian kernel sums up to 1. + std::transform(values.begin(), values.end(), values.begin(), [&sum](const auto & v) { return v/sum; }); + return detail::kernel_2d(values.begin(), values.size(), middle, middle); } @@ -159,7 +167,8 @@ inline detail::kernel_2d generate_gaussian_kernel(std::size_t side /// to obtain the desired degree). /// https://www.researchgate.net/publication/239398674_An_Isotropic_3_3_Image_Gradient_Operator template > -inline detail::kernel_2d generate_dx_sobel(unsigned int degree = 1) +inline auto generate_dx_sobel(unsigned int degree = 1) + -> detail::kernel_2d { switch (degree) { @@ -189,7 +198,8 @@ inline detail::kernel_2d generate_dx_sobel(unsigned int degree = 1 /// to obtain the desired degree). /// https://www.researchgate.net/profile/Hanno_Scharr/publication/220955743_Optimal_Filters_for_Extended_Optical_Flow/links/004635151972eda98f000000/Optimal-Filters-for-Extended-Optical-Flow.pdf template > -inline detail::kernel_2d generate_dx_scharr(unsigned int degree = 1) +inline auto generate_dx_scharr(unsigned int degree = 1) + -> detail::kernel_2d { switch (degree) { @@ -219,7 +229,8 @@ inline detail::kernel_2d generate_dx_scharr(unsigned int degree = /// to obtain the desired degree). /// https://www.researchgate.net/publication/239398674_An_Isotropic_3_3_Image_Gradient_Operator template > -inline detail::kernel_2d generate_dy_sobel(unsigned int degree = 1) +inline auto generate_dy_sobel(unsigned int degree = 1) + -> detail::kernel_2d { switch (degree) { @@ -249,7 +260,8 @@ inline detail::kernel_2d generate_dy_sobel(unsigned int degree = 1 /// to obtain the desired degree). /// https://www.researchgate.net/profile/Hanno_Scharr/publication/220955743_Optimal_Filters_for_Extended_Optical_Flow/links/004635151972eda98f000000/Optimal-Filters-for-Extended-Optical-Flow.pdf template > -inline detail::kernel_2d generate_dy_scharr(unsigned int degree = 1) +inline auto generate_dy_scharr(unsigned int degree = 1) + -> detail::kernel_2d { switch (degree) { diff --git a/include/boost/gil/image_processing/threshold.hpp b/include/boost/gil/image_processing/threshold.hpp index 82ea4d0eca..b3b56356fc 100644 --- a/include/boost/gil/image_processing/threshold.hpp +++ b/include/boost/gil/image_processing/threshold.hpp @@ -1,5 +1,6 @@ // // Copyright 2019 Miral Shah +// Copyright 2021 Pranam Lashkari // // Use, modification and distribution are subject to the Boost Software License, // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -19,8 +20,8 @@ #include #include -#include -#include +#include +#include #include namespace boost { namespace gil { diff --git a/include/boost/gil/image_view_factory.hpp b/include/boost/gil/image_view_factory.hpp index 7d7774c526..73c2da6ecf 100644 --- a/include/boost/gil/image_view_factory.hpp +++ b/include/boost/gil/image_view_factory.hpp @@ -54,9 +54,11 @@ struct dynamic_xy_step_transposed_type /// \ingroup ImageViewConstructors /// \brief Constructing image views from raw interleaved pixel data template -typename type_from_x_iterator::view_t -interleaved_view(std::size_t width, std::size_t height, - Iterator pixels, std::ptrdiff_t rowsize_in_bytes) { +auto interleaved_view( + std::size_t width, std::size_t height, + Iterator pixels, std::ptrdiff_t rowsize_in_bytes) + -> typename type_from_x_iterator::view_t +{ using RView = typename type_from_x_iterator::view_t; return RView(width, height, typename RView::locator(pixels, rowsize_in_bytes)); } @@ -64,8 +66,9 @@ interleaved_view(std::size_t width, std::size_t height, /// \ingroup ImageViewConstructors /// \brief Constructing image views from raw interleaved pixel data template -auto interleaved_view(point dim, Iterator pixels, - std::ptrdiff_t rowsize_in_bytes) +auto interleaved_view( + point dim, Iterator pixels, + std::ptrdiff_t rowsize_in_bytes) -> typename type_from_x_iterator::view_t { using RView = typename type_from_x_iterator::view_t; @@ -77,23 +80,32 @@ auto interleaved_view(point dim, Iterator pixels, ///////////////////////////// namespace detail { - template struct channel_pointer_type_impl; + template + struct channel_pointer_type_impl; - template struct channel_pointer_type_impl { - using type = typename channel_type::type *; + template + struct channel_pointer_type_impl + { + using type = typename channel_type::type*; }; - template struct channel_pointer_type_impl { - using type = const typename channel_type::type *; + + template + struct channel_pointer_type_impl + { + using type = const typename channel_type::type*; }; - template struct channel_pointer_type + template + struct channel_pointer_type : public channel_pointer_type_impl::value> {}; } // namespace detail /// \ingroup ImageViewConstructors /// \brief Returns C pointer to the the channels of an interleaved homogeneous view. template -typename detail::channel_pointer_type::type interleaved_view_get_raw_data(const HomogeneousView& view) { +auto interleaved_view_get_raw_data(HomogeneousView const& view) + -> typename detail::channel_pointer_type::type +{ static_assert(!is_planar::value && view_is_basic::value, ""); static_assert(std::is_pointer::value, ""); @@ -103,7 +115,9 @@ typename detail::channel_pointer_type::type interleaved_view_ge /// \ingroup ImageViewConstructors /// \brief Returns C pointer to the the channels of a given color plane of a planar homogeneous view. template -typename detail::channel_pointer_type::type planar_view_get_raw_data(const HomogeneousView& view, int plane_index) { +auto planar_view_get_raw_data(HomogeneousView const& view, int plane_index) + -> typename detail::channel_pointer_type::type +{ static_assert(is_planar::value && view_is_basic::value, ""); return dynamic_at_c(view.row_begin(0),plane_index); } @@ -167,15 +181,18 @@ struct color_converted_view_type : public detail::_color_converted_view_type -inline typename color_converted_view_type::type color_converted_view(const View& src,CC cc) { +inline auto color_converted_view(View const& src,CC cc) + -> typename color_converted_view_type::type +{ return color_converted_view_type::make(src,cc); } /// \ingroup ImageViewTransformationsColorConvert /// \brief overload of generic color_converted_view with the default color-converter template -inline typename color_converted_view_type::type -color_converted_view(const View& src) { +inline auto color_converted_view(View const& src) + -> typename color_converted_view_type::type +{ return color_converted_view(src,default_color_converter()); } @@ -185,7 +202,9 @@ color_converted_view(const View& src) { /// \ingroup ImageViewTransformationsFlipUD template -inline typename dynamic_y_step_type::type flipped_up_down_view(const View& src) { +inline auto flipped_up_down_view(View const& src) + -> typename dynamic_y_step_type::type +{ using RView = typename dynamic_y_step_type::type; return RView(src.dimensions(),typename RView::xy_locator(src.xy_at(0,src.height()-1),-1)); } @@ -196,7 +215,9 @@ inline typename dynamic_y_step_type::type flipped_up_down_view(const View& /// \ingroup ImageViewTransformationsFlipLR template -inline typename dynamic_x_step_type::type flipped_left_right_view(const View& src) { +inline auto flipped_left_right_view(View const& src) + -> typename dynamic_x_step_type::type +{ using RView = typename dynamic_x_step_type::type; return RView(src.dimensions(),typename RView::xy_locator(src.xy_at(src.width()-1,0),-1,1)); } @@ -207,7 +228,9 @@ inline typename dynamic_x_step_type::type flipped_left_right_view(const Vi /// \ingroup ImageViewTransformationsTransposed template -inline typename dynamic_xy_step_transposed_type::type transposed_view(const View& src) { +inline auto transposed_view(View const& src) + -> typename dynamic_xy_step_transposed_type::type +{ using RView = typename dynamic_xy_step_transposed_type::type; return RView(src.height(),src.width(),typename RView::xy_locator(src.xy_at(0,0),1,1,true)); } @@ -218,7 +241,9 @@ inline typename dynamic_xy_step_transposed_type::type transposed_view(cons /// \ingroup ImageViewTransformations90CW template -inline typename dynamic_xy_step_transposed_type::type rotated90cw_view(const View& src) { +inline auto rotated90cw_view(View const& src) + -> typename dynamic_xy_step_transposed_type::type +{ using RView = typename dynamic_xy_step_transposed_type::type; return RView(src.height(),src.width(),typename RView::xy_locator(src.xy_at(0,src.height()-1),-1,1,true)); } @@ -229,7 +254,9 @@ inline typename dynamic_xy_step_transposed_type::type rotated90cw_view(con /// \ingroup ImageViewTransformations90CCW template -inline typename dynamic_xy_step_transposed_type::type rotated90ccw_view(const View& src) { +inline auto rotated90ccw_view(View const& src) + -> typename dynamic_xy_step_transposed_type::type +{ using RView = typename dynamic_xy_step_transposed_type::type; return RView(src.height(),src.width(),typename RView::xy_locator(src.xy_at(src.width()-1,0),1,-1,true)); } @@ -240,7 +267,9 @@ inline typename dynamic_xy_step_transposed_type::type rotated90ccw_view(co /// \ingroup ImageViewTransformations180 template -inline typename dynamic_xy_step_type::type rotated180_view(const View& src) { +inline auto rotated180_view(View const& src) + -> typename dynamic_xy_step_type::type +{ using RView = typename dynamic_xy_step_type::type; return RView(src.dimensions(),typename RView::xy_locator(src.xy_at(src.width()-1,src.height()-1),-1,-1)); } @@ -309,7 +338,7 @@ namespace detail { struct __nth_channel_view_basic { using type = typename view_type::type, gray_layout_t, false, true, view_is_mutable::value>::type; - static type make(const View& src, int n) { + static type make(View const& src, int n) { using locator_t = typename type::xy_locator; using x_iterator_t = typename type::x_iterator; using x_iterator_base_t = typename iterator_adaptor_get_base::type; @@ -322,7 +351,7 @@ namespace detail { template struct __nth_channel_view_basic { using type = typename view_type::type, gray_layout_t, false, false, view_is_mutable::value>::type; - static type make(const View& src, int n) { + static type make(View const& src, int n) { using x_iterator_t = typename type::x_iterator; return interleaved_view(src.width(),src.height(),(x_iterator_t)&(src(0,0)[n]), src.pixels().row_size()); } @@ -346,7 +375,7 @@ namespace detail { public: using type = typename __nth_channel_view_basic::type; - static type make(const View& src, int n) { + static type make(View const& src, int n) { return __nth_channel_view_basic::make(src,n); } }; @@ -375,11 +404,13 @@ namespace detail { using result_type = reference; nth_channel_deref_fn(int n=0) : _n(n) {} - template nth_channel_deref_fn(const nth_channel_deref_fn

            & d) : _n(d._n) {} + template + nth_channel_deref_fn(const nth_channel_deref_fn

            & d) : _n(d._n) {} int _n; // the channel to use - result_type operator()(argument_type srcP) const { + auto operator()(argument_type srcP) const -> result_type + { return result_type(srcP[_n]); } }; @@ -390,7 +421,7 @@ namespace detail { using AD = typename View::template add_deref; public: using type = typename AD::type; - static type make(const View& src, int n) { + static type make(View const& src, int n) { return AD::make(src, deref_t(n)); } }; @@ -409,22 +440,15 @@ struct nth_channel_view_type { using VB = detail::__nth_channel_view::value>; public: using type = typename VB::type; - static type make(const View& src, int n) { return VB::make(src,n); } + static type make(View const& src, int n) { return VB::make(src,n); } }; - /// \ingroup ImageViewTransformationsNthChannel template -typename nth_channel_view_type::type nth_channel_view(const View& src, int n) { +typename nth_channel_view_type::type nth_channel_view(View const& src, int n) { return nth_channel_view_type::make(src,n); } - - - - - - /// \defgroup ImageViewTransformationsKthChannel kth_channel_view /// \ingroup ImageViewTransformations /// \brief single-channel (grayscale) view of the K-th channel of a given image_view. The channel index is a template parameter @@ -441,7 +465,7 @@ namespace detail { public: using type = typename view_type::value>::type; - static type make(const View& src) { + static type make(View const& src) { using locator_t = typename type::xy_locator; using x_iterator_t = typename type::x_iterator; using x_iterator_base_t = typename iterator_adaptor_get_base::type; @@ -457,7 +481,7 @@ namespace detail { using channel_t = typename kth_element_type::type; public: using type = typename view_type::value>::type; - static type make(const View& src) { + static type make(View const& src) { using x_iterator_t = typename type::x_iterator; return interleaved_view(src.width(),src.height(),(x_iterator_t)&gil::at_c(src(0,0)), src.pixels().row_size()); } @@ -480,7 +504,7 @@ namespace detail { public: using type = typename __kth_channel_view_basic::type; - static type make(const View& src) { + static type make(View const& src) { return __kth_channel_view_basic::make(src); } }; @@ -512,7 +536,8 @@ namespace detail { using result_type = reference; kth_channel_deref_fn() {} - template kth_channel_deref_fn(const kth_channel_deref_fn&) {} + template + kth_channel_deref_fn(const kth_channel_deref_fn&) {} result_type operator()(argument_type srcP) const { return result_type(gil::at_c(srcP)); @@ -525,7 +550,7 @@ namespace detail { using AD = typename View::template add_deref; public: using type = typename AD::type; - static type make(const View& src) { + static type make(View const& src) { return AD::make(src, deref_t()); } }; @@ -544,12 +569,14 @@ struct kth_channel_view_type { using VB = detail::__kth_channel_view::value>; public: using type = typename VB::type; - static type make(const View& src) { return VB::make(src); } + static type make(View const& src) { return VB::make(src); } }; /// \ingroup ImageViewTransformationsKthChannel template -typename kth_channel_view_type::type kth_channel_view(const View& src) { +auto kth_channel_view(View const& src) + -> typename kth_channel_view_type::type +{ return kth_channel_view_type::make(src); } diff --git a/include/boost/gil/io/base.hpp b/include/boost/gil/io/base.hpp index ab9294c114..fd649a371b 100644 --- a/include/boost/gil/io/base.hpp +++ b/include/boost/gil/io/base.hpp @@ -44,8 +44,8 @@ struct image_read_settings_base , _dim ( 0, 0 ) {} - image_read_settings_base( const point_t& top_left - , const point_t& dim + image_read_settings_base( point_t const& top_left + , point_t const& dim ) : _top_left( top_left ) , _dim ( dim ) @@ -54,8 +54,8 @@ struct image_read_settings_base public: - void set( const point_t& top_left - , const point_t& dim + void set( point_t const& top_left + , point_t const& dim ) { _top_left = top_left; diff --git a/include/boost/gil/io/dynamic_io_new.hpp b/include/boost/gil/io/detail/dynamic.hpp similarity index 81% rename from include/boost/gil/io/dynamic_io_new.hpp rename to include/boost/gil/io/detail/dynamic.hpp index 3f529d9013..239f370068 100644 --- a/include/boost/gil/io/dynamic_io_new.hpp +++ b/include/boost/gil/io/detail/dynamic.hpp @@ -5,8 +5,8 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#ifndef BOOST_GIL_IO_DYNAMIC_IO_NEW_HPP -#define BOOST_GIL_IO_DYNAMIC_IO_NEW_HPP +#ifndef BOOST_GIL_IO_DETAIL_DYNAMIC_HPP +#define BOOST_GIL_IO_DETAIL_DYNAMIC_HPP #include @@ -15,14 +15,12 @@ #include -namespace boost { namespace gil { - -namespace detail { +namespace boost { namespace gil { namespace detail { template struct construct_matched_t { - template + template static bool apply(any_image& img, Pred pred) { if (pred.template apply, N-1>>()) @@ -39,11 +37,11 @@ struct construct_matched_t template <> struct construct_matched_t<0> { - template - static bool apply(any_image&,Pred) { return false; } + template + static bool apply(any_image&, Pred) { return false; } }; -// A function object that can be passed to apply_operation. +// A function object that can be passed to variant2::visit. // Given a predicate IsSupported taking a view type and returning an boolean integral coonstant, // calls the apply method of OpClass with the view if the given view IsSupported, or throws an exception otherwise template @@ -81,24 +79,22 @@ class dynamic_io_fnobj apply(view, typename IsSupported::template apply::type()); } - template< typename View, typename Info > + template void operator()(View const& view, Info const& info) { apply(view, info, typename IsSupported::template apply::type()); } }; -} // namespace detail - /// \brief Within the any_image, constructs an image with the given dimensions /// and a type that satisfies the given predicate -template +template inline bool construct_matched(any_image& img, Pred pred) { constexpr auto size = mp11::mp_size>::value; - return detail::construct_matched_t::apply(img, pred); + return construct_matched_t::apply(img, pred); } -} } // namespace boost::gil +} } } // namespace boost::gil::detail #endif diff --git a/include/boost/gil/io/detail/filesystem.hpp b/include/boost/gil/io/detail/filesystem.hpp new file mode 100644 index 0000000000..7ad18cec30 --- /dev/null +++ b/include/boost/gil/io/detail/filesystem.hpp @@ -0,0 +1,65 @@ +// +// Copyright 2022 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 +// +#ifndef BOOST_GIL_IO_DETAIL_FILESYSTEM_HPP +#define BOOST_GIL_IO_DETAIL_FILESYSTEM_HPP + +#include + +#if !defined(BOOST_GIL_IO_USE_BOOST_FILESYSTEM) && !defined(BOOST_NO_CXX17_HDR_FILESYSTEM) +#if defined(__cpp_lib_filesystem) +#include +#define BOOST_GIL_IO_USE_STD_FILESYSTEM +#elif defined(__cpp_lib_experimental_filesystem) +#include +#define BOOST_GIL_IO_USE_STD_FILESYSTEM +#define BOOST_GIL_IO_USE_STD_EXPERIMENTAL_FILESYSTEM +#endif +#endif // !BOOST_GIL_IO_USE_BOOST_FILESYSTEM && !BOOST_NO_CXX17_HDR_FILESYSTEM + +#if !defined(BOOST_GIL_IO_USE_STD_FILESYSTEM) +// Disable warning: conversion to 'std::atomic::__integral_type {aka int}' from 'long int' may alter its value +#if defined(BOOST_CLANG) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshorten-64-to-32" +#endif + +#if defined(BOOST_GCC) && (BOOST_GCC >= 40900) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + +#define BOOST_FILESYSTEM_VERSION 3 +#include +#define BOOST_GIL_IO_USE_BOOST_FILESYSTEM + +#if defined(BOOST_CLANG) +#pragma clang diagnostic pop +#endif + +#if defined(BOOST_GCC) && (BOOST_GCC >= 40900) +#pragma GCC diagnostic pop +#endif + +#endif + +namespace boost { namespace gil { namespace detail { + +#if defined(BOOST_GIL_IO_USE_STD_EXPERIMENTAL_FILESYSTEM) +namespace filesystem = std::experimental::filesystem; +#elif defined(BOOST_GIL_IO_USE_STD_FILESYSTEM) +namespace filesystem = std::filesystem; +#else +#if !defined(BOOST_GIL_IO_USE_BOOST_FILESYSTEM) +#error "Boost.Filesystem is required if C++17 is not available" +#endif +namespace filesystem = boost::filesystem; +#endif + +}}} // namespace boost::gil::detail + +#endif \ No newline at end of file diff --git a/include/boost/gil/io/device.hpp b/include/boost/gil/io/device.hpp index 88dade0ace..41865001f4 100644 --- a/include/boost/gil/io/device.hpp +++ b/include/boost/gil/io/device.hpp @@ -120,8 +120,8 @@ class file_stream_device ) {} - FILE* get() { return _file.get(); } - const FILE* get() const { return _file.get(); } + auto get() -> FILE* { return _file.get(); } + auto get() const -> FILE const* { return _file.get(); } int getc_unchecked() { @@ -140,9 +140,7 @@ class file_stream_device } ///@todo: change byte_t* to void* - std::size_t read( byte_t* data - , std::size_t count - ) + auto read(byte_t* data, std::size_t count) -> std::size_t { std::size_t num_elements = fread( data , 1 @@ -162,9 +160,7 @@ class file_stream_device } /// Reads array - template< typename T - , int N - > + template< typename T, int N> void read( T (&buf)[N] ) { io_error_if( read( buf, N ) < N @@ -201,9 +197,7 @@ class file_stream_device /// Writes number of elements from a buffer template < typename T > - std::size_t write( const T* buf - , std::size_t count - ) + auto write(T const* buf, std::size_t count) -> std::size_t { std::size_t num_elements = fwrite( buf , buff_item::size diff --git a/include/boost/gil/io/make_backend.hpp b/include/boost/gil/io/make_backend.hpp index 54a518e6ca..b3fe40d6be 100644 --- a/include/boost/gil/io/make_backend.hpp +++ b/include/boost/gil/io/make_backend.hpp @@ -55,17 +55,15 @@ auto make_reader_backend( return reader_backend(device, settings); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline auto make_reader_backend( - filesystem::path const& path, + detail::filesystem::path const& path, image_read_settings const& settings) -> typename get_reader_backend::type { return make_reader_backend(path.wstring(), settings); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline diff --git a/include/boost/gil/io/make_dynamic_image_reader.hpp b/include/boost/gil/io/make_dynamic_image_reader.hpp index 88006983bf..e93ea1927e 100644 --- a/include/boost/gil/io/make_dynamic_image_reader.hpp +++ b/include/boost/gil/io/make_dynamic_image_reader.hpp @@ -54,16 +54,14 @@ auto make_dynamic_image_reader( typename get_dynamic_image_reader::type(device, settings); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline auto make_dynamic_image_reader( - filesystem::path const& path, image_read_settings const& settings) + detail::filesystem::path const& path, image_read_settings const& settings) -> typename get_dynamic_image_reader::type { return make_dynamic_image_reader(path.wstring(), settings); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline @@ -109,15 +107,13 @@ auto make_dynamic_image_reader(std::wstring const& file_name, FormatTag const&) return make_dynamic_image_reader(file_name, image_read_settings()); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline -auto make_dynamic_image_reader(filesystem::path const& path, FormatTag const&) +auto make_dynamic_image_reader(detail::filesystem::path const& path, FormatTag const&) -> typename get_dynamic_image_reader::type { return make_dynamic_image_reader(path.wstring(), image_read_settings()); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline diff --git a/include/boost/gil/io/make_dynamic_image_writer.hpp b/include/boost/gil/io/make_dynamic_image_writer.hpp index 978c7165eb..c3d6dae30f 100644 --- a/include/boost/gil/io/make_dynamic_image_writer.hpp +++ b/include/boost/gil/io/make_dynamic_image_writer.hpp @@ -39,12 +39,8 @@ auto make_dynamic_image_writer( template< typename FormatTag > inline -typename get_dynamic_image_writer< std::wstring - , FormatTag - >::type -make_dynamic_image_writer( const std::wstring& file_name - , const image_write_info< FormatTag >& info - ) +auto make_dynamic_image_writer(std::wstring const& file_name, image_write_info const& info) + -> typename get_dynamic_image_writer< std::wstring, FormatTag>::type { const char* str = detail::convert_to_native_string( file_name ); @@ -63,21 +59,13 @@ make_dynamic_image_writer( const std::wstring& file_name ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag > +template inline -typename get_dynamic_image_writer< std::wstring - , FormatTag - >::type -make_dynamic_image_writer( const filesystem::path& path - , const image_write_info< FormatTag >& info - ) +auto make_dynamic_image_writer(detail::filesystem::path const& path, image_write_info const& info) + -> typename get_dynamic_image_writer::type { - return make_dynamic_image_writer( path.wstring() - , info - ); + return make_dynamic_image_writer(path.wstring(), info); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline @@ -114,36 +102,23 @@ auto make_dynamic_image_writer(String const& file_name, FormatTag const&, return make_dynamic_image_writer(file_name, image_write_info()); } -template< typename FormatTag > +template inline -typename get_dynamic_image_writer< std::wstring - , FormatTag - >::type -make_dynamic_image_writer( const std::wstring& file_name - , const FormatTag& - ) +auto make_dynamic_image_writer(std::wstring const& file_name, FormatTag const&) + -> typename get_dynamic_image_writer::type { return make_dynamic_image_writer( file_name , image_write_info< FormatTag >() ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag > +template inline -typename get_dynamic_image_writer< std::wstring - , FormatTag - >::type -make_dynamic_image_writer( const filesystem::path& path - , const FormatTag& - ) +auto make_dynamic_image_writer(detail::filesystem::path const& path, FormatTag const&) + -> typename get_dynamic_image_writer::type { - return make_dynamic_image_writer( path.wstring() - , image_write_info< FormatTag >() - ); + return make_dynamic_image_writer(path.wstring(), image_write_info()); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT - template inline diff --git a/include/boost/gil/io/make_reader.hpp b/include/boost/gil/io/make_reader.hpp index 25b3d6707c..cff8a678fd 100644 --- a/include/boost/gil/io/make_reader.hpp +++ b/include/boost/gil/io/make_reader.hpp @@ -39,18 +39,10 @@ auto make_reader( typename get_reader::type(device, settings); } -template< typename FormatTag - , typename ConversionPolicy - > +template inline -typename get_reader< std::wstring - , FormatTag - , ConversionPolicy - >::type -make_reader( const std::wstring& file_name - , const image_read_settings< FormatTag >& settings - , const ConversionPolicy& - ) +auto make_reader(std::wstring const &file_name, image_read_settings const& settings, ConversionPolicy const&) + -> typename get_reader::type { const char* str = detail::convert_to_native_string( file_name ); @@ -70,26 +62,13 @@ make_reader( const std::wstring& file_name ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag - , typename ConversionPolicy - > +template inline -typename get_reader< std::wstring - , FormatTag - , ConversionPolicy - >::type -make_reader( const filesystem::path& path - , const image_read_settings< FormatTag >& settings - , const ConversionPolicy& cc - ) +auto make_reader(detail::filesystem::path const& path, image_read_settings const& settings, ConversionPolicy const& cc) + -> typename get_reader::type { - return make_reader( path.wstring() - , settings - , cc - ); + return make_reader(path.wstring(), settings, cc); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline @@ -134,18 +113,10 @@ auto make_reader( return make_reader(file_name, image_read_settings(), cc); } -template< typename FormatTag - , typename ConversionPolicy - > +template inline -typename get_reader< std::wstring - , FormatTag - , ConversionPolicy - >::type -make_reader( const std::wstring& file_name - , const FormatTag& - , const ConversionPolicy& cc - ) +auto make_reader(std::wstring const &file_name, FormatTag const&, ConversionPolicy const& cc) + -> typename get_reader::type { return make_reader( file_name , image_read_settings< FormatTag >() @@ -153,26 +124,13 @@ make_reader( const std::wstring& file_name ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag - , typename ConversionPolicy - > +template inline -typename get_reader< std::wstring - , FormatTag - , ConversionPolicy - >::type -make_reader( const filesystem::path& path - , const FormatTag& - , const ConversionPolicy& cc - ) +auto make_reader(detail::filesystem::path const& path, FormatTag const&, ConversionPolicy const& cc) + -> typename get_reader::type { - return make_reader( path.wstring() - , image_read_settings< FormatTag >() - , cc - ); + return make_reader(path.wstring(), image_read_settings(), cc); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline diff --git a/include/boost/gil/io/make_scanline_reader.hpp b/include/boost/gil/io/make_scanline_reader.hpp index 4ca01a72b0..3c630aaa65 100644 --- a/include/boost/gil/io/make_scanline_reader.hpp +++ b/include/boost/gil/io/make_scanline_reader.hpp @@ -37,14 +37,10 @@ auto make_scanline_reader(String const& file_name, FormatTag const&, device, image_read_settings()); } -template< typename FormatTag > +template inline -typename get_scanline_reader< std::wstring - , FormatTag - >::type -make_scanline_reader( const std::wstring& file_name - , FormatTag const& - ) +auto make_scanline_reader(std::wstring const& file_name, FormatTag const&) + -> typename get_scanline_reader::type { const char* str = detail::convert_to_native_string( file_name ); @@ -63,21 +59,13 @@ make_scanline_reader( const std::wstring& file_name ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag > +template inline -typename get_scanline_reader< std::wstring - , FormatTag - >::type -make_scanline_reader( const filesystem::path& path - , FormatTag const& - ) +auto make_scanline_reader(detail::filesystem::path const& path, FormatTag const&) + -> typename get_scanline_reader::type { - return make_scanline_reader( path.wstring() - , image_read_settings< FormatTag >() - ); + return make_scanline_reader(path.wstring(), image_read_settings()); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline diff --git a/include/boost/gil/io/make_writer.hpp b/include/boost/gil/io/make_writer.hpp index 8bad4b7164..b7458fcfad 100644 --- a/include/boost/gil/io/make_writer.hpp +++ b/include/boost/gil/io/make_writer.hpp @@ -34,14 +34,10 @@ auto make_writer(String const& file_name, image_write_info const& inf return typename get_writer::type(device, info); } -template< typename FormatTag > +template inline -typename get_writer< std::wstring - , FormatTag - >::type -make_writer( const std::wstring& file_name - , const image_write_info< FormatTag >& info - ) +auto make_writer(std::wstring const& file_name, image_write_info const& info) + -> typename get_writer::type { const char* str = detail::convert_to_native_string( file_name ); @@ -60,21 +56,13 @@ make_writer( const std::wstring& file_name ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag > +template inline -typename get_writer< std::wstring - , FormatTag - >::type -make_writer( const filesystem::path& path - , const image_write_info< FormatTag >& info - ) +auto make_writer(detail::filesystem::path const& path, image_write_info const& info) + -> typename get_writer::type { - return make_writer( path.wstring() - , info - ); + return make_writer(path.wstring(), info); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline @@ -111,35 +99,23 @@ auto make_writer(String const& file_name, FormatTag const&, return make_writer(file_name, image_write_info()); } -template< typename FormatTag > +template inline -typename get_writer< std::wstring - , FormatTag - >::type -make_writer( const std::wstring& file_name - , const FormatTag& - ) +auto make_writer(std::wstring const &file_name, FormatTag const&) + -> typename get_writer::type { return make_writer( file_name , image_write_info< FormatTag >() ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template< typename FormatTag > +template inline -typename get_writer< std::wstring - , FormatTag - >::type -make_writer( const filesystem::path& path - , const FormatTag& tag - ) +auto make_writer(detail::filesystem::path const& path, FormatTag const& tag) + -> typename get_writer::type { - return make_writer( path.wstring() - , tag - ); + return make_writer(path.wstring(), tag); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT template inline diff --git a/include/boost/gil/io/path_spec.hpp b/include/boost/gil/io/path_spec.hpp index dcb2a85230..7b0c91d2f3 100644 --- a/include/boost/gil/io/path_spec.hpp +++ b/include/boost/gil/io/path_spec.hpp @@ -8,29 +8,7 @@ #ifndef BOOST_GIL_IO_PATH_SPEC_HPP #define BOOST_GIL_IO_PATH_SPEC_HPP -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -// Disable warning: conversion to 'std::atomic::__integral_type {aka int}' from 'long int' may alter its value -#if defined(BOOST_CLANG) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wshorten-64-to-32" -#endif - -#if defined(BOOST_GCC) && (BOOST_GCC >= 40900) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconversion" -#endif - -#define BOOST_FILESYSTEM_VERSION 3 -#include - -#if defined(BOOST_CLANG) -#pragma clang diagnostic pop -#endif - -#if defined(BOOST_GCC) && (BOOST_GCC >= 40900) -#pragma GCC diagnostic pop -#endif -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT +#include #include #include @@ -43,7 +21,7 @@ template<> struct is_supported_path_spec< std::string > : std::true_type template<> struct is_supported_path_spec< const std::string > : std::true_type {}; template<> struct is_supported_path_spec< std::wstring > : std::true_type {}; template<> struct is_supported_path_spec< const std::wstring > : std::true_type {}; -template<> struct is_supported_path_spec< const char* > : std::true_type {}; +template<> struct is_supported_path_spec< char const* > : std::true_type {}; template<> struct is_supported_path_spec< char* > : std::true_type {}; template<> struct is_supported_path_spec< const wchar_t* > : std::true_type {}; template<> struct is_supported_path_spec< wchar_t* > : std::true_type {}; @@ -53,15 +31,8 @@ template struct is_supported_path_spec : std::true_ template struct is_supported_path_spec : std::true_type {}; template struct is_supported_path_spec : std::true_type {}; -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -template<> struct is_supported_path_spec< filesystem::path > : std::true_type {}; -template<> struct is_supported_path_spec< const filesystem::path > : std::true_type {}; -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT - - -/// -/// convert_to_string -/// +template<> struct is_supported_path_spec : std::true_type {}; +template<> struct is_supported_path_spec : std::true_type {}; inline std::string convert_to_string( std::string const& obj) { @@ -77,7 +48,7 @@ inline std::string convert_to_string( std::wstring const& s ) return std::string( c, c + len ); } -inline std::string convert_to_string( const char* str ) +inline std::string convert_to_string( char const* str ) { return std::string( str ); } @@ -87,33 +58,27 @@ inline std::string convert_to_string( char* str ) return std::string( str ); } -#ifdef BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -inline std::string convert_to_string( const filesystem::path& path ) +inline std::string convert_to_string(filesystem::path const& path) { - return convert_to_string( path.string() ); + return convert_to_string(path.string()); } -#endif // BOOST_GIL_IO_ADD_FS_PATH_SUPPORT - -/// -/// convert_to_native_string -/// -inline const char* convert_to_native_string( char* str ) +inline char const* convert_to_native_string( char* str ) { return str; } -inline const char* convert_to_native_string( const char* str ) +inline char const* convert_to_native_string( char const* str ) { return str; } -inline const char* convert_to_native_string( const std::string& str ) +inline char const* convert_to_native_string( const std::string& str ) { return str.c_str(); } -inline const char* convert_to_native_string( const wchar_t* str ) +inline char const* convert_to_native_string( const wchar_t* str ) { std::size_t len = wcslen( str ) + 1; char* c = new char[len]; @@ -122,7 +87,7 @@ inline const char* convert_to_native_string( const wchar_t* str ) return c; } -inline const char* convert_to_native_string( const std::wstring& str ) +inline char const* convert_to_native_string( std::wstring const& str ) { std::size_t len = wcslen( str.c_str() ) + 1; char* c = new char[len]; @@ -131,8 +96,6 @@ inline const char* convert_to_native_string( const std::wstring& str ) return c; } -} // namespace detail -} // namespace gil -} // namespace boost +}}} // namespace boost::gil::detail #endif diff --git a/include/boost/gil/io/reader_base.hpp b/include/boost/gil/io/reader_base.hpp index 309331b761..cc997b0a62 100644 --- a/include/boost/gil/io/reader_base.hpp +++ b/include/boost/gil/io/reader_base.hpp @@ -73,7 +73,7 @@ struct reader_base private: - void setup( const point_t& /* dim */ ) + void setup( point_t const& /* dim */ ) { //check_coordinates( dim ); @@ -88,7 +88,7 @@ struct reader_base //} } - void check_coordinates( const point_t& /* dim */ ) + void check_coordinates( point_t const& /* dim */ ) { //using int_t = point_t::value_type; diff --git a/include/boost/gil/locator.hpp b/include/boost/gil/locator.hpp index 095bc6fac7..f0e7c20366 100644 --- a/include/boost/gil/locator.hpp +++ b/include/boost/gil/locator.hpp @@ -8,8 +8,7 @@ #ifndef BOOST_GIL_LOCATOR_HPP #define BOOST_GIL_LOCATOR_HPP -#include -#include +#include #include #include @@ -140,7 +139,7 @@ class pixel_2d_locator_base template typename axis::iterator& axis_iterator() { return detail::locator_axis()(concrete()); } template typename axis::iterator const& axis_iterator() const { return detail::locator_axis()(concrete()); } - template typename axis::iterator axis_iterator(const point_t& p) const { return detail::locator_axis()(concrete(),p); } + template typename axis::iterator axis_iterator(point_t const& p) const { return detail::locator_axis()(concrete(),p); } reference operator()(x_coord_t dx, y_coord_t dy) const { return *x_at(dx,dy); } reference operator[](const difference_type& d) const { return *x_at(d.x,d.y); } @@ -176,8 +175,8 @@ namespace detail { inline iterator& operator()( Loc& loc) const { return loc.x(); } inline iterator const& operator()(const Loc& loc) const { return loc.x(); } - inline iterator operator()( Loc& loc, const point_t& d) const { return loc.x_at(d); } - inline iterator operator()(const Loc& loc, const point_t& d) const { return loc.x_at(d); } + inline iterator operator()( Loc& loc, point_t const& d) const { return loc.x_at(d); } + inline iterator operator()(const Loc& loc, point_t const& d) const { return loc.x_at(d); } }; template @@ -189,8 +188,8 @@ namespace detail { inline iterator& operator()( Loc& loc) const { return loc.y(); } inline iterator const& operator()(const Loc& loc) const { return loc.y(); } - inline iterator operator()( Loc& loc, const point_t& d) const { return loc.y_at(d); } - inline iterator operator()(const Loc& loc, const point_t& d) const { return loc.y_at(d); } + inline iterator operator()( Loc& loc, point_t const& d) const { return loc.y_at(d); } + inline iterator operator()(const Loc& loc, point_t const& d) const { return loc.y_at(d); } }; } diff --git a/include/boost/gil/metafunctions.hpp b/include/boost/gil/metafunctions.hpp index d2fe04dc49..9f9a4b9678 100644 --- a/include/boost/gil/metafunctions.hpp +++ b/include/boost/gil/metafunctions.hpp @@ -1,5 +1,6 @@ // // Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -27,7 +28,7 @@ template struct planar_pixel_iterator; template class memory_based_step_iterator; template class memory_based_2d_locator; template class image_view; -template class image; +template > class image; template struct channel_type; template struct color_space_type; template struct channel_mapping_type; diff --git a/include/boost/gil/packed_pixel.hpp b/include/boost/gil/packed_pixel.hpp index 32dfae614f..ff1e123c9b 100644 --- a/include/boost/gil/packed_pixel.hpp +++ b/include/boost/gil/packed_pixel.hpp @@ -61,9 +61,6 @@ struct packed_pixel packed_pixel() = default; explicit packed_pixel(const BitField& bitfield) : _bitfield(bitfield) {} - // Construct from another compatible pixel type - packed_pixel(const packed_pixel& p) : _bitfield(p._bitfield) {} - template packed_pixel(Pixel const& p, typename std::enable_if::value>::type* /*dummy*/ = nullptr) @@ -110,12 +107,6 @@ struct packed_pixel gil::at_c<4>(*this) = chan4; } - auto operator=(packed_pixel const& p) -> packed_pixel& - { - _bitfield = p._bitfield; - return *this; - } - template auto operator=(Pixel const& p) -> packed_pixel& { diff --git a/include/boost/gil/pixel_iterator_adaptor.hpp b/include/boost/gil/pixel_iterator_adaptor.hpp index 093a8e9ace..36c4cf02e0 100644 --- a/include/boost/gil/pixel_iterator_adaptor.hpp +++ b/include/boost/gil/pixel_iterator_adaptor.hpp @@ -121,18 +121,20 @@ struct channel_type > : public channel_type< ///////////////////////////// template -struct byte_to_memunit > : public byte_to_memunit {}; +struct byte_to_memunit> : public byte_to_memunit {}; template -inline typename std::iterator_traits::difference_type -memunit_step(const dereference_iterator_adaptor& p) { +inline auto memunit_step(dereference_iterator_adaptor const& p) + -> typename std::iterator_traits::difference_type +{ return memunit_step(p.base()); } template -inline typename std::iterator_traits::difference_type -memunit_distance(const dereference_iterator_adaptor& p1, - const dereference_iterator_adaptor& p2) { +inline auto memunit_distance(dereference_iterator_adaptor const& p1, + dereference_iterator_adaptor const& p2) + -> typename std::iterator_traits::difference_type +{ return memunit_distance(p1.base(),p2.base()); } @@ -143,18 +145,19 @@ inline void memunit_advance(dereference_iterator_adaptor& p, } template -inline dereference_iterator_adaptor -memunit_advanced(const dereference_iterator_adaptor& p, - typename std::iterator_traits::difference_type diff) { +inline auto memunit_advanced(dereference_iterator_adaptor const& p, + typename std::iterator_traits::difference_type diff) + -> dereference_iterator_adaptor +{ return dereference_iterator_adaptor(memunit_advanced(p.base(), diff), p.deref_fn()); } template -inline -typename std::iterator_traits >::reference -memunit_advanced_ref(const dereference_iterator_adaptor& p, - typename std::iterator_traits::difference_type diff) { +inline auto memunit_advanced_ref(dereference_iterator_adaptor const& p, + typename std::iterator_traits::difference_type diff) + -> typename std::iterator_traits >::reference +{ return *memunit_advanced(p, diff); } diff --git a/include/boost/gil/pixel_numeric_operations.hpp b/include/boost/gil/pixel_numeric_operations.hpp new file mode 100644 index 0000000000..93104e3d91 --- /dev/null +++ b/include/boost/gil/pixel_numeric_operations.hpp @@ -0,0 +1,225 @@ +// +// Copyright 2005-2007 Adobe Systems Incorporated +// Copyright 2021 Pranam Lashkari +// +// 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 +// +#ifndef BOOST_GIL_PIXEL_NUMERIC_OPERATIONS_HPP +#define BOOST_GIL_PIXEL_NUMERIC_OPERATIONS_HPP + +#include +#include +#include + +namespace boost { namespace gil { + +// Function objects and utilities for pixel-wise numeric operations. +// +// List of currently defined functors: +// pixel_plus_t (+) +// pixel_minus_t (-) +// pixel_multiplies_scalar_t (*s) +// pixel_multiplies_t (*) +// pixel_divides_scalar_t (/s) +// pixel_divides_t (/) +// pixel_halves_t (/=2), +// pixel_zeros_t (=0) +// pixel_assigns_t (=) + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise addition of two pixels. +/// \tparam PixelRef1 - models PixelConcept +/// \tparam PixelRef2 - models PixelConcept +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_plus_t +{ + auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult + { + PixelResult result; + static_transform(p1, p2, result, + channel_plus_t + < + typename channel_type::type, + typename channel_type::type, + typename channel_type::type + >()); + return result; + } +}; + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise subtraction of two pixels. +/// \tparam PixelRef1 - models PixelConcept +/// \tparam PixelRef2 - models PixelConcept +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_minus_t +{ + auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult + { + PixelResult result; + static_transform(p1, p2, result, + channel_minus_t + < + typename channel_type::type, + typename channel_type::type, + typename channel_type::type + >()); + return result; + } +}; + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise multiplication of pixel elements by scalar. +/// \tparam PixelRef - models PixelConcept +/// \tparam Scalar - models a scalar type +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_multiplies_scalar_t +{ + auto operator()(PixelRef const& p, Scalar const& s) const -> PixelResult + { + PixelResult result; + static_transform(p, result, + std::bind( + channel_multiplies_scalar_t::type, + Scalar, + typename channel_type::type>(), + std::placeholders::_1, s)); + return result; + } +}; + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise multiplication of two pixels. +/// \tparam PixelRef1 - models PixelConcept +/// \tparam PixelRef1 - models PixelConcept +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_multiplies_t +{ + auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult + { + PixelResult result; + static_transform(p1, p2, result, + channel_multiplies_t + < + typename channel_type::type, + typename channel_type::type, + typename channel_type::type + >()); + return result; + } +}; + +template +using pixel_multiply_t [[deprecated]] = pixel_multiplies_t; + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise division of pixel elements by scalar. +/// \tparam PixelRef - models PixelConcept +/// \tparam Scalar - models a scalar type +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_divides_scalar_t +{ + auto operator()(PixelRef const& p, Scalar const& s) const -> PixelResult + { + PixelResult result; + static_transform(p, result, + std::bind(channel_divides_scalar_t::type, + Scalar, + typename channel_type::type>(), + std::placeholders::_1, s)); + return result; + } +}; + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise division of two pixels. +/// \tparam PixelRef1 - models PixelConcept +/// \tparam PixelRef2 - models PixelConcept +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_divides_t +{ + auto operator()(PixelRef1 const& p1, PixelRef2 const& p2) const -> PixelResult + { + PixelResult result; + static_transform(p1, p2, result, + channel_divides_t + < + typename channel_type::type, + typename channel_type::type, + typename channel_type::type + >()); + return result; + } +}; + +template +using pixel_divide_t [[deprecated]] = pixel_divides_t; + +/// \ingroup PixelNumericOperations +/// \brief Performs channel-wise division by 2 +/// \tparam PixelRef - models PixelConcept +template +struct pixel_halves_t +{ + auto operator()(PixelRef& p) const -> PixelRef& + { + static_for_each(p, channel_halves_t::type>()); + return p; + } +}; + +/// \ingroup PixelNumericOperations +/// \brief Sets pixel elements to zero (for whatever zero means) +/// \tparam PixelRef - models PixelConcept +template +struct pixel_zeros_t +{ + auto operator()(PixelRef& p) const -> PixelRef& + { + static_for_each(p, channel_zeros_t::type>()); + return p; + } +}; + +/// \brief Sets pixel elements to zero (for whatever zero means) +/// \tparam Pixel - models PixelConcept +template +void zero_channels(Pixel& p) +{ + static_for_each(p, channel_zeros_t::type>()); +} + +/// \ingroup PixelNumericOperations +/// \brief Casts and assigns a pixel to another +/// +/// A generic implementation for casting and assigning a pixel to another. +/// User should specialize it for better performance. +/// +/// \tparam PixelRef - models PixelConcept +/// \tparam PixelResult - models PixelValueConcept +template +struct pixel_assigns_t +{ + auto operator()(PixelRef const& src, PixelResult& dst) const -> PixelResult + { + static_for_each(src, dst, + channel_assigns_t + < + typename channel_type::type, + typename channel_type::type + >()); + return dst; + } +}; + +}} // namespace boost::gil + +#endif diff --git a/include/boost/gil/planar_pixel_iterator.hpp b/include/boost/gil/planar_pixel_iterator.hpp index 0aead851b4..df4152b68f 100644 --- a/include/boost/gil/planar_pixel_iterator.hpp +++ b/include/boost/gil/planar_pixel_iterator.hpp @@ -201,10 +201,16 @@ struct channel_type> ///////////////////////////// template -inline std::ptrdiff_t memunit_step(const planar_pixel_iterator&) { return sizeof(typename std::iterator_traits::value_type); } +inline auto memunit_step(planar_pixel_iterator const&) + -> std::ptrdiff_t +{ + return sizeof(typename std::iterator_traits::value_type); +} template -inline std::ptrdiff_t memunit_distance(const planar_pixel_iterator& p1, const planar_pixel_iterator& p2) { +inline auto memunit_distance(planar_pixel_iterator const& p1, planar_pixel_iterator const& p2) + -> std::ptrdiff_t +{ return memunit_distance(gil::at_c<0>(p1),gil::at_c<0>(p2)); } @@ -222,15 +228,18 @@ inline void memunit_advance(planar_pixel_iterator& p, std::ptrdiff_t diff) } template -inline planar_pixel_iterator memunit_advanced(const planar_pixel_iterator& p, std::ptrdiff_t diff) { +inline auto memunit_advanced(planar_pixel_iterator const& p, std::ptrdiff_t diff) + -> planar_pixel_iterator +{ planar_pixel_iterator ret=p; memunit_advance(ret, diff); return ret; } template -inline planar_pixel_reference::reference,ColorSpace> - memunit_advanced_ref(const planar_pixel_iterator& ptr, std::ptrdiff_t diff) { +inline auto memunit_advanced_ref(planar_pixel_iterator const& ptr, std::ptrdiff_t diff) + -> planar_pixel_reference::reference,ColorSpace> +{ return planar_pixel_reference::reference,ColorSpace>(ptr, diff); } diff --git a/include/boost/gil/point.hpp b/include/boost/gil/point.hpp index 0f40faf46f..5b9ffacf79 100644 --- a/include/boost/gil/point.hpp +++ b/include/boost/gil/point.hpp @@ -246,44 +246,44 @@ T& axis_value(point& p) /// \ingroup PointAlgorithm template -inline point iround(point const& p) +inline auto iround(point const& p) -> point { static_assert(std::is_integral::value, "T is not integer"); return { static_cast(p.x), static_cast(p.y) }; } /// \ingroup PointAlgorithm -inline point iround(point const& p) +inline auto iround(point const& p) -> point { return { iround(p.x), iround(p.y) }; } /// \ingroup PointAlgorithm -inline point iround(point const& p) +inline auto iround(point const& p) -> point { return { iround(p.x), iround(p.y) }; } /// \ingroup PointAlgorithm -inline point ifloor(point const& p) +inline auto ifloor(point const& p) -> point { return { ifloor(p.x), ifloor(p.y) }; } /// \ingroup PointAlgorithm -inline point ifloor(point const& p) +inline auto ifloor(point const& p) -> point { return { ifloor(p.x), ifloor(p.y) }; } /// \ingroup PointAlgorithm -inline point iceil(point const& p) +inline auto iceil(point const& p) -> point { return { iceil(p.x), iceil(p.y) }; } /// \ingroup PointAlgorithm -inline point iceil(point const& p) +inline auto iceil(point const& p) -> point { return { iceil(p.x), iceil(p.y) }; } diff --git a/include/boost/gil/position_iterator.hpp b/include/boost/gil/position_iterator.hpp index 8d8880438e..7685035058 100644 --- a/include/boost/gil/position_iterator.hpp +++ b/include/boost/gil/position_iterator.hpp @@ -43,20 +43,34 @@ struct position_iterator : public iterator_facade, using point_t = typename Deref::argument_type; position_iterator() {} - position_iterator(const point_t& p, const point_t& step, const Deref& d) : _p(p), _step(step), _d(d) {} - - position_iterator(const position_iterator& p) : _p(p._p), _step(p._step), _d(p._d) {} - template position_iterator(const position_iterator& p) : _p(p._p), _step(p._step), _d(p._d) {} - position_iterator& operator=(const position_iterator& p) { _p=p._p; _d=p._d; _step=p._step; return *this; } - - const point_t& pos() const { return _p; } - const point_t& step() const { return _step; } - const Deref& deref_fn() const { return _d; } + position_iterator(point_t const& p, point_t const& step, Deref const& d) : _p(p), _step(step), _d(d) {} + + position_iterator(position_iterator const& p) : _p(p._p), _step(p._step), _d(p._d) {} + + template + position_iterator(position_iterator const& p) : _p(p._p), _step(p._step), _d(p._d) {} + + auto operator=(position_iterator const& p) -> position_iterator& + { + _p=p._p; + _d=p._d; + _step=p._step; + return *this; + } + + auto pos() const -> point_t const& { return _p; } + auto step() const -> point_t const& { return _step; } + auto deref_fn() const -> Deref const& { return _d; } void set_step(difference_type s) { _step[Dim]=s; } /// For some reason operator[] provided by iterator_adaptor returns a custom class that is convertible to reference /// We require our own reference because it is registered in iterator_traits - reference operator[](difference_type d) const { point_t p=_p; p[Dim]+=d*_step[Dim]; return _d(p); } + auto operator[](difference_type d) const -> reference + { + point_t p=_p; + p[Dim]+=d*_step[Dim]; + return _d(p); + } private: point_t _p, _step; diff --git a/include/boost/gil/premultiply.hpp b/include/boost/gil/premultiply.hpp index 530719cd66..078c311111 100644 --- a/include/boost/gil/premultiply.hpp +++ b/include/boost/gil/premultiply.hpp @@ -98,11 +98,12 @@ struct premultiplied_view_type using add_ref_t = typename SrcView::template add_deref; public: using type = typename add_ref_t::type; // the color converted view type - static type make(const SrcView& sv) { return add_ref_t::make(sv, deref_t()); } + static type make(SrcView const& sv) { return add_ref_t::make(sv, deref_t()); } }; -template inline -typename premultiplied_view_type::type premultiply_view(const View& src) +template +inline auto premultiply_view(View const& src) + -> typename premultiplied_view_type::type { return premultiplied_view_type::make(src); } diff --git a/include/boost/gil/promote_integral.hpp b/include/boost/gil/promote_integral.hpp index a12d341f70..914ee94a4c 100644 --- a/include/boost/gil/promote_integral.hpp +++ b/include/boost/gil/promote_integral.hpp @@ -2,7 +2,7 @@ // // Copyright (c) 2015, Oracle and/or its affiliates. // Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle -// +// // Copyright (c) 2020, Debabrata Mandal // // Licensed under the Boost Software License version 1.0. diff --git a/include/boost/gil/rgb.hpp b/include/boost/gil/rgb.hpp index 1ab15f8e90..a4d342a97f 100644 --- a/include/boost/gil/rgb.hpp +++ b/include/boost/gil/rgb.hpp @@ -42,8 +42,7 @@ using bgr_layout_t = layout>; /// \ingroup ImageViewConstructors /// \brief from raw RGB planar data template -inline -auto planar_rgb_view( +inline auto planar_rgb_view( std::size_t width, std::size_t height, IC r, IC g, IC b, std::ptrdiff_t rowsize_in_bytes) diff --git a/include/boost/gil/rgba.hpp b/include/boost/gil/rgba.hpp index eb76db8bdb..50943e166f 100644 --- a/include/boost/gil/rgba.hpp +++ b/include/boost/gil/rgba.hpp @@ -39,8 +39,7 @@ using abgr_layout_t = layout>; /// \ingroup ImageViewConstructors /// \brief from raw RGBA planar data template -inline -auto planar_rgba_view(std::size_t width, std::size_t height, +inline auto planar_rgba_view(std::size_t width, std::size_t height, ChannelPtr r, ChannelPtr g, ChannelPtr b, ChannelPtr a, std::ptrdiff_t rowsize_in_bytes) -> typename type_from_x_iterator >::view_t diff --git a/include/boost/gil/step_iterator.hpp b/include/boost/gil/step_iterator.hpp index f164746247..5472289b5e 100644 --- a/include/boost/gil/step_iterator.hpp +++ b/include/boost/gil/step_iterator.hpp @@ -47,9 +47,9 @@ class step_iterator_adaptor : public iterator_adaptor::reference; step_iterator_adaptor() {} - step_iterator_adaptor(const Iterator& it, SFn step_fn=SFn()) : parent_t(it), _step_fn(step_fn) {} + step_iterator_adaptor(Iterator const& it, SFn step_fn=SFn()) : parent_t(it), _step_fn(step_fn) {} - difference_type step() const { return _step_fn.step(); } + auto step() const -> difference_type { return _step_fn.step(); } protected: SFn _step_fn; @@ -59,7 +59,11 @@ class step_iterator_adaptor : public iterator_adaptorbase_reference(),1); } void decrement() { _step_fn.advance(this->base_reference(),-1); } void advance(base_difference_type d) { _step_fn.advance(this->base_reference(),d); } - difference_type distance_to(const step_iterator_adaptor& it) const { return _step_fn.difference(this->base_reference(),it.base_reference()); } + + auto distance_to(step_iterator_adaptor const& it) const -> difference_type + { + return _step_fn.difference(this->base_reference(),it.base_reference()); + } }; // although iterator_adaptor defines these, the default implementation computes distance and compares for zero. @@ -124,11 +128,15 @@ struct memunit_step_fn { memunit_step_fn(difference_type step=memunit_step(Iterator())) : _step(step) {} - difference_type difference(const Iterator& it1, const Iterator& it2) const { return memunit_distance(it1,it2)/_step; } - void advance(Iterator& it, difference_type d) const { memunit_advance(it,d*_step); } - difference_type step() const { return _step; } + auto difference(Iterator const& it1, Iterator const& it2) const -> difference_type + { + return memunit_distance(it1,it2)/_step; + } + + void advance(Iterator& it, difference_type d) const { memunit_advance(it,d*_step); } + auto step() const -> difference_type { return _step; } - void set_step(std::ptrdiff_t step) { _step=step; } + void set_step(std::ptrdiff_t step) { _step=step; } private: BOOST_GIL_CLASS_REQUIRE(Iterator, boost::gil, MemoryBasedIteratorConcept) difference_type _step; @@ -156,12 +164,12 @@ class memory_based_step_iterator : public detail::step_iterator_adaptor reference { return *(*this+d); } void set_step(std::ptrdiff_t memunit_step) { this->_step_fn.set_step(memunit_step); } - x_iterator& base() { return parent_t::base_reference(); } - x_iterator const& base() const { return parent_t::base_reference(); } + auto base() -> x_iterator& { return parent_t::base_reference(); } + auto base() const -> x_iterator const& { return parent_t::base_reference(); } }; template @@ -215,11 +223,12 @@ template struct byte_to_memunit> : public byte_to_memunit {}; template -inline std::ptrdiff_t memunit_step(const memory_based_step_iterator& p) { return p.step(); } +inline auto memunit_step(memory_based_step_iterator const& p) -> std::ptrdiff_t { return p.step(); } template -inline std::ptrdiff_t memunit_distance(const memory_based_step_iterator& p1, - const memory_based_step_iterator& p2) { +inline auto memunit_distance(memory_based_step_iterator const& p1, memory_based_step_iterator const& p2) + -> std::ptrdiff_t +{ return memunit_distance(p1.base(),p2.base()); } @@ -230,16 +239,16 @@ inline void memunit_advance(memory_based_step_iterator& p, } template -inline memory_based_step_iterator -memunit_advanced(const memory_based_step_iterator& p, - std::ptrdiff_t diff) { +inline auto memunit_advanced(const memory_based_step_iterator& p, std::ptrdiff_t diff) + -> memory_based_step_iterator +{ return memory_based_step_iterator(memunit_advanced(p.base(), diff),p.step()); } template -inline typename std::iterator_traits::reference -memunit_advanced_ref(const memory_based_step_iterator& p, - std::ptrdiff_t diff) { +inline auto memunit_advanced_ref(const memory_based_step_iterator& p, std::ptrdiff_t diff) + -> typename std::iterator_traits::reference +{ return memunit_advanced_ref(p.base(), diff); } @@ -259,7 +268,10 @@ struct iterator_add_deref,Deref> { using type = memory_based_step_iterator::type>; - static type make(const memory_based_step_iterator& it, const Deref& d) { return type(iterator_add_deref::make(it.base(),d),it.step()); } + static type make(const memory_based_step_iterator& it, const Deref& d) + { + return type(iterator_add_deref::make(it.base(),d),it.step()); + } }; //////////////////////////////////////////////////////////////////////////////////////// @@ -313,7 +325,9 @@ auto make_step_iterator_impl( /// The step iterator can be wrapped inside another iterator. Also, it may not have the /// type memory_based_step_iterator, but it could be a user-provided type. template // Models MemoryBasedIteratorConcept, HasDynamicXStepTypeConcept -typename dynamic_x_step_type::type make_step_iterator(const I& it, std::ptrdiff_t step) { +inline auto make_step_iterator(I const& it, std::ptrdiff_t step) + -> typename dynamic_x_step_type::type +{ return detail::make_step_iterator_impl(it, step, typename is_iterator_adaptor::type()); } diff --git a/include/boost/gil/typedefs.hpp b/include/boost/gil/typedefs.hpp index 4ff10d52d7..44673954ce 100644 --- a/include/boost/gil/typedefs.hpp +++ b/include/boost/gil/typedefs.hpp @@ -18,71 +18,100 @@ #include #include +#if !defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) +# include +#endif //!defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) // B - bits size/signedness, CM - channel model, CS - colour space, LAYOUT - pixel layout // Example: B = '8', CM = 'uint8_t', CS = 'bgr, LAYOUT='bgr_layout_t' -#define BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, LAYOUT) \ - template struct pixel; \ - template struct planar_pixel_reference; \ - template struct planar_pixel_iterator; \ - template class memory_based_step_iterator; \ - template class point; \ - template class memory_based_2d_locator; \ - template class image_view; \ - template class image; \ - using CS##B##_pixel_t = pixel; \ - using CS##B##c_pixel_t = pixel const; \ - using CS##B##_ref_t = pixel&; \ - using CS##B##c_ref_t = pixel const&; \ - using CS##B##_ptr_t = CS##B##_pixel_t*; \ - using CS##B##c_ptr_t = CS##B##c_pixel_t*; \ - using CS##B##_step_ptr_t = memory_based_step_iterator; \ - using CS##B##c_step_ptr_t = memory_based_step_iterator; \ - using CS##B##_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##c_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##_step_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##c_step_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##_view_t = image_view; \ - using CS##B##c_view_t = image_view; \ - using CS##B##_step_view_t = image_view; \ - using CS##B##c_step_view_t = image_view; \ +#define BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, LAYOUT) \ + template \ + struct pixel; \ + template \ + struct planar_pixel_reference; \ + template \ + struct planar_pixel_iterator; \ + template \ + class memory_based_step_iterator; \ + template \ + class point; \ + template \ + class memory_based_2d_locator; \ + template \ + class image_view; \ + template \ + class image; \ + using CS##B##_pixel_t = pixel; \ + using CS##B##c_pixel_t = pixel const; \ + using CS##B##_ref_t = pixel&; \ + using CS##B##c_ref_t = pixel const&; \ + using CS##B##_ptr_t = CS##B##_pixel_t*; \ + using CS##B##c_ptr_t = CS##B##c_pixel_t*; \ + using CS##B##_step_ptr_t = memory_based_step_iterator; \ + using CS##B##c_step_ptr_t = memory_based_step_iterator; \ + using CS##B##_loc_t = memory_based_2d_locator>; \ + using CS##B##c_loc_t = memory_based_2d_locator>; \ + using CS##B##_step_loc_t \ + = memory_based_2d_locator>; \ + using CS##B##c_step_loc_t \ + = memory_based_2d_locator>; \ + using CS##B##_view_t = image_view; \ + using CS##B##c_view_t = image_view; \ + using CS##B##_step_view_t = image_view; \ + using CS##B##c_step_view_t = image_view; \ using CS##B##_image_t = image>; -// Example: B = '8', CM = 'uint8_t', CS = 'bgr' CS_FULL = 'rgb_t' LAYOUT='bgr_layout_t' -#define BOOST_GIL_DEFINE_ALL_TYPEDEFS_INTERNAL(B, CM, CS, CS_FULL, LAYOUT) \ - BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, LAYOUT) \ - using CS##B##_planar_ref_t = planar_pixel_reference; \ - using CS##B##c_planar_ref_t = planar_pixel_reference; \ - using CS##B##_planar_ptr_t = planar_pixel_iterator; \ - using CS##B##c_planar_ptr_t = planar_pixel_iterator; \ - using CS##B##_planar_step_ptr_t = memory_based_step_iterator; \ - using CS##B##c_planar_step_ptr_t \ - = memory_based_step_iterator; \ - using CS##B##_planar_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##c_planar_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##_planar_step_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##c_planar_step_loc_t \ - = memory_based_2d_locator>; \ - using CS##B##_planar_view_t = image_view; \ - using CS##B##c_planar_view_t = image_view; \ - using CS##B##_planar_step_view_t = image_view; \ - using CS##B##c_planar_step_view_t = image_view; \ - using CS##B##_planar_image_t \ - = image>; - -#define BOOST_GIL_DEFINE_BASE_TYPEDEFS(B, CM, CS) \ - BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, CS##_layout_t) - -#define BOOST_GIL_DEFINE_ALL_TYPEDEFS(B, CM, CS) \ - BOOST_GIL_DEFINE_ALL_TYPEDEFS_INTERNAL(B, CM, CS, CS##_t, CS##_layout_t) +#define BOOST_GIL_DEFINE_BASE_PMR_TYPEDEFS_INTERNAL(B, CM, CS, LAYOUT) \ + namespace pmr { \ + using CS##B##_image_t \ + = image>; \ + } +// Example: B = '8', CM = 'uint8_t', CS = 'bgr' CS_FULL = 'rgb_t' LAYOUT='bgr_layout_t' +#define BOOST_GIL_DEFINE_ALL_TYPEDEFS_INTERNAL(B, CM, CS, CS_FULL, LAYOUT) \ + BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, LAYOUT) \ + using CS##B##_planar_ref_t = planar_pixel_reference; \ + using CS##B##c_planar_ref_t = planar_pixel_reference; \ + using CS##B##_planar_ptr_t = planar_pixel_iterator; \ + using CS##B##c_planar_ptr_t = planar_pixel_iterator; \ + using CS##B##_planar_step_ptr_t = memory_based_step_iterator; \ + using CS##B##c_planar_step_ptr_t = memory_based_step_iterator; \ + using CS##B##_planar_loc_t \ + = memory_based_2d_locator>; \ + using CS##B##c_planar_loc_t \ + = memory_based_2d_locator>; \ + using CS##B##_planar_step_loc_t \ + = memory_based_2d_locator>; \ + using CS##B##c_planar_step_loc_t \ + = memory_based_2d_locator>; \ + using CS##B##_planar_view_t = image_view; \ + using CS##B##c_planar_view_t = image_view; \ + using CS##B##_planar_step_view_t = image_view; \ + using CS##B##c_planar_step_view_t = image_view; \ + using CS##B##_planar_image_t = image>; + +#define BOOST_GIL_DEFINE_ALL_PMR_TYPEDEFS_INTERNAL(B, CM, CS, CS_FULL, LAYOUT) \ + BOOST_GIL_DEFINE_BASE_PMR_TYPEDEFS_INTERNAL(B, CM, CS, LAYOUT) \ + namespace pmr { \ + using CS##B##_planar_image_t \ + = image>; \ + } + +#if defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) +# define BOOST_GIL_DEFINE_BASE_TYPEDEFS(B, CM, CS) \ + BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, CS##_layout_t) + +# define BOOST_GIL_DEFINE_ALL_TYPEDEFS(B, CM, CS) \ + BOOST_GIL_DEFINE_ALL_TYPEDEFS_INTERNAL(B, CM, CS, CS##_t, CS##_layout_t) +#else +# define BOOST_GIL_DEFINE_BASE_TYPEDEFS(B, CM, CS) \ + BOOST_GIL_DEFINE_BASE_TYPEDEFS_INTERNAL(B, CM, CS, CS##_layout_t) \ + BOOST_GIL_DEFINE_BASE_PMR_TYPEDEFS_INTERNAL(B, CM, CS, CS##_layout_t) + +# define BOOST_GIL_DEFINE_ALL_TYPEDEFS(B, CM, CS) \ + BOOST_GIL_DEFINE_ALL_TYPEDEFS_INTERNAL(B, CM, CS, CS##_t, CS##_layout_t) \ + BOOST_GIL_DEFINE_ALL_PMR_TYPEDEFS_INTERNAL(B, CM, CS, CS##_t, CS##_layout_t) +#endif //!defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) namespace boost { namespace gil { diff --git a/include/boost/gil/utilities.hpp b/include/boost/gil/utilities.hpp index 5b5926ba04..988efc3209 100644 --- a/include/boost/gil/utilities.hpp +++ b/include/boost/gil/utilities.hpp @@ -152,15 +152,16 @@ class deref_compose : public deref_base // reinterpret_cast is implementation-defined. Static cast is not. template BOOST_FORCEINLINE -OutPtr gil_reinterpret_cast(In* p) +auto gil_reinterpret_cast(In* p) -> OutPtr { return static_cast(static_cast(p)); } -template BOOST_FORCEINLINE -const OutPtr gil_reinterpret_cast_c(const In* p) +template +BOOST_FORCEINLINE +auto gil_reinterpret_cast_c(In const* p) -> OutPtr const { - return static_cast(static_cast(p)); + return static_cast(static_cast(p)); } namespace detail { @@ -170,8 +171,8 @@ namespace detail { //////////////////////////////////////////////////////////////////////////////// template -std::pair _copy_n(InputIter first, Size count, - OutputIter result, std::input_iterator_tag) +auto _copy_n(InputIter first, Size count, OutputIter result, std::input_iterator_tag) + -> std::pair { for ( ; count > 0; --count) { @@ -183,23 +184,23 @@ std::pair _copy_n(InputIter first, Size count, } template -inline std::pair -_copy_n(RAIter first, Size count, OutputIter result, std::random_access_iterator_tag) +inline auto _copy_n(RAIter first, Size count, OutputIter result, std::random_access_iterator_tag) + -> std::pair { RAIter last = first + count; return std::pair(last, std::copy(first, last, result)); } template -inline std::pair -_copy_n(InputIter first, Size count, OutputIter result) +inline auto _copy_n(InputIter first, Size count, OutputIter result) + -> std::pair { return _copy_n(first, count, result, typename std::iterator_traits::iterator_category()); } template -inline std::pair -copy_n(InputIter first, Size count, OutputIter result) +inline auto copy_n(InputIter first, Size count, OutputIter result) + -> std::pair { return detail::_copy_n(first, count, result); } diff --git a/meta/libraries.json b/meta/libraries.json index 18c6c047d5..e87a2fa238 100644 --- a/meta/libraries.json +++ b/meta/libraries.json @@ -6,7 +6,7 @@ "Hailin Jin", "Christian Henning" ], - "description": "(C++11) Generic Image Library", + "description": "(C++14) Generic Image Library", "category": [ "Algorithms", "Containers", @@ -18,5 +18,6 @@ "Stefan Seefeld ", "Mateusz Loskot ", "Pranam Lashkari " - ] + ], + "cxxstd": "14" } diff --git a/test/Jamfile b/test/Jamfile index cfca2f4436..5c674a587f 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -16,51 +16,30 @@ import regex ; import sequence ; import testing ; -# Avoid warnings flood on Travis CI, AppVeyor, CircleCI, Azure Pipelines, GitHub Actions -if ! [ os.environ CI ] && ! [ os.environ AGENT_JOBSTATUS ] && ! [ os.environ GITHUB_ACTIONS ] -{ - DEVELOPMENT_EXTRA_WARNINGS = - msvc:"-W4" - gcc:"-pedantic -Wextra -Wcast-align -Wconversion -Wfloat-equal -Wshadow -Wsign-promo -Wstrict-aliasing -Wunused-parameter" - clang,debug:"-pedantic -Wextra -Wcast-align -Wconversion -Wfloat-equal -Wshadow -Wsign-promo -Wstrict-aliasing -Wunused-parameter -Wsign-conversion" - clang,release:"-pedantic -Wextra -Wcast-align -Wconversion -Wfloat-equal -Wshadow -Wsign-promo -Wstrict-aliasing -Wunused-parameter -Wsign-conversion" - darwin:"-pedantic -Wextra -Wcast-align -Wconversion -Wfloat-equal -Wshadow -Wsign-promo -Wstrict-aliasing -Wunused-parameter" - ; -} -else -{ - DEVELOPMENT_EXTRA_WARNINGS = - msvc:"-W1" - ; -} +# The header providing std::experimental::filesystem +# is deprecated by Microsoft and will be REMOVED. It is superseded by the C++17 +# header providing std::filesystem. +# You can define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +# to acknowledge that you have received this warning. +local msvc-cxxs-with-experimental-fs = 14 ; project : requirements - . - # TODO: Enable concepts check for all, not just test/core - #BOOST_GIL_USE_CONCEPT_CHECK=1 - msvc:"-bigobj" - msvc:on - msvc:_SCL_SECURE_NO_DEPRECATE - msvc:_CRT_SECURE_NO_WARNINGS - msvc:_CRT_NONSTDC_NO_DEPRECATE - msvc:NOMINMAX - intel:off - gcc:"-fstrict-aliasing" - darwin:"-fstrict-aliasing" - # variant filter for clang is necessary to allow ubsan_* - # custom variants declare distinct set of - clang,debug:"-fstrict-aliasing" - clang,release:"-fstrict-aliasing" - $(DEVELOPMENT_EXTRA_WARNINGS) [ requires cxx11_constexpr cxx11_defaulted_functions cxx11_template_aliases cxx11_trailing_result_types # implies decltype and auto cxx11_variadic_templates + cxx14_constexpr + cxx14_return_type_deduction ] + . + # TODO: Enable concepts check for all, not just test/core + #BOOST_GIL_USE_CONCEPT_CHECK=1 + msvc,$(msvc-cxxs-with-experimental-fs):_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING=1 + msvc:/bigobj ; variant gil_ubsan_integer diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index ed3b36f497..2b70482aa0 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -35,7 +35,9 @@ add_subdirectory(color_base) add_subdirectory(pixel) add_subdirectory(iterator) add_subdirectory(locator) +add_subdirectory(virtual_2d_locator) add_subdirectory(image) add_subdirectory(image_view) add_subdirectory(algorithm) add_subdirectory(image_processing) +add_subdirectory(histogram) diff --git a/test/core/Jamfile b/test/core/Jamfile index 7ced838338..9305b8762c 100644 --- a/test/core/Jamfile +++ b/test/core/Jamfile @@ -16,7 +16,7 @@ project ; alias headers_concepts : [ generate_self_contained_headers concepts ] ; -alias headers : [ generate_self_contained_headers : concepts extension io ] ; +alias headers : [ generate_self_contained_headers : concepts extension ] ; run promote_integral.cpp ; run test_fixture.cpp ; @@ -32,3 +32,4 @@ build-project image ; build-project image_view ; build-project algorithm ; build-project image_processing ; +build-project histogram ; diff --git a/test/core/algorithm/CMakeLists.txt b/test/core/algorithm/CMakeLists.txt index 688bd93e1c..13e113fc64 100644 --- a/test/core/algorithm/CMakeLists.txt +++ b/test/core/algorithm/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright (c) 2018 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at @@ -8,7 +9,8 @@ foreach(_name for_each_pixel std_fill - std_uninitialized_fill) + std_uninitialized_fill + extend_boundary) set(_test t_core_algorithm_${_name}) set(_target test_core_algorithm_${_name}) diff --git a/test/core/algorithm/Jamfile b/test/core/algorithm/Jamfile index 8510c80094..99436eca72 100644 --- a/test/core/algorithm/Jamfile +++ b/test/core/algorithm/Jamfile @@ -1,6 +1,7 @@ # Boost.GIL (Generic Image Library) - tests # # Copyright (c) 2018-2019 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or @@ -11,3 +12,4 @@ import testing ; run for_each_pixel.cpp ; run std_fill.cpp ; run std_uninitialized_fill.cpp ; +run extend_boundary.cpp ; diff --git a/test/extension/numeric/extend_boundary.cpp b/test/core/algorithm/extend_boundary.cpp similarity index 98% rename from test/extension/numeric/extend_boundary.cpp rename to test/core/algorithm/extend_boundary.cpp index f432ad69ba..09e524d2f3 100644 --- a/test/extension/numeric/extend_boundary.cpp +++ b/test/core/algorithm/extend_boundary.cpp @@ -1,13 +1,14 @@ // -// Copyright 2019 Pranam Lashkari +// Copyright 2019-2021 Pranam Lashkari // // 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 // #include -#include -#include +#include + +#include #include diff --git a/test/core/algorithm/for_each_pixel.cpp b/test/core/algorithm/for_each_pixel.cpp index 5490b27f3a..25eb9c5cfd 100644 --- a/test/core/algorithm/for_each_pixel.cpp +++ b/test/core/algorithm/for_each_pixel.cpp @@ -7,11 +7,10 @@ // #include #include +#include #include -#include "test_utility_output_stream.hpp" - namespace gil = boost::gil; void test_lambda_expression() @@ -26,9 +25,47 @@ void test_lambda_expression() BOOST_TEST_EQ(sum, 2 * 2 * 128); } +struct accumulator +{ + void operator()(gil::gray8_pixel_t const& p) { + sum += gil::at_c<0>(p); + } + + int sum = 0; +}; + +void test_function_object_1d_traversable() +{ + gil::gray8_pixel_t const gray128(128); + gil::gray8_image_t image(2, 2, gray128); + + accumulator acc; + acc = gil::for_each_pixel( + gil::const_view(image), + acc + ); + BOOST_TEST_EQ(acc.sum, 2 * 2 * 128); +} + + +void test_function_object_not_1d_traversable() +{ + gil::gray8_pixel_t const gray128(128); + gil::gray8_image_t image(4, 4, gray128); + + accumulator acc; + acc = gil::for_each_pixel( + gil::subimage_view(gil::const_view(image), gil::point_t{1, 1}, gil::point_t{2, 2}), + acc + ); + BOOST_TEST_EQ(acc.sum, 2 * 2 * 128); +} + int main() { test_lambda_expression(); + test_function_object_1d_traversable(); + test_function_object_not_1d_traversable(); return ::boost::report_errors(); } diff --git a/test/core/channel/CMakeLists.txt b/test/core/channel/CMakeLists.txt index 365a8274f6..d732e8e8ff 100644 --- a/test/core/channel/CMakeLists.txt +++ b/test/core/channel/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright (c) 2018 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at @@ -16,7 +17,8 @@ foreach(_name is_channel_integral packed_channel_value scoped_channel_value - test_fixture) + test_fixture + channel_numeric_operations) set(_test t_core_channel_${_name}) set(_target test_core_channel_${_name}) diff --git a/test/core/channel/Jamfile b/test/core/channel/Jamfile index 9e6b65027d..428b9c29f8 100644 --- a/test/core/channel/Jamfile +++ b/test/core/channel/Jamfile @@ -1,6 +1,7 @@ # Boost.GIL (Generic Image Library) - tests # # Copyright (c) 2018-2020 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or @@ -15,6 +16,7 @@ run channel_traits.cpp ; run test_fixture.cpp ; run packed_channel_value.cpp ; run scoped_channel_value.cpp ; +run channel_numeric_operations.cpp ; run algorithm_channel_arithmetic.cpp ; run algorithm_channel_convert.cpp ; diff --git a/test/core/channel/algorithm_channel_relation.cpp b/test/core/channel/algorithm_channel_relation.cpp index bad3a1d4bc..28d155f94f 100644 --- a/test/core/channel/algorithm_channel_relation.cpp +++ b/test/core/channel/algorithm_channel_relation.cpp @@ -21,8 +21,6 @@ template void test_channel_relation() { using fixture_t = fixture::channel; - using channel_value_t = typename fixture_t::channel_value_t; - channel_value_t const one = 1; fixture_t f; BOOST_TEST_LE(f.min_v_, f.max_v_); @@ -31,7 +29,16 @@ void test_channel_relation() BOOST_TEST_GT(f.max_v_, f.min_v_); BOOST_TEST_NE(f.max_v_, f.min_v_); BOOST_TEST_EQ(f.min_v_, f.min_v_); + +#if !defined(BOOST_CLANG) || (__clang_major__ == 3 && __clang_minor__ >= 8) + // This particular test fails with optimised build using clang 3.5 or 3.6 + // for unknown reasons. Volunteers are welcome to debug and confirm it is + // either the compiler bug or the library bug: + // b2 toolset=clang variant=release cxxstd=11 define=_GLIBCXX_USE_CXX11_ABI=0 libs/gil/test/core/channel//algorithm_channel_relation + using channel_value_t = typename fixture_t::channel_value_t; + channel_value_t const one = 1; BOOST_TEST_NE(f.min_v_, one); // comparable to integral +#endif } struct test_channel_value diff --git a/test/extension/numeric/channel_numeric_operations.cpp b/test/core/channel/channel_numeric_operations.cpp similarity index 99% rename from test/extension/numeric/channel_numeric_operations.cpp rename to test/core/channel/channel_numeric_operations.cpp index 4f75cbbc66..ff20b35b92 100644 --- a/test/extension/numeric/channel_numeric_operations.cpp +++ b/test/core/channel/channel_numeric_operations.cpp @@ -1,12 +1,13 @@ // // Copyright 2019-2020 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // 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 // #include -#include +#include #include diff --git a/test/core/histogram/CMakeLists.txt b/test/core/histogram/CMakeLists.txt new file mode 100644 index 0000000000..42a4931cfd --- /dev/null +++ b/test/core/histogram/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2020 Debabrata Mandal +# +# 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 +# + +foreach(_name + access + constructor + cumulative + dimension + fill + hash_tuple + helpers + is_compatible + key + sub_histogram + utilities) + set(_test t_core_histogram_${_name}) + set(_target test_core_histogram_${_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/core/histogram/Jamfile b/test/core/histogram/Jamfile new file mode 100644 index 0000000000..883fba7096 --- /dev/null +++ b/test/core/histogram/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright 2020 Debabrata Mandal +# +# 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 +# + +import testing ; + +compile constructor.cpp ; +compile dimension.cpp ; +run access.cpp ; +run cumulative.cpp ; +run fill.cpp ; +run hash_tuple.cpp ; +run helpers.cpp ; +run is_compatible.cpp ; +run key.cpp ; +run sub_histogram.cpp ; +run utilities.cpp ; diff --git a/test/core/histogram/access.cpp b/test/core/histogram/access.cpp new file mode 100644 index 0000000000..4420319ee9 --- /dev/null +++ b/test/core/histogram/access.cpp @@ -0,0 +1,35 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +#include + +#include + +namespace gil = boost::gil; + +void check_indexing_operator() +{ + gil::histogram h1; + h1(1) = 3; + BOOST_TEST(h1(1) == 3); + BOOST_TEST(h1(3) == 0); + + gil::histogram h2; + h2(1, 'a', "A") = 4; + BOOST_TEST(h2(1, 'a', "A") == 4); + BOOST_TEST(h2(1, 'a', "B") == 0); +} + +int main() { + + check_indexing_operator(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/constructor.cpp b/test/core/histogram/constructor.cpp new file mode 100644 index 0000000000..f1d1ca7e71 --- /dev/null +++ b/test/core/histogram/constructor.cpp @@ -0,0 +1,31 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_histogram_constructors() +{ + gil::histogram h1; + gil::histogram h2 = h1; + gil::histogram h3; + gil::histogram h4(h3); + + gil::histogram d1, d2 = d1, d3(d2); +} + +int main() { + + check_histogram_constructors(); + return 0; + +} diff --git a/test/core/histogram/cumulative.cpp b/test/core/histogram/cumulative.cpp new file mode 100644 index 0000000000..34a8e1e028 --- /dev/null +++ b/test/core/histogram/cumulative.cpp @@ -0,0 +1,54 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +#include + +namespace gil = boost::gil; + +void check_cumulative() +{ + gil::histogram h1; + for (int i = 0; i < 8; i++) + { + h1(i) = 1; + } + auto h2 = cumulative_histogram(h1); + bool check1 = true; + for (int i = 0; i < 8; i++) + { + if(h2(i) != i+1) + check1 = false; + } + BOOST_TEST(check1); + + gil::histogram h3; + h3(1, 3) = 1; + h3(1, 4) = 2; + h3(2, 1) = 3; + h3(2, 2) = 1; + h3(2, 5) = 2; + h3(3, 2) = 3; + h3(3, 9) = 1; + auto h4 = cumulative_histogram(h3); + BOOST_TEST(h4(1, 3) == 1); + BOOST_TEST(h4(1, 4) == 3); + BOOST_TEST(h4(2, 1) == 3); + BOOST_TEST(h4(2, 2) == 4); + BOOST_TEST(h4(2, 5) == 9); + BOOST_TEST(h4(3, 2) == 7); + BOOST_TEST(h4(3, 9) == 13); +} + +int main() { + + check_cumulative(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/dimension.cpp b/test/core/histogram/dimension.cpp new file mode 100644 index 0000000000..74dd3374f6 --- /dev/null +++ b/test/core/histogram/dimension.cpp @@ -0,0 +1,31 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_histogram_constructors() +{ + gil::histogram h1; + gil::histogram h2 = h1; + gil::histogram h3; + gil::histogram h4(h3); + + static_assert(h1.dimension() == h2.dimension(),"Dimension mismatch"); + static_assert(h1.dimension() != h3.dimension(),"Dimension mismatch"); + static_assert(h3.dimension() == h4.dimension(),"Dimension mismatch"); +} + +int main() { + + check_histogram_constructors(); + return 0; + +} diff --git a/test/core/histogram/fill.cpp b/test/core/histogram/fill.cpp new file mode 100644 index 0000000000..e43a2faec4 --- /dev/null +++ b/test/core/histogram/fill.cpp @@ -0,0 +1,282 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include +#include +#include +#include + +#include +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +gil::gray8_image_t img1(4, 4, gil::gray8_pixel_t(1)); +gil::gray8_view_t v1 = view(img1); + +gil::rgb8_image_t img2(4, 4, gil::rgb8_pixel_t(1)); +gil::rgb8_view_t v2 = view(img2); + +std::uint8_t sparse_matrix[] = +{ + 1, 1, 1, 1, + 3, 3, 3, 3, + 5, 5, 5, 5, + 7, 7, 7, 7 +}; + +std::uint8_t big_matrix[] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 3, 4, 5, 6, 7, 8, + 3, 4, 3, 4, 3, 4, 3, 4, + 1, 2, 3, 4, 5, 6, 7, 8, + 5, 6, 5, 6, 5, 6, 5, 6, + 1, 2, 3, 4, 5, 6, 7, 8, + 7, 8, 7, 8, 7, 8, 7, 8 +}; + +std::uint8_t big_rgb_matrix[] = +{ + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, 1, 2, 3, 2, 3, 4, + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, 3, 4, 5, 4, 5, 6, + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 5, 6, 7, 6, 7, 8, 5, 6, 7, 6, 7, 8, 5, 6, 7, 6, 7, 8, 5, 6, 7, 6, 7, 8, + 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, + 7, 8, 9, 8, 9, 10, 7, 8, 9, 8, 9, 10, 7, 8, 9, 8, 9, 10, 7, 8, 9, 8, 9, 10, +}; + +std::vector> mask = +{ + {1, 0, 0, 1}, + {0, 0, 1, 1}, + {0, 1, 0, 1}, + {1, 1, 0, 0}, +}; + +gil::gray8c_view_t sparse_gray_view = gil::interleaved_view(4, 4, reinterpret_cast(sparse_matrix), 4); + +gil::gray8c_view_t big_gray_view = gil::interleaved_view(8, 8, reinterpret_cast(big_matrix), 8); + +gil::rgb8c_view_t big_rgb_view = gil::interleaved_view(8, 8, reinterpret_cast(big_rgb_matrix), 24); + +void check_histogram_fill_test1() +{ + gil::histogram h1; + + h1.fill(big_gray_view); + + bool check_gray_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h1(i) != 8) + { + check_gray_fill = false; + } + } + BOOST_TEST(check_gray_fill); +} + +void check_histogram_fill_test2() +{ + gil::histogram h3; + h3.fill(big_rgb_view); + + bool check_rgb_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h3(i, i+1, i+2) != 8) + { + check_rgb_fill = false; + } + } + BOOST_TEST(check_rgb_fill); +} + +void check_histogram_fill_test3() +{ + gil::histogram h2; + h2.fill<1>(big_rgb_view); + bool check_gray_fill2 = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h2(i+1) != 8) + { + check_gray_fill2 = false; + } + } + BOOST_TEST(check_gray_fill2); +} + +void check_histogram_fill_test4() +{ + gil::histogram h1; + // Check with limits + std::tuple lower{2}, higher{6}; + h1.clear(); + h1.fill(big_gray_view, 1, false, {{}}, lower, higher, true); + bool check_gray_fill = true; + check_gray_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(i<2 || i>6) + { + check_gray_fill = check_gray_fill & (h1(i)==0);continue; + } + if(h1(i) != 8) + { + check_gray_fill = false; + } + } + BOOST_TEST(check_gray_fill); +} + +void check_histogram_fill_test5() +{ + gil::histogram h3; + std::tuple lower1{2,2,2}, higher1{6,6,6}; + h3.clear(); + h3.fill(big_rgb_view, 1, false, {{}}, lower1, higher1, true); + + bool check_rgb_fill = true; + check_rgb_fill = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(!(i >= 2 && (i+2) <= 6)) + { + check_rgb_fill = check_rgb_fill & (h3(i, i+1, i+2)==0);continue; + } + if(h3(i, i+1, i+2) != 8) + { + check_rgb_fill = false; + } + } + BOOST_TEST(check_rgb_fill); +} + +void check_histogram_fill_test6() +{ + gil::histogram h2; + h2.clear(); + std::tuple lower{2}, higher{6}; + h2.fill<1>(big_rgb_view, 1, false, {{}}, lower, higher, true); + bool check_gray_fill2 = true; + check_gray_fill2 = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(i+1 < 2 || i+1 > 6) + { + check_gray_fill2 = check_gray_fill2 & (h2(i+1)==0);continue; + } + if(h2(i+1) != 8) + { + check_gray_fill2 = false; + } + } + BOOST_TEST(check_gray_fill2); +} + +void check_histogram_fill_test7() +{ + //Check masking + gil::histogram h4; + std::tuple low{1}, high{8}; + gil::fill_histogram(sparse_gray_view, h4, 1, false, false, true, mask, low, high, true); + + bool check_1d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(i%2==1) + { + check_1d = check_1d & (h4(i)==2); + } + } + BOOST_TEST(check_1d); +} + +void check_histogram_fill_algorithm() +{ + gil::histogram h1; + + gil::fill_histogram<1>(big_rgb_view, h1); + + bool check_1d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h1(i+1) != 8) + { + check_1d = false; + } + } + BOOST_TEST(check_1d); + + gil::histogram h2; + + gil::fill_histogram<2, 1>(big_rgb_view, h2); + + bool check_2d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h2(i+2, i+1) != 8) + { + check_2d = false; + } + } + BOOST_TEST(check_2d); + + gil::histogram h3; + + std::tuple low(1), high(8); + gil::fill_histogram(sparse_gray_view, h3, 1, false, false, false, {{}}, low, high, true); + + check_1d = true; + for (std::size_t i = 1; i <= 8; ++i) + { + if(h3.find(std::tuple(i)) == h3.end()) + { + check_1d = false; + } + else + { + check_1d = check_1d & (i % 2 == 1 ? (h3(i) == 4) : (h3(i) == 0)); + } + } + BOOST_TEST(check_1d); +} + +void check_fill_bin_width() +{ + gil::histogram h1; + gil::fill_histogram(big_gray_view, h1, 2); + bool check1 = true; + for(std::size_t i = 1; i <= 3; ++i) + { + check1 = check1 & (h1(i) == 16); + } + check1 = check1 & (h1(0) == 8) & (h1(4) == 8); + BOOST_TEST(check1); +} + +int main() { + + check_histogram_fill_test1(); + check_histogram_fill_test2(); + check_histogram_fill_test3(); + check_histogram_fill_test4(); + check_histogram_fill_test5(); + check_histogram_fill_test6(); + check_histogram_fill_test7(); + check_histogram_fill_algorithm(); + check_fill_bin_width(); + + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/core/histogram/hash_tuple.cpp b/test/core/histogram/hash_tuple.cpp new file mode 100644 index 0000000000..69d47d16a2 --- /dev/null +++ b/test/core/histogram/hash_tuple.cpp @@ -0,0 +1,35 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +#include +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_detail_hash_tuple () +{ + std::tuple t(1, 1); + std::size_t seed1 = 0; + boost::hash_combine(seed1, std::get<0>(t)); + boost::hash_combine(seed1, std::get<1>(t)); + + gil::detail::hash_tuple g; + std::size_t seed2 = g(t); + BOOST_TEST(seed1 == seed2); +} + +int main() +{ + check_detail_hash_tuple(); + return boost::report_errors(); +} diff --git a/test/core/histogram/helpers.cpp b/test/core/histogram/helpers.cpp new file mode 100644 index 0000000000..ee4797c8f6 --- /dev/null +++ b/test/core/histogram/helpers.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include +#include +#include + +#include +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +void check_helper_fn_pixel_to_tuple() +{ + gil::gray8_pixel_t g1(2); + auto g2 = gil::detail::pixel_to_tuple(g1, mp11::make_index_sequence<1>{}); + + bool const same_gray_type = std::is_same, decltype(g2)>::value; + BOOST_TEST(same_gray_type); + BOOST_TEST(g1[0] == std::get<0>(g2)); + + gil::rgb8_pixel_t r1(1,2,3); + auto r2 = gil::detail::pixel_to_tuple(r1, mp11::index_sequence<0, 1, 2>{}); + + bool const same_rgb_type = std::is_same, + decltype(r2)>::value; + BOOST_TEST(same_rgb_type); + BOOST_TEST(r1[0] == std::get<0>(r2) && r1[1] == std::get<1>(r2) && r1[2] == std::get<2>(r2)); + + auto r3 = gil::detail::pixel_to_tuple(r1, mp11::index_sequence<1, 2, 0>{}); + BOOST_TEST(r1[0] == std::get<2>(r3) && r1[1] == std::get<0>(r3) && r1[2] == std::get<1>(r3)); +} + +void check_helper_fn_tuple_to_tuple() +{ + std::tuple t1(1); + auto t2 = gil::detail::tuple_to_tuple(t1, mp11::make_index_sequence<1>{}); + + bool const same_gray_type = std::is_same, decltype(t2)>::value; + BOOST_TEST(same_gray_type); + BOOST_TEST(std::get<0>(t1) == std::get<0>(t2)); + + std::tuple r1(1, 2, "A"); + auto r2 = gil::detail::tuple_to_tuple(r1, mp11::index_sequence<0, 1, 2>{}); + + bool const same_rgb_type = std::is_same, + decltype(r2)>::value; + BOOST_TEST(same_rgb_type); + BOOST_TEST( std::get<0>(r1) == std::get<0>(r2) && + std::get<1>(r1) == std::get<1>(r2) && + std::get<2>(r1) == std::get<2>(r2)); + + auto r3 = gil::detail::tuple_to_tuple(r1, mp11::index_sequence<1, 2, 0>{}); + BOOST_TEST( std::get<0>(r1) == std::get<2>(r3) && + std::get<1>(r1) == std::get<0>(r3) && + std::get<2>(r1) == std::get<1>(r3)); +} + +void check_helper_tuple_limit() +{ + using type1 = std::tuple; + using type2 = std::tuple; + type1 t1_min(std::numeric_limits::min(), std::numeric_limits::min()); + type1 t1_max(std::numeric_limits::max(), std::numeric_limits::max()); + type2 t2_min(std::numeric_limits::min(), std::numeric_limits::min()); + type2 t2_max(std::numeric_limits::max(), std::numeric_limits::max()); + + BOOST_TEST(t1_min == gil::detail::tuple_limit::min()); + BOOST_TEST(t1_max == gil::detail::tuple_limit::max()); + BOOST_TEST(t2_min == gil::detail::tuple_limit::min()); + BOOST_TEST(t2_max == gil::detail::tuple_limit::max()); + +} + +void check_filler() +{ + boost::gil::histogram h; + boost::gil::detail::filler<1> f; + std::tuple l1{4}, h1{13}; + f(h, l1, h1); + + std::tuple l2{20}, h2{33}; + f(h, l2, h2); + + bool check = true; + for(int i = 0; i < 100; i++) + { + if((i >= 4 && i <= 13) || (i >= 20 && i <= 33)) + { + if(h.find(std::tuple(i))==h.end()) + check = false; + } + } + BOOST_TEST(check); +} + +int main() { + + check_helper_fn_pixel_to_tuple(); + check_helper_fn_tuple_to_tuple(); + check_helper_tuple_limit(); + check_filler(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/is_compatible.cpp b/test/core/histogram/is_compatible.cpp new file mode 100644 index 0000000000..30259a98d3 --- /dev/null +++ b/test/core/histogram/is_compatible.cpp @@ -0,0 +1,72 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +#include + +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + + +void check_is_pixel_compatible() +{ + gil::histogram h1; + gil::histogram h2; + gil::histogram h3; + gil::histogram h4; + + BOOST_TEST(h1.is_pixel_compatible()); + BOOST_TEST(h2.is_pixel_compatible()); + BOOST_TEST(h3.is_pixel_compatible()); + BOOST_TEST(!h4.is_pixel_compatible()); +} + +void check_is_tuple_compatible() +{ + gil::histogram h1; + gil::histogram h2; + gil::histogram h3; + gil::histogram h4; + gil::histogram h5; + + std::tuple t1; + std::tuple t2; + std::tuple t3; + std::tuple t4; + std::tuple t5; + + BOOST_TEST(h1.is_tuple_compatible(t1)); + BOOST_TEST(h1.is_tuple_compatible(t2)); + BOOST_TEST(!h1.is_tuple_compatible(t3)); + BOOST_TEST(!h1.is_tuple_compatible(t5)); + + BOOST_TEST(h2.is_tuple_compatible(t1)); + BOOST_TEST(h2.is_tuple_compatible(t2)); + BOOST_TEST(!h2.is_tuple_compatible(t3)); + + BOOST_TEST(!h3.is_tuple_compatible(t1)); + BOOST_TEST(h3.is_tuple_compatible(t3)); + BOOST_TEST(h3.is_tuple_compatible(t4)); + BOOST_TEST(!h3.is_tuple_compatible(t5)); + + BOOST_TEST(!h4.is_tuple_compatible(t1)); + BOOST_TEST(h4.is_tuple_compatible(t3)); + BOOST_TEST(h4.is_tuple_compatible(t4)); + BOOST_TEST(!h4.is_tuple_compatible(t5)); +} + +int main() { + + check_is_pixel_compatible(); + check_is_tuple_compatible(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/key.cpp b/test/core/histogram/key.cpp new file mode 100644 index 0000000000..3029c46c8f --- /dev/null +++ b/test/core/histogram/key.cpp @@ -0,0 +1,62 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include +#include +#include + +#include + +#include + +namespace gil = boost::gil; + +void check_histogram_key_from_tuple() +{ + gil::histogram h1; + std::tuple t1(1, 2); + auto t2 = h1.key_from_tuple(t1); + const bool same_type = std::is_same, decltype(t2)>::value; + + BOOST_TEST(same_type); + BOOST_TEST(std::get<0>(t2) == 1 && std::get<1>(t2) == 2); + + std::tuple t3(1, 2, 4, 2); + auto t4 = h1.key_from_tuple<0, 2>(t3); + const bool same_type1 = std::is_same, decltype(t4)>::value; + + BOOST_TEST(same_type1); + BOOST_TEST(std::get<0>(t4) == 1 && std::get<1>(t4) == 4); +} + +void check_histogram_key_from_pixel() +{ + gil::histogram h1; + gil::gray8_pixel_t g1(1); + auto t1 = h1.key_from_pixel(g1); + const bool same_type = std::is_same, decltype(t1)>::value; + + BOOST_TEST(same_type); + BOOST_TEST(std::get<0>(t1) == 1); + + gil::histogram h2; + gil::rgb8_pixel_t r1(1, 0, 3); + auto t2 = h2.key_from_pixel<0, 2>(r1); + const bool same_type1 = std::is_same, decltype(t2)>::value; + + BOOST_TEST(same_type1); + BOOST_TEST(std::get<0>(t2) == 1 && std::get<1>(t2) == 3); +} + +int main() { + + check_histogram_key_from_tuple(); + check_histogram_key_from_pixel(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/sub_histogram.cpp b/test/core/histogram/sub_histogram.cpp new file mode 100644 index 0000000000..15748611a6 --- /dev/null +++ b/test/core/histogram/sub_histogram.cpp @@ -0,0 +1,58 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include + +#include + +#include + +namespace gil = boost::gil; + +void check_sub_histogram_without_tuple() +{ + gil::histogram h; + h(1, 1, "A", 1) = 1; + h(1, 2, "B", 1) = 1; + h(2, 1, "C", 1) = 1; + h(2, 1, "D", 1) = 1; + h(2, 3, "E", 4) = 1; + auto h1 = h.sub_histogram<0,3>(); + BOOST_TEST(h1(1, 1) == 2); + BOOST_TEST(h1(2, 1) == 2); + BOOST_TEST(h1(2, 4) == 1); + BOOST_TEST(h1(5, 5) == 0); +} + +void check_sub_histogram_with_tuple() +{ + gil::histogram h; + h(1, 1, "A", 1) = 3; + h(1, 2, "C", 1) = 1; + h(2, 1, "C", 1) = 1; + h(2, 1, "A", 1) = 1; + h(2, 3, "E", 4) = 1; + h(1, 3, "A", 1) = 2; + std::tuple t(1.0, 1000, "A", 1); + // This means 1st dimension is useless for matching. + auto h1 = h.sub_histogram<0,2,3>(t, t); + BOOST_TEST(h1(1, 1, "A", 1) == 3); + BOOST_TEST(h1(1, 2, "C", 1) == 0); + BOOST_TEST(h1(2, 1, "C", 1) == 0); + BOOST_TEST(h1(2, 1, "A", 1) == 0); + BOOST_TEST(h1(2, 1, "A", 1) == 0); + BOOST_TEST(h1(1, 3, "A", 1) == 2); +} + +int main() { + + check_sub_histogram_without_tuple(); + check_sub_histogram_with_tuple(); + + return boost::report_errors(); +} diff --git a/test/core/histogram/utilities.cpp b/test/core/histogram/utilities.cpp new file mode 100644 index 0000000000..e1c8642ca0 --- /dev/null +++ b/test/core/histogram/utilities.cpp @@ -0,0 +1,215 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace gil = boost::gil; +namespace mp11 = boost::mp11; + +std::uint8_t big_matrix[] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 3, 4, 5, 6, 7, 8, + 3, 4, 3, 4, 3, 4, 3, 4, + 1, 2, 3, 4, 5, 6, 7, 8, + 5, 6, 5, 6, 5, 6, 5, 6, + 1, 2, 3, 4, 5, 6, 7, 8, + 7, 8, 7, 8, 7, 8, 7, 8 +}; + +void check_normalize() +{ + auto epsilon = 1e-6; + // 1D histogram + double expected[64]; + gil::histogram h1; + int sum = 0; + for (std::size_t i = 0; i < 64; i++) + { + h1(i) = big_matrix[i]; + sum += big_matrix[i]; + } + for (std::size_t i = 0; i < 64; i++) + { + expected[i] = double(big_matrix[i]) / sum; + } + h1.normalize(); + + bool check = true; + for (std::size_t i = 0; i < 64; i++) + { + check = check & (std::abs(expected[i] - h1(i)) < epsilon); + } + BOOST_TEST(check); + + // 2D histogram + double expected2[8][8]; + gil::histogram h2; + int sum2 = 0; + for (std::size_t i = 0; i < 64; i++) + { + h2(i/8, i%8) = big_matrix[i]; + sum2 += big_matrix[i]; + } + for (std::size_t i = 0; i < 64; i++) + { + expected2[i/8][i%8] = double(big_matrix[i]) / sum2; + } + h2.normalize(); + + bool check2 = true; + for (std::size_t i = 0; i < 64; i++) + { + check2 = check2 & (std::abs(expected2[i/8][i%8] - h2(i/8,i%8)) < epsilon); + } + BOOST_TEST(check2); +} + +void check_nearest_key() +{ + { + gil::histogram h1; + h1(1) = 1; + h1(3) = 4; + h1(4) = 4; + h1(6) = 1; + std::tuple k1{2}, k2{3}, k3{5}; + std::tuple k1_expected{1}, k2_expected{3}, k3_expected{4}; + BOOST_TEST(k1_expected == h1.nearest_key(k1)); + BOOST_TEST(k2_expected == h1.nearest_key(k2)); + BOOST_TEST(k3_expected == h1.nearest_key(k3)); + } + + { + gil::histogram h2; + h2(1, 1) = 1; + h2(1, 4) = 1; + h2(2, 4) = 1; + h2(4, 4) = 1; + std::tuple k1(1, 1), k2(1, 3), k3(2, 1), k4(2, 7), k5(4, 4); + std::tuple k1_exp(1, 1), k2_exp(1, 1), k3_exp(1, 4), k4_exp(2, 4), k5_exp(4, 4); + BOOST_TEST(k1_exp == h2.nearest_key(k1)); + BOOST_TEST(k2_exp == h2.nearest_key(k2)); + BOOST_TEST(k3_exp == h2.nearest_key(k3)); + BOOST_TEST(k4_exp == h2.nearest_key(k4)); + BOOST_TEST(k5_exp == h2.nearest_key(k5)); + } + +} + +void check_equals() +{ + gil::histogram h, h2; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + h2 = h; + BOOST_TEST(h2.equals(h)); + + gil::histogram h3; + h3(1) = 3; + h3(4) = 1; + h3(2) = 6; + h3(7) = 3; + h3(9) = 7; + BOOST_TEST(h3.equals(h)); +} + +void check_sum() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + auto sm = h.sum(); + BOOST_TEST(sm == 20); +} + +void check_max_key() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + BOOST_TEST(std::get<0>(h.max_key()) == 9); + + gil::histogram h2; + h2(1, 4) = 3; + h2(4, 2) = 1; + h2(2, 5) = 6; + h2(7, 4) = 3; + h2(9, 1) = 7; + h2(9, 3) = 7; + BOOST_TEST(std::get<0>(h2.max_key()) == 9 && std::get<1>(h2.max_key()) == 3); +} + +void check_min_key() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + BOOST_TEST(std::get<0>(h.min_key()) == 1); + + gil::histogram h2; + h2(1, 4) = 3; + h2(4, 2) = 1; + h2(2, 5) = 6; + h2(7, 4) = 3; + h2(9, 1) = 7; + h2(9, 3) = 7; + BOOST_TEST(std::get<0>(h2.min_key()) == 1 && std::get<1>(h2.min_key()) == 4); +} + +void check_sorted_keys() +{ + gil::histogram h; + h(1) = 3; + h(4) = 1; + h(2) = 6; + h(7) = 3; + h(9) = 7; + + std::vector> v; + v.push_back(std::tuple(1)); + v.push_back(std::tuple(2)); + v.push_back(std::tuple(4)); + v.push_back(std::tuple(7)); + v.push_back(std::tuple(9)); + BOOST_TEST(v == h.sorted_keys()); +} + +int main() { + + check_normalize(); + check_nearest_key(); + check_equals(); + check_max_key(); + check_min_key(); + check_sum(); + check_sorted_keys(); + + return boost::report_errors(); +} diff --git a/test/core/image/CMakeLists.txt b/test/core/image/CMakeLists.txt index 6574cf2757..2f09041198 100644 --- a/test/core/image/CMakeLists.txt +++ b/test/core/image/CMakeLists.txt @@ -7,6 +7,8 @@ # foreach(_name concepts + alignment + empty_dimensions image) set(_test t_core_image_${_name}) set(_target test_core_image_${_name}) diff --git a/test/core/image/Jamfile b/test/core/image/Jamfile index 07da63467d..c0cf0f4ad6 100644 --- a/test/core/image/Jamfile +++ b/test/core/image/Jamfile @@ -10,4 +10,6 @@ import testing ; compile concepts.cpp ; -run image.cpp ; +run alignment.cpp ; +run empty_dimensions.cpp ; +run image.cpp ; \ No newline at end of file diff --git a/test/core/image/alignment.cpp b/test/core/image/alignment.cpp new file mode 100644 index 0000000000..1ddf9b9772 --- /dev/null +++ b/test/core/image/alignment.cpp @@ -0,0 +1,125 @@ +// +// Copyright 2022 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 +// +#include + +#include + +#include "test_fixture.hpp" +#include "core/pixel/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct test_constructor_with_alignment +{ + template + void operator()(Image const&) + { + std::size_t alignment = 0; + Image image(alignment); + BOOST_TEST(image.width() == 0); + BOOST_TEST(image.height() == 0); + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_alignment{}); + } +}; + +struct test_constructor_with_alignment_allocator +{ + template + void operator()(Image const&) + { + std::size_t alignment = 0; + std::allocator allocator; + Image image(alignment, allocator); + BOOST_TEST(image.width() == 0); + BOOST_TEST(image.height() == 0); + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_alignment_allocator{}); + } +}; + +struct test_constructor_with_dimensions_alignment +{ + template + void operator()(Image const&) + { + gil::point_t const dimensions{256, 128}; + std::size_t alignment = 0; + Image image(dimensions, alignment); + BOOST_TEST(image.width() == dimensions.x); + BOOST_TEST(image.height() == dimensions.y); + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_dimensions_alignment{}); + } +}; + +struct test_constructor_with_dimensions_alignment_allocator +{ + template + void operator()(Image const&) + { + gil::point_t const dimensions{256, 128}; + std::size_t alignment = 0; + std::allocator allocator; + Image image(dimensions, alignment, allocator); + BOOST_TEST(image.width() == dimensions.x); + BOOST_TEST(image.height() == dimensions.y); + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_dimensions_alignment_allocator{}); + } +}; + +struct test_constructor_with_dimensions_pixel_alignment +{ + template + void operator()(Image const &) + { + gil::point_t const dimensions{3, 3}; + using pixel_t = typename Image::view_t::value_type; + pixel_t const rnd_pixel = fixture::pixel_generator::random(); + + Image image(dimensions, rnd_pixel, 32u); + BOOST_TEST(image.width() == dimensions.x); + BOOST_TEST(image.height() == dimensions.y); + + for (pixel_t const& p : gil::view(image)) + { + BOOST_TEST(p == rnd_pixel); + } + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_dimensions_pixel_alignment{}); + } +}; + +int main() +{ + test_constructor_with_alignment::run(); + test_constructor_with_alignment_allocator::run(); + test_constructor_with_dimensions_alignment::run(); + test_constructor_with_dimensions_alignment_allocator::run(); + test_constructor_with_dimensions_pixel_alignment::run(); + test_constructor_with_dimensions_pixel_alignment::run(); + + return ::boost::report_errors(); +} diff --git a/test/core/image/empty_dimensions.cpp b/test/core/image/empty_dimensions.cpp new file mode 100644 index 0000000000..9a665e490f --- /dev/null +++ b/test/core/image/empty_dimensions.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2019-2020 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 +// +#include + +#include + +#include "test_fixture.hpp" +#include "test_utility_output_stream.hpp" +#include "core/pixel/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +// Test cases of memory leak potential for image created with empty dimesions, +// see https://github.com/boostorg/gil/pull/649 +// The main goal of these test cases is to trigger any memory leak detectors. + +void test_default_constructor() +{ + boost::gil::rgb8_image_t image; + BOOST_TEST_EQ(image.width(), 0); + BOOST_TEST_EQ(image.height(), 0); +} + +void test_copy_constructor_with_empty_image() +{ + boost::gil::rgb8_image_t image1; + boost::gil::rgb8_image_t image2(image1); + BOOST_TEST_EQ(image2.width(), 0); + BOOST_TEST_EQ(image2.height(), 0); +} + +struct test_constructor_with_empty_dimensions +{ + template + void operator()(Image const &) + { + using image_t = Image; + image_t image(0, 0); + BOOST_TEST_EQ(image.width(), 0); + BOOST_TEST_EQ(image.height(), 0); + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_empty_dimensions{}); + } +}; + +struct test_constructor_with_empty_dimensions_with_pixel +{ + template + void operator()(Image const &) + { + using image_t = Image; + gil::point_t const dimensions{0, 0}; + using pixel_t = typename image_t::view_t::value_type; + pixel_t const rnd_pixel = fixture::pixel_generator::random(); + image_t image(dimensions, rnd_pixel); + BOOST_TEST_EQ(image.width(), dimensions.x); + BOOST_TEST_EQ(image.height(), dimensions.y); + + bool none_visited = true; + for (pixel_t const &p : gil::view(image)) + { + none_visited = false; + BOOST_TEST_EQ(p, rnd_pixel); + } + BOOST_TEST(none_visited); + } + + static void run() + { + boost::mp11::mp_for_each(test_constructor_with_empty_dimensions_with_pixel{}); + } +}; + +int main() +{ + test_default_constructor(); + test_copy_constructor_with_empty_image(); + test_constructor_with_empty_dimensions::run(); + test_constructor_with_empty_dimensions_with_pixel::run(); + + return ::boost::report_errors(); +} diff --git a/test/core/image/image.cpp b/test/core/image/image.cpp index 52d7bd7034..e974b2b79b 100644 --- a/test/core/image/image.cpp +++ b/test/core/image/image.cpp @@ -35,6 +35,7 @@ struct test_constructor_with_dimensions_pixel static void run() { boost::mp11::mp_for_each(test_constructor_with_dimensions_pixel{}); + boost::mp11::mp_for_each(test_constructor_with_dimensions_pixel{}); } }; @@ -44,27 +45,28 @@ struct test_constructor_from_other_image void operator()(Image const &) { using image_t = Image; + using allocator_t = typename Image::allocator_type; gil::point_t const dimensions{256, 128}; using pixel_t = typename image_t::view_t::value_type; pixel_t const rnd_pixel = fixture::pixel_generator::random(); { //constructor interleaved from planar - gil::image image1(dimensions, rnd_pixel); + gil::image image1(dimensions, rnd_pixel); image_t image2(image1); BOOST_TEST_EQ(image2.dimensions(), dimensions); auto v1 = gil::const_view(image1); auto v2 = gil::const_view(image2); BOOST_TEST_ALL_EQ(v1.begin(), v1.end(), v2.begin(), v2.end()); } - // { - // //constructor planar from interleaved - // image_t image1(dimensions, rnd_pixel); - // gil::image image2(image1); - // BOOST_TEST_EQ(image2.dimensions(), dimensions); - // auto v1 = gil::const_view(image1); - // auto v2 = gil::const_view(image2); - // BOOST_TEST_ALL_EQ(v1.begin(), v1.end(), v2.begin(), v2.end()); - // } + { + // constructor planar from interleaved + image_t image1(dimensions, rnd_pixel); + gil::image image2(image1); + BOOST_TEST_EQ(image2.dimensions(), dimensions); + auto v1 = gil::const_view(image1); + auto v2 = gil::const_view(image2); + BOOST_TEST_ALL_EQ(v1.begin(), v1.end(), v2.begin(), v2.end()); + } } static void run() { @@ -90,6 +92,15 @@ struct test_constructor_from_view auto v2 = gil::const_view(image2); BOOST_TEST_ALL_EQ(v1.begin(), v1.end(), v2.begin(), v2.end()); } + { + //constructor planar from interleaved + image_t image1(dimensions, rnd_pixel); + auto v1 = gil::transposed_view(gil::const_view(image1)); + gil::image image2(gil::transposed_view(v1)); + BOOST_TEST_EQ(image2.dimensions(), dimensions); + auto v2 = gil::const_view(image2); + BOOST_TEST_ALL_EQ(v1.begin(), v1.end(), v2.begin(), v2.end()); + } } static void run() { @@ -97,6 +108,27 @@ struct test_constructor_from_view } }; +struct test_copy_assignement +{ + template + void operator()(Image const&) + { + using image_t = Image; + gil::point_t const dimensions{ 256, 128 }; + { + image_t image = fixture::create_image(dimensions.x, dimensions.y, 0); + image_t image2; + image2 = image; + BOOST_TEST_EQ(image2.dimensions(), dimensions); + } + } + static void run() + { + boost::mp11::mp_for_each(test_copy_assignement{}); + boost::mp11::mp_for_each(test_copy_assignement{}); + } +}; + struct test_move_constructor { template @@ -115,6 +147,7 @@ struct test_move_constructor static void run() { boost::mp11::mp_for_each(test_move_constructor{}); + boost::mp11::mp_for_each(test_move_constructor{}); } }; @@ -133,9 +166,11 @@ struct test_move_assignement BOOST_TEST_EQ(image.dimensions(), gil::point_t{}); } } + static void run() { boost::mp11::mp_for_each(test_move_assignement{}); + boost::mp11::mp_for_each(test_move_assignement{}); } }; @@ -143,6 +178,9 @@ int main() { test_constructor_with_dimensions_pixel::run(); test_constructor_from_other_image::run(); + test_constructor_from_view::run(); + + test_copy_assignement::run(); test_move_constructor::run(); test_move_assignement::run(); diff --git a/test/core/image/test_fixture.hpp b/test/core/image/test_fixture.hpp index 8b149cd630..a7c95617ad 100644 --- a/test/core/image/test_fixture.hpp +++ b/test/core/image/test_fixture.hpp @@ -9,7 +9,6 @@ #define BOOST_GIL_TEST_CORE_IMAGE_TEST_FIXTURE_HPP #include -#include #include #include @@ -18,8 +17,6 @@ #include #include -#include "core/test_fixture.hpp" - namespace boost { namespace gil { namespace test { namespace fixture { using image_types = std::tuple @@ -38,6 +35,26 @@ using image_types = std::tuple gil::rgba32_image_t >; +#if defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) + using pmr_image_types = std::tuple<>; +#else + using pmr_image_types = std::tuple + < + gil::pmr::gray8_image_t, + gil::pmr::gray16_image_t, + gil::pmr::gray32_image_t, + gil::pmr::bgr8_image_t, + gil::pmr::bgr16_image_t, + gil::pmr::bgr32_image_t, + gil::pmr::rgb8_image_t, + gil::pmr::rgb16_image_t, + gil::pmr::rgb32_image_t, + gil::pmr::rgba8_image_t, + gil::pmr::rgba16_image_t, + gil::pmr::rgba32_image_t + >; +#endif //defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) + using rgb_interleaved_image_types = std::tuple < gil::bgr8_image_t, @@ -51,6 +68,16 @@ using rgb_interleaved_image_types = std::tuple gil::rgba32_image_t >; +using bit_aligned_image_types = std::tuple +< + gil::bit_aligned_image1_type<1, gil::gray_layout_t>::type, + gil::bit_aligned_image1_type<3, gil::gray_layout_t>::type, + gil::bit_aligned_image1_type<8, gil::gray_layout_t>::type, + gil::bit_aligned_image3_type<2, 2, 2, gil::rgb_layout_t>::type, + gil::bit_aligned_image3_type<5, 6, 5, gil::rgb_layout_t>::type, + gil::bit_aligned_image3_type<6, 6, 6, gil::rgb_layout_t>::type +>; + template auto generate_image(std::ptrdiff_t size_x, std::ptrdiff_t size_y, Generator&& generate) -> Image { diff --git a/test/core/image_processing/CMakeLists.txt b/test/core/image_processing/CMakeLists.txt index 5ecd590629..a2a8e08fbc 100644 --- a/test/core/image_processing/CMakeLists.txt +++ b/test/core/image_processing/CMakeLists.txt @@ -1,6 +1,7 @@ # Boost.GIL (Generic Image Library) - tests # # Copyright 2019 Miral Shah +# Copyright 2021 Pranam Lashkari # # Use, modification and distribution are subject to the Boost Software License, # Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -9,7 +10,21 @@ foreach(_name threshold_binary threshold_truncate - threshold_otsu) + threshold_otsu + morphology + lanczos_scaling + simple_kernels + harris + hessian + box_filter + median_filter + sobel_scharr + convolve + convolve_2d + convolve_cols + convolve_rows + kernel + kernel_fixed) set(_test t_core_image_processing_${_name}) set(_target test_core_image_processing_${_name}) @@ -25,29 +40,5 @@ foreach(_name unset(_name) unset(_target) -endforeach() - -foreach(_name - lanczos_scaling - simple_kernels - harris - hessian - box_filter - median_filter - sobel_scharr) - set(_test t_core_image_processing_${_name}) - set(_target test_core_image_processing_${_name}) - - add_executable(${_target} "") - target_sources(${_target} PRIVATE ${_name}.cpp) - target_link_libraries(${_target} - PRIVATE - gil_compile_options - gil_include_directories - gil_dependencies) - add_test(NAME ${_test} COMMAND ${_target}) - - unset(_name) - unset(_target) - unset(_test) + unset(_test) endforeach() diff --git a/test/core/image_processing/Jamfile b/test/core/image_processing/Jamfile index d9593f0793..562860e811 100644 --- a/test/core/image_processing/Jamfile +++ b/test/core/image_processing/Jamfile @@ -1,6 +1,7 @@ # Boost.GIL (Generic Image Library) - tests # # Copyright 2019 Miral Shah +# Copyright 2021 Pranam Lashkari # # Use, modification and distribution are subject to the Boost Software License, # Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -9,6 +10,7 @@ import testing ; compile-fail threshold_color_spaces_not_compatible_fail.cpp ; +compile-fail kernel_1d_fixed_even_size_fail.cpp ; run threshold_binary.cpp ; run threshold_truncate.cpp ; run threshold_otsu.cpp ; @@ -19,3 +21,10 @@ run hessian.cpp ; run sobel_scharr.cpp ; run box_filter.cpp ; run median_filter.cpp ; +run morphology.cpp ; +run convolve.cpp ; +run convolve_2d.cpp ; +run convolve_cols.cpp ; +run convolve_rows.cpp ; +run kernel.cpp ; +run kernel_fixed.cpp ; diff --git a/test/core/image_processing/adaptive_he.cpp b/test/core/image_processing/adaptive_he.cpp new file mode 100644 index 0000000000..61e65d7eab --- /dev/null +++ b/test/core/image_processing/adaptive_he.cpp @@ -0,0 +1,114 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include + +#include + +#include +#include + +namespace gil = boost::gil; + +double epsilon = 1.0; + +std::uint8_t image_matrix[] = +{ + 1, 1, 1, 1, + 3, 3, 3, 3, + 5, 5, 5, 5, + 7, 7, 7, 7 +}; +gil::gray8c_view_t gray_view = gil::interleaved_view(4, 4, reinterpret_cast(image_matrix), 4); + +void check_actual_clip_limit() +{ + gil::histogram h; + for(std::size_t i = 0; i < 100; i++) + { + if (i % 40 == 0) + { + h(i) = 60; + } + else + { + h(i) = 5; + } + } + double limit = 0.01; + double value = gil::detail::actual_clip_limit(h, limit); + + long actual_limit = round(value * h.sum()), max_bin_val = 0; + double excess = 0; + for(std::size_t i = 0; i < 100; i++) + { + if (h(i) > actual_limit) + excess += actual_limit - h(i); + max_bin_val = std::max(max_bin_val, h(i)); + } + BOOST_TEST((std::abs(excess / h.size() + actual_limit) - limit * h.sum()) < epsilon); +} + +void check_clip_and_redistribute() +{ + gil::histogram h, h2; + for(std::size_t i = 0; i < 100; i++) + { + if (i % 50 == 0) + { + h(i) = 60; + } + else + { + h(i) = 5; + } + } + bool check = true; + double limit = 0.001; + gil::detail::clip_and_redistribute(h, h2, limit); + for(std::size_t i = 0; i < 100; i++) + { + check = check & (std::abs(limit * h.sum() - h2(i)) < epsilon); + } + BOOST_TEST(check); +} + +void check_non_overlapping_interpolated_clahe() +{ + { + gil::gray8_image_t img1(4, 4), img2(4, 4), img3(4, 4); + gil::histogram_equalization(gray_view, view(img2)); + gil::non_overlapping_interpolated_clahe(gray_view, view(img3), 8, 8, 1.0); + BOOST_TEST(gil::equal_pixels(view(img2), view(img3))); + } + { + gil::gray8_image_t img1(8, 8), img2(8, 8), img3(4, 4); + gil::copy_pixels(gray_view, gil::subimage_view(view(img1), 0, 0, 4, 4)); + gil::copy_pixels(gray_view, gil::subimage_view(view(img1), 0, 4, 4, 4)); + gil::copy_pixels(gray_view, gil::subimage_view(view(img1), 4, 0, 4, 4)); + gil::copy_pixels(gray_view, gil::subimage_view(view(img1), 4, 4, 4, 4)); + gil::histogram_equalization(gray_view, view(img3)); + gil::non_overlapping_interpolated_clahe(view(img1), view(img2), 8, 8, 1.0); + BOOST_TEST(gil::equal_pixels(gil::subimage_view(view(img2), 0, 0, 4, 4), view(img3))); + BOOST_TEST(gil::equal_pixels(gil::subimage_view(view(img2), 0, 4, 4, 4), view(img3))); + BOOST_TEST(gil::equal_pixels(gil::subimage_view(view(img2), 4, 0, 4, 4), view(img3))); + BOOST_TEST(gil::equal_pixels(gil::subimage_view(view(img2), 4, 4, 4, 4), view(img3))); + } +} + +int main() +{ + check_actual_clip_limit(); + check_clip_and_redistribute(); + check_non_overlapping_interpolated_clahe(); + + return boost::report_errors(); +} diff --git a/test/extension/numeric/convolve.cpp b/test/core/image_processing/convolve.cpp similarity index 97% rename from test/extension/numeric/convolve.cpp rename to test/core/image_processing/convolve.cpp index 7fe008ea05..c3ecb431d8 100644 --- a/test/extension/numeric/convolve.cpp +++ b/test/core/image_processing/convolve.cpp @@ -1,12 +1,13 @@ // // Copyright 2019-2020 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // 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 // #include -#include +#include #include diff --git a/test/core/image_processing/convolve_2d.cpp b/test/core/image_processing/convolve_2d.cpp new file mode 100644 index 0000000000..cb6a96f66e --- /dev/null +++ b/test/core/image_processing/convolve_2d.cpp @@ -0,0 +1,201 @@ +// +// Copyright 2019 Miral Shah +// Copyright 2019-2021 Pranam Lashkari +// +// Use, modification and distribution are subject to 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) +// +#include +#include + +#include + +#include + +namespace gil = boost::gil; + +std::uint8_t img[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 255, 0, 0, 0, 255, 0, 0, + 0, 0, 0, 255, 0, 255, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 0, 0, + 0, 0, 0, 255, 0, 255, 0, 0, 0, + 0, 0, 255, 0, 0, 0, 255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +std::uint8_t output[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 28, 28, 28, 0, 28, 28, 28, 0, + 0, 28, 56, 56, 56, 56, 56, 28, 0, + 0, 28, 56, 85, 85, 85, 56, 28, 0, + 0, 0, 56, 85, 141, 85, 56, 0, 0, + 0, 28, 56, 85, 85, 85, 56, 28, 0, + 0, 28, 56, 56, 56, 56, 56, 28, 0, + 0, 28, 28, 28, 0, 28, 28, 28, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +void test_convolve_2d_with_normalized_mean_filter() +{ + gil::gray8c_view_t src_view = + gil::interleaved_view(9, 9, reinterpret_cast(img), 9); + + gil::image temp_img(src_view.width(), src_view.height()); + typename gil::image::view_t temp_view = view(temp_img); + gil::gray8_view_t dst_view(temp_view); + + std::vector v(9, 1.0f / 9.0f); + gil::detail::kernel_2d kernel(v.begin(), v.size(), 1, 1); + + gil::detail::convolve_2d(src_view, kernel, dst_view); + + gil::gray8c_view_t out_view = + gil::interleaved_view(9, 9, reinterpret_cast(output), 9); + + BOOST_TEST(gil::equal_pixels(out_view, dst_view)); +} + +void test_convolve_2d_with_image_using_float32_t() +{ +gil::float32_t img[] = +{ + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.76, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.6, 0.6, 0.1, 0.54, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.84, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.1, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.3, 0.1, 0.1, 0.4, 0.1, 0.32, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.21, 0.1, 0.1 +}; + +gil::float32_t exp_output[] = +{ + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.76, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.6, 0.6, 0.1, 0.54, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.84, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.1, 0.1, 0.1, 0.4, 0.1, 0.5, 0.1, 0.1, + 0.1, 0.3, 0.1, 0.1, 0.4, 0.1, 0.32, 0.1, 0.1, + 0.1, 0.2, 0.1, 0.1, 0.4, 0.1, 0.21, 0.1, 0.1 +}; + +gil::gray32f_image_t img_gray_out(9, 9); +gil::gray32fc_view_t src_view = + gil::interleaved_view(9, 9, reinterpret_cast(img), 9); +gil::gray32fc_view_t exp_out_view = + gil::interleaved_view(9, 9, reinterpret_cast(exp_output), 9); +std::vector v(1, 1); +gil::detail::kernel_2d kernel(v.begin(), v.size(), 0, 0); // impulse kernel +gil::detail::convolve_2d(src_view, kernel, view(img_gray_out)); +BOOST_TEST(gil::equal_pixels(exp_out_view, view(img_gray_out))); +} + +void test_convolve_2d_with_sobel_x_filter() +{ + const gil::uint8_t img[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + const gil::int16_t exp_output[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 0, 0, -255, -255, 0, 0, 0, + 0, 765, 765, 0, 0, -765, -765, 0, 0, 0, + 0, 1020, 1020, 0, 0, -1020, -1020, 0, 0, 0, + 0, 1020, 1020, 0, 0, -1020, -1020, 0, 0, 0, + 0, 1020, 1020, 0, 0, -1020, -1020, 0, 0, 0, + 0, 765, 765, 0, 0, -765, -765, 0, 0, 0, + 0, 255, 255, 0, 0, -255, -255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + gil::gray16s_image_t img_gray_out(10, 13); + const auto src_view = + gil::interleaved_view(10, 13, reinterpret_cast(img), 10); + const auto exp_out_view = + gil::interleaved_view(10, 13, reinterpret_cast(exp_output), 20); + gil::detail::convolve_2d(src_view, gil::generate_dx_sobel(1), view(img_gray_out)); + + BOOST_TEST(gil::equal_pixels(exp_out_view, view(img_gray_out))); +} + +void test_convolve_2d_with_sobel_y_filter() +{ + const gil::uint8_t img[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + const gil::int16_t exp_output[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 765, 1020, 1020, 765, 255, 0, 0, 0, + 0, 255, 765, 1020, 1020, 765, 255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, -255, -765, -1020, -1020, -765, -255, 0, 0, 0, + 0, -255, -765, -1020, -1020, -765, -255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + gil::gray16s_image_t img_gray_out(10, 13); + const auto src_view = + gil::interleaved_view(10, 13, reinterpret_cast(img), 10); + const auto exp_out_view = + gil::interleaved_view(10, 13, reinterpret_cast(exp_output), 20); + gil::detail::convolve_2d(src_view, gil::generate_dy_sobel(1), view(img_gray_out)); + BOOST_TEST(gil::equal_pixels(exp_out_view, view(img_gray_out))); +} + +int main() +{ + test_convolve_2d_with_normalized_mean_filter(); + test_convolve_2d_with_image_using_float32_t(); + test_convolve_2d_with_sobel_x_filter(); + test_convolve_2d_with_sobel_y_filter(); + return ::boost::report_errors(); +} diff --git a/test/extension/numeric/convolve_cols.cpp b/test/core/image_processing/convolve_cols.cpp similarity index 95% rename from test/extension/numeric/convolve_cols.cpp rename to test/core/image_processing/convolve_cols.cpp index c9e5576db4..c78f181b97 100644 --- a/test/extension/numeric/convolve_cols.cpp +++ b/test/core/image_processing/convolve_cols.cpp @@ -1,12 +1,13 @@ // // Copyright 2019-2020 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // 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 // #include -#include +#include #include diff --git a/test/extension/numeric/convolve_rows.cpp b/test/core/image_processing/convolve_rows.cpp similarity index 95% rename from test/extension/numeric/convolve_rows.cpp rename to test/core/image_processing/convolve_rows.cpp index d9b7354796..d4a99fcc8a 100644 --- a/test/extension/numeric/convolve_rows.cpp +++ b/test/core/image_processing/convolve_rows.cpp @@ -1,12 +1,13 @@ // // Copyright 2019-2020 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // 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 // #include -#include +#include #include diff --git a/test/core/image_processing/histogram_equalization.cpp b/test/core/image_processing/histogram_equalization.cpp new file mode 100644 index 0000000000..cf0e53dc79 --- /dev/null +++ b/test/core/image_processing/histogram_equalization.cpp @@ -0,0 +1,280 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include +#include +#include + +#include + +#include + +const int a = 5; +const double epsilon = 0.005; // Decided by the value 1/255 i.e. an error of 1 px in 255 px +boost::gil::gray8_image_t original(a, a); +boost::gil::gray8_image_t processed_1(a, a), processed_2(a, a), expected(a, a); +std::vector > test1_random{ + { 1, 10, 10, 10, 10}, + { 20, 25, 25, 55, 20}, + { 0, 55, 55, 55, 20}, + { 20, 255, 255, 255, 0}, + { 100, 100, 100, 10, 0}}; +std::vector > expected_test1{ + { 40, 91, 91, 91, 91}, + { 132, 153, 153, 193, 132}, + { 30, 193, 193, 193, 132}, + { 132, 255, 255, 255, 30}, + { 224, 224, 224, 91, 30}}; + +std::vector > all_white{ + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}}; + +std::vector > expected_all_white{ + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}}; + +std::vector > binary_img{ + {0 , 0 , 0 , 0 , 0 }, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}}; + +std::vector > expected_binary_img{ + {51 , 51 , 51 , 51 , 51 }, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255}}; + +std::vector > test2_uniform{ + { 0, 10, 20, 30, 40}, + { 50, 60, 70, 80, 90}, + { 100, 110, 120, 130, 140}, + { 150, 160, 170, 180, 190}, + { 200, 210, 220, 230, 240}}; +std::vector > expected_test2{ + { 10, 20, 30, 40, 51}, + { 61, 71, 81, 91, 102}, + { 112, 122, 132, 142, 153}, + { 163, 173, 183, 193, 204}, + { 214, 224, 234, 244, 255}}; + +std::vector > test3_2peaks{ + { 0, 0, 0, 0, 10}, + { 40, 43, 44, 46, 50}, + { 55, 56, 44, 46, 44}, + { 200, 201, 202, 203, 200}, + { 201, 202, 201, 201, 22}}; +std::vector > expected_test3{ + { 40, 40, 40, 40, 51}, + { 71, 81, 112, 132, 142}, + { 153, 163, 112, 132, 112}, + { 183, 224, 244, 255, 183}, + { 224, 244, 224, 224, 61}}; + +std::vector > test_mask{ + {1, 10, 10, 10, 10}, + {20, 25, 25, 25, 20}, + {0, 25, 25, 25, 20}, + {20, 25, 25, 25, 0}, + {100, 100, 100, 10, 0}}; +std::vector > expected_test_mask{ + {1, 10, 10, 10, 10}, + {20, 255, 255, 255, 20}, + {0, 255, 255, 255, 20}, + {20, 255, 255, 255, 0}, + {100, 100, 100, 10, 0}}; + +std::vector > mask{ + {0, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + {0, 1, 1, 1, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 0, 0, 0}}; + +void vector_to_gray_image(boost::gil::gray8_image_t& img, + std::vector >& grid) +{ + for(std::ptrdiff_t y=0; y +bool equal_pixels(SrcView const& v1, SrcView const& v2, double threshold) +{ + double sum=0.0; + using pixel_t = typename boost::gil::get_pixel_type::type; + using channel_t = typename boost::gil::channel_type::type; + channel_t max_p = std::numeric_limits::max(); + channel_t min_p = std::numeric_limits::min(); + long int num_pixels = v1.width() * v1.height(); + std::size_t num_channels = boost::gil::num_channels::value; + for (std::ptrdiff_t y = 0; y < v1.height(); ++y) + { + auto it1 = v1.row_begin(y); + auto it2 = v2.row_begin(y); + for (std::ptrdiff_t x = 0; x < v2.width(); ++x) + { + for(std::ptrdiff_t c = 0; c < num_channels; ++c) + { + sum += abs(it1[x][c]-it2[x][c]); + } + } + } + return ( abs(sum) / (num_pixels * num_channels * (max_p - min_p)) < threshold ); +} + +void test_random_image() +{ + vector_to_gray_image(original,test1_random); + vector_to_gray_image(expected,expected_test1); + + histogram_equalization(boost::gil::const_view(original),boost::gil::view(processed_1)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(expected), epsilon)); + + // Process image again to look for differences + histogram_equalization(boost::gil::const_view(processed_1),boost::gil::view(processed_2)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(processed_2), epsilon)); + + // Test overloaded version + boost::gil::histogram hist, process_1, process_2; + fill_histogram(boost::gil::const_view(original), hist, 1, false, false); + histogram_equalization(hist, process_1); + + histogram_equalization(process_1, process_2); + BOOST_TEST(process_1.equals(process_2)); +} + +void test_random_image_with_mask() +{ + vector_to_gray_image(original,test_mask); + vector_to_gray_image(expected,expected_test_mask); + histogram_equalization(boost::gil::const_view(original),boost::gil::view(processed_1), 1, true, mask); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(expected), epsilon)); + + // Process image again to look for differences + histogram_equalization(boost::gil::const_view(processed_1),boost::gil::view(processed_2), 1, true, mask); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(processed_2), epsilon)); + + // Test overloaded version + boost::gil::histogram hist, process_1, process_2; + fill_histogram(boost::gil::const_view(original), hist, 1, false, false); + histogram_equalization(hist, process_1); + + histogram_equalization(process_1, process_2); + BOOST_TEST(process_1.equals(process_2)); +} + +void test_uniform_image() +{ + vector_to_gray_image(original,test2_uniform); + vector_to_gray_image(expected,expected_test2); + histogram_equalization(boost::gil::const_view(original),boost::gil::view(processed_1)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(expected), epsilon)); + + // Process image again to look for differences + histogram_equalization(boost::gil::const_view(processed_1),boost::gil::view(processed_2)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(processed_2), epsilon)); + + // Test overloaded version + boost::gil::histogram hist, process_1, process_2; + fill_histogram(boost::gil::const_view(original), hist, 1, false, false); + histogram_equalization(hist, process_1); + + histogram_equalization(process_1, process_2); + BOOST_TEST(process_1.equals(process_2)); +} + +void test_all_white_image() +{ + vector_to_gray_image(original,all_white); + vector_to_gray_image(expected,expected_all_white); + histogram_equalization(boost::gil::const_view(original),boost::gil::view(processed_1)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(expected), epsilon)); + + // Process image again to look for differences + histogram_equalization(boost::gil::const_view(processed_1),boost::gil::view(processed_2)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(processed_2), epsilon)); + + // Test overloaded version + boost::gil::histogram hist, process_1, process_2; + fill_histogram(boost::gil::const_view(original), hist, 1, false, false); + histogram_equalization(hist, process_1); + + histogram_equalization(process_1, process_2); + BOOST_TEST(process_1.equals(process_2)); +} + +void test_binary_image() +{ + vector_to_gray_image(original,binary_img); + vector_to_gray_image(expected,expected_binary_img); + histogram_equalization(boost::gil::const_view(original),boost::gil::view(processed_1)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(expected), epsilon)); + + // Process image again to look for differences + histogram_equalization(boost::gil::const_view(processed_1),boost::gil::view(processed_2)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(processed_2), epsilon)); + + // Test overloaded version + boost::gil::histogram hist, process_1, process_2; + fill_histogram(boost::gil::const_view(original), hist, 1, false, false); + histogram_equalization(hist, process_1); + + histogram_equalization(process_1, process_2); + BOOST_TEST(process_1.equals(process_2)); +} + +void test_double_peaked_image() +{ + vector_to_gray_image(original,test3_2peaks); + vector_to_gray_image(expected,expected_test3); + histogram_equalization(boost::gil::const_view(original),boost::gil::view(processed_1)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(expected), epsilon)); + + // Process image again to look for differences + histogram_equalization(boost::gil::const_view(processed_1),boost::gil::view(processed_2)); + BOOST_TEST(equal_pixels(boost::gil::view(processed_1), boost::gil::view(processed_2), epsilon)); + + // Test overloaded version + boost::gil::histogram hist, process_1, process_2; + fill_histogram(boost::gil::const_view(original), hist, 1, false, false); + histogram_equalization(hist, process_1); + + histogram_equalization(process_1, process_2); + BOOST_TEST(process_1.equals(process_2)); +} + +int main() +{ + //Basic tests for grayscale histogram_equalization + test_random_image(); + test_random_image_with_mask(); + test_all_white_image(); + test_binary_image(); + test_uniform_image(); + test_double_peaked_image(); + + return boost::report_errors(); +} diff --git a/test/core/image_processing/histogram_matching.cpp b/test/core/image_processing/histogram_matching.cpp new file mode 100644 index 0000000000..a51db63d13 --- /dev/null +++ b/test/core/image_processing/histogram_matching.cpp @@ -0,0 +1,137 @@ +// +// Copyright 2020 Debabrata Mandal +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include +#include +#include + +#include +#include + +const int a = 5; +const double epsilon = 0.000001; // Decided by the value 5/255 i.e. an error of 5 px in 255 px +boost::gil::gray8_image_t original(a, a), reference(a, a); +boost::gil::gray8_image_t processed(a, a), processed2(a, a); +std::vector > test1_random{ + { 1, 10, 10, 10, 10}, + { 20, 25, 25, 55, 20}, + { 0, 55, 55, 55, 20}, + { 20, 255, 255, 255, 0}, + { 100, 100, 100, 10, 0}}; +std::vector > test1_reference{ + { 0, 10, 20, 30, 40}, + { 50, 60, 70, 80, 90}, + { 100, 110, 120, 130, 140}, + { 150, 160, 170, 180, 190}, + { 200, 210, 220, 230, 240}}; + +std::vector > test2_uniform{ + { 0, 10, 20, 30, 40}, + { 50, 60, 70, 80, 90}, + { 100, 110, 120, 130, 140}, + { 150, 160, 170, 180, 190}, + { 200, 210, 220, 230, 240}}; +std::vector > test2_reference{ + { 10, 20, 30, 40, 51}, + { 61, 71, 81, 91, 102}, + { 112, 122, 132, 142, 153}, + { 163, 173, 183, 193, 204}, + { 214, 224, 234, 244, 255}}; + +std::vector > test3_equal_image{ + { 0, 10, 20, 30, 40}, + { 50, 60, 70, 80, 90}, + { 100, 110, 120, 130, 140}, + { 150, 160, 170, 180, 190}, + { 200, 210, 220, 230, 240}}; + +std::vector > test3_reference{ + { 0, 10, 20, 30, 40}, + { 50, 60, 70, 80, 90}, + { 100, 110, 120, 130, 140}, + { 150, 160, 170, 180, 190}, + { 200, 210, 220, 230, 240}}; + +void vector_to_gray_image(boost::gil::gray8_image_t& img, + std::vector >& grid) +{ + for(std::ptrdiff_t y=0; y +bool equal_histograms(SrcView const& v1, SrcView const& v2, double threshold = epsilon) +{ + double sum=0.0; + using channel_t = typename boost::gil::channel_type::type; + boost::gil::histogram h1, h2; + using value_t = typename boost::gil::histogram::value_type; + channel_t max_p = std::numeric_limits::max(); + channel_t min_p = std::numeric_limits::min(); + long int num_pixels = v1.width() * v1.height(); + + boost::gil::fill_histogram(v1, h1, 1, false, false); + boost::gil::fill_histogram(v2, h2, 1, false, false); + auto ch1 = boost::gil::cumulative_histogram(h1); + auto ch2 = boost::gil::cumulative_histogram(h2); + std::for_each(ch1.begin(), ch1.end(), [&](value_t const& v) { + sum+=abs(v.second-ch1[v.first]); + }); + return ( abs(sum) / (ch1.size() * (max_p - min_p)) < threshold ); +} + +void test_random_image() +{ + vector_to_gray_image(original,test1_random); + vector_to_gray_image(reference,test1_reference); + histogram_matching(boost::gil::const_view(original),boost::gil::const_view(reference),boost::gil::view(processed)); + BOOST_TEST(equal_histograms(boost::gil::view(processed), boost::gil::view(reference))); + + histogram_matching(boost::gil::const_view(processed),boost::gil::const_view(reference),boost::gil::view(processed2)); + BOOST_TEST(equal_histograms(boost::gil::view(processed), boost::gil::view(processed2))); +} + +void test_uniform_image() +{ + vector_to_gray_image(original,test2_uniform); + vector_to_gray_image(reference,test2_reference); + histogram_matching(boost::gil::const_view(original),boost::gil::const_view(reference),boost::gil::view(processed)); + BOOST_TEST(equal_histograms(boost::gil::view(processed), boost::gil::view(reference))); + + histogram_matching(boost::gil::const_view(processed),boost::gil::const_view(reference),boost::gil::view(processed2)); + BOOST_TEST(equal_histograms(boost::gil::view(processed), boost::gil::view(processed2))); +} + +void test_equal_image() +{ + vector_to_gray_image(original,test3_equal_image); + vector_to_gray_image(reference,test3_reference); + histogram_matching(boost::gil::const_view(original),boost::gil::const_view(reference),boost::gil::view(processed)); + BOOST_TEST(equal_histograms(boost::gil::view(processed), boost::gil::view(reference))); + + histogram_matching(boost::gil::const_view(processed),boost::gil::const_view(reference),boost::gil::view(processed2)); + BOOST_TEST(equal_histograms(boost::gil::view(processed), boost::gil::view(processed2))); +} + +int main() +{ + //Basic tests for grayscale histogram_equalization + test_random_image(); + test_uniform_image(); + test_equal_image(); + + return boost::report_errors(); +} diff --git a/test/extension/numeric/kernel.cpp b/test/core/image_processing/kernel.cpp similarity index 99% rename from test/extension/numeric/kernel.cpp rename to test/core/image_processing/kernel.cpp index a75f1cef83..781a74fa22 100644 --- a/test/extension/numeric/kernel.cpp +++ b/test/core/image_processing/kernel.cpp @@ -8,7 +8,7 @@ // #define BOOST_DISABLE_ASSERTS 1 // kernel_1d_adaptor assertions are too strict #include -#include +#include #include diff --git a/test/extension/numeric/kernel_1d_fixed_even_size_fail.cpp b/test/core/image_processing/kernel_1d_fixed_even_size_fail.cpp similarity index 87% rename from test/extension/numeric/kernel_1d_fixed_even_size_fail.cpp rename to test/core/image_processing/kernel_1d_fixed_even_size_fail.cpp index a8be85fcd7..9f78519b7f 100644 --- a/test/extension/numeric/kernel_1d_fixed_even_size_fail.cpp +++ b/test/core/image_processing/kernel_1d_fixed_even_size_fail.cpp @@ -5,7 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#include +#include namespace gil = boost::gil; diff --git a/test/extension/numeric/kernel_fixed.cpp b/test/core/image_processing/kernel_fixed.cpp similarity index 99% rename from test/extension/numeric/kernel_fixed.cpp rename to test/core/image_processing/kernel_fixed.cpp index cfd075eafc..3f10246da1 100644 --- a/test/extension/numeric/kernel_fixed.cpp +++ b/test/core/image_processing/kernel_fixed.cpp @@ -8,7 +8,7 @@ // #define BOOST_DISABLE_ASSERTS 1 // kernel_1d_adaptor assertions are too strict #include -#include +#include #include diff --git a/test/core/image_processing/morphology.cpp b/test/core/image_processing/morphology.cpp new file mode 100644 index 0000000000..b57f605723 --- /dev/null +++ b/test/core/image_processing/morphology.cpp @@ -0,0 +1,136 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include + +namespace gil = boost::gil; + +// This function helps us fill pixels of a view given as 2nd argument with +// elements of the vector given as 1st argument. +void pixel_fill(std::vector>& original_binary_vector, + boost::gil::gray8_image_t& original_img) +{ + for (std::ptrdiff_t view_row = 0; view_row < view(original_img).height(); ++view_row) + { + for (std::ptrdiff_t view_col = 0; view_col < view(original_img).width(); ++view_col) + { + view(original_img)(view_col, view_row) = + gil::gray8_pixel_t(original_binary_vector[view_row][view_col]); + } + } +} + +int main() +{ + std::vector> original_binary_vector{ + {0, 0, 0, 0, 0, 0}, {0, 0, 127, 144, 143, 0}, {0, 0, 128, 0, 142, 0}, + {0, 0, 129, 0, 141, 0}, {0, 0, 130, 140, 139, 0}, {0, 0, 131, 0, 0, 0}, + {0, 0, 132, 137, 136, 138}, {0, 0, 133, 134, 135, 0}}; + std::vector> orig_dil_imp{ + {255, 100, 100, 100}, {100, 100, 100, 100}, {100, 100, 100, 100}}; + // All vectors defined below will be used for creating expected image views + // which are supposed to match the views obtained after applying morphological + // operations. + std::vector> exp_dil{ + {0, 127, 144, 144, 144, 143}, {0, 128, 144, 144, 144, 143}, {0, 129, 144, 144, 144, 143}, + {0, 130, 140, 142, 142, 142}, {0, 131, 140, 141, 141, 141}, {0, 132, 140, 140, 140, 139}, + {0, 133, 137, 137, 138, 138}, {0, 133, 137, 137, 138, 138}}; + // Following vector intends to check result of dilation operation when it is + // applied 2 times on the original image. + std::vector> exp_dil_iter2{ + {128, 144, 144, 144, 144, 144}, {129, 144, 144, 144, 144, 144}, + {130, 144, 144, 144, 144, 144}, {131, 144, 144, 144, 144, 144}, + {132, 140, 142, 142, 142, 142}, {133, 140, 141, 141, 141, 141}, + {133, 140, 140, 140, 140, 140}, {133, 137, 137, 138, 138, 138}}; + std::vector> exp_er{ + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 132, 0, 0}}; + // Following vector intends to check result of erosion operation when it is + // applied 2 times on the original image. + std::vector> exp_er_iter2{ + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + std::vector> exp_opening{ + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 132, 132, 132, 0}, {0, 0, 132, 132, 132, 0}}; + std::vector> exp_closing{ + {0, 0, 127, 144, 143, 143}, {0, 0, 127, 144, 143, 143}, {0, 0, 128, 140, 142, 142}, + {0, 0, 129, 140, 141, 141}, {0, 0, 130, 140, 139, 139}, {0, 0, 131, 137, 137, 138}, + {0, 0, 132, 137, 137, 138}, {0, 0, 133, 137, 137, 138}}; + std::vector> exp_mg{{0, 127, 144, 144, 144, 143}, {0, 128, 144, 144, 144, 143}, + {0, 129, 144, 144, 144, 143}, {0, 130, 140, 142, 142, 142}, + {0, 131, 140, 141, 141, 141}, {0, 132, 140, 140, 140, 139}, + {0, 133, 137, 137, 138, 138}, {0, 133, 137, 5, 138, 138}}; + std::vector> exp_top_hat{{0, 0, 0, 0, 0, 0}, {0, 0, 127, 144, 143, 0}, + {0, 0, 128, 0, 142, 0}, {0, 0, 129, 0, 141, 0}, + {0, 0, 130, 140, 139, 0}, {0, 0, 131, 0, 0, 0}, + {0, 0, 0, 5, 4, 138}, {0, 0, 1, 2, 3, 0}}; + std::vector> exp_black_hat{ + {0, 0, 127, 144, 143, 143}, {0, 0, 0, 0, 0, 143}, {0, 0, 0, 140, 0, 142}, + {0, 0, 0, 140, 0, 141}, {0, 0, 0, 0, 0, 139}, {0, 0, 0, 137, 137, 138}, + {0, 0, 0, 0, 1, 0}, {0, 0, 0, 3, 2, 138}}; + std::vector> exp_dil_imp{ + {255, 255, 100, 100}, {255, 255, 100, 100}, {100, 100, 100, 100}}; + + gil::gray8_image_t original_img(6, 8), obtained_dilation(6, 8), expected_dilation(6, 8); + gil::gray8_image_t obtained_erosion(6, 8), expected_erosion(6, 8); + gil::gray8_image_t obtained_opening(6, 8), expected_opening(6, 8); + gil::gray8_image_t obtained_closing(6, 8), expected_closing(6, 8); + gil::gray8_image_t obtained_mg(6, 8), expected_mg(6, 8); + gil::gray8_image_t obtained_top_hat(6, 8), expected_top_hat(6, 8); + gil::gray8_image_t obtained_black_hat(6, 8), expected_black_hat(6, 8); + gil::gray8_image_t obtained_dil_iter2(6, 8), expected_dil_iter2(6, 8); + gil::gray8_image_t obtained_er_iter2(6, 8), expected_er_iter2(6, 8); + gil::gray8_image_t obtained_imp_dil(4, 3), expected_imp_dil(4, 3), original_imp_dil(4, 3); + + std::vector ker_vec(9, 1.0f); // Structuring element + gil::detail::kernel_2d ker_mat(ker_vec.begin(), ker_vec.size(), 1, 1); + + pixel_fill(original_binary_vector, original_img); + pixel_fill(exp_dil, expected_dilation); + pixel_fill(exp_er, expected_erosion); + pixel_fill(exp_opening, expected_opening); + pixel_fill(exp_closing, expected_closing); + pixel_fill(exp_mg, expected_mg); + pixel_fill(exp_top_hat, expected_top_hat); + pixel_fill(exp_black_hat, expected_black_hat); + pixel_fill(exp_dil_iter2, expected_dil_iter2); + pixel_fill(orig_dil_imp, original_imp_dil); + pixel_fill(exp_dil_imp, expected_imp_dil); + pixel_fill(exp_er_iter2, expected_er_iter2); + + // Different morphological operations are applied on the same initial image to + // obtain results of our implementation which are then compared with expected + // results. + gil::dilate(view(original_img), view(obtained_dilation), ker_mat, 1); + gil::erode(view(original_img), view(obtained_erosion), ker_mat, 1); + gil::opening(view(original_img), view(obtained_opening), ker_mat); + gil::closing(view(original_img), view(obtained_closing), ker_mat); + gil::morphological_gradient(view(original_img), view(obtained_mg), ker_mat); + gil::top_hat(view(original_img), view(obtained_top_hat), ker_mat); + gil::black_hat(view(original_img), view(obtained_black_hat), ker_mat); + gil::dilate(view(original_imp_dil), view(obtained_imp_dil), ker_mat, 1); + gil::dilate(view(original_img), view(obtained_dil_iter2), ker_mat, 2); + gil::erode(view(original_img), view(obtained_er_iter2), ker_mat, 2); + + // Testing obtained results with expected results. + BOOST_TEST(gil::equal_pixels(gil::view(obtained_dilation), gil::view(expected_dilation))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_erosion), gil::view(expected_erosion))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_opening), gil::view(expected_opening))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_closing), gil::view(expected_closing))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_mg), gil::view(expected_mg))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_top_hat), gil::view(expected_top_hat))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_black_hat), gil::view(expected_black_hat))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_imp_dil), gil::view(expected_imp_dil))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_dil_iter2), gil::view(expected_dil_iter2))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_er_iter2), gil::view(expected_er_iter2))); + + return boost::report_errors(); +} diff --git a/test/core/image_processing/simple_kernels.cpp b/test/core/image_processing/simple_kernels.cpp index 522dec2f16..311de88e4b 100644 --- a/test/core/image_processing/simple_kernels.cpp +++ b/test/core/image_processing/simple_kernels.cpp @@ -38,15 +38,16 @@ void test_gaussian_kernel_generation() auto kernel = boost::gil::generate_gaussian_kernel(7, 0.84089642); const float expected_values[7][7] = { - {0.00000067f, 0.00002292f, 0.00019117f, 0.00038771f, 0.00019117f, 0.00002292f, 0.00000067f}, - {0.00002292f, 0.00078633f, 0.00655965f, 0.01330373f, 0.00655965f, 0.00078633f, 0.00002292f}, - {0.00019117f, 0.00655965f, 0.05472157f, 0.11098164f, 0.05472157f, 0.00655965f, 0.00019117f}, - {0.00038771f, 0.01330373f, 0.11098164f, 0.25508352f, 0.11098164f, 0.01330373f, 0.00038711f}, - {0.00019117f, 0.00655965f, 0.05472157f, 0.11098164f, 0.05472157f, 0.00655965f, 0.00019117f}, - {0.00002292f, 0.00078633f, 0.00655965f, 0.01330373f, 0.00655965f, 0.00078633f, 0.00002292f}, - {0.00000067f, 0.00002292f, 0.00019117f, 0.00038771f, 0.00019117f, 0.00002292f, 0.00000067f} + {6.67847706e-07f, 2.29160778e-05f, 1.91169232e-04f, 3.87713181e-04f, 1.91169232e-04f, 2.29160778e-05f, 6.67847706e-07f}, + {2.29160778e-05f, 7.86326905e-04f, 6.55965268e-03f, 1.33037298e-02f, 6.55965268e-03f, 7.86326905e-04f, 2.29160778e-05f}, + {1.91169232e-04f, 6.55965268e-03f, 5.47215706e-02f, 1.10981636e-01f, 5.47215706e-02f, 6.55965268e-03f, 1.91169232e-04f}, + {3.87713181e-04f, 1.33037298e-02f, 1.10981636e-01f, 2.25083518e-01f, 1.10981636e-01f, 1.33037298e-02f, 3.87713181e-04f}, + {1.91169232e-04f, 6.55965268e-03f, 5.47215706e-02f, 1.10981636e-01f, 5.47215706e-02f, 6.55965268e-03f, 1.91169232e-04f}, + {2.29160778e-05f, 7.86326905e-04f, 6.55965268e-03f, 1.33037298e-02f, 6.55965268e-03f, 7.86326905e-04f, 2.29160778e-05f}, + {6.67847706e-07f, 2.29160778e-05f, 1.91169232e-04f, 3.87713181e-04f, 1.91169232e-04f, 2.29160778e-05f, 6.67847706e-07f} }; + double sum{0}; for (gil::gray32f_view_t::coord_t y = 0; static_cast(y) < kernel.size(); ++y) { for (gil::gray32f_view_t::coord_t x = 0; static_cast(x) < kernel.size(); ++x) @@ -55,8 +56,10 @@ void test_gaussian_kernel_generation() auto expected = expected_values[y][x]; auto percent_difference = std::ceil(std::abs(expected - output) / expected); BOOST_TEST_LT(percent_difference, 5); + sum += output; } } + BOOST_TEST_LT(std::abs(sum-1), 1e-7); } int main() diff --git a/test/extension/numeric/test_fixture.hpp b/test/core/image_processing/test_fixture.hpp similarity index 89% rename from test/extension/numeric/test_fixture.hpp rename to test/core/image_processing/test_fixture.hpp index bfd5cf6f2c..b49fb6cd76 100644 --- a/test/extension/numeric/test_fixture.hpp +++ b/test/core/image_processing/test_fixture.hpp @@ -1,12 +1,13 @@ // // Copyright 2019 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // 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 // #include -#include +#include #include #include diff --git a/test/core/image_processing/threshold_binary.cpp b/test/core/image_processing/threshold_binary.cpp index 098cfc0628..fc39046454 100644 --- a/test/core/image_processing/threshold_binary.cpp +++ b/test/core/image_processing/threshold_binary.cpp @@ -87,7 +87,7 @@ void binary_rgb_to_rgb() { //expected_rgb view after thresholding of the original_rgb view with threshold value of 100 //filling expected_rgb view's upper half part with rgb pixels of value 0, 165, 165 - //filling expected_rgb view's lower half part with rgb pixels of value 165, 0, 0 + //filling expected_rgb view's lower half part with rgb pixels of value 165, 0, 0 gil::fill_pixels(gil::subimage_view(gil::view(expected_rgb), 0, 0, original_rgb.width(), original_rgb.height() / 2), gil::rgb8_pixel_t(0, 165, 165)); gil::fill_pixels(gil::subimage_view(gil::view(expected_rgb), 0, original_rgb.height() / 2, @@ -102,7 +102,7 @@ void binary_rgb_to_rgb() void binary_inverse_rgb_to_rgb() { //expected_rgb view after thresholding of the original_rgb view with threshold value of 100 - //filling expected_rgb view's upper half part with rgb pixels of value 90, 0, 0 + //filling expected_rgb view's upper half part with rgb pixels of value 90, 0, 0 //filling expected_rgb view's lower half part with rgb pixels of value 0, 90, 90 gil::fill_pixels(gil::subimage_view(gil::view(expected_rgb), 0, 0, original_rgb.width(), original_rgb.height() / 2), gil::rgb8_pixel_t(90, 0, 0)); @@ -127,7 +127,7 @@ int main() { fill_original_gray(); fill_original_rgb(); - + binary_gray_to_gray(); binary_inverse_gray_to_gray(); binary_rgb_to_rgb(); diff --git a/test/core/image_view/CMakeLists.txt b/test/core/image_view/CMakeLists.txt index e9983da942..5df96c4227 100644 --- a/test/core/image_view/CMakeLists.txt +++ b/test/core/image_view/CMakeLists.txt @@ -12,6 +12,7 @@ foreach(_name derived_view_type dynamic_step is_planar + is_1d_traversable iterator planar_rgba_view reverse_iterator diff --git a/test/core/image_view/Jamfile b/test/core/image_view/Jamfile index 4b654b92cb..03aecea5a7 100644 --- a/test/core/image_view/Jamfile +++ b/test/core/image_view/Jamfile @@ -20,6 +20,7 @@ compile view_type_from_pixel.cpp ; run axis_iterator.cpp ; run collection.cpp ; +run is_1d_traversable.cpp ; run iterator.cpp ; run planar_rgba_view.cpp ; run reverse_iterator.cpp ; diff --git a/test/core/image_view/is_1d_traversable.cpp b/test/core/image_view/is_1d_traversable.cpp new file mode 100644 index 0000000000..bc0cdda62c --- /dev/null +++ b/test/core/image_view/is_1d_traversable.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2022 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 +// +#include + +#include + +#include "core/image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +// See also test_is_1d_traversable in planar_rgba_view.cpp + +struct test_continuous_image_types +{ + template + void operator()(Image const&) + { + gil::point_t const dimensions{ 8, 3 }; + Image image(dimensions); + auto& view = gil::view(image); + BOOST_TEST(view.is_1d_traversable()); + } + + static void run() + { + boost::mp11::mp_for_each(test_continuous_image_types{}); + } +}; + +struct test_interleaved_image_types +{ + template + void operator()(Image const&) + { + gil::point_t const dimensions{ 8, 3 }; + Image image(dimensions); + auto& view = gil::view(image); + BOOST_TEST(view.is_1d_traversable()); + + // planar copy from interleaved + { + using pixel_t = typename Image::view_t::value_type; + gil::image image2(image); + auto& view2 = gil::view(image2); + BOOST_TEST(view2.is_1d_traversable()); + } + } + + static void run() + { + boost::mp11::mp_for_each(test_interleaved_image_types{}); + } +}; + +int main() +{ + test_continuous_image_types::run(); + test_interleaved_image_types::run(); + + return ::boost::report_errors(); +} diff --git a/test/core/image_view/planar_rgba_view.cpp b/test/core/image_view/planar_rgba_view.cpp index 925a2ebec3..9b32277c62 100644 --- a/test/core/image_view/planar_rgba_view.cpp +++ b/test/core/image_view/planar_rgba_view.cpp @@ -50,6 +50,14 @@ void test_back() BOOST_TEST_EQ(v.back(), pb); } +void test_is_1d_traversable() +{ + auto v2x2 = gil::planar_rgba_view(fixture::d.x, fixture::d.y, fixture::r, fixture::g, fixture::b, fixture::a, sizeof(std::uint8_t) * 2); + BOOST_TEST(v2x2.is_1d_traversable()); + auto v3x1 = gil::planar_rgba_view(3, 1, fixture::r, fixture::g, fixture::b, fixture::a, sizeof(std::uint8_t) * 3); + BOOST_TEST(v3x1.is_1d_traversable()); +} + void test_pixel_equal_to_operator() { auto v = gil::planar_rgba_view(fixture::d.x, fixture::d.y, fixture::r, fixture::g, fixture::b, fixture::a, sizeof(std::uint8_t) * 2); @@ -65,6 +73,7 @@ int main() test_dimensions(); test_front(); test_back(); + test_is_1d_traversable(); test_pixel_equal_to_operator(); return ::boost::report_errors(); diff --git a/test/core/pixel/CMakeLists.txt b/test/core/pixel/CMakeLists.txt index 0cdefab254..a0d2d40713 100644 --- a/test/core/pixel/CMakeLists.txt +++ b/test/core/pixel/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright (c) 2019 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at @@ -15,7 +16,9 @@ foreach(_name packed_pixel pixel_reference_is_mutable pixels_are_compatible - test_fixture) + test_fixture + pixel_numeric_operations + pixel_numeric_operations_float) set(_test t_core_pixel_${_name}) set(_target test_core_pixel_${_name}) diff --git a/test/core/pixel/Jamfile b/test/core/pixel/Jamfile index aa7720d0ae..b405ea5381 100644 --- a/test/core/pixel/Jamfile +++ b/test/core/pixel/Jamfile @@ -1,6 +1,7 @@ # Boost.GIL (Generic Image Library) - tests # # Copyright (c) 2019 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or @@ -19,3 +20,5 @@ compile pixels_are_compatible.cpp ; run color_convert_cmyk.cpp ; run packed_pixel.cpp ; run test_fixture.cpp ; +run pixel_numeric_operations.cpp ; +run pixel_numeric_operations_float.cpp ; diff --git a/test/extension/numeric/pixel_numeric_operations.cpp b/test/core/pixel/pixel_numeric_operations.cpp similarity index 97% rename from test/extension/numeric/pixel_numeric_operations.cpp rename to test/core/pixel/pixel_numeric_operations.cpp index 5f18ebfbbb..0c74bef400 100644 --- a/test/extension/numeric/pixel_numeric_operations.cpp +++ b/test/core/pixel/pixel_numeric_operations.cpp @@ -1,15 +1,17 @@ // // Copyright 2019-2020 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // 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 // #include -#include #include +#include + #include #include @@ -134,7 +136,7 @@ struct test_pixel_multiply_integer_same_types { using pixel_t = Pixel; using channel_t = typename gil::channel_type::type; - gil::pixel_multiply_t f; + gil::pixel_multiplies_t f; pixel_t p0; gil::static_fill(p0, static_cast(0)); @@ -188,7 +190,7 @@ struct test_pixel_divide_integer_same_types { using pixel_t = Pixel; using channel_t = typename gil::channel_type::type; - gil::pixel_divide_t f; + gil::pixel_divides_t f; pixel_t p0; gil::static_fill(p0, static_cast(0)); diff --git a/test/extension/numeric/pixel_numeric_operations_float.cpp b/test/core/pixel/pixel_numeric_operations_float.cpp similarity index 88% rename from test/extension/numeric/pixel_numeric_operations_float.cpp rename to test/core/pixel/pixel_numeric_operations_float.cpp index 79caa7a75d..da8ed8c12d 100644 --- a/test/extension/numeric/pixel_numeric_operations_float.cpp +++ b/test/core/pixel/pixel_numeric_operations_float.cpp @@ -1,6 +1,7 @@ // // Copyright 2013 Krzysztof Czainski // Copyright 2020 Mateusz Loskot +// Copyright 2021 Pranam Lashkari // // Distributed under the Boost Software License, Version 1.0 // See accompanying file LICENSE_1_0.txt or copy at @@ -10,7 +11,9 @@ #include #include +#include #include +#include #include @@ -46,7 +49,7 @@ void test_multiply() gil::rgb32f_pixel_t a(1.f, 2.f, 3.f); gil::bgr32f_pixel_t b(2.f, 2.f, 2.f); - gil::pixel_multiply_t< + gil::pixel_multiplies_t< gil::rgb32f_pixel_t, gil::bgr32f_pixel_t, gil::rgb32f_pixel_t> op; gil::rgb32f_pixel_t c = op(a, b); @@ -64,7 +67,7 @@ void test_divide() gil::rgb8_pixel_t a(10, 20, 30); gil::bgr8_pixel_t b(2, 2, 2); - gil::pixel_divide_t op; + gil::pixel_divides_t op; gil::rgb32f_pixel_t c = op(a, b); BOOST_TEST_EQ(get_color(c, gil::red_t()), 5); @@ -77,7 +80,7 @@ void test_divide() gil::rgb32f_pixel_t a(1.f, 2.f, 3.f); gil::bgr32f_pixel_t b(2.f, 2.f, 2.f); - gil::pixel_divide_t op; + gil::pixel_divides_t op; gil::rgb32f_pixel_t c = op(a, b); float epsilon = 1e-6f; diff --git a/test/core/pixel/test_fixture.hpp b/test/core/pixel/test_fixture.hpp index bf4e1ed8dc..d2633356b7 100644 --- a/test/core/pixel/test_fixture.hpp +++ b/test/core/pixel/test_fixture.hpp @@ -21,7 +21,6 @@ #include #include -#include // for compatibility with Boost.Test #include #include @@ -69,7 +68,7 @@ class pixel_value public: using type = Pixel; using pixel_t = type; - type pixel_{}; + type pixel_; pixel_value() = default; explicit pixel_value(pixel_t const& pixel) diff --git a/test/core/point/test_fixture.hpp b/test/core/point/test_fixture.hpp new file mode 100644 index 0000000000..97b910e3a2 --- /dev/null +++ b/test/core/point/test_fixture.hpp @@ -0,0 +1,22 @@ +// +// Copyright 2022 Marco Langer +// +// Use, modification and distribution are subject to 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) +// + +#include + +#include + +namespace boost { namespace gil { + +// necessary to use point directly with boost test macros +template +auto operator<<(std::ostream& os, point const& p) -> std::ostream& +{ + return os << "{x=" << p.x << ", y=" << p.y << "}"; +} + +}} // namespace boost::gil diff --git a/test/core/test_fixture.hpp b/test/core/test_fixture.hpp index 819b2cac22..d7a35abe9a 100644 --- a/test/core/test_fixture.hpp +++ b/test/core/test_fixture.hpp @@ -8,8 +8,8 @@ #ifndef BOOST_GIL_TEST_CORE_TEST_FIXTURE_HPP #define BOOST_GIL_TEST_CORE_TEST_FIXTURE_HPP -#include #include +#include #include #include @@ -60,19 +60,30 @@ template struct random_value { static_assert(std::is_integral::value, "T must be integral type"); - static constexpr auto range_min = std::numeric_limits::min(); - static constexpr auto range_max = std::numeric_limits::max(); - random_value() : rng_(rd_()), uid_(range_min, range_max) {} + random_value(T range_min = std::numeric_limits::min(), + T range_max = std::numeric_limits::max()) + : uid_(range_min, range_max) + {} + + random_value(std::uint32_t seed, T minimum, T maximum) : rng_(seed), uid_(minimum, maximum) + {} T operator()() { - auto value = uid_(rng_); - BOOST_ASSERT(range_min <= value && value <= range_max); - return static_cast(value); + return uid_(rng_); + } + + T range_min() const noexcept + { + return uid_.a(); + } + + T range_max() const noexcept + { + return uid_.b(); } - std::random_device rd_; std::mt19937 rng_; std::uniform_int_distribution::type> uid_; }; diff --git a/test/core/virtual_2d_locator/CMakeLists.txt b/test/core/virtual_2d_locator/CMakeLists.txt new file mode 100644 index 0000000000..fa3ddb8936 --- /dev/null +++ b/test/core/virtual_2d_locator/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# Copyright (c) 2022 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) +# +foreach(_name + is_2d_traversable) + set(_test t_core_virtual_2d_locator_${_name}) + set(_target test_core_virtual_2d_locator_${_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/core/virtual_2d_locator/Jamfile b/test/core/virtual_2d_locator/Jamfile new file mode 100644 index 0000000000..5607cd3741 --- /dev/null +++ b/test/core/virtual_2d_locator/Jamfile @@ -0,0 +1,11 @@ +# Boost.GIL (Generic Image Library) - tests +# +# Copyright (c) 2022 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) + +import testing ; + +run is_2d_traversable.cpp ; diff --git a/test/core/virtual_2d_locator/is_2d_traversable.cpp b/test/core/virtual_2d_locator/is_2d_traversable.cpp new file mode 100644 index 0000000000..f6d3147488 --- /dev/null +++ b/test/core/virtual_2d_locator/is_2d_traversable.cpp @@ -0,0 +1,41 @@ +// +// Copyright 2022 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 +// +#include + +#include + +#include "test_fixture.hpp" +#include "core/pixel/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct test_pixel_types +{ + template + void operator()(Pixel const&) + { + using deref_t = fixture::noop_dereference_fn; + using locator_t = gil::virtual_2d_locator; + using view_t = gil::image_view; + + view_t view; + BOOST_TEST(!view.is_1d_traversable()); + } + static void run() + { + boost::mp11::mp_for_each(test_pixel_types{}); + } +}; + +int main() +{ + test_pixel_types::run(); + + return boost::report_errors(); +} diff --git a/test/core/virtual_2d_locator/test_fixture.hpp b/test/core/virtual_2d_locator/test_fixture.hpp new file mode 100644 index 0000000000..83f1b2fea2 --- /dev/null +++ b/test/core/virtual_2d_locator/test_fixture.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2022 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 +// +#ifndef BOOST_GIL_TEST_CORE_VIRTUAL_2D_LOCATOR_TEST_FIXTURE_HPP +#define BOOST_GIL_TEST_CORE_VIRTUAL_2D_LOCATOR_TEST_FIXTURE_HPP + +#include +#include + +#include + +#include "core/pixel/test_fixture.hpp" + +namespace boost { namespace gil { namespace test { namespace fixture { + +template +struct noop_dereference_fn +{ + static constexpr bool is_mutable =false; + + using const_t = noop_dereference_fn; + using value_type = Pixel; + using reference = value_type; + using const_reference = value_type; + using argument_type = gil::point_t; + using result_type = reference; + + static_assert(std::is_same::value, "value_type != reference"); + static_assert(std::is_same::value, "value_type != result_type"); + + result_type operator()(gil::point_t const&) const + { + return result_type{}; + } +}; + +template +struct locator_dereference_fn +{ + static constexpr bool is_mutable =false; + + using const_t = locator_dereference_fn; + using value_type = Pixel; + using reference = value_type; + using const_reference = value_type; + using argument_type = gil::point_t; + using result_type = reference; + + static_assert(std::is_same::value, "value_type != reference"); + static_assert(std::is_same::value, "value_type != result_type"); + + locator_dereference_fn() = default; + locator_dereference_fn(point_t const& dims) : _dimensions(dims) {} + + result_type operator()(gil::point_t const& position) const + { + if (position.x >= _dimensions.x) + throw std::out_of_range("position x out of range"); + if (position.y >= _dimensions.y) + throw std::out_of_range("position y out of range"); + + auto pixel = pixel_generator::random(); + return pixel; + } + +private: + gil::point_t _dimensions; +}; + +}}}} // namespace boost::gil::test::fixture + +#endif diff --git a/test/extension/CMakeLists.txt b/test/extension/CMakeLists.txt index b642d2b296..df1cfd72b3 100644 --- a/test/extension/CMakeLists.txt +++ b/test/extension/CMakeLists.txt @@ -5,10 +5,22 @@ # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) # +if(BOOST_GIL_ENABLE_EXT_RASTERIZATION) + add_subdirectory(rasterization) +endif() + +if(BOOST_GIL_ENABLE_EXT_IMAGE_PROCESSING) + add_subdirectory(image_processing) +endif() + if(BOOST_GIL_ENABLE_EXT_DYNAMIC_IMAGE) add_subdirectory(dynamic_image) endif() +if(BOOST_GIL_ENABLE_EXT_HISTOGRAM) + add_subdirectory(histogram) +endif() + if(BOOST_GIL_ENABLE_EXT_NUMERIC) add_subdirectory(numeric) endif() diff --git a/test/extension/Jamfile b/test/extension/Jamfile index cf9ef40896..8c7790d2e2 100644 --- a/test/extension/Jamfile +++ b/test/extension/Jamfile @@ -7,6 +7,9 @@ # copy at http://www.boost.org/LICENSE_1_0.txt) build-project dynamic_image ; +build-project histogram ; build-project numeric ; build-project toolbox ; build-project io ; +build-project image_processing ; +build-project rasterization ; diff --git a/test/extension/dynamic_image/CMakeLists.txt b/test/extension/dynamic_image/CMakeLists.txt index d2a37bf10c..0e720b5bab 100644 --- a/test/extension/dynamic_image/CMakeLists.txt +++ b/test/extension/dynamic_image/CMakeLists.txt @@ -8,6 +8,7 @@ message(STATUS "Boost.GIL: Configuring tests in test/extension/dynamic_image") foreach(_name any_image + any_image_view subimage_view) set(_test t_ext_dynamic_image_${_name}) set(_target test_ext_dynamic_image_${_name}) diff --git a/test/extension/dynamic_image/Jamfile b/test/extension/dynamic_image/Jamfile index 8cd2d5184d..3f9e57bbbd 100644 --- a/test/extension/dynamic_image/Jamfile +++ b/test/extension/dynamic_image/Jamfile @@ -11,4 +11,8 @@ import testing ; alias headers : [ generate_self_contained_headers extension/dynamic_image ] ; run any_image.cpp ; +run any_image_view.cpp ; +run image_view_factory.cpp ; run subimage_view.cpp ; + +build-project algorithm ; diff --git a/test/extension/dynamic_image/algorithm/Jamfile b/test/extension/dynamic_image/algorithm/Jamfile new file mode 100644 index 0000000000..071b29a56f --- /dev/null +++ b/test/extension/dynamic_image/algorithm/Jamfile @@ -0,0 +1,15 @@ +# Boost.GIL (Generic Image Library) - tests +# +# Copyright 2022 Marco Langer +# +# 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) + +import testing ; + +run copy_and_convert_pixels.cpp ; +run copy_pixels.cpp ; +run equal_pixels.cpp ; +run fill_pixels.cpp ; +run for_each_pixel.cpp ; diff --git a/test/extension/dynamic_image/algorithm/copy_and_convert_pixels.cpp b/test/extension/dynamic_image/algorithm/copy_and_convert_pixels.cpp new file mode 100644 index 0000000000..851b915551 --- /dev/null +++ b/test/extension/dynamic_image/algorithm/copy_and_convert_pixels.cpp @@ -0,0 +1,202 @@ +// +// Copyright 2022 Marco Langer +// +// 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 +// +#include +#include + +#include + +#include + +#include "core/image/test_fixture.hpp" +#include "extension/dynamic_image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct test_copy_and_convert_pixels +{ + template + void operator()(std::pair const&) + { + using image_lhs_t = ImageLhs; + using image_rhs_t = ImageRhs; + using image_view_lhs_t = typename image_lhs_t::view_t; + using image_view_rhs_t = typename image_rhs_t::view_t; + + gil::default_color_converter cc_default; + image_lhs_t image_lhs1(fixture::create_image(2, 2, 128)); + image_rhs_t image_rhs1(fixture::create_image(2, 2, 128)); + fixture::dynamic_image dyn_image_lhs1(image_lhs1); + fixture::dynamic_image dyn_image_rhs1(image_rhs1); + { + // dynamic_image -> image + image_lhs_t image_lhs2(2, 2); + image_lhs_t image_lhs3(2, 2); + image_rhs_t image_rhs2(2, 2); + image_rhs_t image_rhs3(2, 2); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(image_lhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(image_lhs2))); + + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(image_lhs3), cc_default)); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(image_lhs3))); + + + // lhs, rhs + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(image_rhs2))); + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(image_rhs3), cc_default)); + + if (gil::views_are_compatible::value) + { + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_rhs1), + gil::const_view(image_rhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_rhs1), + gil::const_view(image_rhs3))); + } + } + { + // image -> dynamic_image + fixture::dynamic_image dyn_image_lhs2(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_lhs3(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_rhs2(image_rhs_t(2, 2)); + fixture::dynamic_image dyn_image_rhs3(image_rhs_t(2, 2)); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(image_lhs1), + gil::view(dyn_image_lhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs1), + gil::const_view(dyn_image_lhs2))); + + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(image_lhs1), + gil::view(dyn_image_lhs3), cc_default)); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs1), + gil::const_view(dyn_image_lhs3))); + + // lhs, rhs + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(image_lhs1), + gil::view(dyn_image_rhs2))); + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(image_lhs1), + gil::view(dyn_image_rhs3), cc_default)); + + if (gil::views_are_compatible::value) + { + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs1), + gil::const_view(dyn_image_rhs2))); + + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs1), + gil::const_view(dyn_image_rhs3))); + } + } + { + // dynamic_image -> dynamic_image + fixture::dynamic_image dyn_image_lhs2(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_lhs3(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_rhs2(image_rhs_t(2, 2)); + fixture::dynamic_image dyn_image_rhs3(image_rhs_t(2, 2)); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_lhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_lhs2))); + + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_lhs3), cc_default)); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_lhs3))); + + // lhs, rhs + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_rhs2))); + BOOST_TEST_NO_THROW( + gil::copy_and_convert_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_rhs3), cc_default)); + + if (gil::views_are_compatible::value) + { + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_rhs2))); + + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_rhs3))); + } + } + } + + static void run() + { + boost::mp11::mp_for_each + < + boost::mp11::mp_pairwise_fold + < + fixture::image_types, std::pair + > + >(test_copy_and_convert_pixels{}); + } +}; + +int main() +{ + test_copy_and_convert_pixels::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/algorithm/copy_pixels.cpp b/test/extension/dynamic_image/algorithm/copy_pixels.cpp new file mode 100644 index 0000000000..40ad6eec59 --- /dev/null +++ b/test/extension/dynamic_image/algorithm/copy_pixels.cpp @@ -0,0 +1,163 @@ +// +// Copyright 2022 Marco Langer +// +// 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 +// +#include +#include + +#include + +#include + +#include "core/image/test_fixture.hpp" +#include "extension/dynamic_image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct test_copy_pixels +{ + template + void operator()(std::pair const&) + { + using image_lhs_t = ImageLhs; + using image_rhs_t = ImageRhs; + using image_view_lhs_t = typename image_lhs_t::view_t; + using image_view_rhs_t = typename image_rhs_t::view_t; + + { + // dynamic_image -> image + image_lhs_t image_lhs1(fixture::create_image(2, 2, 128)); + image_lhs_t image_lhs2(2, 2); + image_rhs_t image_rhs(2, 2); + fixture::dynamic_image dyn_image_lhs(image_lhs1); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::copy_pixels( + gil::const_view(dyn_image_lhs), + gil::view(image_lhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs1), + gil::const_view(image_lhs2))); + + // lhs, rhs + if (gil::views_are_compatible::value) + { + BOOST_TEST_NO_THROW( + gil::copy_pixels( + gil::const_view(dyn_image_lhs), + gil::view(image_rhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs1), + gil::const_view(image_rhs))); + } + else + { + BOOST_TEST_THROWS( + gil::copy_pixels( + gil::const_view(dyn_image_lhs), + gil::view(image_rhs)), + std::bad_cast); + } + } + { + // image -> dynamic_image + image_lhs_t image_lhs = fixture::create_image(2, 2, 128); + fixture::dynamic_image dyn_image_lhs1(image_lhs); + fixture::dynamic_image dyn_image_lhs2(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_rhs(image_rhs_t(2, 2)); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::copy_pixels( + gil::const_view(image_lhs), + gil::view(dyn_image_lhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_lhs2))); + + // lhs, rhs + if (gil::views_are_compatible::value) + { + BOOST_TEST_NO_THROW( + gil::copy_pixels( + gil::const_view(image_lhs), + gil::view(dyn_image_rhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_rhs))); + } + else + { + BOOST_TEST_THROWS( + gil::copy_pixels( + gil::const_view(image_lhs), + gil::view(dyn_image_rhs)), + std::bad_cast); + } + } + { + // dynamic_image -> dynamic_image + fixture::dynamic_image dyn_image_lhs1(fixture::create_image(2, 2, 128)); + fixture::dynamic_image dyn_image_lhs2(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_rhs(image_rhs_t(2, 2)); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::copy_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_lhs2))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_lhs2))); + + // lhs, rhs + if (gil::views_are_compatible::value) + { + BOOST_TEST_NO_THROW( + gil::copy_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_rhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_rhs))); + } + else + { + BOOST_TEST_THROWS( + gil::copy_pixels( + gil::const_view(dyn_image_lhs1), + gil::view(dyn_image_rhs)), + std::bad_cast); + } + } + } + + static void run() + { + boost::mp11::mp_for_each + < + boost::mp11::mp_pairwise_fold + < + fixture::image_types, std::pair + > + >(test_copy_pixels{}); + } +}; + +int main() +{ + test_copy_pixels::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/algorithm/equal_pixels.cpp b/test/extension/dynamic_image/algorithm/equal_pixels.cpp new file mode 100644 index 0000000000..eb9ef7440b --- /dev/null +++ b/test/extension/dynamic_image/algorithm/equal_pixels.cpp @@ -0,0 +1,104 @@ +// +// Copyright 2022 Marco Langer +// +// 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 +// +#include +#include + +#include + +#include + +#include "core/image/test_fixture.hpp" +#include "extension/dynamic_image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct test_equal_pixels +{ + template + void operator()(std::pair const&) + { + using image_lhs_t = ImageLhs; + using image_rhs_t = ImageRhs; + using image_view_lhs_t = typename image_lhs_t::view_t; + using image_view_rhs_t = typename image_rhs_t::view_t; + + image_lhs_t image_lhs = fixture::create_image(2, 2, 128); + image_rhs_t image_rhs = fixture::create_image(2, 2, 128); + + fixture::dynamic_image dyn_image_lhs(image_lhs); + fixture::dynamic_image dyn_image_rhs(image_rhs); + + // lhs, lhs + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs), + gil::const_view(image_lhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs), + gil::const_view(dyn_image_lhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs), + gil::const_view(dyn_image_lhs))); + + // lhs, rhs + if (gil::views_are_compatible::value) + { + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs), + gil::const_view(image_rhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(image_lhs), + gil::const_view(dyn_image_rhs))); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs), + gil::const_view(dyn_image_rhs))); + } + else + { + BOOST_TEST_THROWS( + gil::equal_pixels( + gil::const_view(dyn_image_lhs), + gil::const_view(image_rhs)), + std::bad_cast); + BOOST_TEST_THROWS( + gil::equal_pixels( + gil::const_view(image_lhs), + gil::const_view(dyn_image_rhs)), + std::bad_cast); + BOOST_TEST_THROWS( + gil::equal_pixels( + gil::const_view(dyn_image_lhs), + gil::const_view(dyn_image_rhs)), + std::bad_cast); + } + } + + static void run() + { + boost::mp11::mp_for_each + < + boost::mp11::mp_pairwise_fold + < + fixture::image_types, std::pair + > + >(test_equal_pixels{}); + } +}; + +int main() +{ + test_equal_pixels::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/algorithm/fill_pixels.cpp b/test/extension/dynamic_image/algorithm/fill_pixels.cpp new file mode 100644 index 0000000000..31832a63e0 --- /dev/null +++ b/test/extension/dynamic_image/algorithm/fill_pixels.cpp @@ -0,0 +1,86 @@ +// +// Copyright 2022 Marco Langer +// +// 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 +// +#include +#include + +#include + +#include + +#include "core/image/test_fixture.hpp" +#include "extension/dynamic_image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct test_fill_pixels +{ + template + void operator()(std::pair const&) + { + using image_lhs_t = ImageLhs; + using image_rhs_t = ImageRhs; + using image_view_lhs_t = typename image_lhs_t::view_t; + using image_view_rhs_t = typename image_rhs_t::view_t; + using value_lhs_t = typename image_lhs_t::value_type; + using value_rhs_t = typename image_rhs_t::value_type; + + image_lhs_t image_lhs = fixture::create_image(2, 2, 128); + fixture::dynamic_image dyn_image_lhs1(image_lhs); + fixture::dynamic_image dyn_image_lhs2(image_lhs_t(2, 2)); + fixture::dynamic_image dyn_image_lhs3(image_lhs_t(2, 2)); + value_lhs_t value_lhs(128); + value_rhs_t value_rhs(128); + + // lhs, lhs + BOOST_TEST_NO_THROW( + gil::fill_pixels( + gil::view(dyn_image_lhs2), value_lhs)); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_lhs2))); + + // lhs, rhs + if (gil::views_are_compatible::value) + { + BOOST_TEST_NO_THROW( + gil::fill_pixels( + gil::view(dyn_image_lhs3), value_rhs)); + BOOST_TEST( + gil::equal_pixels( + gil::const_view(dyn_image_lhs1), + gil::const_view(dyn_image_lhs3))); + } + else + { + BOOST_TEST_THROWS( + gil::fill_pixels( + gil::view(dyn_image_lhs3), value_rhs), + std::bad_cast); + } + } + + static void run() + { + boost::mp11::mp_for_each + < + boost::mp11::mp_pairwise_fold + < + fixture::image_types, std::pair + > + >(test_fill_pixels{}); + } +}; + +int main() +{ + test_fill_pixels::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/algorithm/for_each_pixel.cpp b/test/extension/dynamic_image/algorithm/for_each_pixel.cpp new file mode 100644 index 0000000000..ad5620a5bf --- /dev/null +++ b/test/extension/dynamic_image/algorithm/for_each_pixel.cpp @@ -0,0 +1,52 @@ +// +// Copyright 2022 Marco Langer +// +// 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 +// +#include +#include + +#include + +#include "core/image/test_fixture.hpp" +#include "extension/dynamic_image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +struct accumulator +{ + template + void operator()(Pixel const& p) + { + sum += gil::at_c<0>(p); + } + + int sum{}; +}; + +struct test_for_each_pixel +{ + template + void operator()(Image const&) + { + using image_t = Image; + fixture::dynamic_image image(fixture::create_image(2, 2, 128)); + accumulator acc = gil::for_each_pixel(gil::const_view(image), accumulator()); + BOOST_TEST_EQ(acc.sum, 2 * 2 * 128); + } + + static void run() + { + boost::mp11::mp_for_each(test_for_each_pixel{}); + } +}; + +int main() +{ + test_for_each_pixel::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/any_image.cpp b/test/extension/dynamic_image/any_image.cpp index c8efc58256..7dfe28e495 100644 --- a/test/extension/dynamic_image/any_image.cpp +++ b/test/extension/dynamic_image/any_image.cpp @@ -22,10 +22,13 @@ struct test_any_image_move_ctor void operator()(Image const&) { using image_t = Image; - fixture::dynamic_image i0(fixture::create_image(4, 4, 128)); + fixture::dynamic_image i0(fixture::create_image(4, 5, 128)); BOOST_TEST_EQ(i0.dimensions().x, 4); - BOOST_TEST_EQ(i0.dimensions().y, 4); - + BOOST_TEST_EQ(i0.dimensions().y, 5); + BOOST_TEST_EQ(i0.width(), 4); + BOOST_TEST_EQ(i0.height(), 5); + BOOST_TEST_EQ(i0.num_channels(), gil::num_channels::value); + fixture::dynamic_image i1 = fixture::create_image(4, 4, 128); BOOST_TEST_EQ(i1.dimensions().x, 4); BOOST_TEST_EQ(i1.dimensions().y, 4); @@ -36,10 +39,29 @@ struct test_any_image_move_ctor } }; +struct test_any_image_recreate +{ + template + void operator()(Image const&) + { + using image_t = Image; + using point_t = typename image_t::point_t; + fixture::dynamic_image image(image_t(4, 5, 128)); + point_t point(5, 6); + + BOOST_TEST_NO_THROW(image.recreate(point)); + } + + static void run() + { + boost::mp11::mp_for_each(test_any_image_recreate{}); + } +}; int main() { test_any_image_move_ctor::run(); + test_any_image_recreate::run(); return ::boost::report_errors(); } diff --git a/test/extension/dynamic_image/any_image_view.cpp b/test/extension/dynamic_image/any_image_view.cpp new file mode 100644 index 0000000000..fc215e0379 --- /dev/null +++ b/test/extension/dynamic_image/any_image_view.cpp @@ -0,0 +1,132 @@ +// +// Copyright 2020 Samuel Debionne +// +// 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 +// +#include +#include + +#include +#include + +#include + +#include "test_fixture.hpp" +#include "core/image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +void test_any_image_view_nested_types() +{ + BOOST_TEST_TRAIT_SAME(gil::any_image_view::const_t, gil::any_image_view); +} + + +struct test_any_image_view_init_ctor +{ + template + void operator()(Image const&) + { + using image_t = Image; + using view_t = typename Image::view_t; + using any_view_t = gil::any_image_view; + using any_const_view_t = typename any_view_t::const_t; + Image i0(fixture::create_image(4, 5, 128)); + + view_t v0 = gil::view(i0); + any_view_t v1 = v0; + + BOOST_TEST_EQ(v1.dimensions().x, 4); + BOOST_TEST_EQ(v1.dimensions().y, 5); + BOOST_TEST_EQ(v1.width(), 4); + BOOST_TEST_EQ(v1.height(), 5); + BOOST_TEST_EQ(v1.size(), 4 * 5); + BOOST_TEST_EQ(v1.num_channels(), gil::num_channels::value); + + any_const_view_t v2 = v0; + + BOOST_TEST_EQ(v2.dimensions().x, 4); + BOOST_TEST_EQ(v2.dimensions().y, 5); + + //any_const_view_t v3 = v1; + } + static void run() + { + boost::mp11::mp_for_each(test_any_image_view_init_ctor{}); + } +}; + +struct test_any_image_view_copy_ctor +{ + template + void operator()(Image const&) + { + using image_t = Image; + using view_t = typename Image::view_t; + using any_view_t = gil::any_image_view; + using any_const_view_t = typename any_view_t::const_t; + Image i0(fixture::create_image(4, 4, 128)); + + view_t v0 = gil::view(i0); + any_view_t v1 = v0; + + BOOST_TEST_EQ(v1.dimensions().x, 4); + BOOST_TEST_EQ(v1.dimensions().y, 4); + + any_const_view_t v2 = v0; + + BOOST_TEST_EQ(v2.dimensions().x, 4); + BOOST_TEST_EQ(v2.dimensions().y, 4); + + //any_const_view_t v3 = v1; + } + static void run() + { + boost::mp11::mp_for_each(test_any_image_view_copy_ctor{}); + } +}; + +struct test_any_image_view_assign_operator +{ + template + void operator()(Image const&) + { + using image_t = Image; + using view_t = typename Image::view_t; + using any_view_t = gil::any_image_view; + using any_const_view_t = typename any_view_t::const_t; + Image i0(fixture::create_image(4, 4, 128)); + + view_t v0 = gil::view(i0); + any_view_t v1; + any_const_view_t v2; + + v1 = v0; + + BOOST_TEST_EQ(v1.dimensions().x, 4); + BOOST_TEST_EQ(v1.dimensions().y, 4); + + v2 = v0; + + BOOST_TEST_EQ(v2.dimensions().x, 4); + BOOST_TEST_EQ(v2.dimensions().y, 4); + + //v2 = v1; + } + static void run() + { + boost::mp11::mp_for_each(test_any_image_view_assign_operator{}); + } +}; + +int main() +{ + test_any_image_view_init_ctor::run(); + test_any_image_view_copy_ctor::run(); + test_any_image_view_assign_operator::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/image_view_factory.cpp b/test/extension/dynamic_image/image_view_factory.cpp new file mode 100644 index 0000000000..3837f740e5 --- /dev/null +++ b/test/extension/dynamic_image/image_view_factory.cpp @@ -0,0 +1,159 @@ +// +// Copyright 2022 Marco Langer +// +// 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 +// +#include +#include + +#include + +#include + +#include "core/image/test_fixture.hpp" +#include "extension/dynamic_image/test_fixture.hpp" + +namespace gil = boost::gil; +namespace fixture = boost::gil::test::fixture; + +template +struct generator +{ + generator(int const* data) : data_(data) {} + + auto operator()() -> int + { + if(++i_ == Channels) { + i_ = 0; + return *data_++; + } + else + { + return *data_; + } + } + + int i_= 0; + int const* data_; +}; + +struct test_flipped_up_down_view +{ + template + void operator()(Image const&) + { + using image_t = Image; + using pixel_t = typename image_t::value_type; + static constexpr std::size_t num_channels = gil::num_channels::value; + + std::array pixel_data = + { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8 + }; + std::array expected_pixel_data = + { + 6, 7, 8, + 3, 4, 5, + 0, 1, 2 + }; + + fixture::dynamic_image source_image = + fixture::generate_image( + 3, 3, generator{pixel_data.data()}); + + fixture::dynamic_image expected_image = + fixture::generate_image( + 3, 3, generator{expected_pixel_data.data()}); + + auto result_view = gil::flipped_up_down_view(gil::const_view(source_image)); + + BOOST_TEST( + gil::equal_pixels( + result_view, + gil::const_view(expected_image))); + } + + static void run() + { + boost::mp11::mp_for_each(test_flipped_up_down_view{}); + } +}; + +template +bool equal_pixels_values(V1&& v1, + V2&& v2, + float threshold = 1e-6f) +{ +// convert both images to rgba32f and compare with threshold + return boost::variant2::visit([=](auto const& v1, auto const& v2) -> bool { + auto it1 = v1.begin(); + auto it2 = v2.begin(); + while(it1 != v1.end() && it2 != v2.end()) { + using pixel_t = gil::rgba32f_pixel_t; + static constexpr std::size_t num_channels = gil::num_channels::value; + pixel_t p1{}; + gil::color_convert(*it1++, p1); + pixel_t p2{}; + gil::color_convert(*it2++, p2); + for(size_t i = 0; i < num_channels; ++i) { + if(std::abs(p1[i] - p2[i]) > threshold){ + return false; + } + } + } + return true; + }, + std::forward(v1), + std::forward(v2)); +} + +struct test_color_converted_view +{ + static void run() + { + using dynamic_image_t = gil::any_image; + using src_image_t = gil::gray8_image_t; + using dst_image_t = gil::gray16_image_t; + using dst_pixel_t = typename dst_image_t::value_type; + static constexpr std::size_t num_channels = 1; + auto color_converter = [](auto const& src, auto& dst) { dst = 2 * src; }; + std::array pixel_data = + { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8 + }; + std::array expected_pixel_data; + std::transform(std::begin(pixel_data), + std::end(pixel_data), + std::begin(expected_pixel_data), + [](auto v) { return 2 * v; }); + + dynamic_image_t source_image = + fixture::generate_image( + 3, 3, generator{pixel_data.data()}); + + dynamic_image_t expected_image = + fixture::generate_image( + 3, 3, generator{expected_pixel_data.data()}); + + auto result_view = gil::color_converted_view(gil::const_view(source_image), color_converter); + + BOOST_TEST(equal_pixels_values(result_view, + gil::const_view(expected_image), + 1e-6f)); + } +}; + +int main() +{ + test_flipped_up_down_view::run(); + + test_color_converted_view::run(); + + return ::boost::report_errors(); +} diff --git a/test/extension/dynamic_image/subimage_view.cpp b/test/extension/dynamic_image/subimage_view.cpp index b1e22ae5f9..37cca2c6d7 100644 --- a/test/extension/dynamic_image/subimage_view.cpp +++ b/test/extension/dynamic_image/subimage_view.cpp @@ -15,6 +15,7 @@ #include "core/image/test_fixture.hpp" namespace gil = boost::gil; +namespace variant2 = boost::variant2; namespace fixture = boost::gil::test::fixture; struct test_subimage_equals_image @@ -61,16 +62,16 @@ struct test_subimage_equals_image_quadrants // create test image and set values of pixels in: // quadrant 1 auto const i1 = fixture::create_image(2, 2, 255); - gil::apply_operation(v0, fixture::fill_any_view({2, 3, 6, 7}, gil::const_view(i1)[0])); + variant2::visit(fixture::fill_any_view({2, 3, 6, 7}, gil::const_view(i1)[0]), v0); // quadrant 2 auto const i2 = fixture::create_image(2, 2, 128); - gil::apply_operation(v0, fixture::fill_any_view({0, 1, 4, 5}, gil::const_view(i2)[0])); + variant2::visit(fixture::fill_any_view({0, 1, 4, 5}, gil::const_view(i2)[0]), v0); // quadrant 3 auto const i3 = fixture::create_image(2, 2, 64); - gil::apply_operation(v0, fixture::fill_any_view({8, 9, 12, 13}, gil::const_view(i3)[0])); + variant2::visit(fixture::fill_any_view({8, 9, 12, 13}, gil::const_view(i3)[0]), v0); // quadrant 4 auto const i4 = fixture::create_image(2, 2, 32); - gil::apply_operation(v0, fixture::fill_any_view({10, 11, 14, 15}, gil::const_view(i4)[0])); + variant2::visit(fixture::fill_any_view({10, 11, 14, 15}, gil::const_view(i4)[0]), v0); auto v1 = gil::subimage_view(gil::view(i0), { 2, 0 }, i0.dimensions() / 2); BOOST_TEST(gil::equal_pixels(v1, gil::const_view(i1))); diff --git a/test/extension/histogram/CMakeLists.txt b/test/extension/histogram/CMakeLists.txt new file mode 100644 index 0000000000..562d4d916c --- /dev/null +++ b/test/extension/histogram/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# Copyright 2020 Debabrata Mandal +# +# 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 +# + +foreach(_name + histogram) + set(_test t_core_histogram_${_name}) + set(_target test_core_histogram_${_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/histogram/Jamfile b/test/extension/histogram/Jamfile new file mode 100644 index 0000000000..d7c7edf238 --- /dev/null +++ b/test/extension/histogram/Jamfile @@ -0,0 +1,11 @@ +# +# Copyright 2020 Debabrata Mandal +# +# 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 +# + +import testing ; + +run histogram.cpp ; diff --git a/test/extension/histogram/histogram.cpp b/test/extension/histogram/histogram.cpp new file mode 100644 index 0000000000..5cfbdab72f --- /dev/null +++ b/test/extension/histogram/histogram.cpp @@ -0,0 +1,176 @@ +// +// Copyright 2020 Debabrata Mandal +// +// 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 +// + +#include +#include +#include +#include + +// Supported Container types for histogram +#include +#include +#include +#include + +// Basic tests to make sure compatible container types produce +// expected output histogram. + +namespace gil = boost::gil; + +gil::gray8_image_t img1(4, 4, gil::gray8_pixel_t(1)); +gil::gray8_view_t v1 = view(img1); + +gil::rgb8_image_t img2(4, 4, gil::rgb8_pixel_t(1)); +gil::rgb8_view_t v2 = view(img2); + +gil::gray16_image_t img3(4, 4, gil::gray16_pixel_t(1)); +gil::gray16_view_t v3 = view(img3); + +template +bool check_equal(T &cont1, T &cont2, std::size_t size) +{ + bool ch = true; + for(std::size_t i = 0; i < size; ++i) + { + ch = ch & (cont1[i] == cont2[i]); + } + return ch; +} + +template +bool check_equal(T &cont1, T &cont2) +{ + bool ch = true; + for(auto &it : cont1) + { + ch = ch & (cont1[it.first] == cont2[it.first]); + } + return ch; +} + +void check_fill_histogram_vector() +{ + std::vector c1, c1_expected(256,0); + c1_expected[1] = 16; + gil::fill_histogram(v1, c1); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + c1_expected[1] = 32; + gil::fill_histogram(v1, c1, true); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + std::vector c2, c2_expected; + gil::fill_histogram(v2, c2); + gil::fill_histogram(gil::color_converted_view(v2), c2_expected); + BOOST_TEST(check_equal(c2, c2_expected, c2_expected.size())); +} + + +void check_fill_histogram_array() +{ + std::array c1{0}, c1_expected{0}; + c1_expected[1] = 16; + gil::fill_histogram(v1, c1); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + c1_expected[1] = 32; + gil::fill_histogram(v1, c1, true); + BOOST_TEST(check_equal(c1, c1_expected, c1_expected.size())); + + std::array c2{0}, c2_expected{0}; + gil::fill_histogram(v1, c2); + gil::fill_histogram(gil::color_converted_view(v2), c2_expected); + BOOST_TEST(check_equal(c2, c2_expected, c2_expected.size())); + + // Check binning + std::array c3{0}, c3_expected{0}; + c3_expected[0] = 16; + gil::fill_histogram(v3, c3); + BOOST_TEST(check_equal(c3, c3_expected, c3_expected.size())); +} + +void check_fill_histogram_map() +{ + std::map c1, c1_expected; + c1_expected[1] = 16; + gil::fill_histogram(v1, c1); + BOOST_TEST(check_equal(c1, c1_expected)); + + c1_expected[1] = 32; + gil::fill_histogram(v1, c1, true); + BOOST_TEST(check_equal(c1, c1_expected)); + + std::map c2, c2_expected; + gil::fill_histogram(v2, c2); + gil::fill_histogram(gil::color_converted_view(v2), c2_expected); + BOOST_TEST(check_equal(c2, c2_expected)); +} + +void check_cumulative_histogram_vector() +{ + std::vector v(8); + for (std::size_t i = 0; i < v.size(); i++) + { + v[i] = 1; + } + auto v1 = gil::cumulative_histogram(v); + bool check = true; + for (std::size_t i = 0; i < v.size(); i++) + { + if(v1[i] != int(i) + 1) + check = false; + } + BOOST_TEST(check); +} + +void check_cumulative_histogram_array() +{ + std::array arr; + for (std::size_t i = 0; i < arr.size(); i++) + { + arr[i] = 1; + } + auto arr1 = gil::cumulative_histogram(arr); + bool check = true; + for (std::size_t i = 0; i < arr.size(); i++) + { + if(arr1[i] != int(i) + 1) + check = false; + } + BOOST_TEST(check); +} + +void check_cumulative_histogram_map() +{ + std::map mp; + for (std::size_t i = 0; i < 8; i++) + { + mp[i] = 1; + } + auto mp1 = gil::cumulative_histogram(mp); + bool check = true; + for (std::size_t i = 0; i < mp.size(); i++) + { + if(mp1[i] != int(i) + 1) + check = false; + } + BOOST_TEST(check); +} + +int main() +{ + check_fill_histogram_vector(); + check_fill_histogram_array(); + check_fill_histogram_map(); + + check_cumulative_histogram_vector(); + check_cumulative_histogram_array(); + check_cumulative_histogram_map(); + + return boost::report_errors(); +} diff --git a/test/extension/image_processing/CMakeLists.txt b/test/extension/image_processing/CMakeLists.txt new file mode 100644 index 0000000000..62e6f79595 --- /dev/null +++ b/test/extension/image_processing/CMakeLists.txt @@ -0,0 +1,22 @@ +foreach(_name + anisotropic_diffusion + hough_circle_transform + hough_line_transform + hough_parameter) + set(_test t_ext_image_processing_${_name}) + set(_target test_ext_image_processing_${_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/image_processing/Jamfile b/test/extension/image_processing/Jamfile new file mode 100644 index 0000000000..6ab73640d1 --- /dev/null +++ b/test/extension/image_processing/Jamfile @@ -0,0 +1,14 @@ +# Boost.GIL (Generic Image Library) - tests +# +# Copyright 2020 Olzhas Zhumabek +# +# Use, modification and distribution are subject to 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) +# +import testing ; + +run anisotropic_diffusion.cpp ; +run hough_circle_transform.cpp ; +run hough_line_transform.cpp ; +run hough_parameter.cpp ; \ No newline at end of file diff --git a/test/extension/image_processing/anisotropic_diffusion.cpp b/test/extension/image_processing/anisotropic_diffusion.cpp new file mode 100644 index 0000000000..302b43de58 --- /dev/null +++ b/test/extension/image_processing/anisotropic_diffusion.cpp @@ -0,0 +1,304 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace gil = boost::gil; + +template +void diffusion_function_check(DiffusionFunction diffusion) +{ + using limits = std::numeric_limits; + using channel_type = typename gil::channel_type::type; + for (channel_type value = limits::min(); value <= limits::max(); ++value) + { + PixelType pixel; + gil::static_fill(pixel, value); + auto diffusivity_value = diffusion(pixel); + gil::static_for_each(diffusivity_value, [](channel_type channel_value) { + BOOST_TEST(0 <= channel_value && channel_value <= 1.0); + }); + } +} + +void brightness_function_test() +{ + std::vector rgb_pixels{ + gil::rgb32f_pixel_t(0, 11, 14), gil::rgb32f_pixel_t(2, 117, 200), + gil::rgb32f_pixel_t(223, 2, 180), gil::rgb32f_pixel_t(250, 254, 100), + gil::rgb32f_pixel_t(255, 255, 255), + }; + for (const auto& pixel : rgb_pixels) + { + boost::gil::laplace_function::stencil_type stencil; + std::fill(stencil.begin(), stencil.end(), pixel); + auto brightness_stencil = boost::gil::brightness_function::identity{}(stencil); + for (const auto& brightness_pixel : brightness_stencil) + { + BOOST_TEST(pixel == brightness_pixel); + } + } + + std::vector corresponding_luminance_values{8.878f, 98.5436f, 61.8362f, 242.0308f, 255}; + std::size_t index = 0; + for (const auto& pixel : rgb_pixels) + { + boost::gil::laplace_function::stencil_type stencil; + std::fill(stencil.begin(), stencil.end(), pixel); + auto brightness_stencil = boost::gil::brightness_function::rgb_luminance{}(stencil); + for (const auto& brightness_pixel : brightness_stencil) + { + gil::static_for_each(brightness_pixel, [&corresponding_luminance_values, + index](boost::gil::float32_t value) { + BOOST_TEST(std::abs(value - corresponding_luminance_values[index]) < 1.0f); + }); + } + ++index; + } + + std::vector gray_pixels{ + gil::gray32f_pixel_t(11), gil::gray32f_pixel_t(0), gil::gray32f_pixel_t(255), + gil::gray32f_pixel_t(123), gil::gray32f_pixel_t(17), + }; + for (const auto& pixel : gray_pixels) + { + boost::gil::laplace_function::stencil_type stencil; + std::fill(stencil.begin(), stencil.end(), pixel); + auto brightness_stencil = boost::gil::brightness_function::identity{}(stencil); + for (const auto& brightness_pixel : brightness_stencil) + { + BOOST_TEST(pixel == brightness_pixel); + } + } +} + +template +void heat_conservation_test(std::uint32_t seed) +{ + gil::test::fixture::random_value dist(seed, 0, + std::numeric_limits::max()); + + ImageType image(32, 32); + auto view = gil::view(image); + constexpr std::ptrdiff_t num_channels = gil::num_channels::value; + double before_diffusion[num_channels] = {0}; + for (auto& pixel : view) + { + for (std::size_t channel_index = 0; channel_index < num_channels; ++channel_index) + { + pixel[channel_index] = static_cast(dist()); + before_diffusion[channel_index] += pixel[channel_index]; + } + } + + OutputImageType output(32, 32); + auto output_view = gil::view(output); + gil::default_anisotropic_diffusion(view, output_view, 10, 5); + double after_diffusion[num_channels] = {0}; + for (const auto& pixel : output_view) + { + for (std::size_t channel_index = 0; channel_index < num_channels; ++channel_index) + { + after_diffusion[channel_index] += pixel[channel_index]; + } + } + + for (std::ptrdiff_t channel_index = 0; channel_index < num_channels; ++channel_index) + { + const auto percentage = + before_diffusion[channel_index] != 0 + ? std::abs(after_diffusion[channel_index] - before_diffusion[channel_index]) / + after_diffusion[channel_index] * 100.0 + : std::abs(after_diffusion[channel_index] - before_diffusion[channel_index]) / + after_diffusion[channel_index] * 100.0; +#ifdef BOOST_GIL_TEST_DEBUG + std::cout << percentage << ' '; +#endif + BOOST_TEST(percentage < 1); + } +#ifdef BOOST_GIL_TEST_DEBUG + std::cout << '\n'; +#endif +} + +template +void test_stencil_pixels(const gil::laplace_function::stencil_type& stencil, + const PixelType& reference) +{ + for (const auto& stencil_entry : stencil) + { + BOOST_TEST(stencil_entry == reference); + } +} + +template +void test_stencil(const gil::laplace_function::stencil_type& stencil, + const std::array& expected_values) +{ + for (std::size_t i = 0; i < 8; ++i) + { + PixelType expected_pixel; + gil::static_fill(expected_pixel, expected_values[i]); + BOOST_TEST(stencil[i] == expected_pixel); + } +} + +void laplace_functions_test() +{ + // sanity checks + auto zero_rgb_pixel = []() { + gil::rgb32f_pixel_t zero_pixel; + gil::static_fill(zero_pixel, 0); + return zero_pixel; + }(); + auto zero_gray_pixel = []() { + gil::gray32f_pixel_t zero_pixel; + gil::static_fill(zero_pixel, 0); + return zero_pixel; + }(); + + auto image_size = gil::point_t(16, 16); + gil::rgb32f_image_t rgb_image(image_size, zero_rgb_pixel); + gil::gray32f_image_t gray_image(image_size, zero_gray_pixel); + auto rgb = gil::view(rgb_image); + auto gray = gil::view(gray_image); + + auto s4way = gil::laplace_function::stencil_5points{}; + auto s8way = gil::laplace_function::stencil_9points_standard{}; + for (std::ptrdiff_t y = 1; y < image_size.y - 1; ++y) + { + for (std::ptrdiff_t x = 1; x < image_size.x - 1; ++x) + { + auto rgb_4way_stencil = s4way.compute_laplace(rgb, {x, y}); + auto gray_4way_stencil = s4way.compute_laplace(gray, {x, y}); + + auto rgb_8way_stencil = s8way.compute_laplace(rgb, {x, y}); + auto gray_8way_stencil = s8way.compute_laplace(gray, {x, y}); + + test_stencil_pixels(rgb_4way_stencil, zero_rgb_pixel); + test_stencil_pixels(rgb_8way_stencil, zero_rgb_pixel); + test_stencil_pixels(gray_4way_stencil, zero_gray_pixel); + test_stencil_pixels(gray_8way_stencil, zero_gray_pixel); + } + } + + // a predefined case + // 5 11 2 + // 17 25 58 + // 90 31 40 + + using rgb_pixel = gil::rgb32f_pixel_t; + rgb(1, 1) = rgb_pixel(5, 5, 5); + rgb(2, 1) = rgb_pixel(11, 11, 11); + rgb(3, 1) = rgb_pixel(2, 2, 2); + rgb(1, 2) = rgb_pixel(17, 17, 17); + rgb(2, 2) = rgb_pixel(25, 25, 25); + rgb(3, 2) = rgb_pixel(58, 58, 58); + rgb(1, 3) = rgb_pixel(90, 90, 90); + rgb(2, 3) = rgb_pixel(31, 31, 31); + rgb(3, 3) = rgb_pixel(40, 40, 40); + + using gray_pixel = gil::gray32f_pixel_t; + gray(1, 1) = gray_pixel(5); + gray(2, 1) = gray_pixel(11); + gray(3, 1) = gray_pixel(2); + gray(1, 2) = gray_pixel(17); + gray(2, 2) = gray_pixel(25); + gray(3, 2) = gray_pixel(58); + gray(1, 3) = gray_pixel(90); + gray(2, 3) = gray_pixel(31); + gray(3, 3) = gray_pixel(40); + + // 4 way stencil should be + // 0 -14 0 + // -8 33 + // 0 6 0 + + auto test_point = gil::point_t(2, 2); + std::array _4way_expected{0, -14, 0, 33, 0, 6, 0, -8}; + auto rgb_4way = s4way.compute_laplace(rgb, test_point); + test_stencil(rgb_4way, _4way_expected); + auto gray_4way = s4way.compute_laplace(gray, test_point); + test_stencil(gray_4way, _4way_expected); + + // 8 way stencil should be + // -20 -14 -23 + // -8 33 + // 65 6 15 + + std::array _8way_expected{-20, -14, -23, 33, 15, 6, 65, -8}; + auto rgb_8way = s8way.compute_laplace(rgb, test_point); + test_stencil(rgb_8way, _8way_expected); + auto gray_8way = s8way.compute_laplace(gray, test_point); + test_stencil(gray_8way, _8way_expected); + + // reduce result for 4 way should be (-14 - 8 + 6 + 33) * 0.25 = 4.25 + auto rgb_reduced_4way = s4way.reduce(rgb_4way); + gil::static_for_each(rgb_reduced_4way, + [](gil::float32_t value) { BOOST_TEST(value == 4.25f); }); + auto gray_reduced_4way = s4way.reduce(gray_4way); + gil::static_for_each(gray_reduced_4way, + [](gil::float32_t value) { BOOST_TEST(value == 4.25f); }); + + // reduce result for 8 way should be ((-20-23+15+65)*0.5+(-14+33+6-8)) * 0.125 = (18.5+17) * + // 0.125 = 4.4375 + auto rgb_reduced_8way = s8way.reduce(rgb_8way); + gil::static_for_each(rgb_reduced_8way, + [](gil::float32_t value) { BOOST_TEST(value == 4.4375); }); + auto gray_reduced_8way = s8way.reduce(gray_8way); + gil::static_for_each(gray_reduced_8way, + [](gil::float32_t value) { BOOST_TEST(value == 4.4375); }); +} + +int main() +{ + for (std::uint32_t seed = 0; seed < 100; ++seed) + { + heat_conservation_test(seed); + heat_conservation_test(seed); + } + + for (double kappa = 5; kappa <= 70; ++kappa) + { + diffusion_function_check( + gil::conductivity::perona_malik_conductivity{kappa}); + diffusion_function_check( + gil::conductivity::gaussian_conductivity{kappa}); + diffusion_function_check( + gil::conductivity::wide_regions_conductivity{kappa}); + diffusion_function_check( + gil::conductivity::more_wide_regions_conductivity{kappa}); + + diffusion_function_check( + gil::conductivity::perona_malik_conductivity{kappa}); + diffusion_function_check( + gil::conductivity::gaussian_conductivity{kappa}); + diffusion_function_check( + gil::conductivity::wide_regions_conductivity{kappa}); + diffusion_function_check( + gil::conductivity::more_wide_regions_conductivity{kappa}); + } + + brightness_function_test(); + + laplace_functions_test(); + + return boost::report_errors(); +} diff --git a/test/extension/image_processing/hough_circle_transform.cpp b/test/extension/image_processing/hough_circle_transform.cpp new file mode 100644 index 0000000000..8ec4eb9f31 --- /dev/null +++ b/test/extension/image_processing/hough_circle_transform.cpp @@ -0,0 +1,84 @@ +// Boost.GIL (Generic Image Library) - tests +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace gil = boost::gil; + +template +void exact_fit_test(std::ptrdiff_t radius, gil::point_t offset, Rasterizer rasterizer) +{ + std::vector circle_points(rasterizer.point_count()); + rasterizer(circle_points.begin()); + // const std::ptrdiff_t diameter = radius * 2 - 1; + const std::ptrdiff_t width = offset.x + radius + 1; + const std::ptrdiff_t height = offset.y + radius + 1; + gil::gray8_image_t image(width, height); + auto input = gil::view(image); + + for (const auto& point : circle_points) + { + input(point) = std::numeric_limits::max(); + } + + using param_t = gil::hough_parameter; + const auto radius_parameter = param_t{radius, 0, 1}; + // const auto x_parameter = param_t::from_step_count(offset.x, neighborhood, half_step_count); + // const auto y_parameter = param_t::from_step_count(offset.y, neighborhood, half_step_count); + const auto x_parameter = param_t{offset.x, 0, 1}; + const auto y_parameter = param_t{offset.y, 0, 1}; + + std::vector output_images( + radius_parameter.step_count, + gil::gray16_image_t(x_parameter.step_count, y_parameter.step_count)); + std::vector output_views(radius_parameter.step_count); + std::transform(output_images.begin(), output_images.end(), output_views.begin(), + [](gil::gray16_image_t& img) + { + return gil::view(img); + }); + gil::hough_circle_transform_brute(input, radius_parameter, x_parameter, y_parameter, + output_views.begin(), rasterizer); + if (output_views[0](0, 0) != rasterizer.point_count()) + { + std::cout << "accumulated value: " << static_cast(output_views[0](0, 0)) + << " expected value: " << rasterizer.point_count() << "\n\n"; + } + BOOST_TEST(output_views[0](0, 0) == rasterizer.point_count()); +} + +int main() +{ + const int test_dim_length = 20; + for (std::ptrdiff_t radius = 5; radius < test_dim_length; ++radius) + { + for (std::ptrdiff_t x_offset = radius; x_offset < radius + test_dim_length; ++x_offset) + { + for (std::ptrdiff_t y_offset = radius; y_offset < radius + test_dim_length; ++y_offset) + { + + exact_fit_test(radius, {x_offset, y_offset}, + gil::midpoint_circle_rasterizer{{x_offset, y_offset}, radius}); + exact_fit_test(radius, {x_offset, y_offset}, + gil::trigonometric_circle_rasterizer{{x_offset, y_offset}, radius}); + } + } + } + + return boost::report_errors(); +} diff --git a/test/extension/image_processing/hough_line_transform.cpp b/test/extension/image_processing/hough_line_transform.cpp new file mode 100644 index 0000000000..6ff4059654 --- /dev/null +++ b/test/extension/image_processing/hough_line_transform.cpp @@ -0,0 +1,100 @@ +// Boost.GIL (Generic Image Library) - tests +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gil = boost::gil; + +const std::ptrdiff_t width = 64; + +void translate(std::vector& points, std::ptrdiff_t intercept) +{ + std::transform(points.begin(), points.end(), points.begin(), + [intercept](gil::point_t point) + { + return gil::point_t{point.x, point.y + intercept}; + }); +} + +void hough_line_test(std::ptrdiff_t height, std::ptrdiff_t intercept) +{ + gil::gray8_image_t image(width, width, gil::gray8_pixel_t(0)); + auto input = gil::view(image); + + const auto rasterizer = gil::bresenham_line_rasterizer{{0, 0}, {width - 1, height - 1}}; + std::vector line_points(rasterizer.point_count()); + rasterizer(line_points.begin()); + translate(line_points, intercept); + for (const auto& p : line_points) + { + input(p) = 255; + } + + double alpha = std::atan2(height, width); + const double theta = alpha + gil::detail::pi / 2; + const auto minimum_angle_step = gil::minimum_angle_step({width, height}); + const double expected_alpha = std::round(alpha / minimum_angle_step) * minimum_angle_step; + const double expected_radius = std::round(intercept * std::cos(expected_alpha)); + const double expected_theta = std::round(theta / minimum_angle_step) * minimum_angle_step; + + const std::size_t half_step_count = 3; + const std::ptrdiff_t expected_index = 3; + const std::size_t accumulator_array_dimensions = half_step_count * 3 + 1; + auto radius_param = + gil::hough_parameter::from_step_count(expected_radius, 3, half_step_count); + auto theta_param = gil::make_theta_parameter( + expected_theta, minimum_angle_step * half_step_count, {width, height}); + gil::gray32_image_t accumulator_array_image( + accumulator_array_dimensions, accumulator_array_dimensions, gil::gray32_pixel_t(0)); + auto accumulator_array = gil::view(accumulator_array_image); + gil::hough_line_transform(input, accumulator_array, theta_param, radius_param); + + auto max_element_iterator = + std::max_element(accumulator_array.begin(), accumulator_array.end()); + gil::point_t candidates[] = { + {expected_index - 1, expected_index - 1}, {expected_index, expected_index - 1}, + {expected_index + 1, expected_index - 1}, {expected_index - 1, expected_index}, + {expected_index, expected_index}, {expected_index + 1, expected_index}, + {expected_index - 1, expected_index + 1}, {expected_index, expected_index + 1}, + {expected_index + 1, expected_index + 1}}; + bool match_found = false; + for (std::size_t i = 0; i < 9; ++i) + { + if (*max_element_iterator == accumulator_array(candidates[i])) + { + match_found = true; + break; + } + } + BOOST_TEST(match_found); +} + +int main() +{ + for (std::ptrdiff_t height = 1; height < width; ++height) + { + for (std::ptrdiff_t intercept = 1; intercept < width - height; ++intercept) + { + hough_line_test(height, intercept); + } + } + return boost::report_errors(); +} diff --git a/test/extension/image_processing/hough_parameter.cpp b/test/extension/image_processing/hough_parameter.cpp new file mode 100644 index 0000000000..9fd4b4a95f --- /dev/null +++ b/test/extension/image_processing/hough_parameter.cpp @@ -0,0 +1,78 @@ +// Boost.GIL (Generic Image Library) - tests +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// +#include +#include + +namespace gil = boost::gil; + +void from_step_count_test() +{ + const double middle_point = 0.5; + const std::size_t step_count = 5; + const double neighborhood = 1.0; + auto param = + gil::hough_parameter::from_step_count(middle_point, neighborhood, step_count); + BOOST_TEST(param.start_point == middle_point - neighborhood); + BOOST_TEST(param.step_count == step_count * 2 + 1); + BOOST_TEST(param.step_size == neighborhood / step_count); + + bool middle_point_occured = false; + for (std::size_t i = 0; i < param.step_count; ++i) + { + auto current = param.start_point + param.step_size * i; + if (current == middle_point) + { + middle_point_occured = true; + break; + } + } + BOOST_TEST(middle_point_occured); +} + +void from_step_size_test(const double middle_point, const double step_size, + const double neighborhood) +{ + const std::size_t expected_step_count = + static_cast(neighborhood / step_size) * 2 + 1; + auto param = + gil::hough_parameter::from_step_size(middle_point, neighborhood, step_size); + BOOST_TEST(param.start_point == middle_point - step_size * std::floor(expected_step_count / 2)); + BOOST_TEST(param.step_count == expected_step_count); + BOOST_TEST(param.step_size == step_size); + + bool middle_point_occured = false; + for (std::size_t i = 0; i < param.step_count; ++i) + { + auto current = param.start_point + param.step_size * i; + if (current == middle_point) + { + middle_point_occured = true; + break; + } + } + BOOST_TEST(middle_point_occured); +} + +void minimum_step_angle_test(const std::ptrdiff_t width, const std::ptrdiff_t height) +{ + const auto bigger_dim = width > height ? width : height; + const double expected_angle = std::atan2(1.0, bigger_dim); + BOOST_TEST(expected_angle == gil::minimum_angle_step({width, height})); +} + +int main() +{ + from_step_count_test(); + // ideal case + from_step_size_test(2.0, 0.25, 1.0); + from_step_size_test(5.0, 2, 5.0); + minimum_step_angle_test(1200, 800); + minimum_step_angle_test(800, 1200); + return boost::report_errors(); +} diff --git a/test/extension/io/bmp/bmp_make.cpp b/test/extension/io/bmp/bmp_make.cpp index 26be077261..1746264398 100644 --- a/test/extension/io/bmp/bmp_make.cpp +++ b/test/extension/io/bmp/bmp_make.cpp @@ -5,8 +5,6 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -#define BOOST_FILESYSTEM_VERSION 3 #include #include @@ -22,7 +20,7 @@ #include "paths.hpp" -namespace fs = boost::filesystem; +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; void test_make_reader_backend() diff --git a/test/extension/io/bmp/bmp_old_test.cpp b/test/extension/io/bmp/bmp_old_test.cpp index bd7fd27d8e..31575de58d 100644 --- a/test/extension/io/bmp/bmp_old_test.cpp +++ b/test/extension/io/bmp/bmp_old_test.cpp @@ -63,15 +63,13 @@ void test_old_write_view() void test_old_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::rgba8_image_t - >; - - gil::any_image image; + > image; gil::bmp_read_image(bmp_filename.c_str(), image); gil::bmp_write_view(bmp_out + "old_dynamic_image_test.bmp", gil::view(image)); diff --git a/test/extension/io/bmp/bmp_test.cpp b/test/extension/io/bmp/bmp_test.cpp index 6b1385464c..8f18f3e66a 100644 --- a/test/extension/io/bmp/bmp_test.cpp +++ b/test/extension/io/bmp/bmp_test.cpp @@ -5,8 +5,6 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT -#define BOOST_FILESYSTEM_VERSION 3 #include #include @@ -21,7 +19,7 @@ #include "paths.hpp" #include "subimage_test.hpp" -namespace fs = boost::filesystem; +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; namespace mp11 = boost::mp11; @@ -247,15 +245,13 @@ void test_subimage() void test_dynamic_image() { - using my_img_types = mp11::mp_list - < - gil::gray8_image_t, - gil::gray16_image_t, - gil::rgb8_image_t, - gil::rgba8_image_t - >; - - gil::any_image image; + gil::any_image + < + gil::gray8_image_t, + gil::gray16_image_t, + gil::rgb8_image_t, + gil::rgba8_image_t + > image; gil::read_image(bmp_filename.c_str(), image, gil::bmp_tag()); gil::write_view(bmp_out + "dynamic_image_test.bmp", gil::view(image), gil::bmp_tag()); diff --git a/test/extension/io/jpeg/jpeg_old_test.cpp b/test/extension/io/jpeg/jpeg_old_test.cpp index 241b51bc6a..0933bda0b6 100644 --- a/test/extension/io/jpeg/jpeg_old_test.cpp +++ b/test/extension/io/jpeg/jpeg_old_test.cpp @@ -67,15 +67,13 @@ void test_old_write_view() void test_old_dynamic_image() { - using my_img_types = mp11::mp_list - < - gil::gray8_image_t, - gil::gray16_image_t, - gil::rgb8_image_t, - gil::rgba8_image_t - >; - - gil::any_image image; + gil::any_image + < + gil::gray8_image_t, + gil::gray16_image_t, + gil::rgb8_image_t, + gil::rgba8_image_t + > image; gil::jpeg_read_image(jpeg_filename.c_str(), image); #ifdef BOOST_GIL_IO_TEST_ALLOW_WRITING_IMAGES diff --git a/test/extension/io/jpeg/jpeg_test.cpp b/test/extension/io/jpeg/jpeg_test.cpp index be7628180e..b961bd19a9 100644 --- a/test/extension/io/jpeg/jpeg_test.cpp +++ b/test/extension/io/jpeg/jpeg_test.cpp @@ -5,8 +5,6 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_FILESYSTEM_VERSION 3 -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #include #include @@ -21,7 +19,7 @@ #include "paths.hpp" #include "subimage_test.hpp" -namespace fs = boost::filesystem; +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; namespace mp11 = boost::mp11; @@ -237,15 +235,13 @@ void test_subimage() void test_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::rgba8_image_t - >; - - gil::any_image image; + > image; gil::read_image(jpeg_filename.c_str(), image, gil::jpeg_tag()); #ifdef BOOST_GIL_IO_TEST_ALLOW_WRITING_IMAGES diff --git a/test/extension/io/paths.hpp b/test/extension/io/paths.hpp index 9726afcccf..cf983240d4 100644 --- a/test/extension/io/paths.hpp +++ b/test/extension/io/paths.hpp @@ -8,33 +8,13 @@ #ifndef BOOST_GIL_TEST_EXTENSION_IO_PATHS_HPP #define BOOST_GIL_TEST_EXTENSION_IO_PATHS_HPP -// Disable warning: conversion to 'std::atomic::__integral_type {aka int}' from 'long int' may alter its value -#if defined(BOOST_CLANG) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wshorten-64-to-32" -#endif - -#if defined(BOOST_GCC) && (BOOST_GCC >= 40900) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconversion" -#endif - -#define BOOST_FILESYSTEM_VERSION 3 -#include - -#if defined(BOOST_CLANG) -#pragma clang diagnostic pop -#endif - -#if defined(BOOST_GCC) && (BOOST_GCC >= 40900) -#pragma GCC diagnostic pop -#endif +#include #include // `base` holds the path to ../.., i.e. the directory containing `images` static const std::string base = - (boost::filesystem::absolute(boost::filesystem::path(__FILE__)).parent_path().string()) + "/"; + (boost::gil::detail::filesystem::absolute(boost::gil::detail::filesystem::path(__FILE__)).parent_path().string()) + "/"; static const std::string bmp_in = base + "images/bmp/"; static const std::string bmp_out = base + "output/"; diff --git a/test/extension/io/png/png_file_format_test.cpp b/test/extension/io/png/png_file_format_test.cpp index cda7fffdeb..f5713ca094 100644 --- a/test/extension/io/png/png_file_format_test.cpp +++ b/test/extension/io/png/png_file_format_test.cpp @@ -5,9 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #define BOOST_GIL_IO_ENABLE_GRAY_ALPHA -#define BOOST_FILESYSTEM_VERSION 3 #include #include @@ -15,8 +13,8 @@ #include "paths.hpp" +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; -namespace fs = boost::filesystem; #ifdef BOOST_GIL_IO_USE_PNG_TEST_SUITE_IMAGES diff --git a/test/extension/io/png/png_old_test.cpp b/test/extension/io/png/png_old_test.cpp index 869f749e09..fc5de954b9 100644 --- a/test/extension/io/png/png_old_test.cpp +++ b/test/extension/io/png/png_old_test.cpp @@ -67,14 +67,13 @@ void test_old_write_view() void test_old_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::rgba8_image_t - >; - gil::any_image image; + > image; gil::png_read_image(png_filename.c_str(), image); diff --git a/test/extension/io/png/png_read_test.cpp b/test/extension/io/png/png_read_test.cpp index 5b51cf5480..0217aa9d67 100644 --- a/test/extension/io/png/png_read_test.cpp +++ b/test/extension/io/png/png_read_test.cpp @@ -5,9 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #define BOOST_GIL_IO_ENABLE_GRAY_ALPHA -#define BOOST_FILESYSTEM_VERSION 3 #include #include @@ -22,8 +20,8 @@ #include "scanline_read_test.hpp" #include "test_utility_output_stream.hpp" +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; -namespace fs = boost::filesystem; using gray_alpha8_pixel_t = gil::pixel; using gray_alpha8c_pixel_t = gil::pixel const; diff --git a/test/extension/io/png/png_test.cpp b/test/extension/io/png/png_test.cpp index 937c4bbe09..de902c56f7 100644 --- a/test/extension/io/png/png_test.cpp +++ b/test/extension/io/png/png_test.cpp @@ -265,14 +265,13 @@ void test_subimage() void test_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::rgba8_image_t - >; - gil::any_image image; + > image; gil::read_image(png_filename.c_str(), image, gil::png_tag()); diff --git a/test/extension/io/png/png_write_test.cpp b/test/extension/io/png/png_write_test.cpp index 85f1541afa..da4e5d66e8 100644 --- a/test/extension/io/png/png_write_test.cpp +++ b/test/extension/io/png/png_write_test.cpp @@ -5,9 +5,7 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #define BOOST_GIL_IO_ENABLE_GRAY_ALPHA -#define BOOST_FILESYSTEM_VERSION 3 #include #include diff --git a/test/extension/io/pnm/pnm_old_test.cpp b/test/extension/io/pnm/pnm_old_test.cpp index 21e07fe3e3..ae10b3aec9 100644 --- a/test/extension/io/pnm/pnm_old_test.cpp +++ b/test/extension/io/pnm/pnm_old_test.cpp @@ -66,10 +66,13 @@ void test_old_write_view() void test_old_dynamic_image() { - using my_img_types = - mp11::mp_list; - - gil::any_image image; + gil::any_image + < + gil::gray8_image_t, + gil::gray16_image_t, + gil::rgb8_image_t, + gil::gray1_image_t + > image; gil::pnm_read_image(pnm_filename.c_str(), image); diff --git a/test/extension/io/pnm/pnm_test.cpp b/test/extension/io/pnm/pnm_test.cpp index 2e398343ea..7ccd7d1bad 100644 --- a/test/extension/io/pnm/pnm_test.cpp +++ b/test/extension/io/pnm/pnm_test.cpp @@ -204,15 +204,13 @@ void test_subimage() void test_dynamic_image_test() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::gray1_image_t - >; - - gil::any_image image; + > image; gil::read_image(pnm_filename.c_str(), image, gil::pnm_tag()); diff --git a/test/extension/io/raw/raw_test.cpp b/test/extension/io/raw/raw_test.cpp index 029eef07fe..71ce6037d4 100644 --- a/test/extension/io/raw/raw_test.cpp +++ b/test/extension/io/raw/raw_test.cpp @@ -5,8 +5,6 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_FILESYSTEM_VERSION 3 -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #include #include @@ -19,7 +17,7 @@ #include "paths.hpp" #include "subimage_test.hpp" -namespace fs = boost::filesystem; +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; namespace mp11 = boost::mp11; @@ -97,16 +95,14 @@ void test_subimage() void test_dynamic_image() { - using my_img_types = mp11::mp_list - < - gil::gray8_image_t, - gil::gray16_image_t, - gil::rgb8_image_t, - gil::rgba8_image_t - >; - - gil::any_image image; - gil::read_image(raw_filename.c_str(), image, gil::raw_tag()); + gil::any_image + < + gil::gray8_image_t, + gil::gray16_image_t, + gil::rgb8_image_t, + gil::rgba8_image_t + > image; + gil::read_image(raw_filename.c_str(), image, gil::raw_tag()); } int main() diff --git a/test/extension/io/simple_all_formats.cpp b/test/extension/io/simple_all_formats.cpp index 2acdb39fe4..1b110dc143 100644 --- a/test/extension/io/simple_all_formats.cpp +++ b/test/extension/io/simple_all_formats.cpp @@ -16,8 +16,8 @@ #include "paths.hpp" +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; -namespace fs = boost::filesystem; // Test will include all format's headers and load and write some images. // This test is more of a compilation test. @@ -74,6 +74,18 @@ void test_tiff() gil::write_view(tiff_out + "simple_all_format.tif", gil::view(img), gil::tiff_tag()); } +void test_tiff_tiled() +{ + gil::rgba8_image_t img; + gil::read_image(tiff_filename, img, gil::tiff_tag()); + + fs::create_directories(fs::path(tiff_out)); + gil::image_write_info info; + info._is_tiled = true; + info._tile_width = info._tile_length = 64; // must be multiples of 16 + gil::write_view(tiff_out + "simple_all_format.tif", gil::view(img), info); +} + int main(int argc, char* argv[]) { try @@ -85,6 +97,7 @@ int main(int argc, char* argv[]) // TODO: test_raw() test_targa(); test_tiff(); + test_tiff_tiled(); } catch (std::exception const& e) { diff --git a/test/extension/io/targa/targa_old_test.cpp b/test/extension/io/targa/targa_old_test.cpp index cfb7aa9a78..d92f011dcd 100644 --- a/test/extension/io/targa/targa_old_test.cpp +++ b/test/extension/io/targa/targa_old_test.cpp @@ -64,15 +64,14 @@ void test_old_write_view() void test_old_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::rgba8_image_t - >; + > image; - gil::any_image image; gil::targa_read_image(targa_filename.c_str(), image); targa_write_view(targa_out + "old_dynamic_image_test.tga", gil::view(image)); diff --git a/test/extension/io/targa/targa_test.cpp b/test/extension/io/targa/targa_test.cpp index bb1d989350..accc1f7c6a 100644 --- a/test/extension/io/targa/targa_test.cpp +++ b/test/extension/io/targa/targa_test.cpp @@ -5,7 +5,6 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #include #include @@ -21,7 +20,7 @@ #include "subimage_test.hpp" #include "test_utility_output_stream.hpp" -namespace fs = boost::filesystem; +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; namespace mp11 = boost::mp11; @@ -254,15 +253,14 @@ void test_subimage() void test_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, gil::rgba8_image_t - >; + > image; - gil::any_image image; gil::read_image(targa_filename.c_str(), image, gil::targa_tag()); gil::write_view(targa_out + "dynamic_image_test.tga", gil::view(image), gil::targa_tag()); } diff --git a/test/extension/io/tiff/tiff_old_test.cpp b/test/extension/io/tiff/tiff_old_test.cpp index 326a75c18c..3d1c42bb1e 100644 --- a/test/extension/io/tiff/tiff_old_test.cpp +++ b/test/extension/io/tiff/tiff_old_test.cpp @@ -60,14 +60,13 @@ void test_old_read_and_convert_view() void test_old_dynamic_image() { - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgba8_image_t, gil::gray1_image_t - >; - gil::any_image image; + > image; gil::tiff_read_image(tiff_filename.c_str(), image); diff --git a/test/extension/io/tiff/tiff_test.cpp b/test/extension/io/tiff/tiff_test.cpp index f0c9d9972c..f62492d1ad 100644 --- a/test/extension/io/tiff/tiff_test.cpp +++ b/test/extension/io/tiff/tiff_test.cpp @@ -5,8 +5,6 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#define BOOST_FILESYSTEM_VERSION 3 -#define BOOST_GIL_IO_ADD_FS_PATH_SUPPORT #include #include @@ -21,7 +19,7 @@ #include "paths.hpp" #include "subimage_test.hpp" -namespace fs = boost::filesystem; +namespace fs = boost::gil::detail::filesystem; namespace gil = boost::gil; namespace mp11 = boost::mp11; @@ -267,15 +265,13 @@ void test_dynamic_image() { // FIXME: This test has been disabled for now because of compilation issues with MSVC10. - using my_img_types = mp11::mp_list + gil::any_image < gil::gray8_image_t, gil::gray16_image_t, gil::rgb8_image_t, - gil::rgba8_image_t, - gil::gray1_image_t - >; - gil::any_image image; + gil::rgba8_image_t + > image; gil::read_image(tiff_filename.c_str(), image, gil::tiff_tag()); diff --git a/test/extension/numeric/CMakeLists.txt b/test/extension/numeric/CMakeLists.txt index 4702c52c87..dccc0cc5c7 100644 --- a/test/extension/numeric/CMakeLists.txt +++ b/test/extension/numeric/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright (c) 2017 Mateusz Loskot +# Copyright (c) 2019-2021 Pranam Lashkari # All rights reserved. # # Distributed under the Boost Software License, Version 1.0. @@ -9,17 +10,7 @@ message(STATUS "Boost.GIL: Configuring tests in test/extension/numeric") foreach(_name - channel_numeric_operations - convolve - convolve_2d - convolve_cols - convolve_rows - extend_boundary - kernel - kernel_fixed matrix3x2 - pixel_numeric_operations - pixel_numeric_operations_float resample) set(_test t_ext_numeric_${_name}) set(_target test_ext_numeric_${_name}) diff --git a/test/extension/numeric/Jamfile b/test/extension/numeric/Jamfile index 9662357c45..d538e7ab26 100644 --- a/test/extension/numeric/Jamfile +++ b/test/extension/numeric/Jamfile @@ -2,6 +2,7 @@ # # Copyright (c) 2013 Christian Henning # Copyright (c) 2019 Mateusz Loskot +# Copyright (c) 2021 Pranam Lashkari # # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or @@ -11,18 +12,8 @@ import testing ; alias headers : [ generate_self_contained_headers extension/numeric ] ; -compile-fail kernel_1d_fixed_even_size_fail.cpp ; -run channel_numeric_operations.cpp ; -run convolve.cpp ; -run convolve_2d.cpp ; -run convolve_cols.cpp ; -run convolve_rows.cpp ; -run extend_boundary.cpp ; -run kernel.cpp ; -run kernel_fixed.cpp ; + run matrix3x2.cpp ; -run pixel_numeric_operations.cpp ; -run pixel_numeric_operations_float.cpp ; run resample.cpp ; diff --git a/test/extension/numeric/convolve_2d.cpp b/test/extension/numeric/convolve_2d.cpp deleted file mode 100644 index fbfce5f96c..0000000000 --- a/test/extension/numeric/convolve_2d.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright 2019 Miral Shah -// -// Use, modification and distribution are subject to 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) -// -#include -#include - -#include - -#include - -namespace gil = boost::gil; - -std::uint8_t img[] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 255, 0, 0, 0, 255, 0, 0, - 0, 0, 0, 255, 0, 255, 0, 0, 0, - 0, 0, 0, 0, 255, 0, 0, 0, 0, - 0, 0, 0, 255, 0, 255, 0, 0, 0, - 0, 0, 255, 0, 0, 0, 255, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -std::uint8_t output[] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 28, 28, 28, 0, 28, 28, 28, 0, - 0, 28, 56, 56, 56, 56, 56, 28, 0, - 0, 28, 56, 85, 85, 85, 56, 28, 0, - 0, 0, 56, 85, 141, 85, 56, 0, 0, - 0, 28, 56, 85, 85, 85, 56, 28, 0, - 0, 28, 56, 56, 56, 56, 56, 28, 0, - 0, 28, 28, 28, 0, 28, 28, 28, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -void test_convolve_2d_with_normalized_mean_filter() -{ - gil::gray8c_view_t src_view = - gil::interleaved_view(9, 9, reinterpret_cast(img), 9); - - gil::image temp_img(src_view.width(), src_view.height()); - typename gil::image::view_t temp_view = view(temp_img); - gil::gray8_view_t dst_view(temp_view); - - std::vector v(9, 1.0f / 9.0f); - gil::detail::kernel_2d kernel(v.begin(), v.size(), 1, 1); - - gil::detail::convolve_2d(src_view, kernel, dst_view); - - gil::gray8c_view_t out_view = - gil::interleaved_view(9, 9, reinterpret_cast(output), 9); - - BOOST_TEST(gil::equal_pixels(out_view, dst_view)); -} - -int main() -{ - test_convolve_2d_with_normalized_mean_filter(); - - return ::boost::report_errors(); -} diff --git a/test/extension/numeric/matrix3x2.cpp b/test/extension/numeric/matrix3x2.cpp index f1c19cd046..afc4048cc6 100644 --- a/test/extension/numeric/matrix3x2.cpp +++ b/test/extension/numeric/matrix3x2.cpp @@ -12,13 +12,12 @@ #include +#include "test_utility_with_tolerance.hpp" + #include namespace gil = boost::gil; -// FIXME: Remove when https://github.com/boostorg/core/issues/38 happens -#define BOOST_GIL_TEST_IS_CLOSE(a, b, epsilon) BOOST_TEST_LT(std::fabs((a) - (b)), (epsilon)) - namespace { constexpr double HALF_PI = 1.57079632679489661923; } @@ -123,10 +122,10 @@ void test_matrix3x2_vector_multiplication() void test_matrix3x2_get_rotate() { auto m1 = gil::matrix3x2::get_rotate(HALF_PI); - BOOST_GIL_TEST_IS_CLOSE(m1.a, std::cos(HALF_PI), 0.03); + BOOST_TEST_WITH(m1.a, std::cos(HALF_PI), gil::test::utility::with_tolerance(0.03)); BOOST_TEST_EQ(m1.b, 1); BOOST_TEST_EQ(m1.c, -1); - BOOST_GIL_TEST_IS_CLOSE(m1.d, std::cos(HALF_PI), 0.03); + BOOST_TEST_WITH(m1.d, std::cos(HALF_PI), gil::test::utility::with_tolerance(0.03)); BOOST_TEST_EQ(m1.e, 0); BOOST_TEST_EQ(m1.f, 0); } @@ -173,6 +172,38 @@ void test_matrix3x2_transform() BOOST_TEST_EQ(v2.y, 4); } +void test_matrix3x2_inverse() +{ + using matrix_t = gil::matrix3x2; + using point_t = gil::point; + + matrix_t mo = matrix_t::get_translate(0, 16); + matrix_t mb = matrix_t::get_rotate(HALF_PI); + auto m = mo * mb; + + point_t p(10, 10); + point_t q = gil::transform(inverse(m), p); + point_t p2 = gil::transform(m, q); + + BOOST_TEST_WITH(p.x, p2.x, gil::test::utility::with_tolerance(1e-9)); + BOOST_TEST_WITH(p.y, p2.y, gil::test::utility::with_tolerance(1e-9)); +} + +void test_matrix3x2_center_rotate() +{ + gil::point dimension(100.0,100.0); + gil::matrix3x2 m1; + + m1 = gil::center_rotate(dimension, HALF_PI); + + BOOST_TEST_WITH(m1.a , std::cos(HALF_PI) , gil::test::utility::with_tolerance(1e-9)); + BOOST_TEST_EQ (m1.b , 1); + BOOST_TEST_EQ (m1.c , -1); + BOOST_TEST_WITH(m1.d , std::cos(HALF_PI) , gil::test::utility::with_tolerance(1e-9)); + BOOST_TEST_EQ (m1.e , 100); + BOOST_TEST_WITH(m1.f , std::cos(HALF_PI) , gil::test::utility::with_tolerance(1e-9)); +} + int main() { test_matrix3x2_default_constructor(); @@ -186,6 +217,8 @@ int main() test_matrix3x2_get_scale(); test_matrix3x2_get_translate(); test_matrix3x2_transform(); + test_matrix3x2_inverse(); + test_matrix3x2_center_rotate(); return ::boost::report_errors(); } diff --git a/test/extension/rasterization/CMakeLists.txt b/test/extension/rasterization/CMakeLists.txt new file mode 100644 index 0000000000..522e743ddf --- /dev/null +++ b/test/extension/rasterization/CMakeLists.txt @@ -0,0 +1,28 @@ +# Boost.GIL (Generic Image Library) - tests +# +# Copyright 2020 Olzhas Zhumabek +# +# Use, modification and distribution are subject to 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) +# +foreach(_name + line + circle + ellipse) + set(_test t_core_rasterization_${_name}) + set(_target test_core_rasterization_${_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) +endforeach() diff --git a/test/extension/rasterization/Jamfile b/test/extension/rasterization/Jamfile new file mode 100644 index 0000000000..71f8855d01 --- /dev/null +++ b/test/extension/rasterization/Jamfile @@ -0,0 +1,14 @@ +# Boost.GIL (Generic Image Library) - tests +# +# Copyright 2020 Olzhas Zhumabek +# +# Use, modification and distribution are subject to 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) +# +import testing ; + +run apply_rasterizer.cpp ; +run line.cpp ; +run circle.cpp ; +run ellipse.cpp ; diff --git a/test/extension/rasterization/apply_rasterizer.cpp b/test/extension/rasterization/apply_rasterizer.cpp new file mode 100644 index 0000000000..589ec1c718 --- /dev/null +++ b/test/extension/rasterization/apply_rasterizer.cpp @@ -0,0 +1,35 @@ +// +// Copyright 2022 Marco Langer +// +// Use, modification and distribution are subject to 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) +// + +#include +#include +#include +#include + +#include + +namespace gil = boost::gil; + +template +void test_apply_rasterizers(Rasterizer const& rasterizer) +{ + gil::rgb8_image_t image(200, 200); + gil::rgb8_pixel_t pixel{255, 0, 0}; + + apply_rasterizer(view(image), rasterizer, pixel); +} + +int main() +{ + test_apply_rasterizers(gil::midpoint_circle_rasterizer{{50, 50}, 30}); + test_apply_rasterizers(gil::trigonometric_circle_rasterizer{{50, 50}, 30}); + test_apply_rasterizers(gil::midpoint_ellipse_rasterizer{{50, 50}, {30, 20}}); + test_apply_rasterizers(gil::bresenham_line_rasterizer{{50, 50}, {30, 20}}); + + return boost::report_errors(); +} diff --git a/test/extension/rasterization/circle.cpp b/test/extension/rasterization/circle.cpp new file mode 100644 index 0000000000..99b3133656 --- /dev/null +++ b/test/extension/rasterization/circle.cpp @@ -0,0 +1,79 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include +#include + +#include + +#include +#include +#include + +namespace gil = boost::gil; + +template +void test_rasterizer_follows_equation(Rasterizer rasterizer) +{ + std::ptrdiff_t const radius = rasterizer.radius; + std::vector circle_points(rasterizer.point_count()); + std::ptrdiff_t const r_squared = radius * radius; + rasterizer(circle_points.begin()); + std::vector first_octant(rasterizer.point_count() / 8); + + for (std::size_t i = 0, octant_index = 0; i < circle_points.size(); i += 8, ++octant_index) + { + first_octant[octant_index] = circle_points[i]; + } + + for (auto const& point : first_octant) + { + double const y_exact = std::sqrt(r_squared - point.x * point.x); + std::ptrdiff_t lower_result = static_cast(std::floor(y_exact)); + std::ptrdiff_t upper_result = static_cast(std::ceil(y_exact)); + BOOST_TEST(point.y >= lower_result && point.y <= upper_result); + } +} + +template +void test_connectivity(Rasterizer rasterizer) +{ + std::vector circle_points(rasterizer.point_count()); + rasterizer(circle_points.begin()); + for (std::size_t i = 0; i < 8; ++i) + { + std::vector octant(circle_points.size() / 8); + for (std::size_t octant_index = i, index = 0; octant_index < circle_points.size(); + octant_index += 8, ++index) + { + octant[index] = circle_points[octant_index]; + } + + for (std::size_t index = 1; index < octant.size(); ++index) + { + const auto diff_x = std::abs(octant[index].x - octant[index - 1].x); + const auto diff_y = std::abs(octant[index].y - octant[index - 1].y); + BOOST_TEST_LE(diff_x, 1); + BOOST_TEST_LE(diff_y, 1); + } + } +} + +int main() +{ + for (std::ptrdiff_t radius = 5; radius <= 512; ++radius) + { + test_rasterizer_follows_equation(gil::midpoint_circle_rasterizer{{0, 0}, radius}); + // TODO: find out a new testing procedure for trigonometric rasterizer + // test_equation_following(radius, gil::trigonometric_circle_rasterizer{}); + test_connectivity(gil::midpoint_circle_rasterizer{{radius, radius}, radius}); + test_connectivity(gil::trigonometric_circle_rasterizer{{radius, radius}, radius}); + } + + return boost::report_errors(); +} diff --git a/test/extension/rasterization/ellipse.cpp b/test/extension/rasterization/ellipse.cpp new file mode 100644 index 0000000000..837301733f --- /dev/null +++ b/test/extension/rasterization/ellipse.cpp @@ -0,0 +1,87 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// Use, modification and distribution are subject to 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) +// + +#include +#include + +#include + +#include +#include +#include + +namespace gil = boost::gil; + +// This function utilizes the fact that sum of distances of a point on an ellipse from its foci +// is equal to the length of major axis of the ellipse. +// Parameters b and a represent half of lengths of vertical and horizontal axis respectively. +void test_rasterizer_follows_equation( + std::vector const& trajectory_points, float a, float b) +{ + float focus_x, focus_y; + if (a > b) // For horizontal ellipse + { + focus_x = a * std::sqrt(1 - b * b / (a * a)); + focus_y = 0; + } + else // For vertical ellipse + { + focus_x = 0; + focus_y = b * std::sqrt(1 - a * a / (b * b)); + } + + for (auto trajectory_point : trajectory_points) + { + // To suppress conversion warnings from compiler + gil::point point { + static_cast(trajectory_point[0]), static_cast(trajectory_point[1])}; + + double dist_sum = std::sqrt(std::pow(focus_x - point[0], 2) + + std::pow(focus_y - point[1], 2)) + std::sqrt(std::pow( - focus_x - point[0], 2) + + std::pow( - focus_y - point[1], 2)); + if (a > b) + { + BOOST_TEST(std::abs(dist_sum - 2 * a) < 1); + } + else + { + BOOST_TEST(std::abs(dist_sum - 2 * b) < 1); + } + } +} + +// This function verifies that the difference between x co-ordinates and y co-ordinates for two +// successive trajectory points is less than or equal to 1. This ensures that the curve is connected. +void test_connectivity(std::vector const& points) +{ + for (std::size_t i = 1; i < points.size(); ++i) + { + std::ptrdiff_t diff_x = points[i][0] - points[i - 1][0]; + std::ptrdiff_t diff_y = points[i][1] - points[i - 1][1]; + BOOST_TEST_LE(diff_x, 1); + BOOST_TEST_LE(diff_y, 1); + } +} + +// We verify all test cases for the portion of ellipse in first quadrant, since all other portions +// can be constructed with simple reflection, they tend to be correct if first quadrant is verified. +int main() +{ + for (float a = 1; a < 101; ++a) + { + for (float b = 1; b < 101; ++b) + { + auto rasterizer = gil::midpoint_ellipse_rasterizer{{}, + {static_cast(a), static_cast(b)}}; + std::vector points = rasterizer.obtain_trajectory(); + test_rasterizer_follows_equation(points, a, b); + test_connectivity(points); + } + } + return boost::report_errors(); +} diff --git a/test/extension/rasterization/line.cpp b/test/extension/rasterization/line.cpp new file mode 100644 index 0000000000..dd48393ecc --- /dev/null +++ b/test/extension/rasterization/line.cpp @@ -0,0 +1,135 @@ +// +// Copyright 2020 Olzhas Zhumabek +// +// Use, modification and distribution are subject to 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) +// + +#include "core/point/test_fixture.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace gil = boost::gil; + +using line_type = std::vector; + +struct endpoints +{ + gil::point_t start; + gil::point_t end; +}; + +endpoints create_endpoints(std::mt19937& twister, + std::uniform_int_distribution& distr) +{ + gil::point_t start{distr(twister), distr(twister)}; + gil::point_t end{distr(twister), distr(twister)}; + return {start, end}; +} + +line_type create_line(endpoints points) +{ + gil::bresenham_line_rasterizer rasterizer(points.start, points.end); + line_type forward_line(rasterizer.point_count()); + rasterizer(forward_line.begin()); + return forward_line; +} + +void test_start_end(line_type const& line_points, endpoints points) +{ + BOOST_TEST_EQ(line_points.front(), points.start); + BOOST_TEST_EQ(line_points.back(), points.end); +} + +// Look at TODO below +// void test_two_way_equivalence(const line_type& forward, line_type backward) +// { +// std::reverse(backward.begin(), backward.end()); +// BOOST_TEST_ALL_EQ(forward.begin(), forward.end(), backward.begin(), backward.end()); +// } + +void test_connectivity(line_type const& line_points) +{ + for (std::size_t i = 1; i < line_points.size(); ++i) + { + const auto x_diff = std::abs(line_points[i].x - line_points[i - 1].x); + const auto y_diff = std::abs(line_points[i].y - line_points[i - 1].y); + BOOST_TEST_LE(x_diff, 1); + BOOST_TEST_LE(y_diff, 1); + } +} + +void test_bresenham_rasterizer_follows_equation(line_type line_points) +{ + auto start = line_points.front(); + auto end = line_points.back(); + + auto width = std::abs(end.x - start.x) + 1; + auto height = std::abs(end.y - start.y) + 1; + if (width < height) + { + std::swap(width, height); + std::transform(line_points.begin(), line_points.end(), line_points.begin(), + [](gil::point_t p) + { + return gil::point_t{p.y, p.x}; + }); + // update start and end + start = line_points.front(); + end = line_points.back(); + } + const double sign = [start, end]() + { + auto const width_sign = end.x < start.x; + auto const height_sign = end.y < start.y; + auto const slope_sign = width_sign != height_sign; + return slope_sign ? -1 : 1; + }(); + const double slope = static_cast(height) / static_cast(width); + const double intercept = + static_cast(start.y) - sign * slope * static_cast(start.x); + for (const auto& point : line_points) + { + double const expected_y = sign * slope * static_cast(point.x) + intercept; + auto const difference = + std::abs(point.y - static_cast(std::round(expected_y))); + BOOST_TEST_LE(difference, static_cast(slope + 1)); + } +} + +int main() +{ + const std::ptrdiff_t size = 256; + for (std::size_t seed = 0; seed <= 100; ++seed) + { + std::mt19937 twister(seed); + std::uniform_int_distribution distr(0, size - 1); + const std::size_t sample_count = 100; + for (std::size_t sample_index = 0; sample_index < sample_count; ++sample_index) + { + auto endpoints = create_endpoints(twister, distr); + auto forward_line = create_line(endpoints); + test_start_end(forward_line, endpoints); + // TODO: figure out if forward/backward equivalence is possible to provide + // auto backward_line = create_line({endpoints.end, endpoints.start}); + // test_two_way_equivalence(forward_line, backward_line); + test_connectivity(forward_line); + // test_connectivity(backward_line); + test_bresenham_rasterizer_follows_equation(forward_line); + // test_bresenham_rasterizer_follows_equation(backward_line); + } + } + + return boost::report_errors(); +} diff --git a/test/extension/toolbox/CMakeLists.txt b/test/extension/toolbox/CMakeLists.txt index 70df68db42..1fdbcb4001 100644 --- a/test/extension/toolbox/CMakeLists.txt +++ b/test/extension/toolbox/CMakeLists.txt @@ -29,7 +29,7 @@ foreach(_name set(_target test_ext_toolbox_${_name}) add_executable(${_target} "") - target_sources(${_target} PRIVATE ${_name}) + target_sources(${_target} PRIVATE "${_name}.cpp") target_link_libraries(${_target} PRIVATE gil_compile_options @@ -50,7 +50,7 @@ foreach(_name set(_target test_ext_toolbox_${_name}) add_executable(${_target} "") - target_sources(${_target} PRIVATE ${_name}) + target_sources(${_target} PRIVATE "${_name}.cpp") target_link_libraries(${_target} PRIVATE gil_compile_options diff --git a/test/extension/toolbox/Jamfile b/test/extension/toolbox/Jamfile index a68af63f38..a2d4980c85 100644 --- a/test/extension/toolbox/Jamfile +++ b/test/extension/toolbox/Jamfile @@ -1,7 +1,7 @@ # Boost.GIL (Generic Image Library) - Toolbox tests # # Copyright (c) 2012 Christian Henning -# Copyright (c) 2012-202 Mateusz Loskot +# Copyright (c) 2012-2020 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) @@ -25,6 +25,7 @@ run color_convert_hsl.cpp ; run color_convert_hsv.cpp ; run color_convert_lab.cpp ; run color_convert_luminance.cpp ; +run color_convert_rgb.cpp ; run color_convert_xyz.cpp ; run indexed_image.cpp ; diff --git a/test/extension/toolbox/color_convert_hsl.cpp b/test/extension/toolbox/color_convert_hsl.cpp index 33c5c19629..8ab46c84ab 100644 --- a/test/extension/toolbox/color_convert_hsl.cpp +++ b/test/extension/toolbox/color_convert_hsl.cpp @@ -12,23 +12,15 @@ #include #include +#include +#include #include "test_utility_output_stream.hpp" +#include "test_utility_with_tolerance.hpp" namespace gil = boost::gil; -void test_rgb_to_hsl() -{ - gil::rgb8_pixel_t p{128, 0, 128}; - gil::hsl32f_pixel_t h; - gil::color_convert(p, h); - - BOOST_TEST_GT(gil::get_color(h, gil::hsl_color_space::hue_t()), 0.8); // 0.83333331 - BOOST_TEST_EQ(gil::get_color(h, gil::hsl_color_space::saturation_t()), 1.0); // 1.00000000 - BOOST_TEST_GT(gil::get_color(h, gil::hsl_color_space::lightness_t()), 0.25); // 0.25098040 -} - -void test_hsl_to_rgb() +void test_basic_hsl_to_rgb() { gil::rgb8_pixel_t p(64, 0, 64); gil::hsl32f_pixel_t h; @@ -39,6 +31,43 @@ void test_hsl_to_rgb() BOOST_TEST_EQ(b, p); } +void test_colors_hsl_to_rgb() +{ + using color_t = std::tuple; + std::vector colors = { + color_t{0, 0, 0, 0, 0, 0}, // black + color_t{0, 0, 1, 255, 255, 255}, // white + color_t{0, 1, 0.5, 255, 0, 0}, // red + color_t{2 / 6.f, 1, 0.5, 0, 255, 0}, // lime + color_t{4 / 6.f, 1, 0.5, 0, 0, 255}, // blue + color_t{5 / 6.f, 1, 0.25, 128, 0, 128}, // purple + color_t{3 / 6.f, 1, 0.25, 0, 128, 128}, // teal + color_t{4 / 6.f, 1, 0.25, 0, 0, 128}, // navy + }; + + for (auto&& color : colors) + { + float h{0}, s{0}, l{0}; + std::uint8_t r{0}, g{0}, b{0}; + std::tie(h, s, l, r, g, b) = color; + + gil::hsl32f_pixel_t hsl(h, s, l); + gil::rgb8_pixel_t rgb; + gil::color_convert(hsl, rgb); + + float const abs_error = 1; + BOOST_TEST_WITH( + r, gil::get_color(rgb, gil::red_t()), + gil::test::utility::with_tolerance(abs_error)); + BOOST_TEST_WITH( + g, gil::get_color(rgb, gil::green_t()), + gil::test::utility::with_tolerance(abs_error)); + BOOST_TEST_WITH( + b, gil::get_color(rgb, gil::blue_t()), + gil::test::utility::with_tolerance(abs_error)); + } +} + void test_image_assign_hsl() { std::ptrdiff_t const w = 320; @@ -48,7 +77,7 @@ void test_image_assign_hsl() for (std::ptrdiff_t y = 0; y < h; y++) { gil::hsl32f_view_t::x_iterator hsl_x_it = view(hsl_img).row_begin(y); - float v = static_cast(h - y) / h; + float v = static_cast(h - y) / h; for (std::ptrdiff_t x = 0; x < w; x++) { float const hue = (x + 1.f) / w; @@ -59,29 +88,11 @@ void test_image_assign_hsl() } } -void test_copy_pixels_rgb_to_hsl() -{ - gil::rgb8_image_t rgb_img(320, 240); - gil::rgb8_pixel_t rgb_pix(64, 32, 64); - gil::fill_pixels(view(rgb_img), rgb_pix); - gil::hsl32f_image_t hsl_img(view(rgb_img).dimensions()); - gil::copy_pixels(gil::color_converted_view(view(rgb_img)), view(hsl_img)); - - auto view = gil::view(hsl_img); - for (auto it = view.begin(), end = view.end(); it != end; ++it) - { - gil::rgb8_pixel_t p; - gil::color_convert(*it, p); - BOOST_TEST_EQ(p, rgb_pix); - } -} - int main() { - test_rgb_to_hsl(); - test_hsl_to_rgb(); + test_basic_hsl_to_rgb(); + test_colors_hsl_to_rgb(); test_image_assign_hsl(); - test_copy_pixels_rgb_to_hsl(); return ::boost::report_errors(); } diff --git a/test/extension/toolbox/color_convert_rgb.cpp b/test/extension/toolbox/color_convert_rgb.cpp new file mode 100644 index 0000000000..90967a3612 --- /dev/null +++ b/test/extension/toolbox/color_convert_rgb.cpp @@ -0,0 +1,102 @@ +// +// Copyright 2013 Christian Henning +// Copyright 2020 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 +// +#include +#include + +#include + +#include +#include +#include + +#include "test_utility_output_stream.hpp" +#include "test_utility_with_tolerance.hpp" + +namespace gil = boost::gil; + +void test_basic_rgb_to_hsl() +{ + gil::rgb8_pixel_t p{128, 0, 128}; + gil::hsl32f_pixel_t h; + gil::color_convert(p, h); + + BOOST_TEST_GT(gil::get_color(h, gil::hsl_color_space::hue_t()), 0.8); // 0.83333331 + BOOST_TEST_EQ(gil::get_color(h, gil::hsl_color_space::saturation_t()), 1.0); // 1.00000000 + BOOST_TEST_GT(gil::get_color(h, gil::hsl_color_space::lightness_t()), 0.25); // 0.25098040 +} + +void test_colors_rgb_to_hsl() +{ + using color_t = std::tuple; + std::vector colors = { + color_t{0, 0, 0, 0, 0, 0}, // black + color_t{255, 255, 255, 0, 0, 1}, // white + color_t{255, 0, 0, 0, 1, 0.5}, // red + color_t{0, 255, 0, 2 / 6.f, 1, 0.5}, // lime + color_t{0, 0, 255, 4 / 6.f, 1, 0.5}, // blue + color_t{255, 255, 0, 1 / 6.f, 1, 0.5}, // yellow + color_t{0, 255, 255, 3 / 6.f, 1, 0.5}, // cyan + color_t{255, 0, 255, 5 / 6.f, 1, 0.5}, // magenta + color_t{191, 191, 191, 0, 0, 0.75}, // silver + color_t{128, 128, 128, 0, 0, 0.5}, // gray + color_t{128, 0, 0, 0, 1, 0.25}, // maroon + color_t{128, 128, 0, 1 / 6.f, 1, 0.25}, // olive + color_t{0, 128, 0, 2 / 6.f, 1, 0.25}, // green + color_t{128, 0, 128, 5 / 6.f, 1, 0.25}, // purple + color_t{0, 128, 128, 3 / 6.f, 1, 0.25}, // teal + color_t{0, 0, 128, 4 / 6.f, 1, 0.25}, // navy + }; + + for (auto&& color : colors) + { + std::uint8_t r{0}, g{0}, b{0}; + float h{0}, s{0}, l{0}; + std::tie(r, g, b, h, s, l) = color; + gil::rgb8_pixel_t rgb(r, g, b); + gil::hsl32f_pixel_t hsl; + gil::color_convert(rgb, hsl); + + float const abs_error = 0.002; + BOOST_TEST_WITH( + h, get_color(hsl, gil::hsl_color_space::hue_t()), + gil::test::utility::with_tolerance(abs_error)); + BOOST_TEST_WITH( + s, get_color(hsl, gil::hsl_color_space::saturation_t()), + gil::test::utility::with_tolerance(abs_error)); + BOOST_TEST_WITH( + l, get_color(hsl, gil::hsl_color_space::lightness_t()), + gil::test::utility::with_tolerance(abs_error)); + } +} + +void test_copy_pixels_rgb_to_hsl() +{ + gil::rgb8_image_t rgb_img(320, 240); + gil::rgb8_pixel_t rgb_pix(64, 32, 64); + gil::fill_pixels(view(rgb_img), rgb_pix); + gil::hsl32f_image_t hsl_img(view(rgb_img).dimensions()); + gil::copy_pixels(gil::color_converted_view(view(rgb_img)), view(hsl_img)); + + auto view = gil::view(hsl_img); + for (auto it = view.begin(), end = view.end(); it != end; ++it) + { + gil::rgb8_pixel_t p; + gil::color_convert(*it, p); + BOOST_TEST_EQ(p, rgb_pix); + } +} + +int main() +{ + test_basic_rgb_to_hsl(); + test_colors_rgb_to_hsl(); + test_copy_pixels_rgb_to_hsl(); + + return ::boost::report_errors(); +} diff --git a/test/extension/toolbox/indexed_image.cpp b/test/extension/toolbox/indexed_image.cpp index 5e78fa23f4..d21ce63938 100644 --- a/test/extension/toolbox/indexed_image.cpp +++ b/test/extension/toolbox/indexed_image.cpp @@ -143,6 +143,16 @@ void test_index_image_view() BOOST_TEST_EQ(gil::get_color(q, gil::red_t()), 70); BOOST_TEST_EQ(gil::get_color(q, gil::green_t()), 80); BOOST_TEST_EQ(gil::get_color(q, gil::blue_t()), 90); + + BOOST_TEST(ii_view.num_dimensions == 2); + BOOST_TEST(ii_view.dimensions().x == width); + BOOST_TEST(ii_view.dimensions().y == height); + BOOST_TEST(ii_view.width() == width); + BOOST_TEST(ii_view.height() == height); + BOOST_TEST(ii_view.num_channels() == gil::num_channels::value); + BOOST_TEST(ii_view.num_colors() == palette_view.dimensions().x); + BOOST_TEST(!ii_view.empty()); + BOOST_TEST(!ii_view.is_1d_traversable()); // by virtual_2d_locator } int main() diff --git a/test/legacy/channel.cpp b/test/legacy/channel.cpp index b4225d20e9..f53d170e4a 100644 --- a/test/legacy/channel.cpp +++ b/test/legacy/channel.cpp @@ -17,6 +17,9 @@ #if defined(BOOST_CLANG) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" +#if (__clang_major__ >= 10) +#pragma clang diagnostic ignored "-Wtautological-overlap-compare" +#endif #elif BOOST_GCC >= 40700 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" diff --git a/test/legacy/pixel_iterator.cpp b/test/legacy/pixel_iterator.cpp index 1b4a6d2a4a..419f7e649d 100644 --- a/test/legacy/pixel_iterator.cpp +++ b/test/legacy/pixel_iterator.cpp @@ -19,6 +19,11 @@ #include #include +#if defined(BOOST_CLANG) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" +#endif + using namespace boost::gil; using namespace std; @@ -353,3 +358,8 @@ int main() return EXIT_FAILURE; } } + +#if defined(BOOST_CLANG) +#pragma clang diagnostic pop +#endif + diff --git a/test/test_utility_output_stream.hpp b/test/test_utility_output_stream.hpp index ec6aff901c..44585332a6 100644 --- a/test/test_utility_output_stream.hpp +++ b/test/test_utility_output_stream.hpp @@ -5,8 +5,8 @@ // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt // -#ifndef BOOST_GIL_TEST_TEST_UTILITY_HPP -#define BOOST_GIL_TEST_TEST_UTILITY_HPP +#ifndef BOOST_GIL_TEST_TEST_UTILITY_OUTPUT_STREAM_HPP +#define BOOST_GIL_TEST_TEST_UTILITY_OUTPUT_STREAM_HPP #include // static_for_each #include diff --git a/test/test_utility_with_tolerance.hpp b/test/test_utility_with_tolerance.hpp new file mode 100644 index 0000000000..af5c7816c5 --- /dev/null +++ b/test/test_utility_with_tolerance.hpp @@ -0,0 +1,41 @@ +// +// Copyright 2020 Samuel Debionne +// +// 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 +// +#ifndef BOOST_GIL_TEST_TEST_UTILITY_WITH_TOLERANCE_HPP +#define BOOST_GIL_TEST_TEST_UTILITY_WITH_TOLERANCE_HPP + +#include +#include +#include + +namespace boost { namespace gil { + +namespace test { namespace utility { + +// Tolerance predicate for floating point comparison to use with BOOST_TEST_WITH. +// See https://github.com/boostorg/core/pull/77 for details. +template +struct with_tolerance +{ + with_tolerance(T tolerance) : tolerance(tolerance) + { + } + + bool operator()(T lhs, T rhs) + { + return (std::abs(lhs - rhs) <= tolerance); + } + +private: + T tolerance; +}; + +}} // namespace test::utility + +}} // namespace boost::gil + +#endif