Image manipulation almost OK in C++.

This commit is contained in:
Jerome 2023-04-30 14:42:10 +02:00
parent 14dd949f10
commit d3082cb5f4
13 changed files with 381 additions and 302 deletions

13
.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
cpp/obj/*
cpp/stb-master/*
cpp/*.png
cpp/*.jpg
cpp/*.tga
cpp/*.bmp
*.png
*.jpg
*.bmp
*.tga
__pycache__/*
*.pyc
settings.json

View file

@ -1,6 +1,6 @@
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
CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic -g
INCLUDES = -I./include -ID:/Users/Jerome/Documents/Ingenierie/Programmation/eigen-3.4.0 -I./stb-master
SRC_DIR = src
OBJ_DIR = obj

105
cpp/include/Image.hpp Normal file
View file

@ -0,0 +1,105 @@
#ifndef H_Image
#define H_Image
#include <Eigen/Dense>
#include <stb_image.h>
#include <stb_image_write.h>
/// @brief This class encapsulates the raw data of an image.
/// The Image class has a constructor that takes the width, height, and optionally depth of the image, and dynamically allocates a buffer of size width * height * depth to store the image data. The destructor deallocates the buffer to avoid memory leaks.
/// The class provides three accessor methods GetWidth(), GetHeight(), and GetDepth() to query the image dimensions.
/// The GetPixel() method takes the x, y, and z coordinates of the pixel (in row-major order) and returns the corresponding value from the buffer.
/// The SetPixel() method takes the x, y, and z coordinates of the pixel and a value, and sets the corresponding pixel in the buffer to the given value.
/// 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 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.
/// @param depth_ Depth of the image.
Image(int width_, int height_, int depth_ = 1);
/// @brief Frees the allocated memory of the image.
~Image();
/// @brief Loads an image from a file.
/// @param filename Path to the image file.
Image(std::string const& filename);
/// @brief Saves the image to a file.
/// Supports the following formats : PNG, BMP, TGA, JPG. The format is determined from the file extension.
/// @param filename Path to save the image to.
Save(std::string const& filename) const;
int GetWidth() const;
int GetHeight() const;
int GetDepth() const;
/// @brief Fills the image with the given value. All channels are set.
/// @param value Value to fill the image with.
Image & Fill(unsigned char value);
/// @brief Fills the image with the given rgb value.
/// @param rgb RGB value to fill the image with.
Image & Fill(Eigen::Vector3i const& rgb);
/// @brief Returns the value of the chosen channel of the given pixel.
/// @param i Row index.
/// @param j Column index.
/// @param c Channel index.
/// @return The value of the chosen channel of the given pixel.
unsigned char GetPixelValue(int i, int j, int c = 0) const;
/// @brief Sets the value of the chosen channel of the given pixel.
/// @param i Row index.
/// @param j Column index.
/// @param c Channel index.
/// @param value Value of the chosen channel of the given pixel.
void SetPixelValue(int i, int j, int c, unsigned char value);
/// @brief Returns a mutable reference to the chosen channel of the given pixel.
/// @param i Row index.
/// @param j Column index.
/// @param c Channel index.
/// @return A mutable reference to the value of the chosen channel of the given pixel.
unsigned char & PixelValue(int i, int j, int c = 0);
/// @brief Returns the given pixel as a 3D-vector containing the RGB values.
/// @param i Row index.
/// @param j Column index.
/// @return Given pixel as a 3D-vector containing the RGB values.
Eigen::Vector3i GetPixel(int i, int j) const;
/// @brief Returns the given pixel as a 3D-vector containing the RGB values.
/// @param pos 2D vector containing the row and column indices.
/// @return Given pixel as a 3D-vector containing the RGB values.
Eigen::Vector3i GetPixel(Eigen::Vector2i const& pos) const;
/// @brief Sets the given pixel to the given RGB values.
/// @param i Row index.
/// @param j Column index.
/// @param rgb 3D-vector containing the RGB values.
/// @return Reference to the image.
Image & SetPixel(int i, int j, Eigen::Vector3i const& rgb);
/// @brief Sets the given pixel to the given RGB values.
/// @param pos 2D vector containing the row and column indices.
/// @param rgb 3D-vector containing the RGB values.
/// @return Reference to the image.
Image & SetPixel(Eigen::Vector2i const& pos, Eigen::Vector3i const& rgb);
/// @brief Accesses the value at the given index of the raw image data.
/// @param i Index of the element. Max is (width * height * depth) - 1.
/// @return The value at the given index of the raw image data.
unsigned char operator[](int i) const;
private:
Image(Image const& other) = delete; // Copy constructor (deleted)
int width;
int height;
int depth;
unsigned char* data;
};
#endif

View file

@ -0,0 +1,17 @@
#ifndef H_Slicer360ToPerspective
#define H_Slicer360ToPerspective
#include <Camera.hpp>
/// @brief This class encapsulates the algorithms to convert a 360-degree image to a perspective image.
class Slicer360ToPerspective {
public:
Slicer360ToPerspective();
private:
std::vector<Camera> cameras;//!< A vector of cameras that are used to convert the 360-degree image to perspective images.
};
#endif

View file

@ -1,146 +0,0 @@
/*
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_ */

100
cpp/src/Image.cpp Normal file
View file

@ -0,0 +1,100 @@
#include <Image.hpp>
Image::Image(int width_, int height_, int depth_) : width(width_), height(height_), depth(depth_) {
data = new unsigned char[width * height * depth];
}
Image::~Image() {
delete[] data;
}
Image::Image(std::string const& filename) {
// Load image data using stb_image.h
unsigned char* image_data = stbi_load(filename.c_str(), &width, &height, &depth, 0);
// Allocate memory using new[] and copy the data from image.data. This avoids allocating memory with malloc and freeing it with delete.
if (image_data != nullptr) {
data = new unsigned char[width * height * depth];
std::memcpy(data, image_data, width * height * depth);
stbi_image_free(image_data);
} else { // If the image loading process has failed
width = 1;
height = 1;
depth = 1;
data = new unsigned char[width * height * depth];
data[0] = 0;
}
}
int Image::Save(std::string const& filename) const {
if(filename.size() > 4 && filename.substr(filename.size() - 4) == ".png")
return stbi_write_png(filename.c_str(), width, height, depth, (const void *)data, 0);
if(filename.size() > 4 && filename.substr(filename.size() - 4) == ".jpg")
return stbi_write_jpg(filename.c_str(), width, height, depth, (const void *)data, 100);
if(filename.size() > 4 && filename.substr(filename.size() - 4) == ".bmp")
return stbi_write_bmp(filename.c_str(), width, height, depth, (const void *)data);
if(filename.size() > 4 && filename.substr(filename.size() - 4) == ".tga")
return stbi_write_tga(filename.c_str(), width, height, depth, (const void *)data);
return -1;// If the file extension is not supported
}
int Image::GetWidth() const {
return width;
}
int Image::GetHeight() const {
return height;
}
int Image::GetDepth() const {
return depth;
}
Image & Image::Fill(unsigned char value) {
std::memset(data, value, width * height * depth);
return *this;
}
Image & Image::Fill(Eigen::Vector3i const& rgb) {
for(int i = 0 ; i < height ; i++)
for(int j = 0 ; j < width ; j++)
SetPixel(i, j, rgb);
return *this;
}
unsigned char Image::GetPixelValue(int i, int j, int c) const {
return data[i*width*depth + j*depth + c];
}
void Image::SetPixelValue(int i, int j, int c, unsigned char value) {
data[i*width*depth + j*depth + c] = value;
}
unsigned char & Image::PixelValue(int i, int j, int c) {
return data[i*width*depth + j*depth + c];
}
Eigen::Vector3i Image::GetPixel(int i, int j) const {
int index = i*width*depth + j*depth;
return Eigen::Vector3i((int)data[index], (int)data[index + 1], (int)data[index + 2]);
}
Eigen::Vector3i Image::GetPixel(Eigen::Vector2i const& pos) const {
return GetPixel(pos[0], 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]);
SetPixelValue(i, j, 2, (unsigned char)rgb[2]);
return *this;
}
Image & Image::SetPixel(Eigen::Vector2i const& pos, Eigen::Vector3i const& rgb) {
SetPixel(pos[0], pos[1], rgb);
return *this;
}
unsigned char Image::operator[](int i) const {
return data[i];
}

View file

@ -0,0 +1,6 @@
#include <Slicer360ToPerspective.hpp>
Slicer360ToPerspective::Slicer360ToPerspective()
{
}

6
cpp/src/main.cpp.nope Normal file
View file

@ -0,0 +1,6 @@
#include <iostream>
int main() {
std::cout << "Hello, World!\n";
return 0;
}

2
cpp/src/stb_image.cpp Normal file
View file

@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

View file

@ -0,0 +1,2 @@
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

View file

@ -1,5 +1,6 @@
#include <iostream>
#include <Camera.hpp>
#include <Image.hpp>
#define _USE_MATH_DEFINES
#include <cmath>
@ -129,7 +130,7 @@ int main() {
camera.SetFOV(20*DEG2RAD);
camera.SetUp(Eigen::Vector3d(0, 0, 1));
camera.SetTarget(Eigen::Vector3d(1.,.5,.3));
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));
@ -166,6 +167,105 @@ int main() {
}
}
{// Image
std::cout << "Image\n";
{
std::cout << "Constructors\n";
Image image1(640, 480);
EXPECT_EQ(image1.GetWidth(), 640);
EXPECT_EQ(image1.GetHeight(), 480);
EXPECT_EQ(image1.GetDepth(), 1);
Image image2(1080, 720, 3);
EXPECT_EQ(image2.GetWidth(), 1080);
EXPECT_EQ(image2.GetHeight(), 720);
EXPECT_EQ(image2.GetDepth(), 3);
}
{
std::cout << "Pixel accessors\n";
Image image(20, 10, 3);
image.SetPixelValue(5, 7, 0, 69);
image.SetPixelValue(5, 7, 1, 100);
image.SetPixelValue(5, 7, 2, 42);
EXPECT_EQ(image.GetPixelValue(5, 7, 0), 69);
EXPECT_EQ(image.GetPixelValue(5, 7, 1), 100);
EXPECT_EQ(image.GetPixelValue(5, 7, 2), 42);
// Test reference accessor
image.PixelValue(5, 7, 0) = 13;
image.PixelValue(5, 7, 1) = 37;
image.PixelValue(5, 7, 2) = 74;
EXPECT_EQ(image.PixelValue(5, 7, 0), 13);
EXPECT_EQ(image.PixelValue(5, 7, 1), 37);
EXPECT_EQ(image.PixelValue(5, 7, 2), 74);
// Test Vector accessors
EXPECT_EQ(image.GetPixel(5, 7), Eigen::Vector3i(13, 37, 74));
EXPECT_EQ(image.GetPixel(Eigen::Vector2i(5, 7)), Eigen::Vector3i(13, 37, 74));
image.SetPixel(5, 7, Eigen::Vector3i(1, 2, 3));
EXPECT_EQ(image.GetPixel(5, 7), Eigen::Vector3i(1, 2, 3));
image.SetPixel(Eigen::Vector2i(6, 8), Eigen::Vector3i(3, 2, 1));
EXPECT_EQ(image.GetPixel(Eigen::Vector2i(6, 8)), Eigen::Vector3i(3, 2, 1));
}
{
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));
}
}
}
{
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));
}
}
}
{
std::cout << "Image loading\n";
Image image("../test_img_4x3.png");
EXPECT_EQ(image.GetWidth(), 4);
EXPECT_EQ(image.GetHeight(), 3);
EXPECT_EQ(image.GetDepth(), 3);
for(int i = 0; i < image.GetHeight(); ++i) {
for(int j = 0; j < image.GetWidth(); ++j) {
auto pixel = image.GetPixel(i, j);
std::cout << i << " " << j << " (" << pixel(0)/255.0 << ", " << pixel(1)/255.0 << ", " << pixel(2)/255.0 << ")\n";
}
}
for(int i = 0 ; i < image.GetWidth()*image.GetHeight()*image.GetDepth() ; i++)
std::cout << i << "\t" << (int)(image[i]) << "\t" << (image[i]/255.0) << "\n";
}
{
std::cout << "Image saving\n";
Image image(512, 256, 3);
for(int i = 0; i < image.GetHeight(); ++i) {
for(int j = 0; j < image.GetWidth(); ++j) {
image.SetPixel(i, j, Eigen::Vector3i(127, i%256, j%256));
}
}
image.Save("test_img_512x256.png");
image.Save("test_img_512x256.jpg");
image.Save("test_img_512x256.bmp");
image.Save("test_img_512x256.tga");
}
}
PRINT_TESTS_SUMMARY();
return 0;

View file

@ -1,153 +0,0 @@
/* 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";
}
}

27
test_interpolation.py Normal file
View file

@ -0,0 +1,27 @@
import numpy as np
import matplotlib.pyplot as plt
def interp2_normalized(f00, f10, f01, f11, x, y):
a00 = f00
a10 = f10 - f00
a01 = f01 - f00
a11 = f11 - f10 - f01 + f00
return a00 + a10*x + a01*y + a11*x*y
def interp2(x0, y0, x1, y1, f00, f10, f01, f11, x, y):
x_n = (x - x0)/(x1 - x0)
y_n = (y - y0)/(y1 - y0)
return interp2_normalized(f00, f10, f01, f11, x_n, y_n)
if __name__ == '__main__':
f00, f10, f01, f11 = 0, 1, 2, -3
N = 101
F = np.zeros((N,N))
for i in range(N):
for j in range(N):
F[i,j] = interp2_normalized(f00, f10, f01, f11, i/(N-1), j/(N-1))
plt.figure()
plt.imshow(F)
plt.colorbar()
plt.show()