FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_colorcorrect.c
Date: 2026-09-24 20:08:24
Exec Total Coverage
Lines: 0 304 0.0%
Functions: 0 11 0.0%
Branches: 0 81 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2021 Paul B Mahol
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <float.h>
22
23 #include "libavutil/mem.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/pixdesc.h"
26 #include "avfilter.h"
27 #include "filters.h"
28 #include "video.h"
29
30 typedef enum AnalyzeMode {
31 MANUAL,
32 AVERAGE,
33 MINMAX,
34 MEDIAN,
35 NB_ANALYZE
36 } AnalyzeMode;
37
38 typedef struct ColorCorrectContext {
39 const AVClass *class;
40
41 float rl, bl;
42 float rh, bh;
43 float saturation;
44 int analyze;
45
46 int depth;
47 float max, imax;
48
49 int chroma_w, chroma_h;
50 int planeheight[4];
51 int planewidth[4];
52
53 unsigned *uhistogram;
54 unsigned *vhistogram;
55
56 float (*analyzeret)[4];
57
58 int (*do_analyze)(AVFilterContext *s, void *arg,
59 int jobnr, int nb_jobs);
60 int (*do_slice)(AVFilterContext *s, void *arg,
61 int jobnr, int nb_jobs);
62 } ColorCorrectContext;
63
64 ✗ static int average_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
65 {
66 ✗ ColorCorrectContext *s = ctx->priv;
67 ✗ AVFrame *frame = arg;
68 ✗ const float imax = s->imax;
69 ✗ const int width = s->planewidth[1];
70 ✗ const int height = s->planeheight[1];
71 ✗ const int slice_start = ff_slice_pos(height, jobnr, nb_jobs);
72 ✗ const int slice_end = ff_slice_pos(height, jobnr + 1, nb_jobs);
73 ✗ const ptrdiff_t ulinesize = frame->linesize[1];
74 ✗ const ptrdiff_t vlinesize = frame->linesize[2];
75 ✗ const uint8_t *uptr = (const uint8_t *)frame->data[1] + slice_start * ulinesize;
76 ✗ const uint8_t *vptr = (const uint8_t *)frame->data[2] + slice_start * vlinesize;
77 ✗ int sum_u = 0, sum_v = 0;
78
79 ✗ for (int y = slice_start; y < slice_end; y++) {
80 ✗ for (int x = 0; x < width; x++) {
81 ✗ sum_u += uptr[x];
82 ✗ sum_v += vptr[x];
83 }
84
85 ✗ uptr += ulinesize;
86 ✗ vptr += vlinesize;
87 }
88
89 ✗ s->analyzeret[jobnr][0] = s->analyzeret[jobnr][2] = imax * sum_u / (float)((slice_end - slice_start) * width) - 0.5f;
90 ✗ s->analyzeret[jobnr][1] = s->analyzeret[jobnr][3] = imax * sum_v / (float)((slice_end - slice_start) * width) - 0.5f;
91
92 ✗ return 0;
93 }
94
95 ✗ static int average_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
96 {
97 ✗ ColorCorrectContext *s = ctx->priv;
98 ✗ AVFrame *frame = arg;
99 ✗ const float imax = s->imax;
100 ✗ const int width = s->planewidth[1];
101 ✗ const int height = s->planeheight[1];
102 ✗ const int slice_start = ff_slice_pos(height, jobnr, nb_jobs);
103 ✗ const int slice_end = ff_slice_pos(height, jobnr + 1, nb_jobs);
104 ✗ const ptrdiff_t ulinesize = frame->linesize[1] / 2;
105 ✗ const ptrdiff_t vlinesize = frame->linesize[2] / 2;
106 ✗ const uint16_t *uptr = (const uint16_t *)frame->data[1] + slice_start * ulinesize;
107 ✗ const uint16_t *vptr = (const uint16_t *)frame->data[2] + slice_start * vlinesize;
108 ✗ int64_t sum_u = 0, sum_v = 0;
109
110 ✗ for (int y = slice_start; y < slice_end; y++) {
111 ✗ for (int x = 0; x < width; x++) {
112 ✗ sum_u += uptr[x];
113 ✗ sum_v += vptr[x];
114 }
115
116 ✗ uptr += ulinesize;
117 ✗ vptr += vlinesize;
118 }
119
120 ✗ s->analyzeret[jobnr][0] = s->analyzeret[jobnr][2] = imax * sum_u / (float)((slice_end - slice_start) * width) - 0.5f;
121 ✗ s->analyzeret[jobnr][1] = s->analyzeret[jobnr][3] = imax * sum_v / (float)((slice_end - slice_start) * width) - 0.5f;
122
123 ✗ return 0;
124 }
125
126 ✗ static int minmax_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
127 {
128 ✗ ColorCorrectContext *s = ctx->priv;
129 ✗ AVFrame *frame = arg;
130 ✗ const float imax = s->imax;
131 ✗ const int width = s->planewidth[1];
132 ✗ const int height = s->planeheight[1];
133 ✗ const int slice_start = ff_slice_pos(height, jobnr, nb_jobs);
134 ✗ const int slice_end = ff_slice_pos(height, jobnr + 1, nb_jobs);
135 ✗ const ptrdiff_t ulinesize = frame->linesize[1];
136 ✗ const ptrdiff_t vlinesize = frame->linesize[2];
137 ✗ const uint8_t *uptr = (const uint8_t *)frame->data[1] + slice_start * ulinesize;
138 ✗ const uint8_t *vptr = (const uint8_t *)frame->data[2] + slice_start * vlinesize;
139 ✗ int min_u = 255, min_v = 255;
140 ✗ int max_u = 0, max_v = 0;
141
142 ✗ for (int y = slice_start; y < slice_end; y++) {
143 ✗ for (int x = 0; x < width; x++) {
144 ✗ min_u = FFMIN(min_u, uptr[x]);
145 ✗ min_v = FFMIN(min_v, vptr[x]);
146 ✗ max_u = FFMAX(max_u, uptr[x]);
147 ✗ max_v = FFMAX(max_v, vptr[x]);
148 }
149
150 ✗ uptr += ulinesize;
151 ✗ vptr += vlinesize;
152 }
153
154 ✗ s->analyzeret[jobnr][0] = imax * min_u - 0.5f;
155 ✗ s->analyzeret[jobnr][1] = imax * min_v - 0.5f;
156 ✗ s->analyzeret[jobnr][2] = imax * max_u - 0.5f;
157 ✗ s->analyzeret[jobnr][3] = imax * max_v - 0.5f;
158
159 ✗ return 0;
160 }
161
162 ✗ static int minmax_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
163 {
164 ✗ ColorCorrectContext *s = ctx->priv;
165 ✗ AVFrame *frame = arg;
166 ✗ const float imax = s->imax;
167 ✗ const int width = s->planewidth[1];
168 ✗ const int height = s->planeheight[1];
169 ✗ const int slice_start = ff_slice_pos(height, jobnr, nb_jobs);
170 ✗ const int slice_end = ff_slice_pos(height, jobnr + 1, nb_jobs);
171 ✗ const ptrdiff_t ulinesize = frame->linesize[1] / 2;
172 ✗ const ptrdiff_t vlinesize = frame->linesize[2] / 2;
173 ✗ const uint16_t *uptr = (const uint16_t *)frame->data[1] + slice_start * ulinesize;
174 ✗ const uint16_t *vptr = (const uint16_t *)frame->data[2] + slice_start * vlinesize;
175 ✗ int min_u = INT_MAX, min_v = INT_MAX;
176 ✗ int max_u = INT_MIN, max_v = INT_MIN;
177
178 ✗ for (int y = slice_start; y < slice_end; y++) {
179 ✗ for (int x = 0; x < width; x++) {
180 ✗ min_u = FFMIN(min_u, uptr[x]);
181 ✗ min_v = FFMIN(min_v, vptr[x]);
182 ✗ max_u = FFMAX(max_u, uptr[x]);
183 ✗ max_v = FFMAX(max_v, vptr[x]);
184 }
185
186 ✗ uptr += ulinesize;
187 ✗ vptr += vlinesize;
188 }
189
190 ✗ s->analyzeret[jobnr][0] = imax * min_u - 0.5f;
191 ✗ s->analyzeret[jobnr][1] = imax * min_v - 0.5f;
192 ✗ s->analyzeret[jobnr][2] = imax * max_u - 0.5f;
193 ✗ s->analyzeret[jobnr][3] = imax * max_v - 0.5f;
194
195 ✗ return 0;
196 }
197
198 ✗ static int median_8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
199 {
200 ✗ ColorCorrectContext *s = ctx->priv;
201 ✗ AVFrame *frame = arg;
202 ✗ const float imax = s->imax;
203 ✗ const int width = s->planewidth[1];
204 ✗ const int height = s->planeheight[1];
205 ✗ const ptrdiff_t ulinesize = frame->linesize[1];
206 ✗ const ptrdiff_t vlinesize = frame->linesize[2];
207 ✗ const uint8_t *uptr = (const uint8_t *)frame->data[1];
208 ✗ const uint8_t *vptr = (const uint8_t *)frame->data[2];
209 ✗ unsigned *uhistogram = s->uhistogram;
210 ✗ unsigned *vhistogram = s->vhistogram;
211 ✗ const int half_size = width * height / 2;
212 ✗ int umedian = s->max, vmedian = s->max;
213 ✗ unsigned ucnt = 0, vcnt = 0;
214
215 ✗ memset(uhistogram, 0, sizeof(*uhistogram) * (s->max + 1));
216 ✗ memset(vhistogram, 0, sizeof(*vhistogram) * (s->max + 1));
217
218 ✗ for (int y = 0; y < height; y++) {
219 ✗ for (int x = 0; x < width; x++) {
220 ✗ uhistogram[uptr[x]]++;
221 ✗ vhistogram[vptr[x]]++;
222 }
223
224 ✗ uptr += ulinesize;
225 ✗ vptr += vlinesize;
226 }
227
228 ✗ for (int i = 0; i < s->max + 1; i++) {
229 ✗ ucnt += uhistogram[i];
230 ✗ if (ucnt >= half_size) {
231 ✗ umedian = i;
232 ✗ break;
233 }
234 }
235
236 ✗ for (int i = 0; i < s->max + 1; i++) {
237 ✗ vcnt += vhistogram[i];
238 ✗ if (vcnt >= half_size) {
239 ✗ vmedian = i;
240 ✗ break;
241 }
242 }
243
244 ✗ s->analyzeret[0][0] = imax * umedian - 0.5f;
245 ✗ s->analyzeret[0][1] = imax * vmedian - 0.5f;
246 ✗ s->analyzeret[0][2] = imax * umedian - 0.5f;
247 ✗ s->analyzeret[0][3] = imax * vmedian - 0.5f;
248
249 ✗ return 0;
250 }
251
252 ✗ static int median_16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
253 {
254 ✗ ColorCorrectContext *s = ctx->priv;
255 ✗ AVFrame *frame = arg;
256 ✗ const float imax = s->imax;
257 ✗ const int width = s->planewidth[1];
258 ✗ const int height = s->planeheight[1];
259 ✗ const ptrdiff_t ulinesize = frame->linesize[1] / 2;
260 ✗ const ptrdiff_t vlinesize = frame->linesize[2] / 2;
261 ✗ const uint16_t *uptr = (const uint16_t *)frame->data[1];
262 ✗ const uint16_t *vptr = (const uint16_t *)frame->data[2];
263 ✗ unsigned *uhistogram = s->uhistogram;
264 ✗ unsigned *vhistogram = s->vhistogram;
265 ✗ const int half_size = width * height / 2;
266 ✗ int umedian = s->max, vmedian = s->max;
267 ✗ unsigned ucnt = 0, vcnt = 0;
268
269 ✗ memset(uhistogram, 0, sizeof(*uhistogram) * (s->max + 1));
270 ✗ memset(vhistogram, 0, sizeof(*vhistogram) * (s->max + 1));
271
272 ✗ for (int y = 0; y < height; y++) {
273 ✗ for (int x = 0; x < width; x++) {
274 ✗ uhistogram[uptr[x]]++;
275 ✗ vhistogram[vptr[x]]++;
276 }
277
278 ✗ uptr += ulinesize;
279 ✗ vptr += vlinesize;
280 }
281
282 ✗ for (int i = 0; i < s->max + 1; i++) {
283 ✗ ucnt += uhistogram[i];
284 ✗ if (ucnt >= half_size) {
285 ✗ umedian = i;
286 ✗ break;
287 }
288 }
289
290 ✗ for (int i = 0; i < s->max + 1; i++) {
291 ✗ vcnt += vhistogram[i];
292 ✗ if (vcnt >= half_size) {
293 ✗ vmedian = i;
294 ✗ break;
295 }
296 }
297
298 ✗ s->analyzeret[0][0] = imax * umedian - 0.5f;
299 ✗ s->analyzeret[0][1] = imax * vmedian - 0.5f;
300 ✗ s->analyzeret[0][2] = imax * umedian - 0.5f;
301 ✗ s->analyzeret[0][3] = imax * vmedian - 0.5f;
302
303 ✗ return 0;
304 }
305
306 #define PROCESS() \
307 float y = yptr[x * chroma_w] * imax; \
308 float u = uptr[x] * imax - .5f; \
309 float v = vptr[x] * imax - .5f; \
310 float nu, nv; \
311 \
312 nu = saturation * (u + y * bd + bl); \
313 nv = saturation * (v + y * rd + rl);
314
315 ✗ static int colorcorrect_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
316 {
317 ✗ ColorCorrectContext *s = ctx->priv;
318 ✗ AVFrame *frame = arg;
319 ✗ const float max = s->max;
320 ✗ const float imax = s->imax;
321 ✗ const int chroma_w = s->chroma_w;
322 ✗ const int chroma_h = s->chroma_h;
323 ✗ const int width = s->planewidth[1];
324 ✗ const int height = s->planeheight[1];
325 ✗ const int slice_start = ff_slice_pos(height, jobnr, nb_jobs);
326 ✗ const int slice_end = ff_slice_pos(height, jobnr + 1, nb_jobs);
327 ✗ const ptrdiff_t ylinesize = frame->linesize[0];
328 ✗ const ptrdiff_t ulinesize = frame->linesize[1];
329 ✗ const ptrdiff_t vlinesize = frame->linesize[2];
330 ✗ uint8_t *yptr = frame->data[0] + slice_start * chroma_h * ylinesize;
331 ✗ uint8_t *uptr = frame->data[1] + slice_start * ulinesize;
332 ✗ uint8_t *vptr = frame->data[2] + slice_start * vlinesize;
333 ✗ const float saturation = s->saturation;
334 ✗ const float bl = s->bl;
335 ✗ const float rl = s->rl;
336 ✗ const float bd = s->bh - bl;
337 ✗ const float rd = s->rh - rl;
338
339 ✗ for (int y = slice_start; y < slice_end; y++) {
340 ✗ for (int x = 0; x < width; x++) {
341 ✗ PROCESS()
342
343 ✗ uptr[x] = av_clip_uint8((nu + 0.5f) * max);
344 ✗ vptr[x] = av_clip_uint8((nv + 0.5f) * max);
345 }
346
347 ✗ yptr += ylinesize * chroma_h;
348 ✗ uptr += ulinesize;
349 ✗ vptr += vlinesize;
350 }
351
352 ✗ return 0;
353 }
354
355 ✗ static int colorcorrect_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
356 {
357 ✗ ColorCorrectContext *s = ctx->priv;
358 ✗ AVFrame *frame = arg;
359 ✗ const int depth = s->depth;
360 ✗ const float max = s->max;
361 ✗ const float imax = s->imax;
362 ✗ const int chroma_w = s->chroma_w;
363 ✗ const int chroma_h = s->chroma_h;
364 ✗ const int width = s->planewidth[1];
365 ✗ const int height = s->planeheight[1];
366 ✗ const int slice_start = ff_slice_pos(height, jobnr, nb_jobs);
367 ✗ const int slice_end = ff_slice_pos(height, jobnr + 1, nb_jobs);
368 ✗ const ptrdiff_t ylinesize = frame->linesize[0] / 2;
369 ✗ const ptrdiff_t ulinesize = frame->linesize[1] / 2;
370 ✗ const ptrdiff_t vlinesize = frame->linesize[2] / 2;
371 ✗ uint16_t *yptr = (uint16_t *)frame->data[0] + slice_start * chroma_h * ylinesize;
372 ✗ uint16_t *uptr = (uint16_t *)frame->data[1] + slice_start * ulinesize;
373 ✗ uint16_t *vptr = (uint16_t *)frame->data[2] + slice_start * vlinesize;
374 ✗ const float saturation = s->saturation;
375 ✗ const float bl = s->bl;
376 ✗ const float rl = s->rl;
377 ✗ const float bd = s->bh - bl;
378 ✗ const float rd = s->rh - rl;
379
380 ✗ for (int y = slice_start; y < slice_end; y++) {
381 ✗ for (int x = 0; x < width; x++) {
382 ✗ PROCESS()
383
384 ✗ uptr[x] = av_clip_uintp2_c((nu + 0.5f) * max, depth);
385 ✗ vptr[x] = av_clip_uintp2_c((nv + 0.5f) * max, depth);
386 }
387
388 ✗ yptr += ylinesize * chroma_h;
389 ✗ uptr += ulinesize;
390 ✗ vptr += vlinesize;
391 }
392
393 ✗ return 0;
394 }
395
396 ✗ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
397 {
398 ✗ AVFilterContext *ctx = inlink->dst;
399 ✗ ColorCorrectContext *s = ctx->priv;
400 ✗ const int nb_threads = s->analyze == MEDIAN ? 1 : FFMIN(s->planeheight[1], ff_filter_get_nb_threads(ctx));
401
402 ✗ if (s->analyze) {
403 ✗ const int nb_athreads = s->analyze == MEDIAN ? 1 : nb_threads;
404 ✗ float bl = 0.f, rl = 0.f, bh = 0.f, rh = 0.f;
405
406 ✗ ff_filter_execute(ctx, s->do_analyze, frame, NULL, nb_athreads);
407
408 ✗ for (int i = 0; i < nb_athreads; i++) {
409 ✗ bl += s->analyzeret[i][0];
410 ✗ rl += s->analyzeret[i][1];
411 ✗ bh += s->analyzeret[i][2];
412 ✗ rh += s->analyzeret[i][3];
413 }
414
415 ✗ bl /= nb_athreads;
416 ✗ rl /= nb_athreads;
417 ✗ bh /= nb_athreads;
418 ✗ rh /= nb_athreads;
419
420 ✗ s->bl = -bl;
421 ✗ s->rl = -rl;
422 ✗ s->bh = -bh;
423 ✗ s->rh = -rh;
424 }
425
426 ✗ ff_filter_execute(ctx, s->do_slice, frame, NULL, nb_threads);
427
428 ✗ return ff_filter_frame(ctx->outputs[0], frame);
429 }
430
431 static const enum AVPixelFormat pixel_fmts[] = {
432 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV444P,
433 AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P,
434 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P,
435 AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
436 AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV440P10, AV_PIX_FMT_YUV444P10,
437 AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV440P12, AV_PIX_FMT_YUV420P12,
438 AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14,
439 AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
440 AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
441 AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
442 AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
443 AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
444 AV_PIX_FMT_NONE
445 };
446
447 ✗ static av_cold int config_input(AVFilterLink *inlink)
448 {
449 ✗ AVFilterContext *ctx = inlink->dst;
450 ✗ ColorCorrectContext *s = ctx->priv;
451 ✗ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
452
453 ✗ s->depth = desc->comp[0].depth;
454 ✗ s->max = (1 << s->depth) - 1;
455 ✗ s->imax = 1.f / s->max;
456 ✗ s->do_slice = s->depth <= 8 ? colorcorrect_slice8 : colorcorrect_slice16;
457
458 ✗ s->uhistogram = av_calloc(s->max == 255 ? 256 : 65536, sizeof(*s->uhistogram));
459 ✗ if (!s->uhistogram)
460 ✗ return AVERROR(ENOMEM);
461
462 ✗ s->vhistogram = av_calloc(s->max == 255 ? 256 : 65536, sizeof(*s->vhistogram));
463 ✗ if (!s->vhistogram)
464 ✗ return AVERROR(ENOMEM);
465
466 ✗ s->analyzeret = av_calloc(inlink->h, sizeof(*s->analyzeret));
467 ✗ if (!s->analyzeret)
468 ✗ return AVERROR(ENOMEM);
469
470 ✗ switch (s->analyze) {
471 ✗ case MANUAL:
472 ✗ break;
473 ✗ case AVERAGE:
474 ✗ s->do_analyze = s->depth <= 8 ? average_slice8 : average_slice16;
475 ✗ break;
476 ✗ case MINMAX:
477 ✗ s->do_analyze = s->depth <= 8 ? minmax_slice8 : minmax_slice16;
478 ✗ break;
479 ✗ case MEDIAN:
480 ✗ s->do_analyze = s->depth <= 8 ? median_8 : median_16;
481 ✗ break;
482 ✗ default:
483 ✗ return AVERROR_BUG;
484 }
485
486 ✗ s->chroma_w = 1 << desc->log2_chroma_w;
487 ✗ s->chroma_h = 1 << desc->log2_chroma_h;
488 ✗ s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
489 ✗ s->planeheight[0] = s->planeheight[3] = inlink->h;
490 ✗ s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
491 ✗ s->planewidth[0] = s->planewidth[3] = inlink->w;
492
493 ✗ return 0;
494 }
495
496 ✗ static av_cold void uninit(AVFilterContext *ctx)
497 {
498 ✗ ColorCorrectContext *s = ctx->priv;
499
500 ✗ av_freep(&s->analyzeret);
501 ✗ av_freep(&s->uhistogram);
502 ✗ av_freep(&s->vhistogram);
503 ✗ }
504
505 static const AVFilterPad colorcorrect_inputs[] = {
506 {
507 .name = "default",
508 .type = AVMEDIA_TYPE_VIDEO,
509 .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
510 .filter_frame = filter_frame,
511 .config_props = config_input,
512 },
513 };
514
515 #define OFFSET(x) offsetof(ColorCorrectContext, x)
516 #define VF AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
517
518 static const AVOption colorcorrect_options[] = {
519 { "rl", "set the red shadow spot", OFFSET(rl), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
520 { "bl", "set the blue shadow spot", OFFSET(bl), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
521 { "rh", "set the red highlight spot", OFFSET(rh), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
522 { "bh", "set the blue highlight spot", OFFSET(bh), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
523 { "saturation", "set the amount of saturation", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl=1}, -3, 3, VF },
524 { "analyze", "set the analyze mode", OFFSET(analyze), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_ANALYZE-1, VF, .unit = "analyze" },
525 { "manual", "manually set options", 0, AV_OPT_TYPE_CONST, {.i64=MANUAL}, 0, 0, VF, .unit = "analyze" },
526 { "average", "use average pixels", 0, AV_OPT_TYPE_CONST, {.i64=AVERAGE}, 0, 0, VF, .unit = "analyze" },
527 { "minmax", "use minmax pixels", 0, AV_OPT_TYPE_CONST, {.i64=MINMAX}, 0, 0, VF, .unit = "analyze" },
528 { "median", "use median pixels", 0, AV_OPT_TYPE_CONST, {.i64=MEDIAN}, 0, 0, VF, .unit = "analyze" },
529 { NULL }
530 };
531
532 AVFILTER_DEFINE_CLASS(colorcorrect);
533
534 const FFFilter ff_vf_colorcorrect = {
535 .p.name = "colorcorrect",
536 .p.description = NULL_IF_CONFIG_SMALL("Adjust color white balance selectively for blacks and whites."),
537 .p.priv_class = &colorcorrect_class,
538 .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
539 .priv_size = sizeof(ColorCorrectContext),
540 .uninit = uninit,
541 FILTER_INPUTS(colorcorrect_inputs),
542 FILTER_OUTPUTS(ff_video_default_filterpad),
543 FILTER_PIXFMTS_ARRAY(pixel_fmts),
544 .process_command = ff_filter_process_command,
545 };
546