LensSerious 0.1
Lens-correction mathematics as data, not as a library of callbacks
Loading...
Searching...
No Matches
lensserious.c File Reference
#include "lensserious.h"
#include "lensserious_eval.h"
#include <float.h>
#include <math.h>
#include <string.h>
+ Include dependency graph for lensserious.c:

Go to the source code of this file.

Data Structures

struct  spline_t
 

Macros

#define LS_RESTRICT
 
#define LS_GEOM_SENTINEL   1.6e16f
 

Typedefs

typedef struct spline_t spline_t
 

Functions

static float _interpolate (float y1, float y2, float y3, float y4, float t)
 
static void _spline_init (spline_t *s)
 
static void _spline_insert (spline_t *s, float dist, const void *val)
 
static int _interp_real_focal (const ls_lens_t *lens, float focal, float *res)
 
static float _hugin_focal_correction (const ls_calib_dist_t *dist, int have_dist)
 
static int _interp_dist (const ls_lens_t *lens, float focal, ls_calib_dist_t *res)
 
static int _interp_tca (const ls_lens_t *lens, float focal, ls_calib_tca_t *res)
 
static float _vig_dist (const ls_lens_t *lens, const ls_calib_vig_t *c, float focal, float aperture, float distance)
 
static int _interp_vig (const ls_lens_t *lens, float focal, float aperture, float distance, ls_calib_vig_t *res)
 
int ls_modifier_init (ls_modifier_t *mod, const ls_lens_t *lens, float crop, int width, int height, float focal, float aperture, float distance, float scale, int target_type, int flags, int reverse)
 Resolve a lens at one (crop, geometry, focal, aperture, distance, scale).
 
static float _autoscale_residual (const ls_eval_t *p, const float max_x, const float max_y, const float x, const float y)
 
static float _autoscale_distance (const ls_eval_t *p, const float ca, const float sa, const float dist, const float max_x, const float max_y)
 
static int _knot_axis_ascends (const float *xs, const int n)
 
int ls_modifier_init_knots (ls_modifier_t *mod, const ls_knots_t *knots, int width, int height, float scale, int flags, int reverse)
 Resolve a maker's embedded profile, in place of a database lens.
 
float ls_modifier_autoscale (const ls_modifier_t *mod)
 The scale that just removes the black borders a correction leaves behind.
 
int ls_modifier_set_projection (ls_modifier_t *mod, const int from_type, const int to_type, const float focal_mm, const float crop_factor)
 Add a projection change to an already-resolved modifier.
 
int ls_eval_from_modifier (const ls_modifier_t *mod, ls_eval_t *out)
 Flatten a resolved modifier into the scalar block a kernel can take by value.
 
int ls_eval_adopt_vignetting (ls_eval_t *dst, const ls_eval_t *src)
 Move src's vignetting into dst, leaving dst's geometry untouched.
 
int ls_modifier_apply_subpixel_geometry (const ls_modifier_t *mod, float xu, float yu, int width, int height, float *res)
 The geometry map: for count output pixels starting at (xu, yu), write 6 floats per pixel — source coordinates for R, G, B — in pixel space. Bit-for-bit the contract of lfModifier::ApplySubpixelGeometryDistortion(), so a caller can compare the two buffers element-wise.
 
int ls_modifier_apply_vignetting (const ls_modifier_t *mod, float xu, float yu, int width, int height, float *rgba, int row_stride_bytes)
 pa vignetting, multiplied in place over RGBA float rows. Contract of lfModifier::ApplyColorModification(LF_CR_4(RED,GREEN,BLUE,UNKNOWN), F32).
 

Macro Definition Documentation

◆ LS_GEOM_SENTINEL

#define LS_GEOM_SENTINEL   1.6e16f

Definition at line 486 of file lensserious.c.

Referenced by _autoscale_distance().

◆ LS_RESTRICT

#define LS_RESTRICT

Definition at line 22 of file lensserious.c.

Referenced by ls_modifier_apply_vignetting().

Typedef Documentation

◆ spline_t

typedef struct spline_t spline_t

Function Documentation

◆ _autoscale_distance()

static float _autoscale_distance ( const ls_eval_t * p,
const float ca,
const float sa,
const float dist,
const float max_x,
const float max_y )
static

Definition at line 491 of file lensserious.c.

References _autoscale_residual(), ls_eval_coord_chain(), LS_FABS, and LS_GEOM_SENTINEL.

Referenced by ls_modifier_autoscale().

◆ _autoscale_residual()

static float _autoscale_residual ( const ls_eval_t * p,
const float max_x,
const float max_y,
const float x,
const float y )
static

Definition at line 466 of file lensserious.c.

Referenced by _autoscale_distance().

◆ _hugin_focal_correction()

static float _hugin_focal_correction ( const ls_calib_dist_t * dist,
int have_dist )
static

Definition at line 114 of file lensserious.c.

References LS_DIST_POLY3, LS_DIST_PTLENS, ls_calib_dist_t::model, and ls_calib_dist_t::terms.

Referenced by ls_modifier_init().

◆ _interp_dist()

static int _interp_dist ( const ls_lens_t * lens,
float focal,
ls_calib_dist_t * res )
static

◆ _interp_real_focal()

static int _interp_real_focal ( const ls_lens_t * lens,
float focal,
float * res )
static

◆ _interp_tca()

static int _interp_tca ( const ls_lens_t * lens,
float focal,
ls_calib_tca_t * res )
static

◆ _interp_vig()

static int _interp_vig ( const ls_lens_t * lens,
float focal,
float aperture,
float distance,
ls_calib_vig_t * res )
static

◆ _interpolate()

static float _interpolate ( float y1,
float y2,
float y3,
float y4,
float t )
static

Definition at line 36 of file lensserious.c.

Referenced by _interp_dist(), _interp_real_focal(), and _interp_tca().

◆ _knot_axis_ascends()

static int _knot_axis_ascends ( const float * xs,
const int n )
static

Definition at line 533 of file lensserious.c.

Referenced by ls_modifier_init_knots().

◆ _spline_init()

static void _spline_init ( spline_t * s)
static

Definition at line 51 of file lensserious.c.

References spline_t::d, and spline_t::v.

Referenced by _interp_dist(), _interp_real_focal(), and _interp_tca().

◆ _spline_insert()

static void _spline_insert ( spline_t * s,
float dist,
const void * val )
static

Definition at line 58 of file lensserious.c.

References spline_t::d, and spline_t::v.

Referenced by _interp_dist(), _interp_real_focal(), and _interp_tca().

◆ _vig_dist()

static float _vig_dist ( const ls_lens_t * lens,
const ls_calib_vig_t * c,
float focal,
float aperture,
float distance )
static

◆ ls_eval_adopt_vignetting()

int ls_eval_adopt_vignetting ( ls_eval_t * dst,
const ls_eval_t * src )

Move src's vignetting into dst, leaving dst's geometry untouched.

For a consumer whose axes do not all come from the same place – an Olympus body embeds distortion and lateral CA but no vignetting, so the falloff has to come from the database while the geometry comes from the file. Resolve one modifier per source, flatten both, and graft the vignetting across.

It is a graft rather than a merge because the two halves of ls_eval_t are genuinely independent: vignetting reads vig_scale, vig_center_x/y, vig_model and either vig_terms or the vignetting knot table, and NOTHING a coordinate transform touches. In particular each half carries its own normalization, which is what makes this safe across resolvers that do not share one – the table path measures radius against the half diagonal, the database path against lensfun's short side, and neither has to know about the other.

Cheaper than the alternative, too: a second block would double what a kernel receives by value, and ls_eval_t is already 632 bytes against the 1024 that OpenCL 1.2 guarantees for a kernel's whole argument list.

Returns
0 if either pointer is NULL, 1 otherwise.

Definition at line 818 of file lensserious.c.

References ls_eval_t::enabled, ls_eval_t::knot_v, ls_eval_t::knot_vn, ls_eval_t::knot_vr, LS_EVAL_ENABLE_VIGNETTING, LS_MAX_KNOTS, ls_eval_t::vig_center_x, ls_eval_t::vig_center_y, ls_eval_t::vig_model, ls_eval_t::vig_scale, and ls_eval_t::vig_terms.

◆ ls_eval_from_modifier()

int ls_eval_from_modifier ( const ls_modifier_t * mod,
ls_eval_t * out )

Flatten a resolved modifier into the scalar block a kernel can take by value.

This is the whole point of the exercise: a correction crosses to the GPU as one fixed 632-byte block that every work-item evaluates for itself, instead of a six-float-per-pixel map that the CPU builds single-threaded and then uploads (measured at 278 ms plus 576 MB of transfer for a 24 Mpx frame). Fixed regardless of which kind of correction it carries – a maker's table is a hundred-odd more floats, and the whole block is still smaller than one row of that map.

Returns
0 if either pointer is NULL, 1 otherwise.

Definition at line 745 of file lensserious.c.

References ls_modifier_t::aspect_ratio_correction, ls_eval_t::center_x, ls_modifier_t::center_x, ls_eval_t::center_y, ls_modifier_t::center_y, ls_modifier_t::dist, ls_eval_t::dist_model, ls_eval_t::dist_terms, ls_eval_t::enabled, ls_modifier_t::enabled, ls_eval_t::geom_focal, ls_modifier_t::geom_focal, ls_eval_t::geom_from, ls_modifier_t::geom_from, ls_eval_t::geom_to, ls_modifier_t::geom_to, ls_eval_t::knot_axes, ls_modifier_t::knot_axes, ls_eval_t::knot_c, ls_modifier_t::knot_c, ls_eval_t::knot_n, ls_modifier_t::knot_n, ls_eval_t::knot_r, ls_modifier_t::knot_r, ls_eval_t::knot_v, ls_modifier_t::knot_v, ls_eval_t::knot_vn, ls_modifier_t::knot_vn, ls_eval_t::knot_vr, ls_modifier_t::knot_vr, LS_ENABLE_TCA, LS_EVAL_TCA_LINEAR, ls_calib_dist_t::model, ls_calib_tca_t::model, ls_calib_vig_t::model, ls_eval_t::norm_scale, ls_modifier_t::norm_scale, ls_eval_t::norm_unscale, ls_modifier_t::norm_unscale, ls_eval_t::reverse, ls_modifier_t::reverse, ls_eval_t::scale, ls_modifier_t::scale, ls_modifier_t::tca, ls_eval_t::tca_model, ls_eval_t::tca_terms, ls_calib_dist_t::terms, ls_calib_tca_t::terms, ls_calib_vig_t::terms, ls_modifier_t::vig, ls_eval_t::vig_center_x, ls_eval_t::vig_center_y, ls_eval_t::vig_model, ls_eval_t::vig_scale, and ls_eval_t::vig_terms.

Referenced by ls_modifier_apply_subpixel_geometry(), ls_modifier_apply_vignetting(), and ls_modifier_autoscale().

◆ ls_modifier_apply_subpixel_geometry()

int ls_modifier_apply_subpixel_geometry ( const ls_modifier_t * mod,
float xu,
float yu,
int width,
int height,
float * res )

The geometry map: for count output pixels starting at (xu, yu), write 6 floats per pixel — source coordinates for R, G, B — in pixel space. Bit-for-bit the contract of lfModifier::ApplySubpixelGeometryDistortion(), so a caller can compare the two buffers element-wise.

Definition at line 844 of file lensserious.c.

References ls_eval_from_modifier(), and ls_eval_map().

◆ ls_modifier_apply_vignetting()

int ls_modifier_apply_vignetting ( const ls_modifier_t * mod,
float xu,
float yu,
int width,
int height,
float * rgba,
int row_stride_bytes )

pa vignetting, multiplied in place over RGBA float rows. Contract of lfModifier::ApplyColorModification(LF_CR_4(RED,GREEN,BLUE,UNKNOWN), F32).

Definition at line 871 of file lensserious.c.

References ls_modifier_t::enabled, LS_ENABLE_VIGNETTING, ls_eval_from_modifier(), ls_eval_vignette_from_r2(), LS_RESTRICT, ls_eval_t::vig_center_x, ls_eval_t::vig_center_y, and ls_eval_t::vig_scale.

◆ ls_modifier_autoscale()

float ls_modifier_autoscale ( const ls_modifier_t * mod)

The scale that just removes the black borders a correction leaves behind.

Parameters
moda resolved modifier. Its own scale factor, if it has one, is part of the transform being measured – exactly as upstream measures whatever callbacks are registered at the time.
Returns
the linear factor to feed back as ls_modifier_init()'s scale, or 1.0 when the modifier transforms no coordinates at all.

Ported from lfModifier::GetAutoScale(). Eight points around the frame – the four edge midpoints and the four corners – are pushed through the coordinate chain, and the largest ratio of where a point should be to where it landed is the scale. It is a measurement, not a formula: there is no closed form for "where does the corner of a distorted, reprojected frame end up", so each point is found by Newton iteration on the chain itself with a numeric derivative.

Two upstream constants are kept rather than tidied, because they are what its renders were produced with: a flat 1.001 margin ("1 permille is our limit of accuracy"), and a second 1.001 when TCA is active, since the per-channel radii extend slightly past the green one that was measured.

Note
Where a point cannot be found – ultrawide fisheye corners extending to infinity – upstream lets the iteration fail and the point simply loses the max(). Same here.

Definition at line 662 of file lensserious.c.

References _autoscale_distance(), ls_modifier_t::enabled, ls_modifier_t::height, LS_ENABLE_DISTORTION, LS_ENABLE_GEOMETRY, LS_ENABLE_SCALE, LS_ENABLE_TCA, ls_eval_from_modifier(), ls_modifier_t::norm_scale, ls_modifier_t::reverse, and ls_modifier_t::width.

◆ ls_modifier_init()

int ls_modifier_init ( ls_modifier_t * mod,
const ls_lens_t * lens,
float crop,
int width,
int height,
float focal,
float aperture,
float distance,
float scale,
int target_type,
int flags,
int reverse )

Resolve a lens at one (crop, geometry, focal, aperture, distance, scale).

Parameters
modfilled in by this call; nothing in it is owned or must be freed.
lensthe lens, as data.
cropthe crop factor of the sensor the picture was TAKEN with.
width,heightthe image dimensions, in pixels.
focal,aperture,distancethe shooting configuration.
scalea linear scaling factor; 1.0 for none.
target_typethe projection the output should be in, as ls_lens_type_t. Pass the lens's own type (or LS_LENS_UNKNOWN) for no projection change; LS_ENABLE_GEOMETRY is only raised when it actually differs and the pair is radially expressible.
flagswhich LS_ENABLE_* axes to attempt.
reversenon-zero to resolve the REVERSE direction – the transform that takes a corrected coordinate back to where it came from in the source image, which is what a consumer needs to place masks and drawn shapes on an image it is correcting. It is not the same chain read backwards: upstream registers different callbacks at different priorities, so the composition order changes too (see ls_eval_t::reverse). An axis whose model cannot be inverted at these coefficients – poly3 distortion with k1 = 0, linear TCA with a zero term – is dropped from the returned flags exactly as upstream drops it.
Returns
the LS_ENABLE_* flags actually in effect, mirroring lensfun's oflags.

Definition at line 268 of file lensserious.c.

References _hugin_focal_correction(), _interp_dist(), _interp_real_focal(), _interp_tca(), _interp_vig(), ls_lens_t::aspect_ratio, ls_modifier_t::aspect_ratio_correction, ls_lens_t::center_x, ls_modifier_t::center_x, ls_lens_t::center_y, ls_modifier_t::center_y, ls_lens_t::crop_factor, ls_modifier_t::dist, ls_modifier_t::enabled, ls_modifier_t::geom_focal, ls_modifier_t::geom_from, ls_modifier_t::geom_to, ls_modifier_t::geometry_unsupported, ls_modifier_t::height, LS_DIST_POLY3, LS_ENABLE_DISTORTION, LS_ENABLE_GEOMETRY, LS_ENABLE_SCALE, LS_ENABLE_TCA, LS_ENABLE_VIGNETTING, LS_EVAL_FULL_FRAME_HALF_DIAG_MM, LS_LENS_EQUIRECTANGULAR, LS_LENS_PANORAMIC, LS_LENS_UNKNOWN, LS_TCA_LINEAR, ls_calib_dist_t::model, ls_calib_tca_t::model, ls_modifier_t::norm_scale, ls_modifier_t::norm_unscale, ls_modifier_t::reverse, ls_modifier_t::scale, ls_modifier_t::tca, ls_calib_dist_t::terms, ls_calib_tca_t::terms, ls_lens_t::type, ls_modifier_t::vig, and ls_modifier_t::width.

◆ ls_modifier_init_knots()

int ls_modifier_init_knots ( ls_modifier_t * mod,
const ls_knots_t * knots,
int width,
int height,
float scale,
int flags,
int reverse )

Resolve a maker's embedded profile, in place of a database lens.

Parameters
modfilled in by this call; nothing in it is owned or must be freed.
knotsthe table, as lifted from the file's metadata. Copied, not retained.
width,heightthe image dimensions, in pixels.
scalea linear scaling factor; 1.0 for none.
flagswhich LS_ENABLE_* axes to attempt. LS_ENABLE_TCA has no meaning here and is ignored: a maker's table is already per channel, so asking for distortion asks for the chromatic part of it too. LS_ENABLE_GEOMETRY likewise – there is no projection to change.
reversenon-zero for the reverse direction, as in ls_modifier_init().
Returns
the LS_ENABLE_* flags actually in effect. An axis with no knots is dropped.

The counterpart to ls_modifier_init(), and deliberately the only difference between the two paths: what comes out is an ls_modifier_t like any other, which ls_eval_from_modifier() flattens like any other, which the same evaluator and the same kernels consume like any other. A consumer that already corrects lenses from the database gains embedded profiles by choosing a different resolver – not by growing a second pixel path, and not by touching the one it has.

That is possible because the models agree on what a correction IS. Every one of them, polynomial or tabulated, answers the same question: at this radius, by what factor is the coordinate scaled. So the table takes the distortion slot in the chain and nothing else moves – scale still composes around it in priority order, and the caller can still ask ls_modifier_autoscale() what scale removes the borders, because that measures the chain rather than the model.

Note
reverse costs nothing per pixel, unlike the polynomial models. Inverting r -> r*cor(r) has no closed form for a polynomial, so those models pay a Newton iteration at every pixel; a piecewise-linear curve inverts by reading the same points the other way round – (r*cor(r), 1/cor(r)) – once, at this call, into a table the same size.

That inverse is exact AT the knots and second-order between them, since a segment that is straight going forwards is not straight coming back. Measured on a nine-knot profile with 2% of distortion at the corner, over a 6000x4000 frame: correcting a point and then un-correcting it returns it to within 0.13 px at worst, and the worst case sits at r = 0.94, mid-segment, exactly where the reasoning says it should. For scale, that is a sixth of the 0.8 px by which liblensfun's own vectorised row walk differs from liblensfun's own scalar answer. tests/knots.c is where the number comes from and will fail if it grows past a quarter of a pixel.

Note
The three channels' inverses land on three different sets of radii, which is why ls_eval_t carries a radius axis per channel where this input carries one shared. Forcing them back onto a common axis would mean resampling two of the three, and there is nothing to gain by it – the tables are already resident in the block either way.
flags decides which axes the table serves, and the distortion/TCA pair is not free. Pass both and the three per-channel curves are evaluated as measured. Pass DISTORTION alone and every channel follows the GREEN curve – the aberration is stripped exactly, since green IS the geometry the maker measured and the other two are defined relative to it – leaving another TCA model free to run after.

Passing TCA ALONE is accepted but does nothing useful, and deliberately so: there is no separate aberration in the table to apply. It would have to be the ratio of each channel's curve to green, resampled onto whatever radius axis the other resolver normalizes against – a different convention again (see ls_modifier_set_projection()) – and it would be the maker's departure from a geometry that is not theirs. A consumer wanting that combination should be prepared to answer the physical objection before the mechanical one.

Definition at line 540 of file lensserious.c.

References _knot_axis_ascends(), ls_modifier_t::aspect_ratio_correction, ls_modifier_t::center_x, ls_modifier_t::center_y, ls_knots_t::cor_rgb, ls_modifier_t::dist, ls_modifier_t::enabled, ls_modifier_t::geom_focal, ls_modifier_t::geom_from, ls_modifier_t::geom_to, ls_modifier_t::height, ls_modifier_t::knot_axes, ls_modifier_t::knot_c, ls_modifier_t::knot_n, ls_modifier_t::knot_r, ls_modifier_t::knot_v, ls_modifier_t::knot_vn, ls_modifier_t::knot_vr, LS_DIST_KNOTS, LS_ENABLE_DISTORTION, LS_ENABLE_SCALE, LS_ENABLE_TCA, LS_ENABLE_VIGNETTING, LS_LENS_UNKNOWN, LS_MAX_KNOTS, LS_VIG_KNOTS, ls_calib_dist_t::model, ls_calib_vig_t::model, ls_knots_t::n, ls_modifier_t::norm_scale, ls_modifier_t::norm_unscale, ls_knots_t::radius, ls_modifier_t::reverse, ls_modifier_t::scale, ls_knots_t::vig, ls_modifier_t::vig, ls_knots_t::vig_radius, ls_knots_t::vn, and ls_modifier_t::width.

◆ ls_modifier_set_projection()

int ls_modifier_set_projection ( ls_modifier_t * mod,
int from_type,
int to_type,
float focal_mm,
float crop_factor )

Add a projection change to an already-resolved modifier.

Parameters
moda modifier from either resolver. Its coordinate system is used as-is.
from_typethe projection the LENS produces, as ls_lens_type_t.
to_typethe projection wanted, as ls_lens_type_t.
focal_mmthe focal the projection runs on. For a lens carrying <real-focal-length> this is NOT the engraved focal – see ls_modifier_init(), which derives it and would pass the derived value.
crop_factorthe crop of the sensor whose HALF DIAGONAL this modifier's coordinates are normalized against. For a table-resolved modifier that is the camera that took the picture; for a database-resolved one it is the sensor the lens was CALIBRATED on, because the shooting crop is already folded into its norm_scale.
Returns
non-zero if a projection change was enabled.

ls_modifier_init() does this itself; this is for the other resolver. A maker's embedded table describes the lens in the projection it shipped with and says nothing about any other, so ls_modifier_init_knots() leaves both endpoints UNKNOWN – but a consumer that ALSO knows the lens's type, typically from a database entry matched alongside, can put the projection back and get the two composed.

That composes because the projection stage reads only the working radius and ls_modifier_t::geom_focal, and the one formula covers both coordinate systems:

geom_focal = focal_mm * crop * aspect_ratio_correction / (half of a 36x24 diagonal)

The aspect_ratio_correction in it is exactly what converts half-diagonal units into the half-short-side ones ls_modifier_init() normalizes to; a table-resolved modifier stores 1.0 there because it already measures radius against the half diagonal, so the same expression lands in the right units without a special case.

Note
Panoramic and equirectangular map x and y differently on either side, so a change involving one is declined rather than approximated: ls_modifier_t::geometry_unsupported is raised, the stage stays off, and this returns 0.

Definition at line 712 of file lensserious.c.

References ls_modifier_t::aspect_ratio_correction, ls_modifier_t::enabled, ls_modifier_t::geom_focal, ls_modifier_t::geom_from, ls_modifier_t::geom_to, ls_modifier_t::geometry_unsupported, LS_ENABLE_GEOMETRY, LS_EVAL_FULL_FRAME_HALF_DIAG_MM, LS_LENS_EQUIRECTANGULAR, LS_LENS_PANORAMIC, and LS_LENS_UNKNOWN.