Line |
Branch |
Exec |
Source |
1 |
|
|
/* |
2 |
|
|
* Copyright (c) 2015-2016 Clément Bœsch <u pkh me> |
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 |
|
|
/** |
22 |
|
|
* @see http://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html |
23 |
|
|
* @todo |
24 |
|
|
* - use integers so it can be made bitexact and a FATE test can be added |
25 |
|
|
*/ |
26 |
|
|
|
27 |
|
|
#include "libavutil/avassert.h" |
28 |
|
|
#include "libavutil/file.h" |
29 |
|
|
#include "libavutil/intreadwrite.h" |
30 |
|
|
#include "libavutil/opt.h" |
31 |
|
|
#include "libavutil/pixdesc.h" |
32 |
|
|
#include "libavcodec/mathops.h" // for mid_pred(), which is a macro so no link dependency |
33 |
|
|
#include "avfilter.h" |
34 |
|
|
#include "drawutils.h" |
35 |
|
|
#include "filters.h" |
36 |
|
|
#include "video.h" |
37 |
|
|
|
38 |
|
|
#define R 0 |
39 |
|
|
#define G 1 |
40 |
|
|
#define B 2 |
41 |
|
|
#define A 3 |
42 |
|
|
|
43 |
|
|
enum color_range { |
44 |
|
|
// WARNING: do NOT reorder (see parse_psfile()) |
45 |
|
|
RANGE_REDS, |
46 |
|
|
RANGE_YELLOWS, |
47 |
|
|
RANGE_GREENS, |
48 |
|
|
RANGE_CYANS, |
49 |
|
|
RANGE_BLUES, |
50 |
|
|
RANGE_MAGENTAS, |
51 |
|
|
RANGE_WHITES, |
52 |
|
|
RANGE_NEUTRALS, |
53 |
|
|
RANGE_BLACKS, |
54 |
|
|
NB_RANGES |
55 |
|
|
}; |
56 |
|
|
|
57 |
|
|
enum correction_method { |
58 |
|
|
CORRECTION_METHOD_ABSOLUTE, |
59 |
|
|
CORRECTION_METHOD_RELATIVE, |
60 |
|
|
NB_CORRECTION_METHODS, |
61 |
|
|
}; |
62 |
|
|
|
63 |
|
|
static const char *const color_names[NB_RANGES] = { |
64 |
|
|
"red", "yellow", "green", "cyan", "blue", "magenta", "white", "neutral", "black" |
65 |
|
|
}; |
66 |
|
|
|
67 |
|
|
typedef int (*get_range_scale_func)(int r, int g, int b, int min_val, int max_val); |
68 |
|
|
|
69 |
|
|
struct process_range { |
70 |
|
|
int range_id; |
71 |
|
|
uint32_t mask; |
72 |
|
|
get_range_scale_func get_scale; |
73 |
|
|
}; |
74 |
|
|
|
75 |
|
|
typedef struct ThreadData { |
76 |
|
|
AVFrame *in, *out; |
77 |
|
|
} ThreadData; |
78 |
|
|
|
79 |
|
|
typedef struct SelectiveColorContext { |
80 |
|
|
const AVClass *class; |
81 |
|
|
int correction_method; |
82 |
|
|
char *opt_cmyk_adjust[NB_RANGES]; |
83 |
|
|
float cmyk_adjust[NB_RANGES][4]; |
84 |
|
|
struct process_range process_ranges[NB_RANGES]; // color ranges to process |
85 |
|
|
int nb_process_ranges; |
86 |
|
|
char *psfile; |
87 |
|
|
uint8_t rgba_map[4]; |
88 |
|
|
int is_16bit; |
89 |
|
|
int step; |
90 |
|
|
} SelectiveColorContext; |
91 |
|
|
|
92 |
|
|
#define OFFSET(x) offsetof(SelectiveColorContext, x) |
93 |
|
|
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
94 |
|
|
#define RANGE_OPTION(color_name, range) \ |
95 |
|
|
{ color_name"s", "adjust "color_name" regions", OFFSET(opt_cmyk_adjust[range]), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS } |
96 |
|
|
|
97 |
|
|
static const AVOption selectivecolor_options[] = { |
98 |
|
|
{ "correction_method", "select correction method", OFFSET(correction_method), AV_OPT_TYPE_INT, {.i64 = CORRECTION_METHOD_ABSOLUTE}, 0, NB_CORRECTION_METHODS-1, FLAGS, .unit = "correction_method" }, |
99 |
|
|
{ "absolute", NULL, 0, AV_OPT_TYPE_CONST, {.i64=CORRECTION_METHOD_ABSOLUTE}, INT_MIN, INT_MAX, FLAGS, .unit = "correction_method" }, |
100 |
|
|
{ "relative", NULL, 0, AV_OPT_TYPE_CONST, {.i64=CORRECTION_METHOD_RELATIVE}, INT_MIN, INT_MAX, FLAGS, .unit = "correction_method" }, |
101 |
|
|
RANGE_OPTION("red", RANGE_REDS), |
102 |
|
|
RANGE_OPTION("yellow", RANGE_YELLOWS), |
103 |
|
|
RANGE_OPTION("green", RANGE_GREENS), |
104 |
|
|
RANGE_OPTION("cyan", RANGE_CYANS), |
105 |
|
|
RANGE_OPTION("blue", RANGE_BLUES), |
106 |
|
|
RANGE_OPTION("magenta", RANGE_MAGENTAS), |
107 |
|
|
RANGE_OPTION("white", RANGE_WHITES), |
108 |
|
|
RANGE_OPTION("neutral", RANGE_NEUTRALS), |
109 |
|
|
RANGE_OPTION("black", RANGE_BLACKS), |
110 |
|
|
{ "psfile", "set Photoshop selectivecolor file name", OFFSET(psfile), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS }, |
111 |
|
|
{ NULL } |
112 |
|
|
}; |
113 |
|
|
|
114 |
|
|
AVFILTER_DEFINE_CLASS(selectivecolor); |
115 |
|
|
|
116 |
|
✗ |
static int get_rgb_scale(int r, int g, int b, int min_val, int max_val) |
117 |
|
|
{ |
118 |
|
✗ |
return max_val - mid_pred(r, g, b); |
119 |
|
|
} |
120 |
|
|
|
121 |
|
✗ |
static int get_cmy_scale(int r, int g, int b, int min_val, int max_val) |
122 |
|
|
{ |
123 |
|
✗ |
return mid_pred(r, g, b) - min_val; |
124 |
|
|
} |
125 |
|
|
|
126 |
|
|
#define DECLARE_RANGE_SCALE_FUNCS(nbits) \ |
127 |
|
|
static int get_neutrals_scale##nbits(int r, int g, int b, int min_val, int max_val) \ |
128 |
|
|
{ \ |
129 |
|
|
/* 1 - (|max-0.5| + |min-0.5|) */ \ |
130 |
|
|
return (((1<<nbits)-1)*2 - ( abs((max_val<<1) - ((1<<nbits)-1)) \ |
131 |
|
|
+ abs((min_val<<1) - ((1<<nbits)-1))) + 1) >> 1; \ |
132 |
|
|
} \ |
133 |
|
|
\ |
134 |
|
|
static int get_whites_scale##nbits(int r, int g, int b, int min_val, int max_val) \ |
135 |
|
|
{ \ |
136 |
|
|
/* (min - 0.5) * 2 */ \ |
137 |
|
|
return (min_val<<1) - ((1<<nbits)-1); \ |
138 |
|
|
} \ |
139 |
|
|
\ |
140 |
|
|
static int get_blacks_scale##nbits(int r, int g, int b, int min_val, int max_val) \ |
141 |
|
|
{ \ |
142 |
|
|
/* (0.5 - max) * 2 */ \ |
143 |
|
|
return ((1<<nbits)-1) - (max_val<<1); \ |
144 |
|
|
} \ |
145 |
|
|
|
146 |
|
✗ |
DECLARE_RANGE_SCALE_FUNCS(8) |
147 |
|
✗ |
DECLARE_RANGE_SCALE_FUNCS(16) |
148 |
|
|
|
149 |
|
✗ |
static int register_range(SelectiveColorContext *s, int range_id) |
150 |
|
|
{ |
151 |
|
✗ |
const float *cmyk = s->cmyk_adjust[range_id]; |
152 |
|
|
|
153 |
|
|
/* If the color range has user settings, register the color range |
154 |
|
|
* as "to be processed" */ |
155 |
|
✗ |
if (cmyk[0] || cmyk[1] || cmyk[2] || cmyk[3]) { |
156 |
|
✗ |
struct process_range *pr = &s->process_ranges[s->nb_process_ranges++]; |
157 |
|
|
|
158 |
|
✗ |
if (cmyk[0] < -1.0 || cmyk[0] > 1.0 || |
159 |
|
✗ |
cmyk[1] < -1.0 || cmyk[1] > 1.0 || |
160 |
|
✗ |
cmyk[2] < -1.0 || cmyk[2] > 1.0 || |
161 |
|
✗ |
cmyk[3] < -1.0 || cmyk[3] > 1.0) { |
162 |
|
✗ |
av_log(s, AV_LOG_ERROR, "Invalid %s adjustments (%g %g %g %g). " |
163 |
|
|
"Settings must be set in [-1;1] range\n", |
164 |
|
✗ |
color_names[range_id], cmyk[0], cmyk[1], cmyk[2], cmyk[3]); |
165 |
|
✗ |
return AVERROR(EINVAL); |
166 |
|
|
} |
167 |
|
|
|
168 |
|
✗ |
pr->range_id = range_id; |
169 |
|
✗ |
pr->mask = 1 << range_id; |
170 |
|
✗ |
if (pr->mask & (1<<RANGE_REDS | 1<<RANGE_GREENS | 1<<RANGE_BLUES)) pr->get_scale = get_rgb_scale; |
171 |
|
✗ |
else if (pr->mask & (1<<RANGE_CYANS | 1<<RANGE_MAGENTAS | 1<<RANGE_YELLOWS)) pr->get_scale = get_cmy_scale; |
172 |
|
✗ |
else if (!s->is_16bit && (pr->mask & 1<<RANGE_WHITES)) pr->get_scale = get_whites_scale8; |
173 |
|
✗ |
else if (!s->is_16bit && (pr->mask & 1<<RANGE_NEUTRALS)) pr->get_scale = get_neutrals_scale8; |
174 |
|
✗ |
else if (!s->is_16bit && (pr->mask & 1<<RANGE_BLACKS)) pr->get_scale = get_blacks_scale8; |
175 |
|
✗ |
else if ( s->is_16bit && (pr->mask & 1<<RANGE_WHITES)) pr->get_scale = get_whites_scale16; |
176 |
|
✗ |
else if ( s->is_16bit && (pr->mask & 1<<RANGE_NEUTRALS)) pr->get_scale = get_neutrals_scale16; |
177 |
|
✗ |
else if ( s->is_16bit && (pr->mask & 1<<RANGE_BLACKS)) pr->get_scale = get_blacks_scale16; |
178 |
|
|
else |
179 |
|
✗ |
av_assert0(0); |
180 |
|
|
} |
181 |
|
✗ |
return 0; |
182 |
|
|
} |
183 |
|
|
|
184 |
|
✗ |
static int parse_psfile(AVFilterContext *ctx, const char *fname) |
185 |
|
|
{ |
186 |
|
|
int16_t val; |
187 |
|
|
int ret, i, version; |
188 |
|
|
uint8_t *buf; |
189 |
|
|
size_t size; |
190 |
|
✗ |
SelectiveColorContext *s = ctx->priv; |
191 |
|
|
|
192 |
|
✗ |
ret = av_file_map(fname, &buf, &size, 0, NULL); |
193 |
|
✗ |
if (ret < 0) |
194 |
|
✗ |
return ret; |
195 |
|
|
|
196 |
|
|
#define READ16(dst) do { \ |
197 |
|
|
if (size < 2) { \ |
198 |
|
|
ret = AVERROR_INVALIDDATA; \ |
199 |
|
|
goto end; \ |
200 |
|
|
} \ |
201 |
|
|
dst = AV_RB16(buf); \ |
202 |
|
|
buf += 2; \ |
203 |
|
|
size -= 2; \ |
204 |
|
|
} while (0) |
205 |
|
|
|
206 |
|
✗ |
READ16(version); |
207 |
|
✗ |
if (version != 1) |
208 |
|
✗ |
av_log(s, AV_LOG_WARNING, "Unsupported selective color file version %d, " |
209 |
|
|
"the settings might not be loaded properly\n", version); |
210 |
|
|
|
211 |
|
✗ |
READ16(s->correction_method); |
212 |
|
|
|
213 |
|
|
// 1st CMYK entry is reserved/unused |
214 |
|
✗ |
for (i = 0; i < FF_ARRAY_ELEMS(s->cmyk_adjust[0]); i++) { |
215 |
|
✗ |
READ16(val); |
216 |
|
✗ |
if (val) |
217 |
|
✗ |
av_log(s, AV_LOG_WARNING, "%c value of first CMYK entry is not 0 " |
218 |
|
✗ |
"but %d\n", "CMYK"[i], val); |
219 |
|
|
} |
220 |
|
|
|
221 |
|
✗ |
for (i = 0; i < FF_ARRAY_ELEMS(s->cmyk_adjust); i++) { |
222 |
|
|
int k; |
223 |
|
✗ |
for (k = 0; k < FF_ARRAY_ELEMS(s->cmyk_adjust[0]); k++) { |
224 |
|
✗ |
READ16(val); |
225 |
|
✗ |
s->cmyk_adjust[i][k] = val / 100.f; |
226 |
|
|
} |
227 |
|
✗ |
ret = register_range(s, i); |
228 |
|
✗ |
if (ret < 0) |
229 |
|
✗ |
goto end; |
230 |
|
|
} |
231 |
|
|
|
232 |
|
✗ |
end: |
233 |
|
✗ |
av_file_unmap(buf, size); |
234 |
|
✗ |
return ret; |
235 |
|
|
} |
236 |
|
|
|
237 |
|
✗ |
static int config_input(AVFilterLink *inlink) |
238 |
|
|
{ |
239 |
|
|
int i, ret; |
240 |
|
✗ |
AVFilterContext *ctx = inlink->dst; |
241 |
|
✗ |
SelectiveColorContext *s = ctx->priv; |
242 |
|
✗ |
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
243 |
|
|
|
244 |
|
✗ |
s->is_16bit = desc->comp[0].depth > 8; |
245 |
|
✗ |
s->step = av_get_padded_bits_per_pixel(desc) >> (3 + s->is_16bit); |
246 |
|
|
|
247 |
|
✗ |
ret = ff_fill_rgba_map(s->rgba_map, inlink->format); |
248 |
|
✗ |
if (ret < 0) |
249 |
|
✗ |
return ret; |
250 |
|
|
|
251 |
|
|
/* If the following conditions are not met, it will cause trouble while |
252 |
|
|
* parsing the PS file */ |
253 |
|
|
av_assert0(FF_ARRAY_ELEMS(s->cmyk_adjust) == 10 - 1); |
254 |
|
|
av_assert0(FF_ARRAY_ELEMS(s->cmyk_adjust[0]) == 4); |
255 |
|
|
|
256 |
|
✗ |
if (s->psfile) { |
257 |
|
✗ |
ret = parse_psfile(ctx, s->psfile); |
258 |
|
✗ |
if (ret < 0) |
259 |
|
✗ |
return ret; |
260 |
|
|
} else { |
261 |
|
✗ |
for (i = 0; i < FF_ARRAY_ELEMS(s->opt_cmyk_adjust); i++) { |
262 |
|
✗ |
const char *opt_cmyk_adjust = s->opt_cmyk_adjust[i]; |
263 |
|
|
|
264 |
|
✗ |
if (opt_cmyk_adjust) { |
265 |
|
✗ |
float *cmyk = s->cmyk_adjust[i]; |
266 |
|
|
|
267 |
|
✗ |
sscanf(s->opt_cmyk_adjust[i], "%f %f %f %f", cmyk, cmyk+1, cmyk+2, cmyk+3); |
268 |
|
✗ |
ret = register_range(s, i); |
269 |
|
✗ |
if (ret < 0) |
270 |
|
✗ |
return ret; |
271 |
|
|
} |
272 |
|
|
} |
273 |
|
|
} |
274 |
|
|
|
275 |
|
✗ |
av_log(s, AV_LOG_VERBOSE, "Adjustments:%s\n", s->nb_process_ranges ? "" : " none"); |
276 |
|
✗ |
for (i = 0; i < s->nb_process_ranges; i++) { |
277 |
|
✗ |
const struct process_range *pr = &s->process_ranges[i]; |
278 |
|
✗ |
const float *cmyk = s->cmyk_adjust[pr->range_id]; |
279 |
|
|
|
280 |
|
✗ |
av_log(s, AV_LOG_VERBOSE, "%8ss: C=%6g M=%6g Y=%6g K=%6g\n", |
281 |
|
✗ |
color_names[pr->range_id], cmyk[0], cmyk[1], cmyk[2], cmyk[3]); |
282 |
|
|
} |
283 |
|
|
|
284 |
|
✗ |
return 0; |
285 |
|
|
} |
286 |
|
|
|
287 |
|
|
static const enum AVPixelFormat pix_fmts[] = { |
288 |
|
|
AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, |
289 |
|
|
AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, |
290 |
|
|
AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, |
291 |
|
|
AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR, |
292 |
|
|
AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0, |
293 |
|
|
AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48, |
294 |
|
|
AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64, |
295 |
|
|
AV_PIX_FMT_NONE |
296 |
|
|
}; |
297 |
|
|
|
298 |
|
✗ |
static inline int comp_adjust(int scale, float value, float adjust, float k, int correction_method) |
299 |
|
|
{ |
300 |
|
✗ |
const float min = -value; |
301 |
|
✗ |
const float max = 1.f - value; |
302 |
|
✗ |
float res = (-1.f - adjust) * k - adjust; |
303 |
|
✗ |
if (correction_method == CORRECTION_METHOD_RELATIVE) |
304 |
|
✗ |
res *= max; |
305 |
|
✗ |
return lrintf(av_clipf(res, min, max) * scale); |
306 |
|
|
} |
307 |
|
|
|
308 |
|
|
#define DECLARE_SELECTIVE_COLOR_FUNC(nbits) \ |
309 |
|
|
static inline int selective_color_##nbits(AVFilterContext *ctx, ThreadData *td, \ |
310 |
|
|
int jobnr, int nb_jobs, int direct, int correction_method) \ |
311 |
|
|
{ \ |
312 |
|
|
int i, x, y; \ |
313 |
|
|
const AVFrame *in = td->in; \ |
314 |
|
|
AVFrame *out = td->out; \ |
315 |
|
|
const SelectiveColorContext *s = ctx->priv; \ |
316 |
|
|
const int height = in->height; \ |
317 |
|
|
const int width = in->width; \ |
318 |
|
|
const int slice_start = (height * jobnr ) / nb_jobs; \ |
319 |
|
|
const int slice_end = (height * (jobnr+1)) / nb_jobs; \ |
320 |
|
|
const int dst_linesize = out->linesize[0] / ((nbits + 7) / 8); \ |
321 |
|
|
const int src_linesize = in->linesize[0] / ((nbits + 7) / 8); \ |
322 |
|
|
const uint8_t roffset = s->rgba_map[R]; \ |
323 |
|
|
const uint8_t goffset = s->rgba_map[G]; \ |
324 |
|
|
const uint8_t boffset = s->rgba_map[B]; \ |
325 |
|
|
const uint8_t aoffset = s->rgba_map[A]; \ |
326 |
|
|
uint##nbits##_t *dst = (uint##nbits##_t *)out->data[0] + slice_start * dst_linesize; \ |
327 |
|
|
const uint##nbits##_t *src = (const uint##nbits##_t *)in->data[0] + slice_start * src_linesize; \ |
328 |
|
|
const uint##nbits##_t *src_r = (const uint##nbits##_t *)src + roffset; \ |
329 |
|
|
const uint##nbits##_t *src_g = (const uint##nbits##_t *)src + goffset; \ |
330 |
|
|
const uint##nbits##_t *src_b = (const uint##nbits##_t *)src + boffset; \ |
331 |
|
|
const uint##nbits##_t *src_a = (const uint##nbits##_t *)src + aoffset; \ |
332 |
|
|
uint##nbits##_t *dst_r = (uint##nbits##_t *)dst + roffset; \ |
333 |
|
|
uint##nbits##_t *dst_g = (uint##nbits##_t *)dst + goffset; \ |
334 |
|
|
uint##nbits##_t *dst_b = (uint##nbits##_t *)dst + boffset; \ |
335 |
|
|
uint##nbits##_t *dst_a = (uint##nbits##_t *)dst + aoffset; \ |
336 |
|
|
const int mid = 1<<(nbits-1); \ |
337 |
|
|
const int max = (1<<nbits)-1; \ |
338 |
|
|
const float scale = 1.f / max; \ |
339 |
|
|
\ |
340 |
|
|
for (y = slice_start; y < slice_end; y++) { \ |
341 |
|
|
for (x = 0; x < width * s->step; x += s->step) { \ |
342 |
|
|
const int r = src_r[x]; \ |
343 |
|
|
const int g = src_g[x]; \ |
344 |
|
|
const int b = src_b[x]; \ |
345 |
|
|
const int min_color = FFMIN3(r, g, b); \ |
346 |
|
|
const int max_color = FFMAX3(r, g, b); \ |
347 |
|
|
const int is_white = (r > mid && g > mid && b > mid); \ |
348 |
|
|
const int is_neutral = (r || g || b) && \ |
349 |
|
|
(r != max || g != max || b != max); \ |
350 |
|
|
const int is_black = (r < mid && g < mid && b < mid); \ |
351 |
|
|
const uint32_t range_flag = (r == max_color) << RANGE_REDS \ |
352 |
|
|
| (r == min_color) << RANGE_CYANS \ |
353 |
|
|
| (g == max_color) << RANGE_GREENS \ |
354 |
|
|
| (g == min_color) << RANGE_MAGENTAS \ |
355 |
|
|
| (b == max_color) << RANGE_BLUES \ |
356 |
|
|
| (b == min_color) << RANGE_YELLOWS \ |
357 |
|
|
| is_white << RANGE_WHITES \ |
358 |
|
|
| is_neutral << RANGE_NEUTRALS \ |
359 |
|
|
| is_black << RANGE_BLACKS; \ |
360 |
|
|
\ |
361 |
|
|
const float rnorm = r * scale; \ |
362 |
|
|
const float gnorm = g * scale; \ |
363 |
|
|
const float bnorm = b * scale; \ |
364 |
|
|
int adjust_r = 0, adjust_g = 0, adjust_b = 0; \ |
365 |
|
|
\ |
366 |
|
|
for (i = 0; i < s->nb_process_ranges; i++) { \ |
367 |
|
|
const struct process_range *pr = &s->process_ranges[i]; \ |
368 |
|
|
\ |
369 |
|
|
if (range_flag & pr->mask) { \ |
370 |
|
|
const int scale = pr->get_scale(r, g, b, min_color, max_color); \ |
371 |
|
|
\ |
372 |
|
|
if (scale > 0) { \ |
373 |
|
|
const float *cmyk_adjust = s->cmyk_adjust[pr->range_id]; \ |
374 |
|
|
const float adj_c = cmyk_adjust[0]; \ |
375 |
|
|
const float adj_m = cmyk_adjust[1]; \ |
376 |
|
|
const float adj_y = cmyk_adjust[2]; \ |
377 |
|
|
const float k = cmyk_adjust[3]; \ |
378 |
|
|
\ |
379 |
|
|
adjust_r += comp_adjust(scale, rnorm, adj_c, k, correction_method); \ |
380 |
|
|
adjust_g += comp_adjust(scale, gnorm, adj_m, k, correction_method); \ |
381 |
|
|
adjust_b += comp_adjust(scale, bnorm, adj_y, k, correction_method); \ |
382 |
|
|
} \ |
383 |
|
|
} \ |
384 |
|
|
} \ |
385 |
|
|
\ |
386 |
|
|
if (!direct || adjust_r || adjust_g || adjust_b) { \ |
387 |
|
|
dst_r[x] = av_clip_uint##nbits(r + adjust_r); \ |
388 |
|
|
dst_g[x] = av_clip_uint##nbits(g + adjust_g); \ |
389 |
|
|
dst_b[x] = av_clip_uint##nbits(b + adjust_b); \ |
390 |
|
|
if (!direct && s->step == 4) \ |
391 |
|
|
dst_a[x] = src_a[x]; \ |
392 |
|
|
} \ |
393 |
|
|
} \ |
394 |
|
|
\ |
395 |
|
|
src_r += src_linesize; \ |
396 |
|
|
src_g += src_linesize; \ |
397 |
|
|
src_b += src_linesize; \ |
398 |
|
|
src_a += src_linesize; \ |
399 |
|
|
\ |
400 |
|
|
dst_r += dst_linesize; \ |
401 |
|
|
dst_g += dst_linesize; \ |
402 |
|
|
dst_b += dst_linesize; \ |
403 |
|
|
dst_a += dst_linesize; \ |
404 |
|
|
} \ |
405 |
|
|
return 0; \ |
406 |
|
|
} |
407 |
|
|
|
408 |
|
|
#define DEF_SELECTIVE_COLOR_FUNC(name, direct, correction_method, nbits) \ |
409 |
|
|
static int selective_color_##name##_##nbits(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \ |
410 |
|
|
{ \ |
411 |
|
|
return selective_color_##nbits(ctx, arg, jobnr, nb_jobs, direct, correction_method); \ |
412 |
|
|
} |
413 |
|
|
|
414 |
|
|
#define DEF_SELECTIVE_COLOR_FUNCS(nbits) \ |
415 |
|
|
DECLARE_SELECTIVE_COLOR_FUNC(nbits) \ |
416 |
|
|
DEF_SELECTIVE_COLOR_FUNC(indirect_absolute, 0, CORRECTION_METHOD_ABSOLUTE, nbits) \ |
417 |
|
|
DEF_SELECTIVE_COLOR_FUNC(indirect_relative, 0, CORRECTION_METHOD_RELATIVE, nbits) \ |
418 |
|
|
DEF_SELECTIVE_COLOR_FUNC( direct_absolute, 1, CORRECTION_METHOD_ABSOLUTE, nbits) \ |
419 |
|
|
DEF_SELECTIVE_COLOR_FUNC( direct_relative, 1, CORRECTION_METHOD_RELATIVE, nbits) |
420 |
|
|
|
421 |
|
✗ |
DEF_SELECTIVE_COLOR_FUNCS(8) |
422 |
|
✗ |
DEF_SELECTIVE_COLOR_FUNCS(16) |
423 |
|
|
|
424 |
|
|
typedef int (*selective_color_func_type)(AVFilterContext *ctx, void *td, int jobnr, int nb_jobs); |
425 |
|
|
|
426 |
|
✗ |
static int filter_frame(AVFilterLink *inlink, AVFrame *in) |
427 |
|
|
{ |
428 |
|
✗ |
AVFilterContext *ctx = inlink->dst; |
429 |
|
✗ |
AVFilterLink *outlink = ctx->outputs[0]; |
430 |
|
|
int direct; |
431 |
|
|
AVFrame *out; |
432 |
|
|
ThreadData td; |
433 |
|
✗ |
const SelectiveColorContext *s = ctx->priv; |
434 |
|
|
static const selective_color_func_type funcs[2][2][2] = { |
435 |
|
|
{ |
436 |
|
|
{selective_color_indirect_absolute_8, selective_color_indirect_relative_8}, |
437 |
|
|
{selective_color_direct_absolute_8, selective_color_direct_relative_8}, |
438 |
|
|
},{ |
439 |
|
|
{selective_color_indirect_absolute_16, selective_color_indirect_relative_16}, |
440 |
|
|
{selective_color_direct_absolute_16, selective_color_direct_relative_16}, |
441 |
|
|
} |
442 |
|
|
}; |
443 |
|
|
|
444 |
|
✗ |
if (av_frame_is_writable(in)) { |
445 |
|
✗ |
direct = 1; |
446 |
|
✗ |
out = in; |
447 |
|
|
} else { |
448 |
|
✗ |
direct = 0; |
449 |
|
✗ |
out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
450 |
|
✗ |
if (!out) { |
451 |
|
✗ |
av_frame_free(&in); |
452 |
|
✗ |
return AVERROR(ENOMEM); |
453 |
|
|
} |
454 |
|
✗ |
av_frame_copy_props(out, in); |
455 |
|
|
} |
456 |
|
|
|
457 |
|
✗ |
td.in = in; |
458 |
|
✗ |
td.out = out; |
459 |
|
✗ |
ff_filter_execute(ctx, funcs[s->is_16bit][direct][s->correction_method], |
460 |
|
✗ |
&td, NULL, FFMIN(inlink->h, ff_filter_get_nb_threads(ctx))); |
461 |
|
|
|
462 |
|
✗ |
if (!direct) |
463 |
|
✗ |
av_frame_free(&in); |
464 |
|
✗ |
return ff_filter_frame(outlink, out); |
465 |
|
|
} |
466 |
|
|
|
467 |
|
|
static const AVFilterPad selectivecolor_inputs[] = { |
468 |
|
|
{ |
469 |
|
|
.name = "default", |
470 |
|
|
.type = AVMEDIA_TYPE_VIDEO, |
471 |
|
|
.filter_frame = filter_frame, |
472 |
|
|
.config_props = config_input, |
473 |
|
|
}, |
474 |
|
|
}; |
475 |
|
|
|
476 |
|
|
const AVFilter ff_vf_selectivecolor = { |
477 |
|
|
.name = "selectivecolor", |
478 |
|
|
.description = NULL_IF_CONFIG_SMALL("Apply CMYK adjustments to specific color ranges."), |
479 |
|
|
.priv_size = sizeof(SelectiveColorContext), |
480 |
|
|
FILTER_INPUTS(selectivecolor_inputs), |
481 |
|
|
FILTER_OUTPUTS(ff_video_default_filterpad), |
482 |
|
|
FILTER_PIXFMTS_ARRAY(pix_fmts), |
483 |
|
|
.priv_class = &selectivecolor_class, |
484 |
|
|
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, |
485 |
|
|
}; |
486 |
|
|
|