| 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(AVFilterContext *ctx, int range_id) | |
| 150 | { | ||
| 151 | ✗ | SelectiveColorContext *s = ctx->priv; | |
| 152 | ✗ | const float *cmyk = s->cmyk_adjust[range_id]; | |
| 153 | |||
| 154 | /* If the color range has user settings, register the color range | ||
| 155 | * as "to be processed" */ | ||
| 156 | ✗ | if (cmyk[0] || cmyk[1] || cmyk[2] || cmyk[3]) { | |
| 157 | ✗ | struct process_range *pr = &s->process_ranges[s->nb_process_ranges++]; | |
| 158 | |||
| 159 | ✗ | if (cmyk[0] < -1.0 || cmyk[0] > 1.0 || | |
| 160 | ✗ | cmyk[1] < -1.0 || cmyk[1] > 1.0 || | |
| 161 | ✗ | cmyk[2] < -1.0 || cmyk[2] > 1.0 || | |
| 162 | ✗ | cmyk[3] < -1.0 || cmyk[3] > 1.0) { | |
| 163 | ✗ | av_log(ctx, AV_LOG_ERROR, "Invalid %s adjustments (%g %g %g %g). " | |
| 164 | "Settings must be set in [-1;1] range\n", | ||
| 165 | ✗ | color_names[range_id], cmyk[0], cmyk[1], cmyk[2], cmyk[3]); | |
| 166 | ✗ | return AVERROR(EINVAL); | |
| 167 | } | ||
| 168 | |||
| 169 | ✗ | pr->range_id = range_id; | |
| 170 | ✗ | pr->mask = 1 << range_id; | |
| 171 | ✗ | if (pr->mask & (1<<RANGE_REDS | 1<<RANGE_GREENS | 1<<RANGE_BLUES)) pr->get_scale = get_rgb_scale; | |
| 172 | ✗ | else if (pr->mask & (1<<RANGE_CYANS | 1<<RANGE_MAGENTAS | 1<<RANGE_YELLOWS)) pr->get_scale = get_cmy_scale; | |
| 173 | ✗ | else if (!s->is_16bit && (pr->mask & 1<<RANGE_WHITES)) pr->get_scale = get_whites_scale8; | |
| 174 | ✗ | else if (!s->is_16bit && (pr->mask & 1<<RANGE_NEUTRALS)) pr->get_scale = get_neutrals_scale8; | |
| 175 | ✗ | else if (!s->is_16bit && (pr->mask & 1<<RANGE_BLACKS)) pr->get_scale = get_blacks_scale8; | |
| 176 | ✗ | else if ( s->is_16bit && (pr->mask & 1<<RANGE_WHITES)) pr->get_scale = get_whites_scale16; | |
| 177 | ✗ | else if ( s->is_16bit && (pr->mask & 1<<RANGE_NEUTRALS)) pr->get_scale = get_neutrals_scale16; | |
| 178 | ✗ | else if ( s->is_16bit && (pr->mask & 1<<RANGE_BLACKS)) pr->get_scale = get_blacks_scale16; | |
| 179 | else | ||
| 180 | ✗ | av_assert0(0); | |
| 181 | } | ||
| 182 | ✗ | return 0; | |
| 183 | } | ||
| 184 | |||
| 185 | ✗ | static int parse_psfile(AVFilterContext *ctx, const char *fname) | |
| 186 | { | ||
| 187 | int16_t val; | ||
| 188 | int ret, i, version; | ||
| 189 | uint8_t *buf; | ||
| 190 | size_t size; | ||
| 191 | ✗ | SelectiveColorContext *s = ctx->priv; | |
| 192 | |||
| 193 | ✗ | ret = av_file_map(fname, &buf, &size, 0, NULL); | |
| 194 | ✗ | if (ret < 0) | |
| 195 | ✗ | return ret; | |
| 196 | |||
| 197 | #define READ16(dst) do { \ | ||
| 198 | if (size < 2) { \ | ||
| 199 | ret = AVERROR_INVALIDDATA; \ | ||
| 200 | goto end; \ | ||
| 201 | } \ | ||
| 202 | dst = AV_RB16(buf); \ | ||
| 203 | buf += 2; \ | ||
| 204 | size -= 2; \ | ||
| 205 | } while (0) | ||
| 206 | |||
| 207 | ✗ | READ16(version); | |
| 208 | ✗ | if (version != 1) | |
| 209 | ✗ | av_log(ctx, AV_LOG_WARNING, "Unsupported selective color file version %d, " | |
| 210 | "the settings might not be loaded properly\n", version); | ||
| 211 | |||
| 212 | ✗ | READ16(s->correction_method); | |
| 213 | |||
| 214 | // 1st CMYK entry is reserved/unused | ||
| 215 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(s->cmyk_adjust[0]); i++) { | |
| 216 | ✗ | READ16(val); | |
| 217 | ✗ | if (val) | |
| 218 | ✗ | av_log(ctx, AV_LOG_WARNING, "%c value of first CMYK entry is not 0 " | |
| 219 | ✗ | "but %d\n", "CMYK"[i], val); | |
| 220 | } | ||
| 221 | |||
| 222 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(s->cmyk_adjust); i++) { | |
| 223 | int k; | ||
| 224 | ✗ | for (k = 0; k < FF_ARRAY_ELEMS(s->cmyk_adjust[0]); k++) { | |
| 225 | ✗ | READ16(val); | |
| 226 | ✗ | s->cmyk_adjust[i][k] = val / 100.f; | |
| 227 | } | ||
| 228 | ✗ | ret = register_range(ctx, i); | |
| 229 | ✗ | if (ret < 0) | |
| 230 | ✗ | goto end; | |
| 231 | } | ||
| 232 | |||
| 233 | ✗ | end: | |
| 234 | ✗ | av_file_unmap(buf, size); | |
| 235 | ✗ | return ret; | |
| 236 | } | ||
| 237 | |||
| 238 | ✗ | static int config_input(AVFilterLink *inlink) | |
| 239 | { | ||
| 240 | int i, ret; | ||
| 241 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 242 | ✗ | SelectiveColorContext *s = ctx->priv; | |
| 243 | ✗ | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 244 | |||
| 245 | ✗ | s->is_16bit = desc->comp[0].depth > 8; | |
| 246 | ✗ | s->step = av_get_padded_bits_per_pixel(desc) >> (3 + s->is_16bit); | |
| 247 | |||
| 248 | ✗ | ret = ff_fill_rgba_map(s->rgba_map, inlink->format); | |
| 249 | ✗ | if (ret < 0) | |
| 250 | ✗ | return ret; | |
| 251 | |||
| 252 | /* If the following conditions are not met, it will cause trouble while | ||
| 253 | * parsing the PS file */ | ||
| 254 | av_assert0(FF_ARRAY_ELEMS(s->cmyk_adjust) == 10 - 1); | ||
| 255 | av_assert0(FF_ARRAY_ELEMS(s->cmyk_adjust[0]) == 4); | ||
| 256 | |||
| 257 | ✗ | if (s->psfile) { | |
| 258 | ✗ | ret = parse_psfile(ctx, s->psfile); | |
| 259 | ✗ | if (ret < 0) | |
| 260 | ✗ | return ret; | |
| 261 | } else { | ||
| 262 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(s->opt_cmyk_adjust); i++) { | |
| 263 | ✗ | const char *opt_cmyk_adjust = s->opt_cmyk_adjust[i]; | |
| 264 | |||
| 265 | ✗ | if (opt_cmyk_adjust) { | |
| 266 | ✗ | float *cmyk = s->cmyk_adjust[i]; | |
| 267 | |||
| 268 | ✗ | sscanf(s->opt_cmyk_adjust[i], "%f %f %f %f", cmyk, cmyk+1, cmyk+2, cmyk+3); | |
| 269 | ✗ | ret = register_range(ctx, i); | |
| 270 | ✗ | if (ret < 0) | |
| 271 | ✗ | return ret; | |
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | ✗ | av_log(ctx, AV_LOG_VERBOSE, "Adjustments:%s\n", s->nb_process_ranges ? "" : " none"); | |
| 277 | ✗ | for (i = 0; i < s->nb_process_ranges; i++) { | |
| 278 | ✗ | const struct process_range *pr = &s->process_ranges[i]; | |
| 279 | ✗ | const float *cmyk = s->cmyk_adjust[pr->range_id]; | |
| 280 | |||
| 281 | ✗ | av_log(ctx, AV_LOG_VERBOSE, "%8ss: C=%6g M=%6g Y=%6g K=%6g\n", | |
| 282 | ✗ | color_names[pr->range_id], cmyk[0], cmyk[1], cmyk[2], cmyk[3]); | |
| 283 | } | ||
| 284 | |||
| 285 | ✗ | return 0; | |
| 286 | } | ||
| 287 | |||
| 288 | static const enum AVPixelFormat pix_fmts[] = { | ||
| 289 | AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, | ||
| 290 | AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, | ||
| 291 | AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, | ||
| 292 | AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR, | ||
| 293 | AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0, | ||
| 294 | AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48, | ||
| 295 | AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64, | ||
| 296 | AV_PIX_FMT_NONE | ||
| 297 | }; | ||
| 298 | |||
| 299 | ✗ | static inline int comp_adjust(int scale, float value, float adjust, float k, int correction_method) | |
| 300 | { | ||
| 301 | ✗ | const float min = -value; | |
| 302 | ✗ | const float max = 1.f - value; | |
| 303 | ✗ | float res = (-1.f - adjust) * k - adjust; | |
| 304 | ✗ | if (correction_method == CORRECTION_METHOD_RELATIVE) | |
| 305 | ✗ | res *= max; | |
| 306 | ✗ | return lrintf(av_clipf(res, min, max) * scale); | |
| 307 | } | ||
| 308 | |||
| 309 | #define DECLARE_SELECTIVE_COLOR_FUNC(nbits) \ | ||
| 310 | static inline int selective_color_##nbits(AVFilterContext *ctx, ThreadData *td, \ | ||
| 311 | int jobnr, int nb_jobs, int direct, int correction_method) \ | ||
| 312 | { \ | ||
| 313 | int i, x, y; \ | ||
| 314 | const AVFrame *in = td->in; \ | ||
| 315 | AVFrame *out = td->out; \ | ||
| 316 | const SelectiveColorContext *s = ctx->priv; \ | ||
| 317 | const int height = in->height; \ | ||
| 318 | const int width = in->width; \ | ||
| 319 | const int slice_start = (height * jobnr ) / nb_jobs; \ | ||
| 320 | const int slice_end = (height * (jobnr+1)) / nb_jobs; \ | ||
| 321 | const int dst_linesize = out->linesize[0] / ((nbits + 7) / 8); \ | ||
| 322 | const int src_linesize = in->linesize[0] / ((nbits + 7) / 8); \ | ||
| 323 | const uint8_t roffset = s->rgba_map[R]; \ | ||
| 324 | const uint8_t goffset = s->rgba_map[G]; \ | ||
| 325 | const uint8_t boffset = s->rgba_map[B]; \ | ||
| 326 | const uint8_t aoffset = s->rgba_map[A]; \ | ||
| 327 | uint##nbits##_t *dst = (uint##nbits##_t *)out->data[0] + slice_start * dst_linesize; \ | ||
| 328 | const uint##nbits##_t *src = (const uint##nbits##_t *)in->data[0] + slice_start * src_linesize; \ | ||
| 329 | const uint##nbits##_t *src_r = (const uint##nbits##_t *)src + roffset; \ | ||
| 330 | const uint##nbits##_t *src_g = (const uint##nbits##_t *)src + goffset; \ | ||
| 331 | const uint##nbits##_t *src_b = (const uint##nbits##_t *)src + boffset; \ | ||
| 332 | const uint##nbits##_t *src_a = (const uint##nbits##_t *)src + aoffset; \ | ||
| 333 | uint##nbits##_t *dst_r = (uint##nbits##_t *)dst + roffset; \ | ||
| 334 | uint##nbits##_t *dst_g = (uint##nbits##_t *)dst + goffset; \ | ||
| 335 | uint##nbits##_t *dst_b = (uint##nbits##_t *)dst + boffset; \ | ||
| 336 | uint##nbits##_t *dst_a = (uint##nbits##_t *)dst + aoffset; \ | ||
| 337 | const int mid = 1<<(nbits-1); \ | ||
| 338 | const int max = (1<<nbits)-1; \ | ||
| 339 | const float scale = 1.f / max; \ | ||
| 340 | \ | ||
| 341 | for (y = slice_start; y < slice_end; y++) { \ | ||
| 342 | for (x = 0; x < width * s->step; x += s->step) { \ | ||
| 343 | const int r = src_r[x]; \ | ||
| 344 | const int g = src_g[x]; \ | ||
| 345 | const int b = src_b[x]; \ | ||
| 346 | const int min_color = FFMIN3(r, g, b); \ | ||
| 347 | const int max_color = FFMAX3(r, g, b); \ | ||
| 348 | const int is_white = (r > mid && g > mid && b > mid); \ | ||
| 349 | const int is_neutral = (r || g || b) && \ | ||
| 350 | (r != max || g != max || b != max); \ | ||
| 351 | const int is_black = (r < mid && g < mid && b < mid); \ | ||
| 352 | const uint32_t range_flag = (r == max_color) << RANGE_REDS \ | ||
| 353 | | (r == min_color) << RANGE_CYANS \ | ||
| 354 | | (g == max_color) << RANGE_GREENS \ | ||
| 355 | | (g == min_color) << RANGE_MAGENTAS \ | ||
| 356 | | (b == max_color) << RANGE_BLUES \ | ||
| 357 | | (b == min_color) << RANGE_YELLOWS \ | ||
| 358 | | is_white << RANGE_WHITES \ | ||
| 359 | | is_neutral << RANGE_NEUTRALS \ | ||
| 360 | | is_black << RANGE_BLACKS; \ | ||
| 361 | \ | ||
| 362 | const float rnorm = r * scale; \ | ||
| 363 | const float gnorm = g * scale; \ | ||
| 364 | const float bnorm = b * scale; \ | ||
| 365 | int adjust_r = 0, adjust_g = 0, adjust_b = 0; \ | ||
| 366 | \ | ||
| 367 | for (i = 0; i < s->nb_process_ranges; i++) { \ | ||
| 368 | const struct process_range *pr = &s->process_ranges[i]; \ | ||
| 369 | \ | ||
| 370 | if (range_flag & pr->mask) { \ | ||
| 371 | const int scale = pr->get_scale(r, g, b, min_color, max_color); \ | ||
| 372 | \ | ||
| 373 | if (scale > 0) { \ | ||
| 374 | const float *cmyk_adjust = s->cmyk_adjust[pr->range_id]; \ | ||
| 375 | const float adj_c = cmyk_adjust[0]; \ | ||
| 376 | const float adj_m = cmyk_adjust[1]; \ | ||
| 377 | const float adj_y = cmyk_adjust[2]; \ | ||
| 378 | const float k = cmyk_adjust[3]; \ | ||
| 379 | \ | ||
| 380 | adjust_r += comp_adjust(scale, rnorm, adj_c, k, correction_method); \ | ||
| 381 | adjust_g += comp_adjust(scale, gnorm, adj_m, k, correction_method); \ | ||
| 382 | adjust_b += comp_adjust(scale, bnorm, adj_y, k, correction_method); \ | ||
| 383 | } \ | ||
| 384 | } \ | ||
| 385 | } \ | ||
| 386 | \ | ||
| 387 | if (!direct || adjust_r || adjust_g || adjust_b) { \ | ||
| 388 | dst_r[x] = av_clip_uint##nbits(r + adjust_r); \ | ||
| 389 | dst_g[x] = av_clip_uint##nbits(g + adjust_g); \ | ||
| 390 | dst_b[x] = av_clip_uint##nbits(b + adjust_b); \ | ||
| 391 | if (!direct && s->step == 4) \ | ||
| 392 | dst_a[x] = src_a[x]; \ | ||
| 393 | } \ | ||
| 394 | } \ | ||
| 395 | \ | ||
| 396 | src_r += src_linesize; \ | ||
| 397 | src_g += src_linesize; \ | ||
| 398 | src_b += src_linesize; \ | ||
| 399 | src_a += src_linesize; \ | ||
| 400 | \ | ||
| 401 | dst_r += dst_linesize; \ | ||
| 402 | dst_g += dst_linesize; \ | ||
| 403 | dst_b += dst_linesize; \ | ||
| 404 | dst_a += dst_linesize; \ | ||
| 405 | } \ | ||
| 406 | return 0; \ | ||
| 407 | } | ||
| 408 | |||
| 409 | #define DEF_SELECTIVE_COLOR_FUNC(name, direct, correction_method, nbits) \ | ||
| 410 | static int selective_color_##name##_##nbits(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) \ | ||
| 411 | { \ | ||
| 412 | return selective_color_##nbits(ctx, arg, jobnr, nb_jobs, direct, correction_method); \ | ||
| 413 | } | ||
| 414 | |||
| 415 | #define DEF_SELECTIVE_COLOR_FUNCS(nbits) \ | ||
| 416 | DECLARE_SELECTIVE_COLOR_FUNC(nbits) \ | ||
| 417 | DEF_SELECTIVE_COLOR_FUNC(indirect_absolute, 0, CORRECTION_METHOD_ABSOLUTE, nbits) \ | ||
| 418 | DEF_SELECTIVE_COLOR_FUNC(indirect_relative, 0, CORRECTION_METHOD_RELATIVE, nbits) \ | ||
| 419 | DEF_SELECTIVE_COLOR_FUNC( direct_absolute, 1, CORRECTION_METHOD_ABSOLUTE, nbits) \ | ||
| 420 | DEF_SELECTIVE_COLOR_FUNC( direct_relative, 1, CORRECTION_METHOD_RELATIVE, nbits) | ||
| 421 | |||
| 422 | ✗ | DEF_SELECTIVE_COLOR_FUNCS(8) | |
| 423 | ✗ | DEF_SELECTIVE_COLOR_FUNCS(16) | |
| 424 | |||
| 425 | typedef int (*selective_color_func_type)(AVFilterContext *ctx, void *td, int jobnr, int nb_jobs); | ||
| 426 | |||
| 427 | ✗ | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
| 428 | { | ||
| 429 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 430 | ✗ | AVFilterLink *outlink = ctx->outputs[0]; | |
| 431 | int direct; | ||
| 432 | AVFrame *out; | ||
| 433 | ThreadData td; | ||
| 434 | ✗ | const SelectiveColorContext *s = ctx->priv; | |
| 435 | static const selective_color_func_type funcs[2][2][2] = { | ||
| 436 | { | ||
| 437 | {selective_color_indirect_absolute_8, selective_color_indirect_relative_8}, | ||
| 438 | {selective_color_direct_absolute_8, selective_color_direct_relative_8}, | ||
| 439 | },{ | ||
| 440 | {selective_color_indirect_absolute_16, selective_color_indirect_relative_16}, | ||
| 441 | {selective_color_direct_absolute_16, selective_color_direct_relative_16}, | ||
| 442 | } | ||
| 443 | }; | ||
| 444 | |||
| 445 | ✗ | if (av_frame_is_writable(in)) { | |
| 446 | ✗ | direct = 1; | |
| 447 | ✗ | out = in; | |
| 448 | } else { | ||
| 449 | ✗ | direct = 0; | |
| 450 | ✗ | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 451 | ✗ | if (!out) { | |
| 452 | ✗ | av_frame_free(&in); | |
| 453 | ✗ | return AVERROR(ENOMEM); | |
| 454 | } | ||
| 455 | ✗ | av_frame_copy_props(out, in); | |
| 456 | } | ||
| 457 | |||
| 458 | ✗ | td.in = in; | |
| 459 | ✗ | td.out = out; | |
| 460 | ✗ | ff_filter_execute(ctx, funcs[s->is_16bit][direct][s->correction_method], | |
| 461 | ✗ | &td, NULL, FFMIN(inlink->h, ff_filter_get_nb_threads(ctx))); | |
| 462 | |||
| 463 | ✗ | if (!direct) | |
| 464 | ✗ | av_frame_free(&in); | |
| 465 | ✗ | return ff_filter_frame(outlink, out); | |
| 466 | } | ||
| 467 | |||
| 468 | static const AVFilterPad selectivecolor_inputs[] = { | ||
| 469 | { | ||
| 470 | .name = "default", | ||
| 471 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 472 | .filter_frame = filter_frame, | ||
| 473 | .config_props = config_input, | ||
| 474 | }, | ||
| 475 | }; | ||
| 476 | |||
| 477 | const FFFilter ff_vf_selectivecolor = { | ||
| 478 | .p.name = "selectivecolor", | ||
| 479 | .p.description = NULL_IF_CONFIG_SMALL("Apply CMYK adjustments to specific color ranges."), | ||
| 480 | .p.priv_class = &selectivecolor_class, | ||
| 481 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, | ||
| 482 | .priv_size = sizeof(SelectiveColorContext), | ||
| 483 | FILTER_INPUTS(selectivecolor_inputs), | ||
| 484 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
| 485 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 486 | }; | ||
| 487 |