diff --git a/cpp/Makefile b/cpp/Makefile new file mode 100644 index 0000000..49772fd --- /dev/null +++ b/cpp/Makefile @@ -0,0 +1,26 @@ +CXX = g++ +CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic -I./include -g +INCLUDES = -I./include -ID:/Users/Jerome/Documents/Ingenierie/Programmation/eigen-3.4.0 + +SRC_DIR = src +OBJ_DIR = obj + +SRCS = $(wildcard $(SRC_DIR)/*.cpp) +OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRCS)) +EXEC = $(OBJ_DIR)/360_to_perspective + +.PHONY: all clean + +all: $(EXEC) + +$(EXEC): $(OBJS) + $(CXX) $(CXXFLAGS) -o $@ $^ + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR) + $(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $< + +$(OBJ_DIR): + mkdir -p $@ + +clean: + rm -rf $(OBJ_DIR) \ No newline at end of file diff --git a/cpp/include/Camera.hpp b/cpp/include/Camera.hpp new file mode 100644 index 0000000..afead6a --- /dev/null +++ b/cpp/include/Camera.hpp @@ -0,0 +1,77 @@ +#ifndef H_Camera +#define H_Camera + +#include + +#define DEG2RAD 0.017453292519943295 + +/// @brief A camera class to perform frame transformations and projections. +class Camera { + public: + // Constructors + Camera(); + + // Accessors + Eigen::Vector3d const& GetForward() const; + Eigen::Vector3d const& GetUp() const; + Eigen::Matrix3d const& GetR() const; + Eigen::Matrix3d const& GetR_T() const; + double GetFOV() const; + + // Setters following the Builder pattern. See https://en.wikipedia.org/wiki/Builder_pattern + Camera & SetForward(Eigen::Vector3d const& fwd_); + Camera & SetUp(Eigen::Vector3d const& up_); + Camera & SetR(Eigen::Matrix3d const& R_); + Camera & SetFOV(double fov); + + // Methods + /// @brief Build the rotation matrix from the camera frame to the inertial frame from the fwd and up vectors. + Camera & BuildRotationMatrix(); + + /// @brief Orients the camera so that the forward vector points at the target. The position of the camera is (0,0,0). + Camera & SetTarget(Eigen::Vector3d const& target); + + /// @brief Computes the ray direction in camera frame for a point (x,y) on the surface of the camera's sensor. p_norm is in normalized coordinates [-1;1]. + /// @param p_norm Normalized coordinates of the point on the sensor. + /// @return The ray direction in camera frame, cartesian coordinates. + Eigen::Vector3d ComputeRayDirInCameraFrame(Eigen::Vector2d const& p_norm) const; + + /// @brief Computes the ray direction in inertial frame for a point (x,y) on the surface of the camera's sensor. p_norm is in normalized coordinates [-1;1]. + /// @param p_norm Normalized coordinates of the point on the sensor. + /// @return The ray direction in inertial frame, cartesian coordinates. + Eigen::Vector3d ComputeRayDirInInertialFrame(Eigen::Vector2d const& p_norm) const; + + /// @brief Projects a point in camera frame (cartesian coordinates) to the sensor. + /// @param p_inertial Cartesian coordinates of the point to project, in camera frame. + /// @return Sensor coordinates of the projected point. The Z component is +/- tan(FOV/2). Negative Z means the point is in front of the camera. + Eigen::Vector3d ProjectPointToSensorCameraFrame(Eigen::Vector3d const& p_camera) const; + + /// @brief Projects a point in inertial frame (cartesian coordinates) to the sensor. + /// @param p_inertial Cartesian coordinates of the point to project, in inertial frame. + /// @return Sensor coordinates of the projected point. The Z component is +/- tan(FOV/2). Negative Z means the point is in front of the camera. + Eigen::Vector3d ProjectPointToSensorInertialFrame(Eigen::Vector3d const& p_inertial) const; + + /// @brief Checks if a point in camera frame is visible by the camera. + /// @param p_inertial Cartesian coordinates of the point to check, in camera frame. + /// @return True if the point is visible, false otherwise. + bool IsPointVisibleCameraFrame(Eigen::Vector3d const& p_camera) const; + + /// @brief Checks if a point in inertial frame is visible by the camera. + /// @param p_inertial Cartesian coordinates of the point to check, in inertial frame. + /// @return True if the point is visible, false otherwise. + bool IsPointVisibleInertialFrame(Eigen::Vector3d const& p_inertial) const; + + // Static methods + /// @brief Converts pixel coordinates to normalized coordinates. i is the row (y), j is the column (x). Normalized coordinates range from -1 to 1. + static Eigen::Vector2d PixelToNormalizedCoordinates(unsigned int i, unsigned int j, unsigned int width, unsigned int height); + + private: + Eigen::Vector3d fwd; //!< Forward vector in inertial frame + Eigen::Vector3d up; //!< Up vector in inertial frame + Eigen::Matrix3d R; //!< Rotation matrix from camera frame to inertial frame + Eigen::Matrix3d R_T; //!< Rotation matrix from inertial frame to camera frame + double FOV; //!< Field of view in radians + double tanFOV2; //!< tan(FOV/2) +}; + +#endif diff --git a/cpp/include/frame_conversions.hpp b/cpp/include/frame_conversions.hpp new file mode 100644 index 0000000..dd9b092 --- /dev/null +++ b/cpp/include/frame_conversions.hpp @@ -0,0 +1,35 @@ +#ifndef H_frame_conversions +#define H_frame_conversions + +#include +#include + +/// @brief Converts a spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+) to a cartesian vector. +Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph); + +/// @brief Converts a cartesian vector to a spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+). +Eigen::Vector3d cart2sph(const Eigen::Vector3d & cart); + +/// @brief Converts equirectangular coordinates to spherical coordinates. +/// @param i Row in the equirectangular image. +/// @param j Column in the equirectangular image. +/// @param width Width of the equirectangular image. +/// @param height Height of the equirectangular image. +/// @return A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+). +Eigen::Vector3d equirectangular2sph(unsigned int i, unsigned int j, unsigned int width, unsigned int height); + +/// @brief Converts spherical coordinates to pixel coordinates of an equirectangular image. +/// @param p_sph A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+). +/// @param width Width of the equirectangular image. +/// @param height Height of the equirectangular image. +/// @return A 2D vector of floating point numbers [i, j] representing the pixel coordinates of the equirectangular image. +Eigen::Vector2d sph2equirectangular_d(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height); + +/// @brief Converts spherical coordinates to pixel coordinates of an equirectangular image. +/// @param p_sph A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+). +/// @param width Width of the equirectangular image. +/// @param height Height of the equirectangular image. +/// @return A 2D vector of integers [i, j] representing the pixel coordinates of the equirectangular image. +Eigen::Vector2i sph2equirectangular_i(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height); + +#endif diff --git a/cpp/include/tinyimage.h b/cpp/include/tinyimage.h new file mode 100644 index 0000000..d0d801e --- /dev/null +++ b/cpp/include/tinyimage.h @@ -0,0 +1,146 @@ +/* + TinyImage + A tiny C/C++ image loading and saving library for graphics APIs. + Author: Charles Dong +*/ + +#ifndef TINYIMAGE_H_ +#define TINYIMAGE_H_ + +enum TinyImgColorType +{ + TINYIMG_RGB, + TINYIMG_RGBA +}; + +enum TinyImgError +{ + TINYIMG_OK, + TINYIMG_FORMAT_UNSUPPORTED, + TINYIMG_FILE_NOT_FOUND, + TINYIMG_INVALID_ARGUMENT +}; + +/* C-style interface begin */ + +/* + @brief Loads an image from disk. + + @param[filename] The name of the image file. + @param[width] Pointer to an integer that stores the image width (should be not NULL). + @param[height] Pointer to an integer that stores the image height (should be not NULL). + @param[type] The color type of the final image data. + + @return The image data in unsigned chars. If an error occurs, then NULL will be returned. +*/ +unsigned char * tinyimg_load(const char * filename, int * width, int * height, TinyImgColorType type); + +/* + @brief Frees the image data. + + @param[image] The image data. +*/ +void tinyimg_free(unsigned char * image); + +/* + @brief Saves the given image to disk. + + @param[filename] The name of the image file. + @param[width] The image width. + @param[height] The image height. + @param[type] The color type of the image data. + @param[image] The image data. + + @return 1 if successful, 0 if fails. +*/ +int tinyimg_save(const char * filename, int width, int height, TinyImgColorType type, const unsigned char * image); + +/* + @brief Gets the error code of the last error. + + @return The error code. If no error occured, 0 will be returned. +*/ +TinyImgError tinyimg_get_error(); + +/* + @brief Gets the description of the last error. + + @return The error string. If no error occured, NULL will be returned. +*/ +const char * tinyimg_get_error_str(); + +/* C-style interface end */ + +/* C++ style interface begin */ + +#ifdef __cplusplus + +#define TINYIMG_BEGIN namespace tinyimg { +#define TINYIMG_END } + +#include + +TINYIMG_BEGIN + +/* + @brief Loads an image from disk. + + @param[filename] The name of the image file. + @param[width] The image width. + @param[height] The image height. + @param[type] The color type of the final image data. + + @return The image data in unsigned chars. If an error occurs, then nullptr will be returned. +*/ +unsigned char * load(const char * filename, int & width, int & height, TinyImgColorType type); + +/* + @brief Frees the image data. + + @param[image] The image data. +*/ +void free(unsigned char * image); +/* + @brief Loads an image from disk into a vector. + + @param[filename] The name of the image file. + @param[width] The image width. + @param[height] The image height. + @param[type] The color type of the final image data. + + @return The image data in a vector. If an error occurs, an empty vector will be returned. +*/ +std::vector load_vec(const char * filename, int & width, int & height, TinyImgColorType type); + +/* + @brief Saves the given image to disk. + + @param[filename] The name of the image file. + @param[width] The image width. + @param[height] The image height. + @param[type] The color type of the image data. + @param[image] The image data. + + @return true if successful, false if fails. +*/ +bool save(const char * filename, int width, int height, TinyImgColorType type, const unsigned char * image); + +/* + @brief Gets the error code of the last error. + + @return The error code. If no error occured, 0 will be returned. +*/ +TinyImgError get_error(); + +/* +@brief Gets the description of the last error. + +@return The error string. If no error occured, nullptr will be returned. +*/ +const char * get_error_str(); + +TINYIMG_END + +#endif /* __cplusplus */ + +#endif /* TINYIMAGE_H_ */ \ No newline at end of file diff --git a/cpp/src/Camera.cpp b/cpp/src/Camera.cpp new file mode 100644 index 0000000..1685c86 --- /dev/null +++ b/cpp/src/Camera.cpp @@ -0,0 +1,104 @@ +#include + +#define _USE_MATH_DEFINES +#include + +Camera::Camera() + : fwd(Eigen::Vector3d::UnitX()), up(Eigen::Vector3d::UnitZ()), R(Eigen::Matrix3d::Identity()), R_T(Eigen::Matrix3d::Identity()), FOV(90.0*DEG2RAD), tanFOV2(tan(FOV/2.0)) +{ + BuildRotationMatrix(); +} + +// Getters +const Eigen::Vector3d& Camera::GetForward() const { + return fwd; +} + +const Eigen::Vector3d& Camera::GetUp() const { + return up; +} + +const Eigen::Matrix3d& Camera::GetR() const { + return R; +} + +const Eigen::Matrix3d& Camera::GetR_T() const { + return R_T; +} + +double Camera::GetFOV() const { + return FOV; +} + +// Setters +Camera& Camera::SetForward(const Eigen::Vector3d& fwd_) { + fwd = fwd_; + return *this; +} + +Camera& Camera::SetUp(const Eigen::Vector3d& up_) { + up = up_; + return *this; +} + +Camera& Camera::SetR(const Eigen::Matrix3d& R_) { + R = R_; + R_T = R.transpose(); + return *this; +} + +Camera& Camera::SetFOV(double fov) { + FOV = fov; + tanFOV2 = tan(FOV/2.0); + return *this; +} + +Camera & Camera::BuildRotationMatrix() { + Eigen::Vector3d left = up.cross(fwd); + left = left/left.norm(); + Eigen::Vector3d cam_up = fwd.cross(left); + R.col(0) = -left; // x-axis is to the right + R.col(1) = cam_up; // y-axis is up + R.col(2) = -fwd; // z-axis is backwards + R_T = R.transpose(); + return *this; +} + +Camera & Camera::SetTarget(Eigen::Vector3d const& target) { + fwd = target/target.norm(); + BuildRotationMatrix(); + return *this; +} + +Eigen::Vector3d Camera::ComputeRayDirInCameraFrame(Eigen::Vector2d const& p_norm) const { + return Eigen::Vector3d(p_norm(0)*tanFOV2, p_norm(1)*tanFOV2, -1.0).normalized(); +} + +Eigen::Vector3d Camera::ComputeRayDirInInertialFrame(Eigen::Vector2d const& p_norm) const { + return R*ComputeRayDirInCameraFrame(p_norm); +} + +Eigen::Vector3d Camera::ProjectPointToSensorCameraFrame(Eigen::Vector3d const& p_camera) const { + Eigen::Vector3d p_sensor = p_camera; + if(p_camera[2] != 0.0) + p_sensor *= tanFOV2/std::abs(p_camera[2]); + return p_sensor; +} + +Eigen::Vector3d Camera::ProjectPointToSensorInertialFrame(Eigen::Vector3d const& p_inertial) const { + return ProjectPointToSensorCameraFrame(R_T*p_inertial); +} + +bool Camera::IsPointVisibleCameraFrame(Eigen::Vector3d const& p_camera) const { + Eigen::Vector3d p_sensor = ProjectPointToSensorCameraFrame(p_camera); + return (p_sensor[0] >= -1.0) && (p_sensor[0] <= 1.0) && (p_sensor[1] >= -1.0) && (p_sensor[1] <= 1.0) && (p_sensor[2] < 0.0); +} + +bool Camera::IsPointVisibleInertialFrame(Eigen::Vector3d const& p_inertial) const { + Eigen::Vector3d p_sensor = ProjectPointToSensorInertialFrame(p_inertial); + return (p_sensor[0] >= -1.0) && (p_sensor[0] <= 1.0) && (p_sensor[1] >= -1.0) && (p_sensor[1] <= 1.0) && (p_sensor[2] < 0.0); +} + +Eigen::Vector2d Camera::PixelToNormalizedCoordinates(unsigned int i, unsigned int j, unsigned int width, unsigned int height) { + return Eigen::Vector2d(2.0*((double)j)/(width - 1) - 1.0, -2.0*((double)i)/(height - 1) + 1.0); +} \ No newline at end of file diff --git a/cpp/src/frame_conversions.cpp b/cpp/src/frame_conversions.cpp new file mode 100644 index 0000000..760ac03 --- /dev/null +++ b/cpp/src/frame_conversions.cpp @@ -0,0 +1,39 @@ +#include + +#define M_PI 3.14159265358979323846 + +Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph) { + double r = sph(0); + double theta = sph(1); + double phi = sph(2); + double x = r*sin(theta)*cos(phi); + double y = r*sin(theta)*sin(phi); + double z = r*cos(theta); + return Eigen::Vector3d(x,y,z); +} + +Eigen::Vector3d cart2sph(const Eigen::Vector3d & cart) { + double x = cart(0); + double y = cart(1); + double z = cart(2); + double r = sqrt(x*x + y*y + z*z); + double theta = acos(z/r); + double phi = atan2(y,x); + return Eigen::Vector3d(r,theta,phi); +} + +Eigen::Vector3d equirectangular2sph(unsigned int i, unsigned int j, unsigned int width, unsigned int height) { + return Eigen::Vector3d(1.0, M_PI*(((double)i)/(height-1)), 2.0*M_PI*(((double)j)/width - 0.5)); +} + +Eigen::Vector2d sph2equirectangular_d(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height) { + double theta = p_sph[1]; + double phi = p_sph[2]; + double i = std::fmod(((height - 1)*theta)/M_PI, height); + double j = std::fmod((width*(M_PI + phi))/(2.0*M_PI), width); + return Eigen::Vector2d(i, j); +} + +Eigen::Vector2i sph2equirectangular_i(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height) { + return sph2equirectangular_d(p_sph, width, height).cast(); +} diff --git a/cpp/src/tests.cpp b/cpp/src/tests.cpp new file mode 100644 index 0000000..f1b82dc --- /dev/null +++ b/cpp/src/tests.cpp @@ -0,0 +1,146 @@ +#include +#include + +#define _USE_MATH_DEFINES +#include + +constexpr double TOL_EQ_TEST = 1e-9; +constexpr double M_PI = 3.14159265358979323846; + +static size_t num_tests = 0; +static size_t num_tests_ok = 0; +static size_t num_tests_ko = 0; + +#define PRINT_VAR(x) std::cout << #x << "\t= " << (x) << std::endl + +// EXPECT_EQ macro +#define EXPECT_EQ(actual, expected) EXPECT_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_EQ_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + auto&& e = (expected); \ + auto&& a = (actual); \ + if (e != a) { \ + std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \ + num_tests_ko++; \ + } \ + else { \ + num_tests_ok++; \ + } \ + } while (false) + +// EXPECT_DOUBLE_EQ macro +#define EXPECT_DOUBLE_EQ(actual, expected) EXPECT_DOUBLE_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_DOUBLE_EQ_IMPL(actual, expected, file, line) \ + do { \ + num_tests++; \ + double e = (expected); \ + double a = (actual); \ + if (std::abs(e - a) > TOL_EQ_TEST) { \ + std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \ + num_tests_ko++; \ + } \ + else { \ + num_tests_ok++; \ + } \ + } while (false) + +// EXPECT_VEC_EQ macro +#define EXPECT_VEC_EQ(actual, expected) EXPECT_VEC_EQ_IMPL(actual, expected, __FILE__, __LINE__) +#define EXPECT_VEC_EQ_IMPL(actual, expected, file, line) \ + do { \ + auto&& d = (actual) - (expected); \ + EXPECT_DOUBLE_EQ_IMPL(d.array().abs().maxCoeff(), 0.0, file, line); \ + } while (false) + +#define PRINT_TESTS_SUMMARY() \ + do { \ + std::cout << "Tests summary: " << num_tests << " tests in total\n " << num_tests_ok << "/" << num_tests << " OK (" << ((100.0*(double)num_tests_ok)/num_tests) << " %)\n " << num_tests_ko << "/" << num_tests << " KO (" << ((100.0*(double)num_tests_ko)/num_tests) << " %)" << std::endl; \ + } while (false) + +int main() { + std::cout.precision(16); + std::cout << "Running Unit Tests\n"; + + {// Camera + std::cout << "Camera\n"; + {// Constructor, accessors and setters + std::cout << "Constructor, accessors and setters\n"; + + // Create a camera object + Camera camera; + + // Check constructor values using the getters + Eigen::Matrix3d default_R;// Rotation matrix with default fwd and up vectors. + default_R << 0, 0, -1, + -1, 0, 0, + 0, 1, 0; + EXPECT_EQ(camera.GetForward(), Eigen::Vector3d(1, 0, 0)); + EXPECT_EQ(camera.GetUp(), Eigen::Vector3d(0, 0, 1)); + EXPECT_DOUBLE_EQ(camera.GetFOV(), 90.0*M_PI/180.0); + EXPECT_EQ(camera.GetR(), default_R); + EXPECT_EQ(camera.GetR_T(), default_R.transpose()); + + // Check target computation + Eigen::Vector3d target(1, 1, 1); + camera.SetTarget(target); + + Eigen::Matrix3d target_R; + target_R << 0.7071067811865476, -0.4082482904638631, -0.5773502691896258, + -0.7071067811865476, -0.4082482904638631, -0.5773502691896258, + -0. , 0.8164965809277261, -0.5773502691896258; + + EXPECT_VEC_EQ(camera.GetForward(), Eigen::Vector3d(0.57735027, 0.57735027, 0.57735027)); + EXPECT_VEC_EQ(camera.GetR(), target_R); + + // Set some values using the setters + Eigen::Vector3d fwd(1, 0, 0); + Eigen::Vector3d up(0, 1, 0); + Eigen::Matrix3d R; + R << 0, 0, 1, + 1, 0, 0, + 0, 1, 0; + double fov = M_PI / 3.0; + + camera.SetForward(fwd) + .SetUp(up) + .SetR(R) + .SetFOV(fov); + + // Check the values using the getters + EXPECT_EQ(camera.GetForward(), fwd); + EXPECT_EQ(camera.GetUp(), up); + EXPECT_EQ(camera.GetR(), R); + EXPECT_DOUBLE_EQ(camera.GetFOV(), fov); + + // Test point projections + camera.SetFOV(20*DEG2RAD); + camera.SetTarget(Eigen::Vector3d(1.,.5,.3)); + PRINT_VAR(camera.GetR()); + PRINT_VAR(camera.GetR_T()); + + EXPECT_VEC_EQ(camera.ComputeRayDirInCameraFrame(Eigen::Vector2d( 0.0, 0.0)), Eigen::Vector3d(0.0, 0.0, -1.0)); + EXPECT_VEC_EQ(camera.ComputeRayDirInCameraFrame(Eigen::Vector2d( 1.0, 0.0)), Eigen::Vector3d(0.1736481776669304, 0, -0.9848077530122081)); + EXPECT_VEC_EQ(camera.ComputeRayDirInCameraFrame(Eigen::Vector2d( 1.0, 1.0)), Eigen::Vector3d(0.1710878697460355, 0.1710878697460355, -0.970287525247814)); + EXPECT_VEC_EQ(camera.ComputeRayDirInCameraFrame(Eigen::Vector2d( 1.0, -1.0)), Eigen::Vector3d(0.1710878697460355, -0.1710878697460355, -0.970287525247814)); + EXPECT_VEC_EQ(camera.ComputeRayDirInCameraFrame(Eigen::Vector2d(-1.0, -1.0)), Eigen::Vector3d(-0.1710878697460355, -0.1710878697460355, -0.970287525247814)); + + std::cout << (camera.ComputeRayDirInInertialFrame(Eigen::Vector2d(0.0, 0.0)).transpose()) << std::endl; + std::cout << (camera.ComputeRayDirInInertialFrame(Eigen::Vector2d(1.0, 0.0)).transpose()) << std::endl; + std::cout << (camera.ComputeRayDirInInertialFrame(Eigen::Vector2d(1.0, 1.0)).transpose()) << std::endl; + std::cout << (camera.ComputeRayDirInInertialFrame(Eigen::Vector2d(1.0, -1.0)).transpose()) << std::endl; + std::cout << (camera.ComputeRayDirInInertialFrame(Eigen::Vector2d(-1.0, -1.0)).transpose()) << std::endl; + + // Check static methods + unsigned int width = 640, height = 480; + EXPECT_VEC_EQ(Camera::PixelToNormalizedCoordinates( 0, 0, width, height), Eigen::Vector2d(-1.0, 1.0)); + EXPECT_VEC_EQ(Camera::PixelToNormalizedCoordinates(height-1, 0, width, height), Eigen::Vector2d(-1.0, -1.0)); + EXPECT_VEC_EQ(Camera::PixelToNormalizedCoordinates(height-1, width-1, width, height), Eigen::Vector2d(1.0, -1.0)); + EXPECT_VEC_EQ(Camera::PixelToNormalizedCoordinates( 0, width-1, width, height), Eigen::Vector2d(1.0, 1.0)); + } + } + + PRINT_TESTS_SUMMARY(); + + return 0; +} diff --git a/cpp/src/tinyimage.cpp b/cpp/src/tinyimage.cpp new file mode 100644 index 0000000..85acd88 --- /dev/null +++ b/cpp/src/tinyimage.cpp @@ -0,0 +1,153 @@ +/* Source file for TinyImage */ + +/* Prevent error C4996 in Visual Studio compilers */ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "tinyimage.h" + +#include +#include +#include // for pow() + +/* The error code */ +TinyImgError _error; + +/* Internal functions */ + +int _equal(const char * str1, const char * str2); +int _tinyimg_check_format(const char * extension); +int _tinyimg_open_file(FILE ** fp, const char * filename); +unsigned char * _tinyimg_load(FILE * fp, const char * extension, int * width, int * height); +unsigned char * _tinyimg_load_bmp(FILE * fp, int * width, int * height); + +int _equal(const char * str1, const char * str2) +{ + return strcmp(str1, str2) == 0; +} + +int _tinyimg_check_format(const char * extension) +{ + return extension != NULL && (_equal(extension, ".bmp")); +} + +int _tinyimg_open_file(FILE ** fp, const char * filename) +{ + *fp = fopen(filename, "rb"); + return *fp != NULL; +} + +unsigned char * _tinyimg_load(FILE * fp, const char * extension, int * width, int * height) +{ + if (_equal(extension, ".bmp")) + return _tinyimg_load_bmp(fp, width, height); +} + +unsigned char * _tinyimg_load_bmp(FILE * fp, int * width, int * height) +{ + unsigned char * image; // image data + unsigned char * palette = NULL; // palette data (if uses palette) + int width_file; // pixels per line in BMP (note that BMP has 4-pixel alignment for each line) + int size; // pixel count + short bit_count; // bit count per pixel (24 if no palette) + int color_count = 0; // colors used (if uses palette) + unsigned char temp; + int count = 0; + + /* Checks if the pointers are not NULL */ + if (width == NULL || height == NULL) + { + _error = TINYIMG_INVALID_ARGUMENT; + return NULL; + } + + fseek(fp, 18, SEEK_SET); + fread(width, sizeof(int), 1, fp); // read width + fread(height, sizeof(int), 1, fp); // read height + + fseek(fp, 28, SEEK_SET); + fread(&bit_count, sizeof(short), 1, fp); // read bit count + + fseek(fp, 54, SEEK_SET); + /* If uses palette (bit count is not 24) */ + if (bit_count != 24) + { + color_count = (int)pow(2.0, bit_count); + palette = (unsigned char *)malloc(sizeof(unsigned char) * 4 * color_count); + fread(palette, sizeof(unsigned char), 4 * color_count, fp); + } + + /* Allocates memory for the image */ + width_file = ((*width) + 3) / 4 * 4; // Increase the width to make sure it's multiple of 4 + size = width_file * (*height); + image = (unsigned char *)malloc(sizeof(unsigned char) * size * 3); + + /* Reads the image data */ + if (bit_count == 24) + fread(image, sizeof(unsigned char), size * 3, fp); + else + { + while (count < size) + { + fread(&temp, sizeof(unsigned char), 1, fp); + image[count * 3] = palette[temp * 4]; + image[count * 3 + 1] = palette[temp * 4 + 1]; + image[count * 3 + 2] = palette[temp * 4 + 2]; + count++; + } + } + + fclose(fp); + return image; +} + +/* External functions */ + +unsigned char * tinyimg_load(const char * filename, int * width, int * height, TinyImgColorType type) +{ + const char * extension; + FILE * fp; + + /* Check if the image format is supported */ + extension = strrchr(filename, '.'); + if (!_tinyimg_check_format(extension)) + { + _error = TINYIMG_FORMAT_UNSUPPORTED; + return NULL; + } + + /* Check if the file exists */ + if (!_tinyimg_open_file(&fp, filename)) + { + _error = TINYIMG_FILE_NOT_FOUND; + return NULL; + } + + return _tinyimg_load(fp, extension, width, height); +} + +void tinyimg_free(unsigned char * image) +{ + free(image); +} + +TinyImgError tinyimg_get_error() +{ + return _error; +} + +const char * tinyimg_get_error_str() +{ + switch (_error) + { + case TINYIMG_OK: + return NULL; + case TINYIMG_FORMAT_UNSUPPORTED: + return "format unsupported"; + case TINYIMG_FILE_NOT_FOUND: + return "file not found"; + case TINYIMG_INVALID_ARGUMENT: + return "invalid argument"; + } +} \ No newline at end of file