diff --git a/cpp/include/Image.hpp b/cpp/include/Image.hpp index d25b64d..cc7945d 100644 --- a/cpp/include/Image.hpp +++ b/cpp/include/Image.hpp @@ -13,6 +13,13 @@ /// Note that the GetPixel() and SetPixel() methods use the formula z * width * height + y * width + x to compute the index of the pixel in the buffer. This formula assumes row-major order, where the x coordinate varies fastest, followed by the y coordinate, and then the z coordinate. If the image had a different ordering, the formula would need to be adjusted accordingly. class Image { public: + /// @brief Interpolation methods. + enum class InterpMethod { + NEAREST, ///< Nearest neighbor interpolation. + BILINEAR, ///< Bilinear interpolation. + LANCZOS ///< Lanczos filtering interpolation. + }; + /// @brief Initializes the image with the given width, height, and optionally depth. Warning : the values are uninitialized. /// @param width_ Width of the image. /// @param height_ Height of the image. @@ -75,6 +82,12 @@ class Image { /// @return Given pixel as a 3D-vector containing the RGB values. Eigen::Vector3i GetPixel(Eigen::Vector2i const& pos) const; + /// @brief Returns the given pixel as a 3D-vector containing the RGB values, using the selected interpolation method. + /// @param pos 2D vector containing the row and column indices. Floating point values are used. + /// @param interp_method Interpolation method to use. + /// @return Interpolated pixel as a 3D-vector containing the RGB values. + Eigen::Vector3i GetPixelInterp(Eigen::Vector2d const& pos, InterpMethod const& interp_method) const; + /// @brief Sets the given pixel to the given RGB values. /// @param i Row index. /// @param j Column index. diff --git a/cpp/src/Image.cpp b/cpp/src/Image.cpp index db7a6f3..294c467 100644 --- a/cpp/src/Image.cpp +++ b/cpp/src/Image.cpp @@ -83,6 +83,19 @@ Eigen::Vector3i Image::GetPixel(Eigen::Vector2i const& pos) const { return GetPixel(pos[0], pos[1]); } +Eigen::Vector3i Image::GetPixelInterp(Eigen::Vector2d const& pos, InterpMethod const& interp_method) const { + if(interp_method == Image::InterpMethod::BILINEAR) { + int i = (int)std::floor(pos[0]); + int j = (int)std::floor(pos[1]); + double u = pos[0] - i; + double v = pos[1] - j; + Eigen::Vector3i rgb = ((1 - u) * (1 - v) * GetPixel(i, j).cast() + u * (1 - v) * GetPixel(i + 1, j).cast() + (1 - u) * v * GetPixel(i, j + 1).cast() + u * v * GetPixel(i + 1, j + 1).cast()).cast(); + return rgb; + } + else // By default, use nearest neighbor interpolation + return GetPixel((int)std::round(pos[0]), (int)std::round(pos[1])); +} + Image & Image::SetPixel(int i, int j, Eigen::Vector3i const& rgb) { SetPixelValue(i, j, 0, (unsigned char)rgb[0]); SetPixelValue(i, j, 1, (unsigned char)rgb[1]); diff --git a/cpp/src/tests.cpp b/cpp/src/tests.cpp index b288964..6445909 100644 --- a/cpp/src/tests.cpp +++ b/cpp/src/tests.cpp @@ -215,9 +215,9 @@ int main() { std::cout << "Test Fill(rgb)\n"; Image image(3, 2, 3); image.Fill(Eigen::Vector3i(1, 2, 3)); - for (int y = 0; y < image.GetHeight(); ++y) { - for (int x = 0; x < image.GetWidth(); ++x) { - EXPECT_EQ(image.GetPixel(x, y), Eigen::Vector3i(1, 2, 3)); + for (int i = 0; i < image.GetHeight(); ++i) { + for (int j = 0; j < image.GetWidth(); ++j) { + EXPECT_EQ(image.GetPixel(i, j), Eigen::Vector3i(1, 2, 3)); } } } @@ -226,9 +226,9 @@ int main() { std::cout << "Test Fill(uchar)\n"; Image image(3, 2, 3); image.Fill(69); - for (int y = 0; y < image.GetHeight(); ++y) { - for (int x = 0; x < image.GetWidth(); ++x) { - EXPECT_EQ(image.GetPixel(x, y), Eigen::Vector3i(69, 69, 69)); + for (int i = 0; i < image.GetHeight(); ++i) { + for (int j = 0; j < image.GetWidth(); ++j) { + EXPECT_EQ(image.GetPixel(i, j), Eigen::Vector3i(69, 69, 69)); } } } @@ -264,6 +264,34 @@ int main() { image.Save("test_img_512x256.bmp"); image.Save("test_img_512x256.tga"); } + + { + std::cout << "Bilinear interpolation\n"; + Image image1(3, 3, 3); // Source image + Image image2(512, 512, 3); // Interpolated image (nearest neighbor) + Image image3(512, 512, 3); // Interpolated image (bilinear) + image1.SetPixel(0, 0, Eigen::Vector3i(255, 127, 0)); + image1.SetPixel(0, 1, Eigen::Vector3i(127, 192, 64)); + image1.SetPixel(0, 2, Eigen::Vector3i(100, 100, 50)); + image1.SetPixel(1, 0, Eigen::Vector3i(30, 0, 210)); + image1.SetPixel(1, 1, Eigen::Vector3i(192, 64, 0)); + image1.SetPixel(1, 2, Eigen::Vector3i(64, 150, 192)); + image1.SetPixel(2, 0, Eigen::Vector3i(100, 50, 150)); + image1.SetPixel(2, 1, Eigen::Vector3i(50, 120, 190)); + image1.SetPixel(2, 2, Eigen::Vector3i(240, 50, 15)); + + for(int i = 0; i < image2.GetHeight(); ++i) { + for(int j = 0; j < image2.GetWidth(); ++j) { + Eigen::Vector2d pos_float((double)i/image2.GetHeight()*(image1.GetHeight()-1), (double)j/image2.GetWidth()*(image1.GetWidth()-1)); + image2.SetPixel(i, j, image1.GetPixelInterp(pos_float, Image::InterpMethod::NEAREST)); + image3.SetPixel(i, j, image1.GetPixelInterp(pos_float, Image::InterpMethod::BILINEAR)); + } + } + + image1.Save("interp_src_3x3.png"); + image2.Save("interp_nearest_512x512.png"); + image3.Save("interp_bilinear_512x512.png"); + } } PRINT_TESTS_SUMMARY();