Initial C++ version. Camera partially implemented.

This commit is contained in:
Jerome 2023-04-30 10:52:23 +02:00
parent 269d3c2fe6
commit f1a2c4860f
8 changed files with 726 additions and 0 deletions

26
cpp/Makefile Normal file
View file

@ -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)

77
cpp/include/Camera.hpp Normal file
View file

@ -0,0 +1,77 @@
#ifndef H_Camera
#define H_Camera
#include <Eigen/Dense>
#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

View file

@ -0,0 +1,35 @@
#ifndef H_frame_conversions
#define H_frame_conversions
#include <Eigen/Dense>
#include <cmath>
/// @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

146
cpp/include/tinyimage.h Normal file
View file

@ -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 <vector>
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<unsigned char> 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_ */

104
cpp/src/Camera.cpp Normal file
View file

@ -0,0 +1,104 @@
#include <Camera.hpp>
#define _USE_MATH_DEFINES
#include <cmath>
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);
}

View file

@ -0,0 +1,39 @@
#include <frame_conversions.hpp>
#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<int>();
}

146
cpp/src/tests.cpp Normal file
View file

@ -0,0 +1,146 @@
#include <iostream>
#include <Camera.hpp>
#define _USE_MATH_DEFINES
#include <cmath>
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;
}

153
cpp/src/tinyimage.cpp Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#include <math.h> // 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";
}
}