LensSerious 0.1
Lens-correction mathematics as data, not as a library of callbacks
Loading...
Searching...
No Matches
lensserious_eval.h
Go to the documentation of this file.
1/*
2 LensSerious — the evaluators, as one source text for the CPU and the GPU.
3
4 Copyright (C) 2026 Aurélien PIERRE.
5 Model evaluators, coordinate conventions and interpolation semantics ported from
6 Lensfun 0.3.4 (libs/lensfun/{modifier,mod-coord,mod-subpix,mod-color}.cpp),
7 Copyright (C) 2007 Andrew Zabolotny and the Lensfun contributors.
8
9 This library is free software: you can redistribute it and/or modify it under the
10 terms of the GNU Lesser General Public License as published by the Free Software
11 Foundation, either version 3 of the License, or (at your option) any later version.
12
13 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
14 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 PURPOSE. See the GNU Lesser General Public License for more details.
16*/
17
38#ifndef LENSSERIOUS_EVAL_H
39#define LENSSERIOUS_EVAL_H
40
41/* Which toolchain is compiling this. All three spellings, because no single one is
42 * dependable: OpenCL mandates __OPENCL_VERSION__ for RUNTIME compilation (clBuildProgram,
43 * which is how Ansel loads this), while clang's offline OpenCL mode -- the only way to
44 * syntax-check the kernel without a device, and what tests/ uses -- defines
45 * __OPENCL_C_VERSION__ and not the other. Guarding on one alone means the file silently
46 * takes the host branch under the other and tries to #include <math.h> into a kernel. */
47#if defined(__OPENCL_VERSION__) || defined(__OPENCL_C_VERSION__) || defined(__OPENCL__)
48 #define LS_EVAL_IS_OPENCL 1
49#endif
50
51/* Every transcendental goes through one of these, and the reason is single precision.
52 *
53 * In OpenCL C these names ARE the float overloads. In C they are the DOUBLE functions:
54 * `atan(some_float)` promotes its argument to double, evaluates in double, and truncates
55 * the result back on assignment. That is slower than the float form, and it stops the
56 * loops below from vectorising, since a `double` temporary halves the lane count. Writing
57 * the `f`-suffixed name in the host branch is what keeps this file honestly single
58 * precision on both sides.
59 *
60 * Deliberately NOT the native_* family on the OpenCL side: these results address a
61 * resampler, and native_sqrt() promises only ~12 bits. The kernel this file replaced used
62 * native_sqrt() against the library's sqrtf(), and nothing in the harness could see it. */
63#ifdef LS_EVAL_IS_OPENCL
64 #define LS_SQRT(x) sqrt(x)
65 #define LS_ATAN(x) atan(x)
66 #define LS_ASIN(x) asin(x)
67 #define LS_SIN(x) sin(x)
68 #define LS_TAN(x) tan(x)
69 #define LS_FABS(x) fabs(x)
70#else
71 #include <math.h>
72 #define LS_SQRT(x) sqrtf(x)
73 #define LS_ATAN(x) atanf(x)
74 #define LS_ASIN(x) asinf(x)
75 #define LS_SIN(x) sinf(x)
76 #define LS_TAN(x) tanf(x)
77 #define LS_FABS(x) fabsf(x)
78#endif
79
80/* Mirrors of the model enumerations in lensserious.h. Spelled as plain integers so this
81 * file stays free of any host-only declaration; ls_eval_from_modifier() is what converts,
82 * and tests/parity_lensfun.c asserts the two agree. */
83/* A radial correction given as a TABLE rather than a polynomial: vendors embed their own
84 * profiles in the raw's metadata that way, as a handful of knots the manufacturer measured.
85 * Everything else about the model is unchanged -- it is still "scale the centred coordinate
86 * by a factor of its radius", which is what every model here does -- so this rides through
87 * the same evaluator, the same kernels and the same by-value transport. */
88#define LS_EVAL_DIST_KNOTS 4
89#define LS_EVAL_VIG_KNOTS 2
90
91/* Sixteen matches what the vendor formats actually carry: Fuji ships nine distortion knots,
92 * Sony and Olympus fewer. It brings ls_eval_t to 632 bytes, which the by-value kernel
93 * argument still absorbs -- the smallest CL_DEVICE_MAX_PARAMETER_SIZE that OpenCL 1.2
94 * guarantees is 1024, for a kernel's whole argument list, and the measured floor on this
95 * project's devices is the same. src/lensserious.c asserts both the size and that headroom,
96 * so raising this fails the build rather than the device. */
97#define LS_MAX_KNOTS 16
98
99#define LS_EVAL_DIST_NONE 0
100#define LS_EVAL_DIST_POLY3 1
101#define LS_EVAL_DIST_POLY5 2
102#define LS_EVAL_DIST_PTLENS 3
103
104#define LS_EVAL_TCA_NONE 0
105#define LS_EVAL_TCA_LINEAR 1
106#define LS_EVAL_TCA_POLY3 2
107
108#define LS_EVAL_VIG_NONE 0
109#define LS_EVAL_VIG_PA 1
110
113#define LS_EVAL_FULL_FRAME_HALF_DIAG_MM 21.633307f
114
115/* Mirrors of ls_lens_type_t, for the same reason as the model mirrors above. */
116#define LS_EVAL_LENS_UNKNOWN 0
117#define LS_EVAL_LENS_RECTILINEAR 1
118#define LS_EVAL_LENS_FISHEYE 2
119#define LS_EVAL_LENS_PANORAMIC 3
120#define LS_EVAL_LENS_EQUIRECTANGULAR 4
121#define LS_EVAL_LENS_FISHEYE_ORTHOGRAPHIC 5
122#define LS_EVAL_LENS_FISHEYE_STEREOGRAPHIC 6
123#define LS_EVAL_LENS_FISHEYE_EQUISOLID 7
124#define LS_EVAL_LENS_FISHEYE_THOBY 8
125
126#define LS_EVAL_ENABLE_DISTORTION (1 << 0)
127#define LS_EVAL_ENABLE_TCA (1 << 1)
128#define LS_EVAL_ENABLE_VIGNETTING (1 << 2)
129#define LS_EVAL_ENABLE_SCALE (1 << 3)
130#define LS_EVAL_ENABLE_GEOMETRY (1 << 4)
131
143typedef struct ls_eval_t
144{
148 /* Vignetting works in the half-diagonal system, i.e. the geometry coordinates divided by
149 * the aspect-ratio correction. Folded into a scale and an offset here rather than stored
150 * as the correction itself, so the per-pixel path needs one multiply and one subtract
151 * instead of a divide -- per axis, per pixel. That was three divides per pixel against
152 * lensfun's one. */
153 float vig_scale;
155 float scale;
172 float dist_terms[3];
173 float tca_terms[6];
174 float vig_terms[3];
175
176 /* Knot tables, used when dist_model / vig_model say so. Per CHANNEL for the geometry,
177 * because that is what a vendor profile is: one radial curve per channel, which is
178 * distortion and TCA expressed together rather than as two stages.
179 *
180 * The radii are per-channel too. Going forwards that is redundant -- a vendor gives ONE
181 * set of knots shared by all three curves -- and it is what makes the REVERSE direction
182 * exact rather than merely close. Inverting r_src = r * cor(r) has no closed form in
183 * general, but a piecewise-linear curve inverts exactly at its own knots: (r*cor(r),
184 * 1/cor(r)) is the same curve read the other way. Each channel's inverse lands on its own
185 * radii, and forcing all three back onto a shared axis would mean resampling two of them.
186 * The resolver builds whichever direction was asked for, so the evaluator below never
187 * inverts anything -- no Newton, no per-pixel search, unlike the polynomial models. */
203
216static inline float ls_eval_knot_lookup(const float *xs, const float *ys, const int n,
217 const float x)
218{
219 if(n <= 0) return 1.f;
220 if(x <= xs[0]) return ys[0];
221
222 for(int i = 1; i < n; i++)
223 {
224 if(x <= xs[i])
225 {
226 const float d = xs[i] - xs[i - 1];
227 if(d <= 0.f) return ys[i - 1];
228 return ys[i - 1] + (x - xs[i - 1]) * (ys[i] - ys[i - 1]) / d;
229 }
230 }
231 return ys[n - 1];
232}
233
247static inline float ls_eval_knot_factor(const ls_eval_t *p, const int c, const float x,
248 const float y)
249{
250 const float r = LS_SQRT(x * x + y * y);
251 return ls_eval_knot_lookup(p->knot_r[c], p->knot_c[c], p->knot_n, r);
252}
253
255static inline void ls_eval_dist(const ls_eval_t *p, float *x, float *y)
256{
257 const float xu = *x, yu = *y;
258 const float ru2 = xu * xu + yu * yu;
259 float m = 1.f;
260
262 { /* mod-coord.cpp: Rd = Ru · (1 − k1 + k1·Ru²) */
263 const float k1 = p->dist_terms[0];
264 m = (1.f - k1) + k1 * ru2;
265 }
266 else if(p->dist_model == LS_EVAL_DIST_POLY5)
267 { /* Rd = Ru · (1 + k1·Ru² + k2·Ru⁴) */
268 m = 1.f + p->dist_terms[0] * ru2 + p->dist_terms[1] * ru2 * ru2;
269 }
270 else if(p->dist_model == LS_EVAL_DIST_PTLENS)
271 { /* Rd = Ru · (a·Ru³ + b·Ru² + c·Ru + d), d = 1−a−b−c */
272 const float a = p->dist_terms[0], b = p->dist_terms[1], c = p->dist_terms[2];
273 const float r = LS_SQRT(ru2);
274 m = a * ru2 * r + b * ru2 + c * r + (1.f - a - b - c);
275 }
276
277 *x = xu * m;
278 *y = yu * m;
279}
280
282static inline void ls_eval_tca(const ls_eval_t *p, float *xr, float *yr, float *xb, float *yb)
283{
285 { /* mod-subpix.cpp: per-channel radial scale */
286 const float kr = p->tca_terms[0], kb = p->tca_terms[1];
287 *xr *= kr; *yr *= kr;
288 *xb *= kb; *yb *= kb;
289 }
290 else if(p->tca_model == LS_EVAL_TCA_POLY3)
291 { /* Rd = Ru · (b·Ru² + c·Ru + v), terms packed vr vb cr cb br bb */
292 const float vr = p->tca_terms[0], vb = p->tca_terms[1];
293 const float cr = p->tca_terms[2], cb = p->tca_terms[3];
294 const float br = p->tca_terms[4], bb = p->tca_terms[5];
295
296 float x = *xr, y = *yr;
297 float ru2 = x * x + y * y;
298 float m = br * ru2 + vr + ((cr != 0.f) ? cr * LS_SQRT(ru2) : 0.f);
299 *xr = x * m; *yr = y * m;
300
301 x = *xb; y = *yb;
302 ru2 = x * x + y * y;
303 m = bb * ru2 + vb + ((cb != 0.f) ? cb * LS_SQRT(ru2) : 0.f);
304 *xb = x * m; *yb = y * m;
305 }
306}
307
316static inline float ls_eval_geom_angle(const int model, const float f, const float r)
317{
318 if(f <= 0.f) return -1.f;
319 switch(model)
320 {
321 case LS_EVAL_LENS_RECTILINEAR: return LS_ATAN(r / f);
322 case LS_EVAL_LENS_FISHEYE: return r / f;
323 case LS_EVAL_LENS_FISHEYE_ORTHOGRAPHIC: return (r <= f) ? LS_ASIN(r / f) : -1.f;
324 case LS_EVAL_LENS_FISHEYE_STEREOGRAPHIC: return 2.f * LS_ATAN(r / (2.f * f));
325 case LS_EVAL_LENS_FISHEYE_EQUISOLID: return (r <= 2.f * f) ? 2.f * LS_ASIN(r / (2.f * f)) : -1.f;
327 return (r <= 1.47f * f) ? LS_ASIN(r / (1.47f * f)) / 0.713f : -1.f;
328 default: return -1.f; /* panoramic and equirectangular are not radial; see the header */
329 }
330}
331
333static inline float ls_eval_geom_radius(const int model, const float f, const float theta)
334{
335 if(f <= 0.f || theta < 0.f) return -1.f;
336 switch(model)
337 {
339 /* Beyond a right angle a rectilinear lens images nothing: LS_TAN() would silently wrap
340 * a point behind the camera round to the front. */
341 return (theta < 1.5707963f) ? f * LS_TAN(theta) : -1.f;
342 case LS_EVAL_LENS_FISHEYE: return f * theta;
343 case LS_EVAL_LENS_FISHEYE_ORTHOGRAPHIC: return f * LS_SIN(theta);
344 case LS_EVAL_LENS_FISHEYE_STEREOGRAPHIC: return 2.f * f * LS_TAN(theta * 0.5f);
345 case LS_EVAL_LENS_FISHEYE_EQUISOLID: return 2.f * f * LS_SIN(theta * 0.5f);
346 case LS_EVAL_LENS_FISHEYE_THOBY: return 1.47f * f * LS_SIN(0.713f * theta);
347 default: return -1.f;
348 }
349}
350
360static inline int ls_eval_geometry(const ls_eval_t *p, float *x, float *y)
361{
362 const float r = LS_SQRT((*x) * (*x) + (*y) * (*y));
363 if(r <= 0.f) return 1; /* the centre maps to itself */
364
365 const float theta = ls_eval_geom_angle(p->geom_to, p->geom_focal, r);
366 if(theta < 0.f) return 0;
367 const float r_src = ls_eval_geom_radius(p->geom_from, p->geom_focal, theta);
368 if(r_src < 0.f) return 0;
369
370 const float k = r_src / r;
371 *x *= k;
372 *y *= k;
373 return 1;
374}
375
376/* Newton, ported from mod-coord.cpp with ONE deliberate difference, which cost a bench
377 * round to find and is the reason this comment is long.
378 *
379 * Upstream iterates in double and tests an ABSOLUTE residual, |f(ru)| < 1e-5. For poly3 it
380 * also divides the whole equation by k1 to make it monic: ru^3 + ru*(1-k1)/k1 - rd/k1. That
381 * is free in double and fatal in float. Real lenses have small k1 -- the Beroflex 500mm has
382 * k1 = 0.00108 -- so the scaled equation carries terms of magnitude 1/k1 = 926, whose float
383 * rounding noise alone is ~5.5e-5. The residual can then NEVER fall below 1e-5, every pixel
384 * exhausts the six-step budget, and the coordinate is returned uncorrected: measured as
385 * 21177 samples out of tolerance, up to 6 px, on 0.6% of the reverse database.
386 *
387 * So the equations here are the UNSCALED ones -- same roots, magnitudes near 1 -- and
388 * convergence is judged on the relative STEP SIZE rather than an absolute residual. That is
389 * scale-free, which is what float needs, and it tests the thing actually wanted: that the
390 * iteration has stopped moving. The budget stays at upstream's six steps and a
391 * non-converging pixel is still left untouched, so genuinely divergent regions (ultrawide
392 * fisheye corners, where upstream gives up too) behave the same on both sides.
393 *
394 * Iterating in float rather than double is not a shortcut either: one source text has to
395 * compile as OpenCL C, where double is an optional extension, and CPU/GPU bit-exactness is
396 * a property this project asserts. The cost of that choice is measured over the whole
397 * database in both directions by tests/parity_lensfun.c. */
398#define LS_NEWTON_STEPS 6
399#define LS_NEWTON_RTOL 1e-6f
400
410static inline float ls_eval_undist_factor(const ls_eval_t *p, const float rd)
411{
412 if(rd == 0.f) return 1.f;
413
414 float ru = rd;
415 int converged = 0;
416
418 {
419 /* Rd = k1*Ru^3 + (1-k1)*Ru, solved as written -- see the note above on why this is not
420 * divided through by k1 the way upstream does it. */
421 const float k1 = p->dist_terms[0], one_minus_k1 = 1.f - k1;
422 for(int step = 0; step < LS_NEWTON_STEPS && !converged; step++)
423 {
424 const float f = k1 * ru * ru * ru + one_minus_k1 * ru - rd;
425 const float fp = 3.f * k1 * ru * ru + one_minus_k1;
426 if(fp == 0.f) return 1.f;
427 const float d = f / fp;
428 ru -= d;
429 if(LS_FABS(d) <= LS_NEWTON_RTOL * LS_FABS(ru)) converged = 1;
430 }
431 }
432 else if(p->dist_model == LS_EVAL_DIST_POLY5)
433 {
434 const float k1 = p->dist_terms[0], k2 = p->dist_terms[1];
435 for(int step = 0; step < LS_NEWTON_STEPS && !converged; step++)
436 {
437 const float ru2 = ru * ru;
438 const float f = ru * (1.f + k1 * ru2 + k2 * ru2 * ru2) - rd;
439 const float fp = 1.f + 3.f * k1 * ru2 + 5.f * k2 * ru2 * ru2;
440 if(fp == 0.f) return 1.f;
441 const float d = f / fp;
442 ru -= d;
443 if(LS_FABS(d) <= LS_NEWTON_RTOL * LS_FABS(ru)) converged = 1;
444 }
445 }
446 else if(p->dist_model == LS_EVAL_DIST_PTLENS)
447 {
448 const float a = p->dist_terms[0], b = p->dist_terms[1], c = p->dist_terms[2];
449 const float d0 = 1.f - a - b - c;
450 for(int step = 0; step < LS_NEWTON_STEPS && !converged; step++)
451 {
452 const float f = ru * (a * ru * ru * ru + b * ru * ru + c * ru + d0) - rd;
453 const float fp = 4.f * a * ru * ru * ru + 3.f * b * ru * ru + 2.f * c * ru + d0;
454 if(fp == 0.f) return 1.f;
455 const float d = f / fp;
456 ru -= d;
457 if(LS_FABS(d) <= LS_NEWTON_RTOL * LS_FABS(ru)) converged = 1;
458 }
459 }
460 else
461 return 1.f;
462
463 if(!converged || ru < 0.f) return 1.f; /* a negative radius is not a position */
464 return ru / rd;
465}
466
468static inline void ls_eval_undist(const ls_eval_t *p, float *x, float *y)
469{
470 const float rd = LS_SQRT(*x * *x + *y * *y);
471 const float m = ls_eval_undist_factor(p, rd);
472 *x *= m;
473 *y *= m;
474}
475
484static inline void ls_eval_untca(const ls_eval_t *p, float *xr, float *yr, float *xb, float *yb)
485{
487 {
488 const float kr = p->tca_terms[0], kb = p->tca_terms[1]; /* already reciprocals */
489 *xr *= kr; *yr *= kr;
490 *xb *= kb; *yb *= kb;
491 }
492 else if(p->tca_model == LS_EVAL_TCA_POLY3)
493 {
494 const float vr = p->tca_terms[0], vb = p->tca_terms[1];
495 const float cr = p->tca_terms[2], cb = p->tca_terms[3];
496 const float br = p->tca_terms[4], bb = p->tca_terms[5];
497
498 for(int ch = 0; ch < 2; ch++)
499 {
500 const float v = ch ? vb : vr, c = ch ? cb : cr, b = ch ? bb : br;
501 float *px = ch ? xb : xr, *py = ch ? yb : yr;
502 const float rd = LS_SQRT(*px * *px + *py * *py);
503 if(rd == 0.f) continue;
504
505 float ru = rd;
506 int ok = 0;
507 for(int step = 0; step < LS_NEWTON_STEPS && !ok; step++)
508 {
509 const float ru2 = ru * ru;
510 const float f = b * ru2 * ru + c * ru2 + v * ru - rd;
511 const float fp = 3.f * b * ru2 + 2.f * c * ru + v;
512 if(fp == 0.f) break;
513 const float dd = f / fp;
514 ru -= dd;
515 if(LS_FABS(dd) <= LS_NEWTON_RTOL * LS_FABS(ru)) ok = 1;
516 }
517 /* Upstream requires ru STRICTLY positive here, where the coordinate path accepts
518 * zero. Kept as it is rather than unified: they are different functions. */
519 if(ok && ru > 0.f)
520 {
521 const float m = ru / rd;
522 *px *= m; *py *= m;
523 }
524 }
525 }
526}
527
559static inline int ls_eval_coord_chain(const ls_eval_t *p, const int c, float *x, float *y)
560{
561 if(!p->reverse && (p->enabled & LS_EVAL_ENABLE_SCALE))
562 {
563 *x *= p->scale;
564 *y *= p->scale;
565 }
568 ls_eval_undist(p, x, y);
569
570 if((p->enabled & LS_EVAL_ENABLE_GEOMETRY) && !ls_eval_geometry(p, x, y)) return 0;
571
573 {
575 {
576 /* Green for every channel when the table is not serving TCA: that strips the
577 * aberration out of the curve and leaves pure distortion, which is what lets a
578 * different TCA source run on top without the two corrections being applied twice. */
579 const int curve = (p->knot_axes & LS_EVAL_ENABLE_TCA) ? c : 1;
580 const float f = ls_eval_knot_factor(p, curve, *x, *y);
581 *x *= f;
582 *y *= f;
583 }
584 }
585 else if(!p->reverse && (p->enabled & LS_EVAL_ENABLE_DISTORTION))
586 ls_eval_dist(p, x, y);
587 if(p->reverse && (p->enabled & LS_EVAL_ENABLE_SCALE))
588 {
589 *x *= p->scale;
590 *y *= p->scale;
591 }
592 return 1;
593}
594
613static inline void ls_eval_map(const ls_eval_t *p, float xu, float yu, float *out)
614{
615 float x = xu * p->norm_scale - p->center_x;
616 float y = yu * p->norm_scale - p->center_y;
617
618 /* lensfun's callback-priority order, which is NOT symmetric between the two directions:
619 * forward scale (100) -> projection (500) -> distortion (750)
620 * reverse undistortion (250) -> projection (500) -> scale (900)
621 * Projection sits in the middle either way; scale moves from first to last, and the
622 * resolver has already swapped the projection endpoints and un-reciprocated the scale.
623 * The TCA subpixel stage runs after the coordinate chain in both directions. */
624 float xr = x, yr = y, xb = x, yb = y;
625
627 {
628 /* Three chains, one per channel, because a vendor profile serving BOTH axes has no
629 * single geometry to run a TCA correction on top of -- the red, green and blue curves
630 * ARE the correction, and the difference between them is the lateral chromatic
631 * aberration. Running the chain three times rather than once-plus-a-delta keeps that
632 * exact. When the table serves distortion alone every channel follows green instead,
633 * and the branch below runs the requested TCA model after it.
634 *
635 * It is also cheap where it is used: a vendor table is measured on the lens as shipped,
636 * so the projection stage is an identity that ls_eval_coord_chain() skips outright, and
637 * what repeats is a multiply and a table lookup.
638 *
639 * Only the green chain decides whether there is a source pixel at all. The three differ
640 * by the TCA amount, tenths of a percent, so a per-channel verdict here could hand the
641 * caller two valid channels and one NaN for the same pixel. */
642 ls_eval_coord_chain(p, 0, &xr, &yr);
643 ls_eval_coord_chain(p, 2, &xb, &yb);
644 if(!ls_eval_coord_chain(p, 1, &x, &y))
645 {
646 for(int k = 0; k < 6; k++) out[k] = (float)(0.0f / 0.0f);
647 return;
648 }
649 }
650 else
651 {
652 if(!ls_eval_coord_chain(p, 1, &x, &y))
653 {
654 /* No source pixel. NaN is what lensfun writes here too, and every consumer in this
655 * project already checks for it (do_nan_checks in the kernels, isfinite() on the CPU)
656 * before sampling. */
657 for(int k = 0; k < 6; k++) out[k] = (float)(0.0f / 0.0f);
658 return;
659 }
660 xr = x; yr = y; xb = x; yb = y;
662 {
663 if(p->reverse) ls_eval_untca(p, &xr, &yr, &xb, &yb);
664 else ls_eval_tca(p, &xr, &yr, &xb, &yb);
665 }
666 }
667
668 out[0] = (xr + p->center_x) * p->norm_unscale;
669 out[1] = (yr + p->center_y) * p->norm_unscale;
670 out[2] = (x + p->center_x) * p->norm_unscale;
671 out[3] = (y + p->center_y) * p->norm_unscale;
672 out[4] = (xb + p->center_x) * p->norm_unscale;
673 out[5] = (yb + p->center_y) * p->norm_unscale;
674}
675
697static inline float ls_eval_vignette_from_r2(const ls_eval_t *p, const float r2)
698{
700 {
701 /* The table already states the multiplier that CORRECTS the falloff, so the direction
702 * is handled the same way the polynomial's is -- reversing puts it back. */
703 const float r = LS_SQRT(r2);
704 const float v = ls_eval_knot_lookup(p->knot_vr, p->knot_v, p->knot_vn, r);
705 /* The table states the falloff -- what the lens DID -- so correcting divides by it and
706 * applying multiplies, which is the same way round as the polynomial below and the same
707 * way round as lensfun's two callbacks. */
708 const float m = p->reverse ? v : ((v != 0.f) ? (1.f / v) : 0.f);
709 return (m > 0.f) ? m : 0.f;
710 }
711
712 const float r4 = r2 * r2;
713 const float c = 1.f + p->vig_terms[0] * r2 + p->vig_terms[1] * r4
714 + p->vig_terms[2] * r4 * r2;
715
716 /* Clamped at zero, because the pa polynomial is not constrained to stay positive and for
717 * some lenses it crosses zero INSIDE the frame -- the Canon EF 8-15mm Fisheye at 8mm has
718 * k = (-0.625, 5.648, -19.330), whose root sits near r = 0.65. Past that root 1/c is
719 * negative, which is not a brightness.
720 *
721 * Upstream clamps the same thing one step later: apply_multiplier() writes
722 * clampd(pixel * c, 0, type_max) (mod-color.cpp), so for the non-negative pixels this
723 * ever sees, clamping the factor here is equivalent. Without it this returned -0.203
724 * where lensfun returns 0, on every fisheye whose model has a root in frame. */
725 /* One divide and one select, and nothing else -- which is exactly what upstream does:
726 * ModifyColor_DeVignetting_PA divides by c with no special case for zero, and
727 * apply_multiplier() clamps the product at zero afterwards. An earlier version guarded
728 * c == 0 here; that guard was not upstream behaviour AND it was a second conditional,
729 * which is what stopped a caller's row loop from vectorising. Dividing by zero yields an
730 * infinity that the clamp below leaves alone for +0 and flattens for -0, matching
731 * upstream on a case that no real calibration reaches anyway.
732 *
733 * Keeping this to a single select matters more than it looks: it is the difference
734 * between one divide per pixel and one per four. */
735 /* The DIRECTION decides whether the lens's falloff is removed or re-applied, and
736 * upstream implements the two as separate callbacks: ModifyColor_DeVignetting_PA
737 * multiplies by 1/c to correct, ModifyColor_Vignetting_PA multiplies by c to put the
738 * falloff back (mod-color.cpp, priorities 750 and 250). Ignoring the flag here meant the
739 * reverse direction BRIGHTENED the corners it was supposed to darken -- the correction
740 * applied twice instead of undone.
741 *
742 * Still one select on top of the divide: the reciprocal is computed either way, and the
743 * branch is on a field that is constant for the whole frame. */
744 const float m = p->reverse ? c : (1.f / c);
745 return (m > 0.f) ? m : 0.f;
746}
747
752static inline float ls_eval_vignette_factor(const ls_eval_t *p, float xu, float yu)
753{
754 if(!(p->enabled & LS_EVAL_ENABLE_VIGNETTING)) return 1.f;
755
756 const float x = xu * p->vig_scale - p->vig_center_x;
757 const float y = yu * p->vig_scale - p->vig_center_y;
758 return ls_eval_vignette_from_r2(p, x * x + y * y);
759}
760
761#endif /* LENSSERIOUS_EVAL_H */
#define LS_EVAL_DIST_POLY3
#define LS_EVAL_TCA_POLY3
static int ls_eval_coord_chain(const ls_eval_t *p, const int c, float *x, float *y)
The coordinate chain: scale, projection and distortion, in direction order.
#define LS_EVAL_ENABLE_DISTORTION
#define LS_EVAL_DIST_PTLENS
static float ls_eval_geom_angle(const int model, const float f, const float r)
Field angle for a radius, under one projection. Negative if model has none.
static void ls_eval_tca(const ls_eval_t *p, float *xr, float *yr, float *xb, float *yb)
Transverse chromatic aberration: the red and blue coordinates diverge from green.
#define LS_EVAL_LENS_FISHEYE_ORTHOGRAPHIC
static float ls_eval_knot_factor(const ls_eval_t *p, const int c, const float x, const float y)
The per-channel radial scale a knot table gives at this point.
static void ls_eval_dist(const ls_eval_t *p, float *x, float *y)
Distortion, in place, in normalized coordinates.
static float ls_eval_vignette_from_r2(const ls_eval_t *p, const float r2)
The vignetting multiplier for ONE output pixel. Multiply the pixel by it.
#define LS_EVAL_LENS_FISHEYE_EQUISOLID
#define LS_EVAL_ENABLE_VIGNETTING
#define LS_EVAL_LENS_FISHEYE_THOBY
#define LS_EVAL_LENS_RECTILINEAR
#define LS_EVAL_ENABLE_SCALE
static void ls_eval_untca(const ls_eval_t *p, float *xr, float *yr, float *xb, float *yb)
The reverse of ls_eval_tca(): recover each channel's undistorted radius.
#define LS_TAN(x)
#define LS_SIN(x)
#define LS_ASIN(x)
#define LS_FABS(x)
#define LS_EVAL_DIST_KNOTS
#define LS_EVAL_VIG_KNOTS
static float ls_eval_undist_factor(const ls_eval_t *p, const float rd)
Solve Rd = f(Ru) for Ru, given a radius Rd. The inverse of ls_eval_dist().
#define LS_EVAL_LENS_FISHEYE_STEREOGRAPHIC
#define LS_EVAL_LENS_FISHEYE
static float ls_eval_geom_radius(const int model, const float f, const float theta)
Radius for a field angle, under one projection. The inverse of the above.
#define LS_EVAL_DIST_POLY5
#define LS_EVAL_ENABLE_GEOMETRY
static void ls_eval_map(const ls_eval_t *p, float xu, float yu, float *out)
The map for ONE output pixel: six floats, source coordinates for R, G, B.
struct ls_eval_t ls_eval_t
One lens resolved at one shooting configuration, as a flat block of scalars.
static float ls_eval_vignette_factor(const ls_eval_t *p, float xu, float yu)
The vignetting multiplier for ONE output pixel. Multiply the pixel by it.
#define LS_EVAL_ENABLE_TCA
#define LS_SQRT(x)
#define LS_EVAL_TCA_LINEAR
static float ls_eval_knot_lookup(const float *xs, const float *ys, const int n, const float x)
Piecewise-linear lookup over a knot table.
#define LS_NEWTON_RTOL
#define LS_ATAN(x)
static int ls_eval_geometry(const ls_eval_t *p, float *x, float *y)
Reproject one point from the target geometry into the lens's own.
static void ls_eval_undist(const ls_eval_t *p, float *x, float *y)
Undistort in place. The reverse of ls_eval_dist().
#define LS_MAX_KNOTS
#define LS_NEWTON_STEPS
One lens resolved at one shooting configuration, as a flat block of scalars.
float knot_v[16]
float vig_terms[3]
float knot_c[3][16]
float knot_r[3][16]
float dist_terms[3]
float tca_terms[6]
float knot_vr[16]