• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
28 #include <float.h>
29 #include <math.h>
30 
31 #include "libavutil/imgutils.h"
32 #include "libavutil/opt.h"
33 #include "libswscale/swscale.h"
34 #include "avfilter.h"
35 #include "formats.h"
36 #include "internal.h"
37 #include "video.h"
38 
39 #include <lensfun.h>
40 
41 #define LANCZOS_RESOLUTION 256
42 
43 enum Mode {
44     VIGNETTING = 0x1,
45     GEOMETRY_DISTORTION = 0x2,
46     SUBPIXEL_DISTORTION = 0x4
47 };
48 
49 enum InterpolationType {
50     NEAREST,
51     LINEAR,
52     LANCZOS
53 };
54 
55 typedef struct VignettingThreadData {
56     int width, height;
57     uint8_t *data_in;
58     int linesize_in;
59     int pixel_composition;
60     lfModifier *modifier;
61 } VignettingThreadData;
62 
63 typedef struct DistortionCorrectionThreadData {
64     int width, height;
65     const float *distortion_coords;
66     const uint8_t *data_in;
67     uint8_t *data_out;
68     int linesize_in, linesize_out;
69     const float *interpolation;
70     int mode;
71     int interpolation_type;
72 } DistortionCorrectionThreadData;
73 
74 typedef struct LensfunContext {
75     const AVClass *class;
76     const char *make, *model, *lens_model, *db_path;
77     int mode;
78     float focal_length;
79     float aperture;
80     float focus_distance;
81     float scale;
82     int target_geometry;
83     int reverse;
84     int interpolation_type;
85 
86     float *distortion_coords;
87     float *interpolation;
88 
89     lfLens *lens;
90     lfCamera *camera;
91     lfModifier *modifier;
92 } LensfunContext;
93 
94 #define OFFSET(x) offsetof(LensfunContext, x)
95 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
96 static const AVOption lensfun_options[] = {
97     { "make", "set camera maker", OFFSET(make), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
98     { "model", "set camera model", OFFSET(model), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
99     { "lens_model", "set lens model", OFFSET(lens_model), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
100     { "db_path", "set path to database", OFFSET(db_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
101     { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=GEOMETRY_DISTORTION}, 0, VIGNETTING | GEOMETRY_DISTORTION | SUBPIXEL_DISTORTION, FLAGS, "mode" },
102         { "vignetting", "fix lens vignetting", 0, AV_OPT_TYPE_CONST, {.i64=VIGNETTING}, 0, 0, FLAGS, "mode" },
103         { "geometry", "correct geometry distortion", 0, AV_OPT_TYPE_CONST, {.i64=GEOMETRY_DISTORTION}, 0, 0, FLAGS, "mode" },
104         { "subpixel", "fix chromatic aberrations", 0, AV_OPT_TYPE_CONST, {.i64=SUBPIXEL_DISTORTION}, 0, 0, FLAGS, "mode" },
105         { "vig_geo", "fix lens vignetting and correct geometry distortion", 0, AV_OPT_TYPE_CONST, {.i64=VIGNETTING | GEOMETRY_DISTORTION}, 0, 0, FLAGS, "mode" },
106         { "vig_subpixel", "fix lens vignetting and chromatic aberrations", 0, AV_OPT_TYPE_CONST, {.i64=VIGNETTING | SUBPIXEL_DISTORTION}, 0, 0, FLAGS, "mode" },
107         { "distortion", "correct geometry distortion and chromatic aberrations", 0, AV_OPT_TYPE_CONST, {.i64=GEOMETRY_DISTORTION | SUBPIXEL_DISTORTION}, 0, 0, FLAGS, "mode" },
108         { "all", NULL, 0, AV_OPT_TYPE_CONST, {.i64=VIGNETTING | GEOMETRY_DISTORTION | SUBPIXEL_DISTORTION}, 0, 0, FLAGS, "mode" },
109     { "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 },
110     { "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 },
111     { "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 },
112     { "scale", "scale factor applied after corrections (0.0 means automatic scaling)", OFFSET(scale), AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, DBL_MAX, FLAGS },
113     { "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, "lens_geometry" },
114         { "rectilinear", "rectilinear lens (default)", 0, AV_OPT_TYPE_CONST, {.i64=LF_RECTILINEAR}, 0, 0, FLAGS, "lens_geometry" },
115         { "fisheye", "fisheye lens", 0, AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE}, 0, 0, FLAGS, "lens_geometry" },
116         { "panoramic", "panoramic (cylindrical)", 0, AV_OPT_TYPE_CONST, {.i64=LF_PANORAMIC}, 0, 0, FLAGS, "lens_geometry" },
117         { "equirectangular", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=LF_EQUIRECTANGULAR}, 0, 0, FLAGS, "lens_geometry" },
118         { "fisheye_orthographic", "orthographic fisheye", 0, AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_ORTHOGRAPHIC}, 0, 0, FLAGS, "lens_geometry" },
119         { "fisheye_stereographic", "stereographic fisheye", 0, AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_STEREOGRAPHIC}, 0, 0, FLAGS, "lens_geometry" },
120         { "fisheye_equisolid", "equisolid fisheye", 0, AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_EQUISOLID}, 0, 0, FLAGS, "lens_geometry" },
121         { "fisheye_thoby", "fisheye as measured by thoby", 0, AV_OPT_TYPE_CONST, {.i64=LF_FISHEYE_THOBY}, 0, 0, FLAGS, "lens_geometry" },
122     { "reverse", "Does reverse correction (regular image to lens distorted)", OFFSET(reverse), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS },
123     { "interpolation", "Type of interpolation", OFFSET(interpolation_type), AV_OPT_TYPE_INT, {.i64=LINEAR}, 0, LANCZOS, FLAGS, "interpolation" },
124         { "nearest", NULL, 0, AV_OPT_TYPE_CONST, {.i64=NEAREST}, 0, 0, FLAGS, "interpolation" },
125         { "linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "interpolation" },
126         { "lanczos", NULL, 0, AV_OPT_TYPE_CONST, {.i64=LANCZOS}, 0, 0, FLAGS, "interpolation" },
127     { NULL }
128 };
129 
130 AVFILTER_DEFINE_CLASS(lensfun);
131 
init(AVFilterContext * ctx)132 static av_cold int init(AVFilterContext *ctx)
133 {
134     LensfunContext *lensfun = ctx->priv;
135     lfDatabase *db;
136     const lfCamera **cameras;
137     const lfLens **lenses;
138 
139     db = lf_db_create();
140     if ((lensfun->db_path ? lf_db_load_path(db, lensfun->db_path) : lf_db_load(db)) != LF_NO_ERROR) {
141         lf_db_destroy(db);
142         av_log(ctx, AV_LOG_FATAL, "Failed to load lensfun database from %s path\n",
143                lensfun->db_path ? lensfun->db_path : "default");
144         return AVERROR_INVALIDDATA;
145     }
146 
147     if (!lensfun->make || !lensfun->model) {
148         const lfCamera *const *cameras = lf_db_get_cameras(db);
149 
150         av_log(ctx, AV_LOG_FATAL, "Option \"make\" or option \"model\" not specified\n");
151         av_log(ctx, AV_LOG_INFO, "Available values for \"make\" and \"model\":\n");
152         for (int i = 0; cameras && cameras[i]; i++)
153             av_log(ctx, AV_LOG_INFO, "\t%s\t%s\n", cameras[i]->Maker, cameras[i]->Model);
154         lf_db_destroy(db);
155         return AVERROR(EINVAL);
156     } else if (!lensfun->lens_model) {
157         const lfLens *const *lenses = lf_db_get_lenses(db);
158 
159         av_log(ctx, AV_LOG_FATAL, "Option \"lens_model\" not specified\n");
160         av_log(ctx, AV_LOG_INFO, "Available values for \"lens_model\":\n");
161         for (int i = 0; lenses && lenses[i]; i++)
162             av_log(ctx, AV_LOG_INFO, "\t%s\t(make %s)\n", lenses[i]->Model, lenses[i]->Maker);
163         lf_db_destroy(db);
164         return AVERROR(EINVAL);
165     }
166 
167     lensfun->lens = lf_lens_create();
168     lensfun->camera = lf_camera_create();
169 
170     cameras = lf_db_find_cameras(db, lensfun->make, lensfun->model);
171     if (cameras && *cameras) {
172         lf_camera_copy(lensfun->camera, *cameras);
173         av_log(ctx, AV_LOG_INFO, "Using camera %s\n", lensfun->camera->Model);
174     } else {
175         lf_free(cameras);
176         lf_db_destroy(db);
177         av_log(ctx, AV_LOG_FATAL, "Failed to find camera in lensfun database\n");
178         return AVERROR_INVALIDDATA;
179     }
180     lf_free(cameras);
181 
182     lenses = lf_db_find_lenses(db, lensfun->camera, NULL, lensfun->lens_model, 0);
183     if (lenses && *lenses) {
184         lf_lens_copy(lensfun->lens, *lenses);
185         av_log(ctx, AV_LOG_INFO, "Using lens %s\n", lensfun->lens->Model);
186     } else {
187         lf_free(lenses);
188         lf_db_destroy(db);
189         av_log(ctx, AV_LOG_FATAL, "Failed to find lens in lensfun database\n");
190         return AVERROR_INVALIDDATA;
191     }
192     lf_free(lenses);
193 
194     lf_db_destroy(db);
195     return 0;
196 }
197 
lanczos_kernel(float x)198 static float lanczos_kernel(float x)
199 {
200     if (x == 0.0f) {
201         return 1.0f;
202     } else if (x > -2.0f && x < 2.0f) {
203         return (2.0f * sin(M_PI * x) * sin(M_PI / 2.0f * x)) / (M_PI * M_PI * x * x);
204     } else {
205         return 0.0f;
206     }
207 }
208 
config_props(AVFilterLink * inlink)209 static int config_props(AVFilterLink *inlink)
210 {
211     AVFilterContext *ctx = inlink->dst;
212     LensfunContext *lensfun = ctx->priv;
213     int index;
214     float a;
215 
216     if (!lensfun->modifier) {
217         if (lensfun->camera && lensfun->lens) {
218             lensfun->modifier = lf_modifier_create(lensfun->lens,
219                                                    lensfun->focal_length,
220                                                    lensfun->camera->CropFactor,
221                                                    inlink->w,
222                                                    inlink->h, LF_PF_U8, lensfun->reverse);
223             if (lensfun->mode & VIGNETTING)
224                 lf_modifier_enable_vignetting_correction(lensfun->modifier, lensfun->aperture, lensfun->focus_distance);
225             if (lensfun->mode & GEOMETRY_DISTORTION) {
226                 lf_modifier_enable_distortion_correction(lensfun->modifier);
227                 lf_modifier_enable_projection_transform(lensfun->modifier, lensfun->target_geometry);
228                 lf_modifier_enable_scaling(lensfun->modifier, lensfun->scale);
229             }
230             if (lensfun->mode & SUBPIXEL_DISTORTION)
231                 lf_modifier_enable_tca_correction(lensfun->modifier);
232         } else {
233             // lensfun->camera and lensfun->lens should have been initialized
234             return AVERROR_BUG;
235         }
236     }
237 
238     if (!lensfun->distortion_coords) {
239         if (lensfun->mode & SUBPIXEL_DISTORTION) {
240             lensfun->distortion_coords = av_malloc_array(inlink->w * inlink->h, sizeof(float) * 2 * 3);
241             if (!lensfun->distortion_coords)
242                 return AVERROR(ENOMEM);
243             if (lensfun->mode & GEOMETRY_DISTORTION) {
244                 // apply both geometry and subpixel distortion
245                 lf_modifier_apply_subpixel_geometry_distortion(lensfun->modifier,
246                                                                0, 0,
247                                                                inlink->w, inlink->h,
248                                                                lensfun->distortion_coords);
249             } else {
250                 // apply only subpixel distortion
251                 lf_modifier_apply_subpixel_distortion(lensfun->modifier,
252                                                       0, 0,
253                                                       inlink->w, inlink->h,
254                                                       lensfun->distortion_coords);
255             }
256         } else if (lensfun->mode & GEOMETRY_DISTORTION) {
257             lensfun->distortion_coords = av_malloc_array(inlink->w * inlink->h, sizeof(float) * 2);
258             if (!lensfun->distortion_coords)
259                 return AVERROR(ENOMEM);
260             // apply only geometry distortion
261             lf_modifier_apply_geometry_distortion(lensfun->modifier,
262                                                   0, 0,
263                                                   inlink->w, inlink->h,
264                                                   lensfun->distortion_coords);
265         }
266     }
267 
268     if (!lensfun->interpolation)
269         if (lensfun->interpolation_type == LANCZOS) {
270             lensfun->interpolation = av_malloc_array(LANCZOS_RESOLUTION, sizeof(float) * 4);
271             if (!lensfun->interpolation)
272                 return AVERROR(ENOMEM);
273             for (index = 0; index < 4 * LANCZOS_RESOLUTION; ++index) {
274                 if (index == 0) {
275                     lensfun->interpolation[index] = 1.0f;
276                 } else {
277                     a = sqrtf((float)index / LANCZOS_RESOLUTION);
278                     lensfun->interpolation[index] = lanczos_kernel(a);
279                 }
280             }
281         }
282 
283     return 0;
284 }
285 
vignetting_filter_slice(AVFilterContext * ctx,void * arg,int jobnr,int nb_jobs)286 static int vignetting_filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
287 {
288     const VignettingThreadData *thread_data = arg;
289     const int slice_start = thread_data->height *  jobnr      / nb_jobs;
290     const int slice_end   = thread_data->height * (jobnr + 1) / nb_jobs;
291 
292     lf_modifier_apply_color_modification(thread_data->modifier,
293                                          thread_data->data_in + slice_start * thread_data->linesize_in,
294                                          0,
295                                          slice_start,
296                                          thread_data->width,
297                                          slice_end - slice_start,
298                                          thread_data->pixel_composition,
299                                          thread_data->linesize_in);
300 
301     return 0;
302 }
303 
square(float x)304 static float square(float x)
305 {
306     return x * x;
307 }
308 
distortion_correction_filter_slice(AVFilterContext * ctx,void * arg,int jobnr,int nb_jobs)309 static int distortion_correction_filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
310 {
311     const DistortionCorrectionThreadData *thread_data = arg;
312     const int slice_start = thread_data->height *  jobnr      / nb_jobs;
313     const int slice_end   = thread_data->height * (jobnr + 1) / nb_jobs;
314 
315     int x, y, i, j, rgb_index;
316     float interpolated, new_x, new_y, d, norm;
317     int new_x_int, new_y_int;
318     for (y = slice_start; y < slice_end; ++y)
319         for (x = 0; x < thread_data->width; ++x)
320             for (rgb_index = 0; rgb_index < 3; ++rgb_index) {
321                 if (thread_data->mode & SUBPIXEL_DISTORTION) {
322                     // subpixel (and possibly geometry) distortion correction was applied, correct distortion
323                     switch(thread_data->interpolation_type) {
324                     case NEAREST:
325                         new_x_int = thread_data->distortion_coords[x * 2 * 3 + y * thread_data->width * 2 * 3 + rgb_index * 2]     + 0.5f;
326                         new_y_int = thread_data->distortion_coords[x * 2 * 3 + y * thread_data->width * 2 * 3 + rgb_index * 2 + 1] + 0.5f;
327                         if (new_x_int < 0 || new_x_int >= thread_data->width || new_y_int < 0 || new_y_int >= thread_data->height) {
328                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = 0;
329                         } else {
330                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = thread_data->data_in[new_x_int * 3 + rgb_index + new_y_int * thread_data->linesize_in];
331                         }
332                         break;
333                     case LINEAR:
334                         interpolated = 0.0f;
335                         new_x = thread_data->distortion_coords[x * 2 * 3 + y * thread_data->width * 2 * 3 + rgb_index * 2];
336                         new_x_int = new_x;
337                         new_y = thread_data->distortion_coords[x * 2 * 3 + y * thread_data->width * 2 * 3 + rgb_index * 2 + 1];
338                         new_y_int = new_y;
339                         if (new_x_int < 0 || new_x_int + 1 >= thread_data->width || new_y_int < 0 || new_y_int + 1 >= thread_data->height) {
340                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = 0;
341                         } else {
342                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] =
343                                   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)
344                                 + 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)
345                                 + 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)
346                                 + 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);
347                         }
348                         break;
349                     case LANCZOS:
350                         interpolated = 0.0f;
351                         norm = 0.0f;
352                         new_x = thread_data->distortion_coords[x * 2 * 3 + y * thread_data->width * 2 * 3 + rgb_index * 2];
353                         new_x_int = new_x;
354                         new_y = thread_data->distortion_coords[x * 2 * 3 + y * thread_data->width * 2 * 3 + rgb_index * 2 + 1];
355                         new_y_int = new_y;
356                         for (j = 0; j < 4; ++j)
357                             for (i = 0; i < 4; ++i) {
358                                 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)
359                                     continue;
360                                 d = square(new_x - (new_x_int + i - 2)) * square(new_y - (new_y_int + j - 2));
361                                 if (d >= 4.0f)
362                                     continue;
363                                 d = thread_data->interpolation[(int)(d * LANCZOS_RESOLUTION)];
364                                 norm += d;
365                                 interpolated += thread_data->data_in[(new_x_int + i - 2) * 3 + rgb_index + (new_y_int + j - 2) * thread_data->linesize_in] * d;
366                             }
367                         if (norm == 0.0f) {
368                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = 0;
369                         } else {
370                             interpolated /= norm;
371                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = interpolated < 0.0f ? 0.0f : interpolated > 255.0f ? 255.0f : interpolated;
372                         }
373                         break;
374                     }
375                 } else if (thread_data->mode & GEOMETRY_DISTORTION) {
376                     // geometry distortion correction was applied, correct distortion
377                     switch(thread_data->interpolation_type) {
378                     case NEAREST:
379                         new_x_int = thread_data->distortion_coords[x * 2 + y * thread_data->width * 2]     + 0.5f;
380                         new_y_int = thread_data->distortion_coords[x * 2 + y * thread_data->width * 2 + 1] + 0.5f;
381                         if (new_x_int < 0 || new_x_int >= thread_data->width || new_y_int < 0 || new_y_int >= thread_data->height) {
382                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = 0;
383                         } else {
384                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = thread_data->data_in[new_x_int * 3 + rgb_index + new_y_int * thread_data->linesize_in];
385                         }
386                         break;
387                     case LINEAR:
388                         interpolated = 0.0f;
389                         new_x = thread_data->distortion_coords[x * 2 + y * thread_data->width * 2];
390                         new_x_int = new_x;
391                         new_y = thread_data->distortion_coords[x * 2 + y * thread_data->width * 2 + 1];
392                         new_y_int = new_y;
393                         if (new_x_int < 0 || new_x_int + 1 >= thread_data->width || new_y_int < 0 || new_y_int + 1 >= thread_data->height) {
394                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = 0;
395                         } else {
396                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] =
397                                   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)
398                                 + 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)
399                                 + 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)
400                                 + 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);
401                         }
402                         break;
403                     case LANCZOS:
404                         interpolated = 0.0f;
405                         norm = 0.0f;
406                         new_x = thread_data->distortion_coords[x * 2     + y * thread_data->width * 2];
407                         new_x_int = new_x;
408                         new_y = thread_data->distortion_coords[x * 2 + 1 + y * thread_data->width * 2];
409                         new_y_int = new_y;
410                         for (j = 0; j < 4; ++j)
411                             for (i = 0; i < 4; ++i) {
412                                 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)
413                                     continue;
414                                 d = square(new_x - (new_x_int + i - 2)) * square(new_y - (new_y_int + j - 2));
415                                 if (d >= 4.0f)
416                                     continue;
417                                 d = thread_data->interpolation[(int)(d * LANCZOS_RESOLUTION)];
418                                 norm += d;
419                                 interpolated += thread_data->data_in[(new_x_int + i - 2) * 3 + rgb_index + (new_y_int + j - 2) * thread_data->linesize_in] * d;
420                             }
421                         if (norm == 0.0f) {
422                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = 0;
423                         } else {
424                             interpolated /= norm;
425                             thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = interpolated < 0.0f ? 0.0f : interpolated > 255.0f ? 255.0f : interpolated;
426                         }
427                         break;
428                     }
429                 } else {
430                     // no distortion correction was applied
431                     thread_data->data_out[x * 3 + rgb_index + y * thread_data->linesize_out] = thread_data->data_in[x * 3 + rgb_index + y * thread_data->linesize_in];
432                 }
433             }
434 
435     return 0;
436 }
437 
filter_frame(AVFilterLink * inlink,AVFrame * in)438 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
439 {
440     AVFilterContext *ctx = inlink->dst;
441     LensfunContext *lensfun = ctx->priv;
442     AVFilterLink *outlink = ctx->outputs[0];
443     AVFrame *out;
444     VignettingThreadData vignetting_thread_data;
445     DistortionCorrectionThreadData distortion_correction_thread_data;
446 
447     if (lensfun->mode & VIGNETTING) {
448         av_frame_make_writable(in);
449 
450         vignetting_thread_data = (VignettingThreadData) {
451             .width = inlink->w,
452             .height = inlink->h,
453             .data_in = in->data[0],
454             .linesize_in = in->linesize[0],
455             .pixel_composition = LF_CR_3(RED, GREEN, BLUE),
456             .modifier = lensfun->modifier
457         };
458 
459         ff_filter_execute(ctx, vignetting_filter_slice,
460                           &vignetting_thread_data, NULL,
461                           FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
462     }
463 
464     if (lensfun->mode & (GEOMETRY_DISTORTION | SUBPIXEL_DISTORTION)) {
465         out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
466         if (!out) {
467             av_frame_free(&in);
468             return AVERROR(ENOMEM);
469         }
470         av_frame_copy_props(out, in);
471 
472         distortion_correction_thread_data = (DistortionCorrectionThreadData) {
473             .width = inlink->w,
474             .height = inlink->h,
475             .distortion_coords = lensfun->distortion_coords,
476             .data_in = in->data[0],
477             .data_out = out->data[0],
478             .linesize_in = in->linesize[0],
479             .linesize_out = out->linesize[0],
480             .interpolation = lensfun->interpolation,
481             .mode = lensfun->mode,
482             .interpolation_type = lensfun->interpolation_type
483         };
484 
485         ff_filter_execute(ctx, distortion_correction_filter_slice,
486                           &distortion_correction_thread_data, NULL,
487                           FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
488 
489         av_frame_free(&in);
490         return ff_filter_frame(outlink, out);
491     } else {
492         return ff_filter_frame(outlink, in);
493     }
494 }
495 
uninit(AVFilterContext * ctx)496 static av_cold void uninit(AVFilterContext *ctx)
497 {
498     LensfunContext *lensfun = ctx->priv;
499 
500     if (lensfun->camera)
501         lf_camera_destroy(lensfun->camera);
502     if (lensfun->lens)
503         lf_lens_destroy(lensfun->lens);
504     if (lensfun->modifier)
505         lf_modifier_destroy(lensfun->modifier);
506     av_freep(&lensfun->distortion_coords);
507     av_freep(&lensfun->interpolation);
508 }
509 
510 static const AVFilterPad lensfun_inputs[] = {
511     {
512         .name         = "default",
513         .type         = AVMEDIA_TYPE_VIDEO,
514         .config_props = config_props,
515         .filter_frame = filter_frame,
516     },
517 };
518 
519 static const AVFilterPad lensfun_outputs[] = {
520     {
521         .name = "default",
522         .type = AVMEDIA_TYPE_VIDEO,
523     },
524 };
525 
526 const AVFilter ff_vf_lensfun = {
527     .name          = "lensfun",
528     .description   = NULL_IF_CONFIG_SMALL("Apply correction to an image based on info derived from the lensfun database."),
529     .priv_size     = sizeof(LensfunContext),
530     .init          = init,
531     .uninit        = uninit,
532     FILTER_INPUTS(lensfun_inputs),
533     FILTER_OUTPUTS(lensfun_outputs),
534     FILTER_SINGLE_PIXFMT(AV_PIX_FMT_RGB24),
535     .priv_class    = &lensfun_class,
536     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
537 };
538