Skip to main content
We’ve updated our Terms of Service. A new AI Addendum clarifies how Stack Overflow utilizes AI interactions.
Update code
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            const SigmaT sigma1,
            const SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(const ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            const SigmaT sigma,
            const SizeT filter_size = 0,
            const BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            const ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            const SigmaT sigma1,
            const SigmaT sigma2,
            const SizeT filter_size1,
            const SizeT filter_size2,
            const BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            const ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image = generate_padded_image(
                execution_policy, input,
                filter_size1,
                filter_size2,
                boundaryCondition,
                value_for_constant_padding
            );
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(execution_policy, filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(static_cast<std::size_t>(0), static_cast<std::size_t>(0)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - static_cast<std::size_t>(1), static_cast<std::size_t>(0)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(static_cast<std::size_t>(0), input.getHeight() - static_cast<std::size_t>(1)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - static_cast<std::size_t>(1), input.getHeight() - static_cast<std::size_t>(1)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(
            const Image<ElementT>& background,
            const Image<ElementT>& target,
            const std::size_t x_location,
            const std::size_t y_location,
            const ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& background,
            const Image<ElementT>& target,
            const std::size_t x_location,
            const std::size_t y_location,
            const ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            const auto width = output.getWidth();
            const auto height = output.getHeight();
            #pragma omp parallel for collapse(2)
            for (std::size_t y = 0; y < height; ++y)
            {
                for (std::size_t x = 0; x < width; ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • flip_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, input.getHeight() - y - 1) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  flip_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, input.getHeight() - y - 1) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(input.getWidth() - x - 1, y) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  flip_horizontal template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(input.getWidth() - x - 1, y) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(
                        input.getWidth() - x - 1,
                        input.getHeight() - y - 1
                        ) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
    namespace TinyDIP
    {
        //  flip_horizontal_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(
                        input.getWidth() - x - 1,
                        input.getHeight() - y - 1
                        ) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
void differenceOfGaussianTest(std::string_view input_image_path = "InputImages/1", std::string_view output_image_path = "OutputImages/differenceOfGaussianTest")
{
    auto input_img = TinyDIP::bmp_read(std::string(input_image_path).c_str(), false);
    for(int sigma = 1; sigma < 10; ++sigma)
    {
        auto output_img = TinyDIP::im2uint8(
                                TinyDIP::multiplies(
                                    TinyDIP::abs(
                                        TinyDIP::difference_of_gaussian(TinyDIP::im2double(input_img), static_cast<double>(sigma), static_cast<double>(sigma) - 1.0)
                                    ),
                                    3
                                )
                            );
        TinyDIP::bmp_write(
            (std::string(output_image_path) + std::string("_sigma=") + std::to_string(sigma)).c_str(),
            output_img);
    }
    return;
}

int main(int argc, char* argv[])
{
    auto start = std::chrono::system_clock::now();
    differenceOfGaussianTest();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return EXIT_SUCCESS;
}
void differenceOfGaussianTest(std::string_view input_image_path = "InputImages/1", std::string_view output_image_path = "OutputImages/differenceOfGaussianTest")
{
    auto input_img = TinyDIP::bmp_read(std::string(input_image_path).c_str(), false);
    for(int sigma = 1; sigma < 10; ++sigma)
    {
        auto output_img = TinyDIP::im2uint8(
                                TinyDIP::multiplies(
                                    TinyDIP::abs(
                                        TinyDIP::difference_of_gaussian(TinyDIP::im2double(input_img), static_cast<double>(sigma), static_cast<double>(sigma) - 1.0)
                                    ),
                                    3
                                )
                            );
        TinyDIP::bmp_write(
            (std::string(output_image_path) + std::string("_sigma=") + std::to_string(sigma)).c_str(),
            output_img);
    }
    return;
}

int main(int argc, char* argv[])
{
    auto start = std::chrono::system_clock::now();
    differenceOfGaussianTest();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return EXIT_SUCCESS;
}
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • flip_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, input.getHeight() - y - 1) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(input.getWidth() - x - 1, y) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(
                        input.getWidth() - x - 1,
                        input.getHeight() - y - 1
                        ) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
void differenceOfGaussianTest(std::string_view input_image_path = "InputImages/1", std::string_view output_image_path = "OutputImages/differenceOfGaussianTest")
{
    auto input_img = TinyDIP::bmp_read(std::string(input_image_path).c_str(), false);
    for(int sigma = 1; sigma < 10; ++sigma)
    {
        auto output_img = TinyDIP::im2uint8(
                                TinyDIP::multiplies(
                                    TinyDIP::abs(
                                        TinyDIP::difference_of_gaussian(TinyDIP::im2double(input_img), static_cast<double>(sigma), static_cast<double>(sigma) - 1.0)
                                    ),
                                    3
                                )
                            );
        TinyDIP::bmp_write(
            (std::string(output_image_path) + std::string("_sigma=") + std::to_string(sigma)).c_str(),
            output_img);
    }
    return;
}

int main(int argc, char* argv[])
{
    auto start = std::chrono::system_clock::now();
    differenceOfGaussianTest();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return EXIT_SUCCESS;
}
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            const SigmaT sigma1,
            const SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(const ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            const SigmaT sigma,
            const SizeT filter_size = 0,
            const BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            const ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            const SigmaT sigma1,
            const SigmaT sigma2,
            const SizeT filter_size1,
            const SizeT filter_size2,
            const BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            const ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image = generate_padded_image(
                execution_policy, input,
                filter_size1,
                filter_size2,
                boundaryCondition,
                value_for_constant_padding
            );
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(execution_policy, filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT>& input,
            const std::size_t width_expansion,
            const std::size_t height_expansion,
            const ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(static_cast<std::size_t>(0), static_cast<std::size_t>(0)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - static_cast<std::size_t>(1), static_cast<std::size_t>(0)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(static_cast<std::size_t>(0), input.getHeight() - static_cast<std::size_t>(1)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - static_cast<std::size_t>(1), input.getHeight() - static_cast<std::size_t>(1)));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(
            const Image<ElementT>& background,
            const Image<ElementT>& target,
            const std::size_t x_location,
            const std::size_t y_location,
            const ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& background,
            const Image<ElementT>& target,
            const std::size_t x_location,
            const std::size_t y_location,
            const ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            const auto width = output.getWidth();
            const auto height = output.getHeight();
            #pragma omp parallel for collapse(2)
            for (std::size_t y = 0; y < height; ++y)
            {
                for (std::size_t x = 0; x < width; ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • flip_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, input.getHeight() - y - 1) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(input.getWidth() - x - 1, y) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(
                        input.getWidth() - x - 1,
                        input.getHeight() - y - 1
                        ) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
void differenceOfGaussianTest(std::string_view input_image_path = "InputImages/1", std::string_view output_image_path = "OutputImages/differenceOfGaussianTest")
{
    auto input_img = TinyDIP::bmp_read(std::string(input_image_path).c_str(), false);
    for(int sigma = 1; sigma < 10; ++sigma)
    {
        auto output_img = TinyDIP::im2uint8(
                                TinyDIP::multiplies(
                                    TinyDIP::abs(
                                        TinyDIP::difference_of_gaussian(TinyDIP::im2double(input_img), static_cast<double>(sigma), static_cast<double>(sigma) - 1.0)
                                    ),
                                    3
                                )
                            );
        TinyDIP::bmp_write(
            (std::string(output_image_path) + std::string("_sigma=") + std::to_string(sigma)).c_str(),
            output_img);
    }
    return;
}

int main(int argc, char* argv[])
{
    auto start = std::chrono::system_clock::now();
    differenceOfGaussianTest();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);
    std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
    return EXIT_SUCCESS;
}
Update contents
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • flip_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, input.getHeight() - y - 1) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(input.getWidth() - x - 1, y) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(
                        input.getWidth() - x - 1,
                        input.getHeight() - y - 1
                        ) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • flip_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, input.getHeight() - y - 1) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(input.getWidth() - x - 1, y) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
  • flip_horizontal_vertical template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  flip_horizontal_vertical template function implementation
        template<typename ElementT>
        constexpr static auto flip_horizontal_vertical(const Image<ElementT>& input)
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output = input;
            #pragma omp parallel for collapse(2)
            for(std::size_t y = 0; y < input.getHeight(); ++y)
            {
                for(std::size_t x = 0; x < input.getWidth(); ++x)
                {
                    output.at_without_boundary_check(
                        input.getWidth() - x - 1,
                        input.getHeight() - y - 1
                        ) = input.at_without_boundary_check(x, y);
                }
            }
            return output;
        }
    }
    
Post Undeleted by JimmyHu
Post Deleted by JimmyHu
Update contents
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • difference_of_gaussian template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  difference_of_gaussian template function implementation
        template<typename ElementT, typename SigmaT = double>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto difference_of_gaussian(
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2)
        {
            return subtract(
                imgaussfilt(input, sigma1, static_cast<int>(computeFilterSizeFromSigma(sigma1))),
                imgaussfilt(input, sigma2, static_cast<int>(computeFilterSizeFromSigma(sigma2)))
                );
        }
    }
    
  • computeFilterSizeFromSigma template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  computeFilterSizeFromSigma template function implementation
        template<typename ElementT>
        constexpr static auto computeFilterSizeFromSigma(ElementT sigma)
        {
            return 2 * std::ceil(2 * sigma) + 1;
        }
    }
    
  • imgaussfilt template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        //  giving filter_size a default value of 0, and having the function compute an appropriate size unless the user specifies a positive value.
        template<typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            const Image<ElementT>& input,
            SigmaT sigma,
            SizeT filter_size = 0,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (filter_size == 0)
            {
                return imgaussfilt(
                    std::execution::seq,
                    input,
                    sigma,
                    sigma,
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    static_cast<int>(computeFilterSizeFromSigma(sigma)),
                    boundaryCondition,
                    value_for_constant_padding);
            }
            return imgaussfilt(
                            std::execution::seq,
                            input,
                            sigma,
                            sigma,
                            filter_size,
                            filter_size,
                            boundaryCondition,
                            value_for_constant_padding);
        }
    
        //  imgaussfilt template function implementation
        //  https://codereview.stackexchange.com/q/292985/231235
        template<class ExecutionPolicy, typename ElementT, typename SigmaT = double, std::integral SizeT = int>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)&&
                (std::floating_point<SigmaT> || std::integral<SigmaT>)
        constexpr static auto imgaussfilt(
            ExecutionPolicy&& execution_policy,
            const Image<ElementT>& input,
            SigmaT sigma1,
            SigmaT sigma2,
            SizeT filter_size1,
            SizeT filter_size2,
            BoundaryCondition boundaryCondition = BoundaryCondition::mirror,
            ElementT value_for_constant_padding = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> padded_image;
            switch(boundaryCondition)
            {
                case constant:
                    padded_image = generate_constant_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case mirror:
                    padded_image = generate_mirror_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
                case replicate:
                    padded_image = generate_replicate_padding_image(execution_policy, input, filter_size1, filter_size2, value_for_constant_padding);
                    break;
            }
    
            auto filter_mask_x = gaussianFigure1D(
                                        filter_size1,
                                        (static_cast<double>(filter_size1) + 1.0) / 2.0,
                                        sigma1);
            auto sum_result = sum(filter_mask_x);
            filter_mask_x = divides(filter_mask_x, sum_result);             //  Normalization
            auto output = conv2(padded_image, filter_mask_x, true);
            auto filter_mask_y = transpose(gaussianFigure1D(
                                            filter_size2,
                                            (static_cast<double>(filter_size2) + 1.0) / 2.0,
                                            sigma2));
            sum_result = sum(filter_mask_y);
            filter_mask_y = divides(filter_mask_y, sum_result);             //  Normalization
            output = conv2(output, filter_mask_y, true);
            output = subimage(output, input.getWidth(), input.getHeight(), static_cast<double>(output.getWidth()) / 2.0, static_cast<double>(output.getHeight()) / 2.0);
            return output;
        }
    }
    
  • BoundaryCondition enumeration declaration

    enum BoundaryCondition {
          constant,
          mirror,
          replicate
      };
    
  • generate_constant_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_constant_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_constant_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            return generate_constant_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_constant_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_constant_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            Image<ElementT> output(input.getWidth() + 2 * width_expansion, input.getHeight() + 2 * height_expansion);
            output.setAllValue(default_value);
            output = paste2D(execution_policy, output, input, width_expansion, height_expansion, default_value);
            return output;
        }
    }
    
  • generate_mirror_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_mirror_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_mirror_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_mirror_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_mirror_padding_image template function implementation (with Execution Policy)
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_mirror_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            auto flipped_vertical = flip_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, input.getHeight() - height_expansion - 1, flipped_vertical.getHeight() - 1),
                width_expansion,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_vertical, 0, flipped_vertical.getWidth() - 1, 0, height_expansion),
                width_expansion,
                input.getHeight() + height_expansion - 1,
                default_value);
            auto flipped_horizontal = flip_horizontal(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, input.getWidth() - width_expansion - 1, flipped_horizontal.getWidth() - 1, 0, flipped_horizontal.getHeight() - 1),
                0,
                height_expansion,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(flipped_horizontal, 0, width_expansion, 0, flipped_horizontal.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                height_expansion,
                default_value);
            auto flipped_horizontal_vertical = flip_horizontal_vertical(input);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                0,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    flipped_horizontal_vertical.getHeight() - height_expansion - 1,
                    flipped_horizontal_vertical.getHeight() - 1),
                input.getWidth() + width_expansion - 1,
                0,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    flipped_horizontal_vertical.getWidth() - width_expansion - 1,
                    flipped_horizontal_vertical.getWidth() - 1,
                    0,
                    height_expansion),
                0,
                input.getHeight() + height_expansion - 1,
                default_value);
            output = paste2D(
                execution_policy,
                output,
                subimage2(
                    flipped_horizontal_vertical,
                    0,
                    width_expansion,
                    0,
                    height_expansion),
                input.getWidth() + width_expansion - 1,
                input.getHeight() + height_expansion - 1,
                default_value);
            return output;
        }
    }
    
  • generate_replicate_padding_image template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  generate_replicate_padding_image template function implementation
        template<typename ElementT>
        constexpr static auto generate_replicate_padding_image(
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            return generate_replicate_padding_image(std::execution::seq, input, width_expansion, height_expansion, default_value);
        }
    
        //  generate_replicate_padding_image template function implementation (with Execution Policy)
        //  Test: https://godbolt.org/z/1hebz7hEh
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto generate_replicate_padding_image(
            ExecutionPolicy&& execution_policy, 
            const Image<ElementT> input,
            std::size_t width_expansion,
            std::size_t height_expansion,
            ElementT default_value = ElementT{})
        {
            if (input.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            auto output = generate_constant_padding_image(execution_policy, input, width_expansion, height_expansion, default_value);
            //  Top block
            for(std::size_t y = 0; y < height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, 0, 0),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Bottom block
            for(std::size_t y = input.getHeight() + height_expansion; y < input.getHeight() + 2 * height_expansion; ++y)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, input.getWidth() - 1, input.getHeight() - 1, input.getHeight() - 1),
                    width_expansion,
                    y,
                    default_value);
            }
            //  Left block
            for(std::size_t x = 0; x < width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, 0, 0, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            //  Right block
            for(std::size_t x = input.getWidth() + width_expansion; x < input.getWidth() + 2 * width_expansion; ++x)
            {
                output = paste2D(
                    execution_policy,
                    output,
                    subimage2(input, input.getWidth() - 1, input.getWidth() - 1, 0, input.getHeight() - 1),
                    x,
                    height_expansion,
                    default_value);
            }
            Image<ElementT> temp(width_expansion, height_expansion);
            //  Left-top corner
            temp.setAllValue(input.at(0, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                0,
                default_value);
            //  Right-top corner
            temp.setAllValue(input.at(input.getWidth() - 1, 0));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                0,
                default_value);
            //  Left-bottom corner
            temp.setAllValue(input.at(0, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                0,
                height_expansion + input.getHeight(),
                default_value);
            //  Right-bottom corner
            temp.setAllValue(input.at(input.getWidth() - 1, input.getHeight() - 1));
            output = paste2D(
                execution_policy,
                output,
                temp,
                width_expansion + input.getWidth(),
                height_expansion + input.getHeight(),
                default_value);
            return output;
        }
    }
    
  • subtract template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        constexpr static auto subtract(const std::vector<Image<InputT>>& input1, const std::vector<Image<InputT>>& input2)
        {
            assert(input1.size() == input2.size());
            return recursive_transform<1>(
                [](auto&& input1_element, auto&& input2_element)
                {
                    return subtract(input1_element, input2_element);
                }, input1, input2);
        }
    
        //  subtract Function Implementation
        constexpr static Image<RGB> subtract(const Image<RGB>& input1, const Image<RGB>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](RGB x, RGB y)
                    {
                        RGB rgb;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            rgb.channels[channel_index] = 
                            std::clamp(
                                x.channels[channel_index] - 
                                y.channels[channel_index],
                                0,
                                255);
                        }
                        return rgb;
                    },
                    input1,
                    input2
                );
        }
    
        //  subtract Template Function Implementation
        template<class InputT>
        requires((std::same_as<InputT, RGB_DOUBLE>) || (std::same_as<InputT, HSV>))
        constexpr static auto subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            check_size_same(input1, input2);
            return pixelwiseOperation(
                    [](InputT x, InputT y)
                    {
                        InputT output;
                        for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                        {
                            output.channels[channel_index] = x.channels[channel_index] - y.channels[channel_index];
                        }
                        return output;
                    },
                    input1,
                    input2
                );
        }
    }
    
  • paste2D template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  paste2D template function implementation
        template<typename ElementT>
        constexpr static auto paste2D(const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            return paste2D(std::execution::seq, background, target, x_location, y_location, default_value);
        }
    
        //  paste2D template function implementation (with execution policy)
        //  Test: https://godbolt.org/z/5hjns1nGP
        template<class ExecutionPolicy, typename ElementT>
        requires(std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>)
        constexpr static auto paste2D(ExecutionPolicy&& execution_policy, const Image<ElementT>& background, const Image<ElementT>& target, std::size_t x_location, std::size_t y_location, ElementT default_value = ElementT{})
        {
            if (background.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if (target.getDimensionality()!=2)
            {
                throw std::runtime_error("Unsupported dimension!");
            }
            if((background.getWidth() >= target.getWidth() + x_location) &&
               (background.getHeight() >= target.getHeight() + y_location))
            {
                auto output = background;
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
            else
            {
                std::vector<ElementT> data;
                auto xsize = (background.getWidth() >= target.getWidth() + x_location)?
                        background.getWidth():
                        (target.getWidth() + x_location);
                auto ysize = (background.getHeight() >= target.getHeight() + y_location)?
                        background.getHeight():
                        (target.getHeight() + y_location);
                data.resize(xsize * ysize);
                std::fill(execution_policy, std::ranges::begin(data), std::ranges::end(data), default_value);
                Image<ElementT> output(data, xsize, ysize);
                for (std::size_t y = 0; y < background.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < background.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x, y) = background.at_without_boundary_check(x, y);
                    }
                }
                for (std::size_t y = 0; y < target.getHeight(); ++y)
                {
                    for (std::size_t x = 0; x < target.getWidth(); ++x)
                    {
                        output.at_without_boundary_check(x_location + x, y_location + y) = target.at_without_boundary_check(x, y);
                    }
                }
                return output;
            }
        }
    }
    
  • subimage2 template function implementation (in file image_operations.h)

    namespace TinyDIP
    {
        //  subimage2 template function implementation
        template<typename ElementT>
        constexpr static auto subimage2(const Image<ElementT>& input, const std::size_t startx, const std::size_t endx, const std::size_t starty, const std::size_t endy)
        {
            assert(startx <= endx);
            assert(starty <= endy);
            Image<ElementT> output(endx - startx + 1, endy - starty + 1);
            for (std::size_t y = 0; y < output.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < output.getWidth(); ++x)
                {
                    output.at_without_boundary_check(x, y) = input.at_without_boundary_check(startx + x, starty + y);
                }
            }
            return output;
        }
    }
    
Update contents
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48
Loading
Source Link
JimmyHu
  • 7.6k
  • 2
  • 11
  • 48
Loading