1 /* -*- C++ -*-
2 *
3 * This file is part of ART.
4 *
5 * Copyright (c) 2020 Alberto Griggio <alberto.griggio@gmail.com>
6 *
7 * ART is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * ART is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with ART. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 // adapted from mlens.c in PR #7092 of darktable
22 // original author: Freddie Witherden (https://freddie.witherden.org/)
23 // copyright of original code follows
24 /*
25 This file is part of darktable,
26 Copyright (C) 2010-2020 darktable developers.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40 */
41
42 #include "lensexif.h"
43 #include "metadata.h"
44 #include "settings.h"
45 #include <array>
46 #include <iostream>
47
48 namespace rtengine {
49
50 extern const Settings *settings;
51
52
53 namespace {
54
55 class SonyCorrectionData: public ExifLensCorrection::CorrectionData {
56 public:
57 int nc;
58 std::array<short, 16> distortion;
59 std::array<short, 16> ca_r;
60 std::array<short, 16> ca_b;
61 std::array<short, 16> vignetting;
62
get_coeffs(std::vector<float> & knots,std::vector<float> & dist,std::vector<float> & vig,std::array<std::vector<float>,3> & ca,bool & is_dng) const63 void get_coeffs(std::vector<float> &knots, std::vector<float> &dist, std::vector<float> &vig, std::array<std::vector<float>, 3> &ca, bool &is_dng) const override
64 {
65 is_dng = false;
66 const float scale = 1.f;
67
68 knots.resize(nc);
69 for (int i = 0; i < 3; ++i) {
70 ca[i].resize(nc);
71 }
72 dist.resize(nc);
73 vig.resize(nc);
74
75 constexpr float vig_scaling = 0.7f; // empirically determined on my lenses
76
77 for (int i = 0; i < nc; i++) {
78 knots[i] = float(i) / (nc - 1);
79
80 dist[i] = (distortion[i] * std::pow(2.f, -14.f) + 1) * scale;
81
82 ca[0][i] = ca[1][i] = ca[2][i] = 1.f;
83 ca[0][i] *= ca_r[i] * std::pow(2.f, -21.f) + 1;
84 ca[2][i] *= ca_b[i] * std::pow(2.f, -21.f) + 1;
85
86 vig[i] = std::pow(2.f, 0.5f - std::pow(2.f, vig_scaling * vignetting[i] * std::pow(2.f, -13.f) -1));
87 }
88 }
89 };
90
91
92 class FujiCorrectionData: public ExifLensCorrection::CorrectionData {
93 public:
94 float cropf;
95 std::array<float, 9> knots;
96 std::array<float, 9> distortion;
97 std::array<float, 9> ca_r;
98 std::array<float, 9> ca_b;
99 std::array<float, 9> vignetting;
100
get_coeffs(std::vector<float> & knots,std::vector<float> & dist,std::vector<float> & vig,std::array<std::vector<float>,3> & ca,bool & is_dng) const101 void get_coeffs(std::vector<float> &knots, std::vector<float> &dist, std::vector<float> &vig, std::array<std::vector<float>, 3> &ca, bool &is_dng) const override
102 {
103 is_dng = false;
104 knots.resize(9);
105 dist.resize(9);
106 vig.resize(9);
107 for (int i = 0; i < 3; ++i) {
108 ca[i].resize(9);
109 }
110
111 for (int i = 0; i < 9; i++) {
112 knots[i] = cropf * this->knots[i];
113
114 dist[i] = distortion[i] / 100 + 1;
115
116 ca[0][i] = ca[1][i] = ca[2][i] = 1.f;
117
118 ca[0][i] *= ca_r[i] + 1;
119 ca[2][i] *= ca_b[i] + 1;
120
121 vig[i] = 1 - (1 - vignetting[i] / 100);
122 }
123 }
124 };
125
126
127
128 class DNGCorrectionData: public ExifLensCorrection::CorrectionData {
129 public:
130 float cx_d;
131 float cy_d;
132 float cx_v;
133 float cy_v;
134 std::vector<float> warp_rectilinear;
135 std::vector<float> vignette_radial;
136
DNGCorrectionData()137 DNGCorrectionData():
138 cx_d(0), cy_d(0),
139 cx_v(0), cy_v(0),
140 warp_rectilinear(),
141 vignette_radial()
142 {}
143
get_coeffs(std::vector<float> & knots,std::vector<float> & dist,std::vector<float> & vig,std::array<std::vector<float>,3> & ca,bool & is_dng) const144 void get_coeffs(std::vector<float> &knots, std::vector<float> &dist, std::vector<float> &vig, std::array<std::vector<float>, 3> &ca, bool &is_dng) const override
145 {
146 is_dng = true;
147 knots = { cx_d, cy_d, cx_v, cy_v };
148 dist = warp_rectilinear;
149 vig = vignette_radial;
150 }
151
parse(const std::vector<Exiv2::byte> & buf)152 static DNGCorrectionData *parse(const std::vector<Exiv2::byte> &buf)
153 {
154 DNGCorrectionData ret;
155 const Exiv2::byte *data = &buf[0];
156 uint32_t num_entries = Exiv2::getULong(data, Exiv2::bigEndian);
157 size_t idx = 4;
158 bool ok = false;
159
160 for (size_t i = 0; i < num_entries && idx < buf.size(); ++i) {
161 uint32_t opid = Exiv2::getULong(data+idx, Exiv2::bigEndian);
162 idx += 4;
163 idx += 4; // version
164 idx += 4; // flags
165 size_t size = Exiv2::getULong(data+idx, Exiv2::bigEndian);
166 idx += 4;
167 if (opid == 1) { // WarpRectilinear
168 uint32_t n = Exiv2::getULong(data + idx, Exiv2::bigEndian);
169 size_t wstart = idx + 4;
170 size_t cstart = wstart + 6 * 8;
171 if (n == 3) {
172 wstart += 6 * 8;
173 cstart += 6 * 8 * 2;
174 } else if (n != 1) {
175 cstart = buf.size() + 1;
176 }
177 if (cstart + 8 * 2 <= buf.size()) {
178 ret.warp_rectilinear.resize(6);
179 for (int j = 0; j < 6; ++j) {
180 ret.warp_rectilinear[j] = Exiv2::getDouble(data + wstart, Exiv2::bigEndian);
181 wstart += 8;
182 }
183 ret.cx_d = Exiv2::getDouble(data + cstart, Exiv2::bigEndian);
184 cstart += 8;
185 ret.cy_d = Exiv2::getDouble(data + cstart, Exiv2::bigEndian);
186 ok = true;
187 }
188 if (settings->verbose) {
189 std::cout << "WARP RECTILINEAR: ";
190 for (auto w : ret.warp_rectilinear) {
191 std::cout << w << " ";
192 }
193 std::cout << "| " << ret.cx_d << " " << ret.cy_d << std::endl;
194 }
195 } else if (opid == 3) { // FixVignetteRadial
196 size_t start = idx;
197 size_t end = idx + 7 * 8;
198 if (end <= buf.size()) {
199 ret.vignette_radial.resize(6);
200 for (int j = 0; j < 5; ++j) {
201 ret.vignette_radial[j] = Exiv2::getDouble(data + start, Exiv2::bigEndian);
202 start += 8;
203 }
204 ret.cx_v = Exiv2::getDouble(data + start, Exiv2::bigEndian);
205 start += 8;
206 ret.cy_v = Exiv2::getDouble(data + start, Exiv2::bigEndian);
207 ok = true;
208 }
209 }
210 idx += size;
211 }
212
213 if (!ok) {
214 return nullptr;
215 } else {
216 return new DNGCorrectionData(ret);
217 }
218 }
219 };
220
221
interpolate(const std::vector<float> & xi,const std::vector<float> & yi,float x)222 float interpolate(const std::vector<float> &xi, const std::vector<float> &yi, float x)
223 {
224 if (x < xi[0]) {
225 return yi[0];
226 }
227
228 for (size_t i = 1; i < xi.size(); i++) {
229 if (x >= xi[i - 1] && x <= xi[i]) {
230 float dydx = (yi[i] - yi[i - 1]) / (xi[i] - xi[i - 1]);
231
232 return yi[i - 1] + (x - xi[i - 1]) * dydx;
233 }
234 }
235
236 return yi[yi.size() - 1];
237 }
238
239 } // namespace
240
241
ExifLensCorrection(const FramesMetaData * meta,int width,int height,const CoarseTransformParams & coarse,int rawRotationDeg)242 ExifLensCorrection::ExifLensCorrection(const FramesMetaData *meta, int width, int height, const CoarseTransformParams &coarse, int rawRotationDeg):
243 data_(),
244 is_dng_(false),
245 swap_xy_(false)
246 {
247 if (rawRotationDeg >= 0) {
248 int rot = (coarse.rotate + rawRotationDeg) % 360;
249 swap_xy_ = (rot == 90 || rot == 270);
250 if (swap_xy_) {
251 std::swap(width, height);
252 }
253 }
254
255 w2_ = width * 0.5f;
256 h2_ = height * 0.5f;
257 r_ = 1 / std::sqrt(SQR(w2_) + SQR(h2_));
258
259 auto make = meta->getMake();
260 if (make != "SONY" && make != "FUJIFILM" && !meta->isDNG()) {
261 return;
262 }
263
264 try {
265 auto md = Exiv2Metadata(meta->getFileName());
266 if (meta->isDNG()) {
267 // check if this is a DNG
268 md.load();
269 auto &exif = md.exifData();
270 auto it = exif.findKey(Exiv2::ExifKey("Exif.SubImage1.OpcodeList3"));
271 if (it != exif.end()) {
272 std::vector<Exiv2::byte> buf;
273 buf.resize(it->value().size());
274 it->value().copy(&buf[0], Exiv2::invalidByteOrder);
275 data_.reset(DNGCorrectionData::parse(buf));
276 }
277 } else {
278 auto mn = md.getMakernotes();
279
280 const auto gettag =
281 [&](const char *name) -> std::string
282 {
283 auto it = mn.find(name);
284 if (it != mn.end()) {
285 return it->second;
286 }
287 return "";
288 };
289
290 const auto getvec =
291 [&](const char *tag) -> std::vector<float>
292 {
293 std::istringstream src(gettag(tag));
294 std::vector<float> ret;
295 float val;
296 while (src >> val) {
297 ret.push_back(val);
298 }
299 return ret;
300 };
301
302
303 if (make == "SONY") {
304 auto posd = getvec("DistortionCorrParams");
305 auto posc = getvec("ChromaticAberrationCorrParams");
306 auto posv = getvec("VignettingCorrParams");
307
308 if (!posd.empty() && !posc.empty() && !posv.empty() &&
309 posd[0] <= 16 && posc[0] == 2 * posd[0] && posv[0] == posd[0]) {
310 SonyCorrectionData *sony = new SonyCorrectionData();
311 data_.reset(sony);
312 sony->nc = posd[0];
313 for (int i = 0; i < sony->nc; ++i) {
314 sony->distortion[i] = posd[i+1];
315 sony->ca_r[i] = posc[i+1];
316 sony->ca_b[i] = posc[sony->nc + i+1];
317 sony->vignetting[i] = posv[i+1];
318 }
319 }
320 } else if (make == "FUJIFILM") {
321 auto posd = getvec("GeometricDistortionParams");
322 auto posc = getvec("ChromaticAberrationParams");
323 auto posv = getvec("VignettingParams");
324
325 if (posd.size() == 19 && posc.size() == 29 && posv.size() == 19) {
326 FujiCorrectionData *fuji = new FujiCorrectionData();
327 data_.reset(fuji);
328
329 for(int i = 0; i < 9; i++) {
330 float kd = posd[i+1], kc = posc[i + 1], kv = posv[i + 1];
331 if (kd != kc || kd != kv) {
332 data_.reset(nullptr);
333 break;
334 }
335
336 fuji->knots[i] = kd;
337 fuji->distortion[i] = posd[i + 10];
338 fuji->ca_r[i] = posc[i + 10];
339 fuji->ca_b[i] = posc[i + 19];
340 fuji->vignetting[i] = posv[i + 10];
341 }
342
343 if (data_) {
344 // Account for the 1.25x crop modes in some Fuji cameras
345 std::string val = gettag("CropMode");
346 if (val == "2" || val == "4") {
347 fuji->cropf = 1.25f;
348 } else {
349 fuji->cropf = 1;
350 }
351 }
352 }
353 }
354 }
355 } catch (std::exception &exc) {
356 data_.reset(nullptr);
357 }
358
359 if (data_) {
360 data_->get_coeffs(knots_, dist_, vig_, ca_, is_dng_);
361
362 if (is_dng_) {
363 float cx_d = knots_[0] * width;
364 float cy_d = knots_[1] * height;
365 float cx_v = knots_[2] * width;
366 float cy_v = knots_[3] * height;
367 float mx_d = std::max(cx_d, width - cx_d);
368 float my_d = std::max(cy_d, height - cy_d);
369 float m_d = std::sqrt(SQR(mx_d) + SQR(my_d));
370 float mx_v = std::max(cx_v, width - cx_v);
371 float my_v = std::max(cy_v, height - cy_v);
372 float m_v = std::sqrt(SQR(mx_v) + SQR(my_v));
373 knots_[0] = cx_d;
374 knots_[1] = cy_d;
375 knots_[2] = cx_v;
376 knots_[3] = cy_v;
377 knots_.push_back(m_d);
378 knots_.push_back(m_v);
379 }
380 }
381 }
382
383
ok() const384 bool ExifLensCorrection::ok() const
385 {
386 return data_.get();
387 }
388
389
ok(const FramesMetaData * meta)390 bool ExifLensCorrection::ok(const FramesMetaData *meta)
391 {
392 ExifLensCorrection corr(meta, -1, -1, CoarseTransformParams(), -1);
393 return corr.ok();
394 }
395
396
correctDistortion(double & x,double & y,int cx,int cy,double scale) const397 void ExifLensCorrection::correctDistortion(double &x, double &y, int cx, int cy, double scale) const
398 {
399 if (!data_) {
400 return;
401 }
402
403 if (!is_dng_) {
404 float xx = x + cx;
405 float yy = y + cy;
406 if (swap_xy_) {
407 std::swap(xx, yy);
408 }
409
410 float ccx = xx - w2_;
411 float ccy = yy - h2_;
412 float dr = interpolate(knots_, dist_, r_ * std::sqrt(SQR(ccx) + SQR(ccy)));
413
414 x = dr * ccx + w2_;
415 y = dr * ccy + h2_;
416 if (swap_xy_) {
417 std::swap(x, y);
418 }
419 x -= cx;
420 y -= cy;
421
422 x *= scale;
423 y *= scale;
424 } else if (dist_.size() == 6) {
425 float xx = x + cx;
426 float yy = y + cy;
427 if (swap_xy_) {
428 std::swap(xx, yy);
429 }
430
431 const float cx1 = knots_[0];
432 const float cy1 = knots_[1];
433 const float m = knots_[4];
434
435 const float dx = (xx - cx1) / m;
436 const float dy = (yy - cy1) / m;
437 const float dx2 = SQR(dx);
438 const float dy2 = SQR(dy);
439 const float r2 = dx2 + dy2;
440 const float f = dist_[0] + r2 * (dist_[1] + r2 * (dist_[2] + r2 * dist_[3]));
441 const float dx_r = f * dx;
442 const float dy_r = f * dy;
443 const float dxdy2 = 2 * dx * dy;
444 const float dx_t = dist_[4] * dxdy2 + dist_[5] * (r2 + 2 * dx2);
445 const float dy_t = dist_[5] * dxdy2 + dist_[4] * (r2 + 2 * dx2);
446
447 x = cx1 + m * (dx_r + dx_t);
448 y = cy1 + m * (dy_r + dy_t);
449
450 if (swap_xy_) {
451 std::swap(x, y);
452 }
453 x -= cx;
454 y -= cy;
455
456 x *= scale;
457 y *= scale;
458 }
459 }
460
461
isCACorrectionAvailable() const462 bool ExifLensCorrection::isCACorrectionAvailable() const
463 {
464 return data_.get() && !is_dng_;
465 }
466
467
correctCA(double & x,double & y,int cx,int cy,int channel) const468 void ExifLensCorrection::correctCA(double &x, double &y, int cx, int cy, int channel) const
469 {
470 if (data_ && !is_dng_) {
471 float xx = x + cx;
472 float yy = y + cy;
473 if (swap_xy_) {
474 std::swap(xx, yy);
475 }
476
477 float ccx = xx - w2_;
478 float ccy = yy - h2_;
479 float dr = interpolate(knots_, ca_[channel], r_ * std::sqrt(SQR(ccx) + SQR(ccy)));
480
481 x = dr * ccx + w2_;
482 y = dr * ccy + h2_;
483 if (swap_xy_) {
484 std::swap(x, y);
485 }
486 x -= cx;
487 y -= cy;
488 }
489 }
490
491
processVignette(int width,int height,float ** rawData) const492 void ExifLensCorrection::processVignette(int width, int height, float** rawData) const
493 {
494 if (!data_) {
495 return;
496 }
497
498 if (!is_dng_) {
499 for (int y = 0; y < height; ++y) {
500 for (int x = 0; x < width; ++x) {
501 float cx = x - w2_;
502 float cy = y - h2_;
503 float sf = interpolate(knots_, vig_, r_ * std::sqrt(SQR(cx) + SQR(cy)));
504 rawData[y][x] /= SQR(sf);
505 }
506 }
507 } else if (vig_.size() == 5) {
508 const float cx = knots_[2];
509 const float cy = knots_[3];
510 const float m2 = 1.f / SQR(knots_[5]);
511
512 for (int y = 0; y < height; ++y) {
513 for (int x = 0; x < width; ++x) {
514 const float r2 = m2 * (SQR(x - cx) + SQR(y - cy));
515 const float g = 1.f + r2 * (vig_[0] + r2 * (vig_[1] + r2 * (vig_[2] + r2 * (vig_[3] + r2 * vig_[4]))));
516 rawData[y][x] *= g;
517 }
518 }
519 }
520 }
521
522 } // namespace rtengine
523