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& 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; }
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:
Which question it is a follow-up to?
Two dimensional bicubic interpolation implementation in C and
What changes has been made in the code since last question?
I am attempting to make a C++ version bicubic interpolation function
bicubicInterpolation
which can be applied on two dimensional nested vectorsstd::vector<std::vector<>>
structure.Why a new review is being asked for?
If there is any possible improvement, please let me know.
ndata
) is still in here \$\endgroup\$