LensSerious 0.1
Lens-correction mathematics as data, not as a library of callbacks
Loading...
Searching...
No Matches
lensserious_vendor.c
Go to the documentation of this file.
1/*
2 LensSerious — a maker's own correction tables, decoded into the library's generic knots.
3
4 Copyright (C) 2026 Aurélien PIERRE.
5 Ported from darktable's embedded-metadata lens correction (src/iop/lens.cc and
6 src/common/dng_opcode.c), Copyright (C) the darktable contributors.
7
8 License: GPL-3.0-or-later — see the note in lensserious_vendor.h.
9*/
10
23#include "lensserious_vendor.h"
24
25#include <math.h>
26#include <string.h>
27
28/* ------------------------------------------------------------------------- */
29/* Shared helpers */
30/* ------------------------------------------------------------------------- */
31
34static float _linear_spline(const float *xi, const float *yi, const int ni, const float x)
35{
36 if(ni <= 0) return 1.0f;
37 if(x < xi[0]) return yi[0];
38
39 for(int i = 1; i < ni; i++)
40 {
41 if(x >= xi[i - 1] && x <= xi[i])
42 {
43 const float denom = xi[i] - xi[i - 1];
44 if(denom == 0.0f) return yi[i - 1];
45 const float dydx = (yi[i] - yi[i - 1]) / denom;
46 return yi[i - 1] + (x - xi[i - 1]) * dydx;
47 }
48 }
49
50 return yi[ni - 1];
51}
52
53static const ls_vendor_finetune_t _ft_identity = { 1.f, 1.f, 1.f, 1.f };
54
55/* ------------------------------------------------------------------------- */
56/* Sony */
57/* ------------------------------------------------------------------------- */
58
59static int _sony_has_data(const ls_vendor_data_t *d)
60{
61 return d->u.sony.nc >= 2 && d->u.sony.nc <= LS_MAX_KNOTS;
62}
63
64static int _sony_axes(const ls_vendor_data_t *d)
65{
66 if(!_sony_has_data(d)) return 0;
67 const ls_vendor_sony_t *const sony = &d->u.sony;
68
69 int axes = 0;
70 for(int i = 0; i < sony->nc; i++)
71 {
72 if(sony->distortion[i] != 0) axes |= LS_ENABLE_DISTORTION;
73 if(sony->ca_r[i] != 0 || sony->ca_b[i] != 0) axes |= LS_ENABLE_TCA;
74 if(sony->vignetting[i] != 0) axes |= LS_ENABLE_VIGNETTING;
75 }
76 return axes;
77}
78
80 ls_knots_t *knots)
81{
82 static const float SONY_DIST_SCALE = 1.0f / 16384.0f;
83 static const float SONY_CA_SCALE = 1.0f / 2097152.0f;
84 static const float SONY_VIG_SCALE = 1.0f / 8192.0f;
85
86 const ls_vendor_sony_t *const sony = &d->u.sony;
87 const int nc = sony->nc;
88
89 for(int i = 0; i < nc; i++)
90 {
91 const float frac = (float)(i + 0.5) / (float)(nc - 1);
92 knots->radius[i] = frac;
93 knots->vig_radius[i] = frac;
94
95 const float dist_cor = ft->distortion * ((float)sony->distortion[i] * SONY_DIST_SCALE) + 1.0f;
96 knots->cor_rgb[0][i] = dist_cor;
97 knots->cor_rgb[1][i] = dist_cor;
98 knots->cor_rgb[2][i] = dist_cor;
99
100 knots->cor_rgb[0][i] *= ft->ca_red * ((float)sony->ca_r[i] * SONY_CA_SCALE) + 1.0f;
101 knots->cor_rgb[2][i] *= ft->ca_blue * ((float)sony->ca_b[i] * SONY_CA_SCALE) + 1.0f;
102
103 const float val = ft->vignette * ((float)sony->vignetting[i] * SONY_VIG_SCALE);
104 knots->vig[i] = powf(2.0f, 0.5f - powf(2.0f, val - 1.0f));
105 }
106
107 return nc;
108}
109
110/* ------------------------------------------------------------------------- */
111/* Fujifilm */
112/* ------------------------------------------------------------------------- */
113
115{
116 return d->u.fuji.nc > 0 && d->u.fuji.nc <= 11;
117}
118
119static int _fuji_axes(const ls_vendor_data_t *d)
120{
121 if(!_fuji_has_data(d)) return 0;
122 const ls_vendor_fuji_t *const fuji = &d->u.fuji;
123
124 int axes = 0;
125 for(int i = 0; i < fuji->nc; i++)
126 {
127 if(fuji->distortion[i] != 0.0f) axes |= LS_ENABLE_DISTORTION;
128 if(fuji->ca_r[i] != 0.0f || fuji->ca_b[i] != 0.0f) axes |= LS_ENABLE_TCA;
129 if(fuji->vignetting[i] != 0.0f) axes |= LS_ENABLE_VIGNETTING;
130 }
131 return axes;
132}
133
134/* Fujifilm publishes the tables at ITS knots, in percent, indexed by SOURCE radius; the
135 * knot model wants them at OUR radii, as factors, indexed by DESTINATION radius. So:
136 * resample onto a regular grid, convert each sample to a factor, then move the radius from
137 * the source system to the destination one by dividing the correction back out. cropf is
138 * applied to the maker's radii first — see the contract in lensserious_vendor.h. */
140 ls_knots_t *knots)
141{
142 const ls_vendor_fuji_t *const fuji = &d->u.fuji;
143 const int ncsrc = fuji->nc;
144
145 float knots_in[LS_MAX_KNOTS] = { 0.f };
146 float cor_rgb_in[LS_MAX_KNOTS] = { 0.f };
147 float cor_ca_r_in[LS_MAX_KNOTS] = { 0.f };
148 float cor_ca_b_in[LS_MAX_KNOTS] = { 0.f };
149
150 int j = 0;
151 if(fuji->knots[0] > 0.0f)
152 {
153 knots_in[j] = 0.0f;
154 cor_rgb_in[j] = 1.0f;
155 cor_ca_r_in[j] = 0.0f;
156 cor_ca_b_in[j] = 0.0f;
157 knots->vig_radius[j] = 0.0f;
158 knots->vig[j] = 1.0f;
159 j++;
160 }
161
162 for(int i = 0; i < ncsrc; i++, j++)
163 {
164 knots_in[j] = fuji->cropf * fuji->knots[i];
165 cor_rgb_in[j] = ft->distortion * (fuji->distortion[i] / 100.0f) + 1.0f;
166 cor_ca_r_in[j] = ft->ca_red * fuji->ca_r[i];
167 cor_ca_b_in[j] = ft->ca_blue * fuji->ca_b[i];
168
169 knots->vig_radius[j] = fuji->cropf * fuji->knots[i];
170 knots->vig[j] = 1.0f - ft->vignette * (1.0f - fuji->vignetting[i] / 100.0f);
171 }
172 const int ncin = j;
173 if(ncin <= 0) return 0;
174
175 for(int k = ncin; k < LS_MAX_KNOTS; k++)
176 {
177 knots->vig_radius[k] = knots->vig_radius[ncin - 1] + (float)(k - ncin + 1);
178 knots->vig[k] = knots->vig[ncin - 1];
179 }
180
181 const int nc = LS_MAX_KNOTS;
182 for(int i = 0; i < nc; i++)
183 {
184 const float rin = (float)i / (float)(nc - 1);
185 const float m = _linear_spline(knots_in, cor_rgb_in, ncin, rin);
186 const float r = (fabsf(m) > 1e-6f) ? rin / m : rin;
187 knots->radius[i] = r;
188
189 knots->cor_rgb[0][i] = m;
190 knots->cor_rgb[1][i] = m;
191 knots->cor_rgb[2][i] = m;
192
193 const float mcar = _linear_spline(knots_in, cor_ca_r_in, ncin, rin);
194 const float mcab = _linear_spline(knots_in, cor_ca_b_in, ncin, rin);
195 knots->cor_rgb[0][i] *= mcar + 1.0f;
196 knots->cor_rgb[2][i] *= mcab + 1.0f;
197 }
198
199 return nc;
200}
201
202/* ------------------------------------------------------------------------- */
203/* DNG */
204/* ------------------------------------------------------------------------- */
205
206static int _dng_axes(const ls_vendor_data_t *d)
207{
208 const ls_vendor_dng_t *const dng = &d->u.dng;
209 int axes = 0;
210 if(dng->has_warp) axes |= LS_ENABLE_DISTORTION;
211 if(dng->has_warp && dng->warp_planes > 1) axes |= LS_ENABLE_TCA;
212 if(dng->has_vignette) axes |= LS_ENABLE_VIGNETTING;
213 return axes;
214}
215
217static double _dng_warp_radial(const double coeffs[6], const double r2)
218{
219 return coeffs[0] + r2 * (coeffs[1] + r2 * (coeffs[2] + r2 * coeffs[3]));
220}
221
223 ls_knots_t *knots)
224{
225 const ls_vendor_dng_t *const dng = &d->u.dng;
226 const int nc = LS_MAX_KNOTS;
227 const uint32_t three = 3;
228 const int nplanes = (int)(dng->warp_planes < three ? dng->warp_planes : three);
229 const int canonical_plane = (dng->warp_planes > 1) ? 1 : 0;
230 const int apply_tca = dng->warp_planes > 1;
231
232 for(int i = 0; i < nc; i++)
233 {
234 const float r = (float)i / (float)(nc - 1);
235 knots->radius[i] = r;
236 knots->vig_radius[i] = r;
237 const double r2 = (double)r * (double)r;
238
239 if(dng->has_warp)
240 {
241 for(int c = 0; c < 3; c++)
242 {
243 const int plane = apply_tca ? (c < nplanes - 1 ? c : nplanes - 1) : canonical_plane;
244 const double r_cor = _dng_warp_radial(dng->warp_coeffs[plane], r2);
245 knots->cor_rgb[c][i] = (float)(ft->distortion * (r_cor - 1.0) + 1.0);
246 }
247 }
248 else
249 {
250 knots->cor_rgb[0][i] = 1.0f;
251 knots->cor_rgb[1][i] = 1.0f;
252 knots->cor_rgb[2][i] = 1.0f;
253 }
254
255 if(dng->has_vignette)
256 {
257 const double dvig = r2
258 * (dng->vig_coeffs[0]
259 + r2 * (dng->vig_coeffs[1]
260 + r2 * (dng->vig_coeffs[2] + r2 * (dng->vig_coeffs[3] + r2 * dng->vig_coeffs[4]))));
261 knots->vig[i] = (float)(1.0 / (1.0 + ft->vignette * dvig));
262 }
263 else
264 {
265 knots->vig[i] = 1.0f;
266 }
267 }
268
269 return nc;
270}
271
272/* ------------------------------------------------------------------------- */
273/* Olympus */
274/* ------------------------------------------------------------------------- */
275
276static int _olympus_axes(const ls_vendor_data_t *d)
277{
278 const ls_vendor_olympus_t *const oly = &d->u.olympus;
279 int axes = 0;
280 if(oly->has_dist) axes |= LS_ENABLE_DISTORTION;
281 if(oly->has_ca) axes |= LS_ENABLE_TCA;
282 /* No vignetting, ever: Olympus does not publish one. */
283 return axes;
284}
285
287 ls_knots_t *knots)
288{
289 const ls_vendor_olympus_t *const oly = &d->u.olympus;
290 const int nc = LS_MAX_KNOTS;
291
292 float drs = 1.0f;
293 float dk2 = 0.0f;
294 float dk4 = 0.0f;
295 float dk6 = 0.0f;
296 if(oly->has_dist)
297 {
298 dk2 = oly->dist[0];
299 dk4 = oly->dist[1];
300 dk6 = oly->dist[2];
301 drs = oly->dist[3];
302 }
303 float car0 = 0.0f;
304 float car2 = 0.0f;
305 float car4 = 0.0f;
306 float cab0 = 0.0f;
307 float cab2 = 0.0f;
308 float cab4 = 0.0f;
309 if(oly->has_ca)
310 {
311 car0 = oly->ca[0];
312 car2 = oly->ca[1];
313 car4 = oly->ca[2];
314 cab0 = oly->ca[3];
315 cab2 = oly->ca[4];
316 cab4 = oly->ca[5];
317 }
318
319 for(int i = 0; i < nc; i++)
320 {
321 const float r = (float)i / (float)(nc - 1);
322 knots->radius[i] = r;
323 knots->vig_radius[i] = r;
324 knots->vig[i] = 1.0f;
325
326 float base = 1.0f;
327 if(oly->has_dist)
328 {
329 const float rs2 = (r * drs) * (r * drs);
330 const float r_cor = drs * (1.0f + rs2 * (dk2 + rs2 * (dk4 + rs2 * dk6)));
331 base = ft->distortion * (r_cor - 1.0f) + 1.0f;
332 }
333 knots->cor_rgb[0][i] = base;
334 knots->cor_rgb[1][i] = base;
335 knots->cor_rgb[2][i] = base;
336
337 if(oly->has_ca && r > 0.0f)
338 {
339 const float rd = base * r;
340 const float rd2 = rd * rd;
341 knots->cor_rgb[0][i] += ft->ca_red * (rd * (car0 + rd2 * (car2 + rd2 * car4))) / r;
342 knots->cor_rgb[2][i] += ft->ca_blue * (rd * (cab0 + rd2 * (cab2 + rd2 * cab4))) / r;
343 }
344 }
345
346 return nc;
347}
348
349/* ------------------------------------------------------------------------- */
350/* The public entry points */
351/* ------------------------------------------------------------------------- */
352
354{
355 if(!d) return 0;
356
357 switch(d->type)
358 {
359 case LS_VENDOR_SONY: return _sony_axes(d);
360 case LS_VENDOR_FUJI: return _fuji_axes(d);
361 case LS_VENDOR_DNG: return _dng_axes(d);
362 case LS_VENDOR_OLYMPUS: return _olympus_axes(d);
363 case LS_VENDOR_NONE:
364 default: return 0;
365 }
366}
367
369 int w, int h, ls_knots_t *knots, float *autoscale)
370{
371 if(autoscale) *autoscale = 1.0f;
372 if(!knots) return LS_VENDOR_EBADDATA;
373 memset(knots, 0, sizeof(*knots));
374 if(!d) return 0;
375
376 /* NULL means "as the maker measured", not "reject". The opposite convention already
377 * cost a consumer a silent no-op that took a pixel-diff to find. */
378 if(!ft) ft = &_ft_identity;
379
380 /* An all-zero table with a valid type is "carries nothing", not "malformed": several
381 * bodies write zero-filled tables for lenses they choose not to correct, and telling
382 * their users the file could not be decoded would be noise about a non-event. Malformed
383 * is reserved for tables that CLAIM an axis and then fail to convert, below. */
384 const int axes = ls_vendor_axes(d);
385 if(axes == 0) return 0;
386
387 int nc = 0;
388 switch(d->type)
389 {
390 case LS_VENDOR_SONY: nc = _sony_populate(d, ft, knots); break;
391 case LS_VENDOR_FUJI: nc = _fuji_populate(d, ft, knots); break;
392 case LS_VENDOR_DNG: nc = _dng_populate(d, ft, knots); break;
393 case LS_VENDOR_OLYMPUS: nc = _olympus_populate(d, ft, knots); break;
394 default: break;
395 }
396 if(nc <= 0)
397 {
398 memset(knots, 0, sizeof(*knots));
399 return LS_VENDOR_EBADDATA;
400 }
401
402 /* The autoscale: the largest correction factor over the region a border can appear in —
403 * between the inscribed circle and the corner. Dividing it out of the factors and into
404 * the radii re-normalises the table so the returned scale is exactly the zoom that
405 * clears the borders, and the knots are neutral without it. */
406 const float iwd2 = 0.5f * (float)w;
407 const float iht2 = 0.5f * (float)h;
408 const float diag = hypotf(iwd2, iht2);
409 const float sr = fminf(iwd2, iht2);
410 const float srr = (diag > 1e-6f) ? sr / diag : 0.0f;
411
412 const int tested = 200;
413 float scale = 0.0f;
414 for(int i = 0; i < tested; i++)
415 {
416 const float x = srr + (1.0f - srr) * (float)i / (float)(tested - 1);
417 for(int c = 0; c < 3; c++)
418 scale = fmaxf(scale, _linear_spline(knots->radius, knots->cor_rgb[c], nc, x));
419 }
420 if(scale <= 1e-6f) scale = 1.0f;
421
422 for(int i = 0; i < nc; i++)
423 {
424 knots->radius[i] *= scale;
425 for(int c = 0; c < 3; c++) knots->cor_rgb[c][i] /= scale;
426 }
427
428 knots->n = nc;
429 knots->vn = (axes & LS_ENABLE_VIGNETTING) ? nc : 0;
430
431 if(autoscale) *autoscale = scale;
432 return axes;
433}
434
435/* ------------------------------------------------------------------------- */
436/* DNG OpcodeList3 */
437/* ------------------------------------------------------------------------- */
438
439/* The DNG 1.3 specification, chapter 7: an opcode list is a big-endian count followed by
440 * entries of [id, DNG version, flags, param_size, params]. */
441
442#define DNG_OPCODE_ID_WARP_RECTILINEAR 1u
443#define DNG_OPCODE_ID_VIGNETTE_RADIAL 3u
444
445#define DNG_WARP_PLANES_MIN 1u
446#define DNG_WARP_PLANES_MAX 3u
447#define DNG_WARP_HEADER_SIZE 4u /* the plane count */
448#define DNG_WARP_PLANE_SIZE (6u * 8u) /* six doubles per plane */
449#define DNG_WARP_CENTER_SIZE (2u * 8u) /* cx, cy */
450#define DNG_VIGNETTE_COEFFS_SIZE (5u * 8u)
451#define DNG_VIGNETTE_CENTER_SIZE (2u * 8u)
452
453static double _get_be_double(const uint8_t *p)
454{
455 uint64_t v = 0;
456 for(int i = 0; i < 8; i++) v = (v << 8) | p[i];
457 double out;
458 memcpy(&out, &v, sizeof(out));
459 return out;
460}
461
462static uint32_t _get_be_long(const uint8_t *p)
463{
464 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
465}
466
467/* Parses a WarpRectilinear param body already known to be param_size bytes long and fully
468 * inside the caller's buffer. Writes out->warp_* only on success. */
469static void _parse_warp_rectilinear(const uint8_t *param, uint32_t param_size, ls_vendor_dng_t *out)
470{
471 if(param_size < DNG_WARP_HEADER_SIZE) return;
472
473 const uint32_t planes = _get_be_long(&param[0]);
474 if(planes < DNG_WARP_PLANES_MIN || planes > DNG_WARP_PLANES_MAX) return;
475 const uint64_t min_size
476 = (uint64_t)DNG_WARP_HEADER_SIZE + (uint64_t)planes * DNG_WARP_PLANE_SIZE + DNG_WARP_CENTER_SIZE;
477 if(param_size < min_size) return;
478
479 uint32_t off = DNG_WARP_HEADER_SIZE;
480 for(uint32_t p = 0; p < planes; p++)
481 {
482 for(int c = 0; c < 6; c++)
483 {
484 out->warp_coeffs[p][c] = _get_be_double(&param[off]);
485 off += 8;
486 }
487 }
488 out->warp_cx = _get_be_double(&param[off]);
489 out->warp_cy = _get_be_double(&param[off + 8]);
490 out->warp_planes = planes;
491 out->has_warp = 1;
492}
493
494/* Parses a VignetteRadial param body under the same contract. */
495static void _parse_vignette_radial(const uint8_t *param, uint32_t param_size, ls_vendor_dng_t *out)
496{
497 if(param_size < DNG_VIGNETTE_COEFFS_SIZE + DNG_VIGNETTE_CENTER_SIZE) return;
498
499 for(int c = 0; c < 5; c++) out->vig_coeffs[c] = _get_be_double(&param[c * 8]);
502 out->has_vignette = 1;
503}
504
505int ls_vendor_parse_dng_opcodelist3(const uint8_t *blob, size_t len, ls_vendor_dng_t *out)
506{
507 if(!out) return LS_VENDOR_EBADDATA;
508 memset(out, 0, sizeof(*out));
509 if(!blob || len < 4) return LS_VENDOR_EBADDATA;
510
511 /* Bounds guard, applied before every read: the 4-byte count first; then each opcode's
512 * 16-byte header against len before any header field; then the header's declared
513 * param_size against len before the body is touched; then each opcode class enforces
514 * its own minimum size before indexing the bytes it uses. Unknown ids are skipped —
515 * no correction is invented for a class the data does not actually hold. */
516 uint32_t count = _get_be_long(&blob[0]);
517 uint64_t offset = 4;
518
519 int broke = 0;
520 while(count > 0)
521 {
522 if(offset + 16 > len) { broke = 1; break; }
523
524 const uint32_t opcode_id = _get_be_long(&blob[offset]);
525 const uint32_t param_size = _get_be_long(&blob[offset + 12]);
526 const uint8_t *param = &blob[offset + 16];
527
528 if(offset + 16 + (uint64_t)param_size > len) { broke = 1; break; }
529
530 if(opcode_id == DNG_OPCODE_ID_WARP_RECTILINEAR)
531 _parse_warp_rectilinear(param, param_size, out);
532 else if(opcode_id == DNG_OPCODE_ID_VIGNETTE_RADIAL)
533 _parse_vignette_radial(param, param_size, out);
534
535 offset += 16 + (uint64_t)param_size;
536 count--;
537 }
538
539 const ls_vendor_data_t probe = { .type = LS_VENDOR_DNG, .u.dng = *out };
540 const int axes = ls_vendor_axes(&probe);
541
542 /* A list that breaks after yielding a usable opcode returns what it yielded: partial
543 * data is data. Only a structure broken before anything was recognised is an error. */
544 if(axes == 0 && broke) return LS_VENDOR_EBADDATA;
545 return axes;
546}
#define LS_ENABLE_VIGNETTING
#define LS_ENABLE_DISTORTION
#define LS_ENABLE_TCA
#define LS_MAX_KNOTS
static int _dng_axes(const ls_vendor_data_t *d)
static int _sony_axes(const ls_vendor_data_t *d)
#define DNG_OPCODE_ID_WARP_RECTILINEAR
static void _parse_vignette_radial(const uint8_t *param, uint32_t param_size, ls_vendor_dng_t *out)
static int _fuji_axes(const ls_vendor_data_t *d)
static int _sony_populate(const ls_vendor_data_t *d, const ls_vendor_finetune_t *ft, ls_knots_t *knots)
static double _get_be_double(const uint8_t *p)
static int _fuji_has_data(const ls_vendor_data_t *d)
static float _linear_spline(const float *xi, const float *yi, const int ni, const float x)
#define DNG_VIGNETTE_CENTER_SIZE
#define DNG_VIGNETTE_COEFFS_SIZE
static int _olympus_populate(const ls_vendor_data_t *d, const ls_vendor_finetune_t *ft, ls_knots_t *knots)
static void _parse_warp_rectilinear(const uint8_t *param, uint32_t param_size, ls_vendor_dng_t *out)
static uint32_t _get_be_long(const uint8_t *p)
static const ls_vendor_finetune_t _ft_identity
#define DNG_OPCODE_ID_VIGNETTE_RADIAL
static int _fuji_populate(const ls_vendor_data_t *d, const ls_vendor_finetune_t *ft, ls_knots_t *knots)
static int _dng_populate(const ls_vendor_data_t *d, const ls_vendor_finetune_t *ft, ls_knots_t *knots)
static int _olympus_axes(const ls_vendor_data_t *d)
#define DNG_WARP_CENTER_SIZE
#define DNG_WARP_PLANE_SIZE
#define DNG_WARP_HEADER_SIZE
int ls_vendor_parse_dng_opcodelist3(const uint8_t *blob, size_t len, ls_vendor_dng_t *out)
Parse a DNG OpcodeList3 payload: WarpRectilinear (1) and VignetteRadial (3).
int ls_vendor_axes(const ls_vendor_data_t *d)
Which correction axes this data carries.
int ls_vendor_resolve(const ls_vendor_data_t *d, const ls_vendor_finetune_t *ft, int w, int h, ls_knots_t *knots, float *autoscale)
The conversion: vendor tables in, generic knots out.
static double _dng_warp_radial(const double coeffs[6], const double r2)
#define DNG_WARP_PLANES_MAX
static int _sony_has_data(const ls_vendor_data_t *d)
Turn a camera maker's embedded lens-correction tables into ls_knots_t.
#define LS_VENDOR_EBADDATA
Resolve/parse result: the data is structurally malformed. Distinct from 0 ("carries nothing") so the ...
@ LS_VENDOR_DNG
@ LS_VENDOR_OLYMPUS
@ LS_VENDOR_FUJI
@ LS_VENDOR_SONY
@ LS_VENDOR_NONE
A lens correction the camera maker measured and wrote into the file, as knots.
float cor_rgb[3][16]
float vig_radius[16]
float vig[16]
float radius[16]
One image's embedded correction data: the type tag and the matching member. A plain value — embed it ...
ls_vendor_dng_t dng
ls_vendor_fuji_t fuji
ls_vendor_sony_t sony
ls_vendor_type_t type
ls_vendor_olympus_t olympus
union ls_vendor_data_t::@1 u
A DNG's OpcodeList3 corrections, parsed. Fill it with ls_vendor_parse_dng_opcodelist3() — or by hand,...
double warp_coeffs[3][6]
Per-axis strength blend, 1.0 = exactly as the maker measured. The vendors' own GUIs expose these as "...
Fujifilm maker-note tables, verbatim: explicit knot radii shared by the three corrections,...
Olympus maker-note polynomials, verbatim. No vignetting: Olympus does not publish one,...
Sony maker-note tables, verbatim: fixed-point int16 over nc evenly spaced radii, which the file leave...