Initial commit

First (sort-of) working version of a pinhole camera model.

Improvement idea:
- Stretch result image to the whole size
This commit is contained in:
Kevin Trogant 2023-04-06 18:54:30 +02:00
commit ffbe68606b
16 changed files with 10437 additions and 0 deletions

16
.clang-format Normal file
View File

@ -0,0 +1,16 @@
---
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakStringLiterals: 'false'
ColumnLimit: '120'
IndentCaseLabels: 'false'
IndentPPDirectives: None
IndentWidth: '4'
BreakBeforeBraces: 'Custom'
BraceWrapping:
AfterEnum: 'true'
AfterStruct: 'true'
AfterFunction: 'true'
SortIncludes: 'false'
...

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.o
builddir
.cache

7987
3p/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

1724
3p/stb_image_write.h Normal file

File diff suppressed because it is too large Load Diff

3
README.md Normal file
View File

@ -0,0 +1,3 @@
Defocus Modules

123
bin/defocus.c Normal file
View File

@ -0,0 +1,123 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <defocus/defocus.h>
int pinhole_fn(int argc, char **argv);
static const char *_model_names[] = {"pinhole"};
typedef int (*model_fn)(int argc, char **argv);
static model_fn _model_fns[] = {
pinhole_fn,
};
void usage(const char *pname)
{
printf("Usage: %s <model-name> [model parameters]\n", pname);
printf("Available models:\n");
for (int i = 0; i < DF_ARRAY_COUNT(_model_names); ++i) {
printf(" %s\n", _model_names[i]);
}
}
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "Missing model name!\n");
usage(argv[0]);
return 1;
}
model_fn fn = NULL;
const char *model = argv[1];
for (int i = 0; i < DF_ARRAY_COUNT(_model_names); ++i) {
if (strcmp(model, _model_names[i]) == 0) {
fn = _model_fns[i];
break;
}
}
if (!fn) {
fprintf(stderr, "Invalid model name!\n");
usage(argv[0]);
return 1;
}
return fn(argc - 2, &argv[2]);
}
int pinhole_fn(int argc, char **argv)
{
const char *in_path = NULL;
const char *out_path = "out.png";
const char *focal_str = NULL;
const char *in_z_str = NULL;
const char *out_z_str = NULL;
for (int i = 0; i < argc; ++i) {
if ((strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--input") == 0) && i < argc - 1) {
++i;
in_path = argv[i];
} else if ((strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) && i < argc - 1) {
++i;
out_path = argv[i];
} else if ((strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--focal-length") == 0) && i < argc - 1) {
++i;
focal_str = argv[i];
} else if ((strcmp(argv[i], "-iz") == 0 || strcmp(argv[i], "--input-z") == 0) && i < argc - 1) {
++i;
in_z_str = argv[i];
} else if ((strcmp(argv[i], "-oz") == 0 || strcmp(argv[i], "--output-z") == 0) && i < argc - 1) {
++i;
out_z_str = argv[i];
} else {
in_path = argv[i];
}
}
if (!in_path || !focal_str || !in_z_str || !out_z_str) {
fprintf(stderr, "Missing model parameters!\n");
printf("Pinhole model parameters:\n");
printf(" -i | --input <path>\t\tPath to the input image.\n");
printf(" -o | --output <path>\t\tPath to the output image (Default: out.png).\n");
printf(" -iz | --input-z <number>\t\tDistance to the input image plane.\n");
printf(" -oz | --output-z <number>\t\tDistance to the output image plane.\n");
printf(" -f | --focal-length <number>\t\tThe focal length of the camera lens.\n");
return 1;
}
float focal, in_z, out_z;
focal = atof(focal_str);
in_z = atof(in_z_str);
out_z = atof(out_z_str);
df_image *in_image, *out_image;
int w, h;
df_result res = df_load_image(in_path, &w, &h, &in_image);
if (res != df_result_success) {
fprintf(stderr, "Failed to load %s! (%d)\n", in_path, (int)res);
return 1;
}
res = df_create_image(w, h, &out_image);
if (res != df_result_success) {
fprintf(stderr, "Failed to create output image (%d)\n", (int)res);
df_release_image(in_image);
return 1;
}
df_pinhole(in_image, focal, in_z, out_z, out_image);
int error_code = 0;
res = df_write_image(out_image, out_path);
if (res != df_result_success) {
fprintf(stderr, "Failed to write to output image %s (%d)\n", out_path, (int)res);
error_code = 1;
}
df_release_image(in_image);
df_release_image(out_image);
return error_code;
}

203
include/defocus/base.h Normal file
View File

@ -0,0 +1,203 @@
#ifndef DEFOCUS_BASE_H
#define DEFOCUS_BASE_H
#include <inttypes.h>
#include <string.h>
/** @file base.h
* @brief basic utilities
*/
/** @brief Result codes used throughout the codebase */
typedef enum
{
/** @brief operation successfull */
df_result_success = 0,
/** @brief an opengl error occured */
df_result_gl_error = 1,
/** @brief memory allocation failed */
df_result_out_of_memory = 2,
/** @brief file i/o error */
df_result_io_error = 3,
} df_result;
/** @brief Used to silence warnings about unused variables */
#define DF_UNUSED(x) ((void)sizeof((x)))
/** @brief Evaluates to the number of elements in a static array. */
#define DF_ARRAY_COUNT(a) (sizeof((a)) / sizeof((a)[0]))
/** @brief Zero a memory range */
#define DF_ZERO_MEMORY(ptr, n) memset((ptr), 0, (n))
/** @brief Zero a struct */
#define DF_ZERO_STRUCT(ptr) DF_ZERO_MEMORY(ptr, sizeof(*(ptr)))
/** @brief Zero an array */
#define DF_ZERO_ARRAY(a, n) DF_ZERO_MEMORY(a, sizeof((a)[0]) * (n))
/* Simple logging function */
#ifndef DF_ENABLE_LOGGING
#define DF_ENABLE_LOGGING 1
#endif
enum
{
df_log_level_verbose = 0,
df_log_level_info = 1,
df_log_level_warn = 2,
df_log_level_error = 3,
};
#if DF_ENABLE_LOGGING
/** @brief The actual log implementation.
*
* There should be no need to call this directly.
*
* Instead use one of:
* - df_log_verbose()
* - df_log_info()
* - df_log_warn()
* - df_log_error()
* @param level the log level. Used to filter the messages.
* @param file file name of the log location
* @param line line number of the log location
* @param fmt format string of the message.
*/
void df_log_impl(int level, const char *file, int line, const char *fmt, ...);
#define df_log_verbose(...) df_log_impl(df_log_level_verbose, __FILE__, __LINE__, __VA_ARGS__)
#define df_log_info(...) df_log_impl(df_log_level_info, __FILE__, __LINE__, __VA_ARGS__)
#define df_log_warn(...) df_log_impl(df_log_level_warn, __FILE__, __LINE__, __VA_ARGS__)
#define df_log_error(...) df_log_impl(df_log_level_error, __FILE__, __LINE__, __VA_ARGS__)
#else /* DF_ENABLE_LOGGING = 0 */
#define df_log_verbose(fmt, ...)
#define df_log_info(fmt, ...)
#define df_log_warn(fmt, ...)
#define df_log_error(fmt, ...)
#endif
/* Basic types */
/** @brief RGBA color */
typedef union {
struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
uint8_t e[4];
} df_color;
/** @brief Linear interpolation between two colors.
*
* @param a first color
* @param b second color
* @param t interpolation factor
* @return a + (b - a) * t
*/
df_color df_lerp_color(df_color a, df_color b, double t);
/** @brief 2d vector */
typedef union {
struct
{
float x;
float y;
};
struct
{
float u;
float v;
};
float e[2];
} df_v2;
/** @brief 3d vector */
typedef union {
struct
{
float x;
float y;
float z;
};
float e[3];
} df_v3;
/** @brief Add two 3d vectors */
df_v3 df_add_v3(df_v3 a, df_v3 b);
/** @brief Subtract two 3d vectors */
df_v3 df_sub_v3(df_v3 a, df_v3 b);
/** @brief Calculate the dot product of 3d vectors a and b.
*/
float df_dot_v3(df_v3 a, df_v3 b);
/** @brief Multiply a 3d vector with a scalar.
*/
df_v3 df_mul_v3(float t, df_v3 v);
/** @brief Returns the normalized version of a 3d vector v
*/
df_v3 df_normalize_v3(df_v3 v);
/** @brief A plane in 3d space.
*
* Expressed as the set of all points p for which
* dot((p - base), normal) = 0
*/
typedef struct
{
df_v3 base;
df_v3 normal;
} df_plane;
/** @brief A line in 3d space.
*
* Expressed as the vector equation p = base + t * direction.
*/
typedef struct
{
df_v3 base;
df_v3 direction;
} df_line;
/** @brief The three possible result types of a line-plane intersection test.
*/
typedef enum
{
/** The line and plane intersect in one point */
df_line_plane_intersection_vector,
/** The line is contained in the plane */
df_line_plane_intersection_contained,
/** The line and plane don't intersect (-> they are parallel) */
df_line_plane_intersection_none,
} df_line_plane_intersection_type;
/** @brief Result of a line-plane intersection test.
* The contained vector is only valid if @c type = @c df_line_plane_intersection_vector.
*/
typedef struct
{
df_line_plane_intersection_type type;
df_v3 vec;
} df_line_plane_intersection;
/** @brief Calculate the intersection between a line and a plane in 3d space */
df_line_plane_intersection df_calc_line_plane_intersection(df_line line, df_plane plane);
#endif

11
include/defocus/defocus.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef DEFOCUS_DEFOCUS_H
#define DEFOCUS_DEFOCUS_H
/** @file defocus.h
* @brief meta header
*/
#include "base.h"
#include "image.h"
#include "pinhole.h"
#endif

62
include/defocus/image.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef DEFOCUS_IMAGE_H
#define DEFOCUS_IMAGE_H
/** @file image.h
* @brief image type and functions
*/
#include "base.h"
/** @brief Opaque rgba8 image object */
typedef struct df_image df_image;
/** @brief create an image
*
* The contents of the image will be undefined.
* @param w the image width
* @param h the image height
* @param out_image receives the image object
* @return error code
*/
df_result df_create_image(int w, int h, df_image **out_image);
/** @brief load an image file
*
* The image data will be converted to rgba8
*
* @param w the image width
* @param h the image height
* @param out_image receives the image object
* @return error code
*/
df_result df_load_image(const char *path, int *out_w, int *out_h, df_image **out_image);
/** @brief Write an image to a PNG file
* @param img the image
* @param path the path
*/
df_result df_write_image(df_image *img, const char *path);
/** @brief Free an image.
*
* Any pointer to the image will be invalid after this.
* @param img the image
*/
void df_release_image(df_image *img);
/** @brief Returns the dimensions of the image */
void df_get_image_size(const df_image *image, int *w, int *h);
/** @brief Returns the color value at pixel coordinates x, y
*
* Returns black for coordinates outside the image.
*/
df_color df_get_image_pixel(const df_image *image, int x, int y);
/** @brief Set the color value at pixel coordinates x, y.
*
* Does nothing for coordinates outside the image.
*/
void df_set_image_pixel(df_image *image, int x, int y, df_color c);
#endif

12
include/defocus/pinhole.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef DF_PINHOLE_H
#define DF_PINHOLE_H
/** @file pinhole.h
* @brief Defocus based on pinhole camera model.
*/
#include "image.h"
void df_pinhole(const df_image *in_image, float focal_length, float orig_z, float new_z, df_image *out_image);
#endif

14
lib/color.c Normal file
View File

@ -0,0 +1,14 @@
#include <defocus/base.h>
#include <math.h>
df_color df_lerp_color(df_color a, df_color b, double t)
{
df_color r;
for (int i = 0; i < 4; ++i) {
double af = (double)a.e[i];
double bf = (double)b.e[i];
r.e[i] = (uint8_t)floor(af + (bf - af) * t);
}
return r;
}

110
lib/image.c Normal file
View File

@ -0,0 +1,110 @@
#include <defocus/image.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
struct df_image
{
int width;
int height;
uint8_t *pixels;
};
df_result df_create_image(int w, int h, df_image **out_image)
{
df_image *img = malloc(sizeof(df_image));
if (!img)
return df_result_out_of_memory;
DF_ZERO_STRUCT(img);
img->width = w;
img->height = h;
img->pixels = malloc(4 * sizeof(uint8_t) * w * h);
if (!img->pixels) {
free(img);
return df_result_out_of_memory;
}
memset(img->pixels, 0, 4 * w * h);
*out_image = img;
return df_result_success;
}
df_result df_load_image(const char *path, int *out_w, int *out_h, df_image **out_image)
{
int w, h, c;
stbi_uc *pixels = stbi_load(path, &w, &h, &c, 4);
if (!pixels) {
fprintf(stderr, "ERROR: failed to load %s: %s\n", path, stbi_failure_reason());
return df_result_io_error;
}
df_result res = df_result_success;
df_image *img = malloc(sizeof(df_image));
if (!img) {
res = df_result_out_of_memory;
goto err;
}
DF_ZERO_STRUCT(img);
img->width = w;
img->height = h;
img->pixels = (uint8_t *)pixels;
*out_image = img;
*out_w = w;
*out_h = h;
goto out;
err:
free(img);
out:
return res;
}
void df_release_image(df_image *img)
{
if (img) {
free(img->pixels);
free(img);
}
}
df_result df_write_image(df_image *image, const char *path)
{
df_result res = stbi_write_png(path, image->width, image->height, 4, image->pixels, image->width * 4)
? df_result_success
: df_result_io_error;
return res;
}
void df_get_image_size(const df_image *image, int *w, int *h)
{
if (w)
*w = image->width;
if (h)
*h = image->height;
}
df_color df_get_image_pixel(const df_image *image, int x, int y)
{
df_color c = {0, 0, 0, 255};
if (x >= 0 && x < image->width && y >= 0 && y < image->height) {
memcpy(&c.e[0], &image->pixels[4 * (y * image->width + x)], 4);
}
return c;
}
void df_set_image_pixel(df_image *image, int x, int y, df_color c)
{
if (x >= 0 && x < image->width && y >= 0 && y < image->height) {
memcpy(&image->pixels[4 * (y * image->width + x)], &c.e[0], 4);
}
}

15
lib/log.c Normal file
View File

@ -0,0 +1,15 @@
#include <defocus/base.h>
#include <stdio.h>
#include <stdarg.h>
static const char *log_level_names[] = {"VERBOSE", "INFO", "WARN", "ERROR"};
void df_log_impl(int level, const char *file, int line, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[%s] %s:%d: ", log_level_names[level], file, line);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}

60
lib/math.c Normal file
View File

@ -0,0 +1,60 @@
#include <defocus/base.h>
#include <math.h>
df_v3 df_add_v3(df_v3 a, df_v3 b)
{
df_v3 v = {a.x + b.x, a.y + b.y, a.z + b.z};
return v;
}
df_v3 df_sub_v3(df_v3 a, df_v3 b)
{
df_v3 v = {a.x - b.x, a.y - b.y, a.z - b.z};
return v;
}
float df_dot_v3(df_v3 a, df_v3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
df_v3 df_mul_v3(float t, df_v3 v)
{
df_v3 r = {t * v.x, t * v.y, t * v.z};
return r;
}
df_v3 df_normalize_v3(df_v3 v)
{
float len_square = df_dot_v3(v, v);
float len = sqrtf(len_square);
df_v3 n = {v.x / len, v.y / len, v.z / len};
return n;
}
df_line_plane_intersection df_calc_line_plane_intersection(df_line line, df_plane plane)
{
/* check case */
float dot = df_dot_v3(line.direction, plane.normal);
df_v3 base_diff = df_sub_v3(plane.base, line.base);
float dot2 = df_dot_v3(base_diff, plane.normal);
if (dot != 0) {
float t = dot2 / dot;
df_v3 point = df_add_v3(line.base, df_mul_v3(t, line.direction));
df_line_plane_intersection intersection = {
.type = df_line_plane_intersection_vector,
.vec = point,
};
return intersection;
} else {
if (dot2 == 0) {
df_line_plane_intersection intersection = {
.type = df_line_plane_intersection_contained,
};
return intersection;
} else {
df_line_plane_intersection intersection = {
.type = df_line_plane_intersection_none,
};
return intersection;
}
}
}

76
lib/pinhole.c Normal file
View File

@ -0,0 +1,76 @@
#include <defocus/pinhole.h>
#include <defocus/base.h>
#include <defocus/image.h>
#include <math.h>
void df_pinhole(const df_image *in_image, float focal_length, float orig_z, float new_z, df_image *out_image)
{
/* orig_z is the z-coordinate of the original image plane (=> in_image is located there)
* new_z is the z-coordinate of the output image plane (=> out_image is located there)
*
* We can map from image coordinates to world coordinates, because we know the z coordinate of
* the input image plane and the focal length of the camera.
*
* Let x,y,z be the world coordinates and u, v the image coordintes.
* The pinhole camera model gives us:
* u = f/z * x; v = f/z * y
* => x = u * z / f
* => y = v * z / f
*/
int w, h;
df_get_image_size(in_image, &w, &h);
int out_w, out_h;
df_get_image_size(out_image, &out_w, &out_h);
double in_z_over_f = orig_z / focal_length;
double out_f_over_z = focal_length / new_z;
int prev_out_iy = -1;
for (int iy = 0; iy < h; ++iy) {
double v = (double)iy / (double)h;
double y = in_z_over_f * v;
double out_v = out_f_over_z * y;
int out_iy = (int)floor(out_v * (double)out_h);
int prev_out_ix = -1;
for (int ix = 0; ix < w; ++ix) {
double u = (double)ix / (double)w;
double x = in_z_over_f * u;
double out_u = out_f_over_z * x;
int out_ix = (int)floor(out_u * (double)out_w);
df_color px = df_get_image_pixel(in_image, ix, iy);
df_set_image_pixel(out_image, out_ix, out_iy, px);
/* Go back and interpolate between this pixel and the previous one. */
if ((out_ix - prev_out_ix) > 1) {
df_color prev_px = df_get_image_pixel(out_image, prev_out_ix, out_iy);
for (int d = prev_out_ix + 1; d < out_ix; ++d) {
double t = (double)(d - prev_out_ix) / (double)(out_ix - prev_out_ix);
df_color color = df_lerp_color(prev_px, px, t);
df_set_image_pixel(out_image, d, out_iy, color);
}
}
prev_out_ix = out_ix;
}
/* Go back and interpolate between this row and the previous one */
if ((out_iy - prev_out_iy) > 1) {
for (int y = prev_out_iy + 1; y < out_iy; ++y) {
double t = (double)(y - prev_out_iy) / (double)(out_iy - prev_out_iy);
for (int x = 0; x < out_w; ++x) {
df_color a = df_get_image_pixel(out_image, x, prev_out_iy);
df_color b = df_get_image_pixel(out_image, x, out_iy);
df_color color = df_lerp_color(a, b, t);
df_set_image_pixel(out_image, x, y, color);
}
}
}
prev_out_iy = out_iy;
}
}

18
meson.build Normal file
View File

@ -0,0 +1,18 @@
project('defocus-modules', 'c', default_options: ['c_std=c11'])
incdir = include_directories('include', '3p')
cc = meson.get_compiler('c')
m_dep = cc.find_library('m', required: false)
lib = library('df',
'lib/log.c',
'lib/math.c',
'lib/pinhole.c',
'lib/image.c',
'lib/color.c',
include_directories: incdir,
dependencies: m_dep,
version: '0.1.0',
soversion: '0')
executable('defocus', 'bin/defocus.c', include_directories: incdir, link_with: lib)