Skip to main content
3 of 4
Update question description
JimmyHu
  • 5.5k
  • 2
  • 10
  • 40

Two dimensional bicubic interpolation implementation in C++

This is a follow-up question for Two dimensional bicubic interpolation implementation in C and A recursive_transform Template Function with Unwrap Level for Various Type Arbitrary Nested Iterable Implementation in C++. Besides the C version code, I am attempting to make a C++ version bicubic interpolation function bicubicInterpolation which can be applied on two dimensional nested vectors std::vector<std::vector<>> structure.

Example input matrix:

1       1       1
1       100     1
1       1       1

Output matrix (bicubic interpolation result from the input matrix above):

1       1       1       1       1       1       1       1       1       1       1       1
1       9       19      27      30      27      19      9       1       0       0       0
1       19      39      56      62      56      39      19      1       0       0       0
1       27      56      79      89      79      56      27      1       0       0       0
1       30      62      89      100     89      62      30      1       0       0       0
1       27      56      79      89      79      56      27      1       0       0       0
1       19      39      56      62      56      39      19      1       0       0       0
1       9       19      27      30      27      19      9       1       0       0       0
1       1       1       1       1       1       1       1       1       1       1       1
1       0       0       0       0       0       0       0       1       2       2       1
1       0       0       0       0       0       0       0       1       2       2       1
1       0       0       0       0       0       0       0       1       1       1       1

The experimental implementation

  • namespace: TinyDIP

  • bicubicInterpolation function implementation:

    constexpr auto bicubicInterpolation(const int newSizeX, const int newSizeY)
    {
        auto output = Image<ElementT>(newSizeX, newSizeY);
        auto ratiox = (float)this->getSizeX() / (float)newSizeX;
        auto ratioy = (float)this->getSizeY() / (float)newSizeY;
    
        for (size_t y = 0; y < newSizeY; y++)
        {
            for (size_t x = 0; x < newSizeX; x++)
            {
                float xMappingToOrigin = (float)x * ratiox;
                float yMappingToOrigin = (float)y * ratioy;
                float xMappingToOriginFloor = floor(xMappingToOrigin);
                float yMappingToOriginFloor = floor(yMappingToOrigin);
                float xMappingToOriginFrac = xMappingToOrigin - xMappingToOriginFloor;
                float yMappingToOriginFrac = yMappingToOrigin - yMappingToOriginFloor;
    
                ElementT ndata[4 * 4];
                for (int ndatay = -1; ndatay <= 2; ndatay++)
                {
                    for (int ndatax = -1; ndatax <= 2; ndatax++)
                    {
                        ndata[(ndatay + 1) * 4 + (ndatax + 1)] = this->get(
                            clip(xMappingToOriginFloor + ndatax, 0, this->getSizeX() - 1), 
                            clip(yMappingToOriginFloor + ndatay, 0, this->getSizeY() - 1));
                    }
    
                }
                output.set(x, y, bicubicPolate(ndata, xMappingToOriginFrac, yMappingToOriginFrac));
            }
        }
        return output;
    }
    
  • Helper functions for bicubicInterpolation function:

    template<class InputT>
    constexpr auto bicubicPolate(const ElementT* const ndata, const InputT fracx, const InputT fracy)
    {
        auto x1 = cubicPolate( ndata[0], ndata[1], ndata[2], ndata[3], fracx );
        auto x2 = cubicPolate( ndata[4], ndata[5], ndata[6], ndata[7], fracx );
        auto x3 = cubicPolate( ndata[8], ndata[9], ndata[10], ndata[11], fracx );
        auto x4 = cubicPolate( ndata[12], ndata[13], ndata[14], ndata[15], fracx );
    
        return clip(cubicPolate( x1, x2, x3, x4, fracy ), 0.0, 255.0);
    }
    
    template<class InputT1, class InputT2>
    constexpr auto cubicPolate(const InputT1 v0, const InputT1 v1, const InputT1 v2, const InputT1 v3, const InputT2 fracy)
    {
        auto A = (v3-v2)-(v0-v1);
        auto B = (v0-v1)-A;
        auto C = v2-v0;
        auto D = v1;
        return D + fracy * (C + fracy * (B + fracy * A));
    }
    
    template<class InputT1, class InputT2, class InputT3>
    constexpr auto clip(const InputT1 input, const InputT2 lowerbound, const InputT3 upperbound)
    {
        if (input < lowerbound)
        {
            return static_cast<InputT1>(lowerbound);
        }
        if (input > upperbound)
        {
            return static_cast<InputT1>(upperbound);
        }
        return input;
    }
    
  • Image template class implementation (image.h):

    /* Develop by Jimmy Hu */
    
    #ifndef Image_H
    #define Image_H
    
    #include <algorithm>
    #include <array>
    #include <chrono>
    #include <complex>
    #include <concepts>
    #include <functional>
    #include <iostream>
    #include <iterator>
    #include <list>
    #include <numeric>
    #include <string>
    #include <type_traits>
    #include <variant>
    #include <vector>
    #include "basic_functions.h"
    
    namespace TinyDIP
    {
        template <typename ElementT>
        class Image
        {
        public:
            Image()
            {
            }
    
            Image(const int newWidth, const int newHeight)
            {
                this->image_data.resize(newHeight);
                for (size_t i = 0; i < newHeight; ++i) {
                    this->image_data[i].resize(newWidth);
                }
                this->image_data = recursive_transform<2>(this->image_data, [](ElementT element) { return ElementT{}; });
                return;
            }
    
            Image(const int newWidth, const int newHeight, const ElementT initVal)
            {
                this->image_data.resize(newHeight);
                for (size_t i = 0; i < newHeight; ++i) {
                    this->image_data[i].resize(newWidth);
                }
                this->image_data = recursive_transform<2>(this->image_data, [initVal](ElementT element) { return initVal; });
                return;
            }
    
            Image(const std::vector<std::vector<ElementT>>& input)
            {
                this->image_data = recursive_transform<2>(input, [](ElementT element) {return element; } ); //  Deep copy
                return;
            }
    
            template<class OutputT>
            constexpr auto cast()
            {
                return this->transform([](ElementT element) { return static_cast<OutputT>(element); });
            }
    
            constexpr auto get(const unsigned int locationx, const unsigned int locationy)
            {
                return this->image_data[locationy][locationx];
            }
    
            constexpr auto set(const unsigned int locationx, const unsigned int locationy, const ElementT& element)
            {
                this->image_data[locationy][locationx] = element;
                return *this;
            }
    
            template<class InputT>
            constexpr auto set(const unsigned int locationx, const unsigned int locationy, const InputT& element)
            {
                this->image_data[locationy][locationx] = static_cast<ElementT>(element);
                return *this;
            }
    
            constexpr auto getSizeX()
            {
                return this->image_data[0].size();
            }
    
            constexpr auto getSizeY()
            {
                return this->image_data.size();
            }
    
            constexpr auto getData()
            {
                return this->transform([](ElementT element) { return element; });   //  Deep copy
            }
    
            void print()
            {
                for (auto& row_element : this->toString())
                {
                    for (auto& element : row_element)
                    {
                        std::cout << element << "\t";
                    }
                    std::cout << "\n";
                }
                std::cout << "\n";
                return;
            }
    
            constexpr auto toString()
            {
                return this->transform([](ElementT element) { return std::to_string(element); });
            }
    
            constexpr auto bicubicInterpolation(const int newSizeX, const int newSizeY)
            {
                auto output = Image<ElementT>(newSizeX, newSizeY);
                auto ratiox = (float)this->getSizeX() / (float)newSizeX;
                auto ratioy = (float)this->getSizeY() / (float)newSizeY;
    
                for (size_t y = 0; y < newSizeY; y++)
                {
                    for (size_t x = 0; x < newSizeX; x++)
                    {
                        float xMappingToOrigin = (float)x * ratiox;
                        float yMappingToOrigin = (float)y * ratioy;
                        float xMappingToOriginFloor = floor(xMappingToOrigin);
                        float yMappingToOriginFloor = floor(yMappingToOrigin);
                        float xMappingToOriginFrac = xMappingToOrigin - xMappingToOriginFloor;
                        float yMappingToOriginFrac = yMappingToOrigin - yMappingToOriginFloor;
    
                        ElementT ndata[4 * 4];
                        for (int ndatay = -1; ndatay <= 2; ndatay++)
                        {
                            for (int ndatax = -1; ndatax <= 2; ndatax++)
                            {
                                ndata[(ndatay + 1) * 4 + (ndatax + 1)] = this->get(
                                    clip(xMappingToOriginFloor + ndatax, 0, this->getSizeX() - 1), 
                                    clip(yMappingToOriginFloor + ndatay, 0, this->getSizeY() - 1));
                            }
    
                        }
                        output.set(x, y, bicubicPolate(ndata, xMappingToOriginFrac, yMappingToOriginFrac));
                    }
                }
                return output;
            }
    
            Image<ElementT>& operator=(Image<ElementT> const& input)  //  Copy Assign
            {
                this->image_data = input.getData();
                return *this;
            }
    
            Image<ElementT>& operator=(Image<ElementT>&& other)       //  Move Assign
            {
                this->image_data = std::move(other.image_data);
                std::cout << "move assigned\n";
                return *this;
            }
    
            Image(const Image<ElementT> &input)                      //  Copy Constructor
            {
                this->image_data = input.getData();
            }
    
            /*    Move Constructor
             */
            Image(Image<ElementT> &&input) : image_data(std::move(input.image_data))
            {
            }
    
        private:
            std::vector<std::vector<ElementT>> image_data;
    
            template<class F>
            constexpr auto transform(const F& f)
            {
                return recursive_transform<2>(this->image_data, f);
            }
    
            template<class InputT>
            constexpr auto bicubicPolate(const ElementT* const ndata, const InputT fracx, const InputT fracy)
            {
                auto x1 = cubicPolate( ndata[0], ndata[1], ndata[2], ndata[3], fracx );
                auto x2 = cubicPolate( ndata[4], ndata[5], ndata[6], ndata[7], fracx );
                auto x3 = cubicPolate( ndata[8], ndata[9], ndata[10], ndata[11], fracx );
                auto x4 = cubicPolate( ndata[12], ndata[13], ndata[14], ndata[15], fracx );
    
                return clip(cubicPolate( x1, x2, x3, x4, fracy ), 0.0, 255.0);
            }
    
            template<class InputT1, class InputT2>
            constexpr auto cubicPolate(const InputT1 v0, const InputT1 v1, const InputT1 v2, const InputT1 v3, const InputT2 frac)
            {
                auto A = (v3-v2)-(v0-v1);
                auto B = (v0-v1)-A;
                auto C = v2-v0;
                auto D = v1;
                return D + frac * (C + frac * (B + frac * A));
            }
    
            template<class InputT1, class InputT2, class InputT3>
            constexpr auto clip(const InputT1 input, const InputT2 lowerbound, const InputT3 upperbound)
            {
                if (input < lowerbound)
                {
                    return static_cast<InputT1>(lowerbound);
                }
                if (input > upperbound)
                {
                    return static_cast<InputT1>(upperbound);
                }
                return input;
            }
        };
    }
    
    #endif
    
  • base_types.h: The base types

    /* Develop by Jimmy Hu */
    
    #ifndef BASE_H
    #define BASE_H
    
    #include <cmath>
    #include <cstdbool>
    #include <cstdio>
    #include <cstdlib>
    #include <string>
    
    #define MAX_PATH 256
    #define FILE_ROOT_PATH "./"
    
    #define True true
    #define False false
    
    typedef unsigned char BYTE;
    
    typedef struct RGB
    {
        unsigned char channels[3];
    } RGB;
    
    typedef BYTE GrayScale;
    
    typedef struct HSV
    {
        long double channels[3];    //  Range: 0 <= H < 360, 0 <= S <= 1, 0 <= V <= 255
    }HSV;
    
    #endif
    
  • basic_functions.h: The basic functions

    /* Develop by Jimmy Hu */
    
    #ifndef BasicFunctions_H
    #define BasicFunctions_H
    
    #include <algorithm>
    #include <array>
    #include <cassert>
    #include <chrono>
    #include <complex>
    #include <concepts>
    #include <deque>
    #include <execution>
    #include <exception>
    #include <functional>
    #include <iostream>
    #include <iterator>
    #include <list>
    #include <map>
    #include <mutex>
    #include <numeric>
    #include <optional>
    #include <ranges>
    #include <stdexcept>
    #include <string>
    #include <tuple>
    #include <type_traits>
    #include <utility>
    #include <variant>
    #include <vector>
    
    namespace TinyDIP
    {
        template<typename T>
        concept is_back_inserterable = requires(T x)
        {
            std::back_inserter(x);
        };
    
        template<typename T>
        concept is_inserterable = requires(T x)
        {
            std::inserter(x, std::ranges::end(x));
        };
    
        //  recursive_invoke_result_t implementation
        template<typename, typename>
        struct recursive_invoke_result { };
    
        template<typename T, std::invocable<T> F>
        struct recursive_invoke_result<F, T> { using type = std::invoke_result_t<F, T>; };
    
        template<typename F, template<typename...> typename Container, typename... Ts>
        requires (
            !std::invocable<F, Container<Ts...>>&&
            std::ranges::input_range<Container<Ts...>>&&
            requires { typename recursive_invoke_result<F, std::ranges::range_value_t<Container<Ts...>>>::type; })
            struct recursive_invoke_result<F, Container<Ts...>>
        {
            using type = Container<typename recursive_invoke_result<F, std::ranges::range_value_t<Container<Ts...>>>::type>;
        };
    
        template<typename F, typename T>
        using recursive_invoke_result_t = typename recursive_invoke_result<F, T>::type;
    
        //  recursive_transform implementation (the version with unwrap_level)
        template<std::size_t unwrap_level = 1, class T, class F>
        constexpr auto recursive_transform(const T& input, const F& f)
        {
            if constexpr (unwrap_level > 0)
            {
                recursive_invoke_result_t<F, T> output{};
                std::ranges::transform(
                    std::ranges::cbegin(input),
                    std::ranges::cend(input),
                    std::inserter(output, std::ranges::end(output)),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
                );
                return output;
            }
            else
            {
                return f(input);
            }
        }
    
        template<std::size_t dim, class T>
        constexpr auto n_dim_vector_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_vector_generator<dim - 1>(input, times);
                std::vector<decltype(element)> output(times, element);
                return output;
            }
        }
    
        template<std::size_t dim, std::size_t times, class T>
        constexpr auto n_dim_array_generator(T input)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_array_generator<dim - 1, times>(input);
                std::array<decltype(element), times> output;
                std::fill(std::ranges::begin(output), std::ranges::end(output), element);
                return output;
            }
        }
    
        template<std::size_t dim, class T>
        constexpr auto n_dim_deque_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_deque_generator<dim - 1>(input, times);
                std::deque<decltype(element)> output(times, element);
                return output;
            }
        }
    
        template<std::size_t dim, class T>
        constexpr auto n_dim_list_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_list_generator<dim - 1>(input, times);
                std::list<decltype(element)> output(times, element);
                return output;
            }
        }
    
        template<std::size_t dim, template<class...> class Container = std::vector, class T>
        constexpr auto n_dim_container_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                return Container(times, n_dim_container_generator<dim - 1, Container, T>(input, times));
            }
        }
    }
    #endif
    

The full testing code

The grayscale type data has been tested here.

/* Develop by Jimmy Hu */

#include "base_types.h"
#include "basic_functions.h"
#include "image.h"

void bicubicInterpolationTest();

int main()
{
    bicubicInterpolationTest();
    return 0;
}

void bicubicInterpolationTest()
{
    TinyDIP::Image<GrayScale> image1(3, 3, 1);
    std::cout << "Width: " + std::to_string(image1.getSizeX()) + "\n";
    std::cout << "Height: " + std::to_string(image1.getSizeY()) + "\n";
    image1 = image1.set(1, 1, 100);
    image1.print();

    auto image2 = image1.bicubicInterpolation(12, 12);
    image2.print();
}

All suggestions are welcome.

The summary information:

JimmyHu
  • 5.5k
  • 2
  • 10
  • 40