1 /*
2 * Copyright (C) 2007 by Andrew Zabolotny (author of lensfun, from which this filter derives from)
3 * Copyright (C) 2018 Stephen Seo
4 *
5 * This file is part of FFmpeg.
6 *
7 * This program 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 * This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 /**
22 * @file
23 * Lensfun filter, applies lens correction with parameters from the lensfun database
24 *
25 * @see https://lensfun.sourceforge.net/
26 */
27
29 #include <math.h>
30
36
37 #include <lensfun.h>
38
39 #define LANCZOS_RESOLUTION 256
40
45 };
46
51 };
52
60
71
83
86
91
92 #define OFFSET(x) offsetof(LensfunContext, x)
93 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
107 {
"focal_length",
"focal length of video (zoom; constant for the duration of the use of this filter)",
OFFSET(focal_length),
AV_OPT_TYPE_FLOAT, {.dbl=18}, 0.0, DBL_MAX,
FLAGS },
108 {
"aperture",
"aperture (constant for the duration of the use of this filter)",
OFFSET(aperture),
AV_OPT_TYPE_FLOAT, {.dbl=3.5}, 0.0, DBL_MAX,
FLAGS },
109 {
"focus_distance",
"focus distance (constant for the duration of the use of this filter)",
OFFSET(focus_distance),
AV_OPT_TYPE_FLOAT, {.dbl=1000.0f}, 0.0, DBL_MAX,
FLAGS },
111 {
"target_geometry",
"target geometry of the lens correction (only when geometry correction is enabled)",
OFFSET(target_geometry),
AV_OPT_TYPE_INT, {.i64=LF_RECTILINEAR}, 0, INT_MAX,
FLAGS, .unit =
"lens_geometry" },
112 {
"rectilinear",
"rectilinear lens (default)", 0,
AV_OPT_TYPE_CONST, {.i64=LF_RECTILINEAR}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
113 {
"fisheye",
"fisheye lens", 0,
AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
114 {
"panoramic",
"panoramic (cylindrical)", 0,
AV_OPT_TYPE_CONST, {.i64=LF_PANORAMIC}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
115 {
"equirectangular",
"equirectangular", 0,
AV_OPT_TYPE_CONST, {.i64=LF_EQUIRECTANGULAR}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
116 {
"fisheye_orthographic",
"orthographic fisheye", 0,
AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_ORTHOGRAPHIC}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
117 {
"fisheye_stereographic",
"stereographic fisheye", 0,
AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_STEREOGRAPHIC}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
118 {
"fisheye_equisolid",
"equisolid fisheye", 0,
AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_EQUISOLID}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
119 {
"fisheye_thoby",
"fisheye as measured by thoby", 0,
AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_THOBY}, 0, 0,
FLAGS, .unit =
"lens_geometry" },
120 {
"reverse",
"Does reverse correction (regular image to lens distorted)",
OFFSET(reverse),
AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1,
FLAGS },
126 };
127
129
131 {
133 lfDatabase *db;
134 const lfCamera **cameras;
135 const lfLens **lenses;
136
137 db = lf_db_create();
138 if ((lensfun->
db_path ? lf_db_load_path(db, lensfun->
db_path) : lf_db_load(db)) != LF_NO_ERROR) {
139 lf_db_destroy(db);
143 }
144
146 const lfCamera *const *cameras = lf_db_get_cameras(db);
147
150 for (
int i = 0; cameras && cameras[
i];
i++)
152 lf_db_destroy(db);
155 const lfLens *const *lenses = lf_db_get_lenses(db);
156
159 for (
int i = 0; lenses && lenses[
i];
i++)
161 lf_db_destroy(db);
163 }
164
165 lensfun->
lens = lf_lens_create();
166 lensfun->
camera = lf_camera_create();
167
168 cameras = lf_db_find_cameras(db, lensfun->
make, lensfun->
model);
169 if (cameras && *cameras) {
170 lf_camera_copy(lensfun->
camera, *cameras);
172 } else {
173 lf_free(cameras);
174 lf_db_destroy(db);
177 }
178 lf_free(cameras);
179
181 if (lenses && *lenses) {
182 lf_lens_copy(lensfun->
lens, *lenses);
184 } else {
185 lf_free(lenses);
186 lf_db_destroy(db);
189 }
190 lf_free(lenses);
191
192 lf_db_destroy(db);
193 return 0;
194 }
195
197 {
199 return 1.0f;
200 }
else if (x > -2.0
f && x < 2.0
f) {
202 } else {
203 return 0.0f;
204 }
205 }
206
208 {
213
218 lensfun->
camera->CropFactor,
224 lf_modifier_enable_distortion_correction(lensfun->
modifier);
226 lf_modifier_enable_scaling(lensfun->
modifier, lensfun->
scale);
227 }
229 lf_modifier_enable_tca_correction(lensfun->
modifier);
230 } else {
231 // lensfun->camera and lensfun->lens should have been initialized
233 }
234 }
235
242 // apply both geometry and subpixel distortion
243 lf_modifier_apply_subpixel_geometry_distortion(lensfun->
modifier,
244 0, 0,
247 } else {
248 // apply only subpixel distortion
249 lf_modifier_apply_subpixel_distortion(lensfun->
modifier,
250 0, 0,
253 }
258 // apply only geometry distortion
259 lf_modifier_apply_geometry_distortion(lensfun->
modifier,
260 0, 0,
263 }
264 }
265
274 } else {
277 }
278 }
279 }
280
281 return 0;
282 }
283
285 {
289
290 lf_modifier_apply_color_modification(
thread_data->modifier,
292 0,
298
299 return 0;
300 }
301
303 {
304 return x * x;
305 }
306
308 {
312
313 int x, y,
i, j, rgb_index;
314 float interpolated, new_x, new_y, d, norm;
315 int new_x_int, new_y_int;
318 for (rgb_index = 0; rgb_index < 3; ++rgb_index) {
320 // subpixel (and possibly geometry) distortion correction was applied, correct distortion
323 new_x_int =
thread_data->distortion_coords[x * 2 * 3 + y *
thread_data->width * 2 * 3 + rgb_index * 2] + 0.5f;
324 new_y_int =
thread_data->distortion_coords[x * 2 * 3 + y *
thread_data->width * 2 * 3 + rgb_index * 2 + 1] + 0.5f;
325 if (new_x_int < 0 || new_x_int >=
thread_data->width || new_y_int < 0 || new_y_int >=
thread_data->height) {
327 } else {
329 }
330 break;
332 interpolated = 0.0f;
334 new_x_int = new_x;
336 new_y_int = new_y;
337 if (new_x_int < 0 || new_x_int + 1 >=
thread_data->width || new_y_int < 0 || new_y_int + 1 >=
thread_data->height) {
339 } else {
341 thread_data->data_in[ new_x_int * 3 + rgb_index + new_y_int *
thread_data->linesize_in] * (new_x_int + 1 - new_x) * (new_y_int + 1 - new_y)
342 +
thread_data->data_in[(new_x_int + 1) * 3 + rgb_index + new_y_int *
thread_data->linesize_in] * (new_x - new_x_int) * (new_y_int + 1 - new_y)
343 +
thread_data->data_in[ new_x_int * 3 + rgb_index + (new_y_int + 1) *
thread_data->linesize_in] * (new_x_int + 1 - new_x) * (new_y - new_y_int)
344 +
thread_data->data_in[(new_x_int + 1) * 3 + rgb_index + (new_y_int + 1) *
thread_data->linesize_in] * (new_x - new_x_int) * (new_y - new_y_int);
345 }
346 break;
348 interpolated = 0.0f;
349 norm = 0.0f;
351 new_x_int = new_x;
353 new_y_int = new_y;
354 for (j = 0; j < 4; ++j)
355 for (
i = 0;
i < 4; ++
i) {
356 if (new_x_int +
i - 2 < 0 || new_x_int +
i - 2 >=
thread_data->width || new_y_int + j - 2 < 0 || new_y_int + j - 2 >=
thread_data->height)
357 continue;
358 d =
square(new_x - (new_x_int +
i - 2)) *
square(new_y - (new_y_int + j - 2));
360 continue;
362 norm += d;
363 interpolated +=
thread_data->data_in[(new_x_int +
i - 2) * 3 + rgb_index + (new_y_int + j - 2) *
thread_data->linesize_in] * d;
364 }
367 } else {
368 interpolated /= norm;
369 thread_data->data_out[x * 3 + rgb_index + y *
thread_data->linesize_out] = interpolated < 0.0f ? 0.0f : interpolated > 255.0f ? 255.0f : interpolated;
370 }
371 break;
372 }
374 // geometry distortion correction was applied, correct distortion
379 if (new_x_int < 0 || new_x_int >=
thread_data->width || new_y_int < 0 || new_y_int >=
thread_data->height) {
381 } else {
383 }
384 break;
386 interpolated = 0.0f;
388 new_x_int = new_x;
390 new_y_int = new_y;
391 if (new_x_int < 0 || new_x_int + 1 >=
thread_data->width || new_y_int < 0 || new_y_int + 1 >=
thread_data->height) {
393 } else {
395 thread_data->data_in[ new_x_int * 3 + rgb_index + new_y_int *
thread_data->linesize_in] * (new_x_int + 1 - new_x) * (new_y_int + 1 - new_y)
396 +
thread_data->data_in[(new_x_int + 1) * 3 + rgb_index + new_y_int *
thread_data->linesize_in] * (new_x - new_x_int) * (new_y_int + 1 - new_y)
397 +
thread_data->data_in[ new_x_int * 3 + rgb_index + (new_y_int + 1) *
thread_data->linesize_in] * (new_x_int + 1 - new_x) * (new_y - new_y_int)
398 +
thread_data->data_in[(new_x_int + 1) * 3 + rgb_index + (new_y_int + 1) *
thread_data->linesize_in] * (new_x - new_x_int) * (new_y - new_y_int);
399 }
400 break;
402 interpolated = 0.0f;
403 norm = 0.0f;
405 new_x_int = new_x;
407 new_y_int = new_y;
408 for (j = 0; j < 4; ++j)
409 for (
i = 0;
i < 4; ++
i) {
410 if (new_x_int +
i - 2 < 0 || new_x_int +
i - 2 >=
thread_data->width || new_y_int + j - 2 < 0 || new_y_int + j - 2 >=
thread_data->height)
411 continue;
412 d =
square(new_x - (new_x_int +
i - 2)) *
square(new_y - (new_y_int + j - 2));
414 continue;
416 norm += d;
417 interpolated +=
thread_data->data_in[(new_x_int +
i - 2) * 3 + rgb_index + (new_y_int + j - 2) *
thread_data->linesize_in] * d;
418 }
421 } else {
422 interpolated /= norm;
423 thread_data->data_out[x * 3 + rgb_index + y *
thread_data->linesize_out] = interpolated < 0.0f ? 0.0f : interpolated > 255.0f ? 255.0f : interpolated;
424 }
425 break;
426 }
427 } else {
428 // no distortion correction was applied
430 }
431 }
432
433 return 0;
434 }
435
437 {
445
451 }
452
456 .data_in = in->
data[0],
460 };
461
463 &vignetting_thread_data,
NULL,
465 }
466
472 }
474
479 .data_in = in->
data[0],
480 .data_out =
out->data[0],
482 .linesize_out =
out->linesize[0],
484 .mode = lensfun->
mode,
486 };
487
489 &distortion_correction_thread_data,
NULL,
491
494 } else {
496 }
497 }
498
500 {
502
504 lf_camera_destroy(lensfun->
camera);
506 lf_lens_destroy(lensfun->
lens);
508 lf_modifier_destroy(lensfun->
modifier);
511 }
512
514 {
519 },
520 };
521
524 .p.description =
NULL_IF_CONFIG_SMALL(
"Apply correction to an image based on info derived from the lensfun database."),
525 .p.priv_class = &lensfun_class,
533 };