2 * Copyright (c) 2015 Stupeflix
3 * Copyright (c) 2022 Clément Bœsch <u pkh me>
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg 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 GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 * Generate one palette for a whole video stream.
27 #include "libavutil/avassert.h"
28 #include "libavutil/internal.h"
29 #include "libavutil/mem.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/intreadwrite.h"
38 /* Reference a color and how much it's used */
45 /* Store a range of colors */
47 uint32_t color
; // average color
48 struct Lab avg
; // average color in perceptual OkLab space
49 int major_axis
; // best axis candidate for cutting the box
50 int64_t weight
; // sum of all the weights of the colors
51 int64_t cut_score
; // how likely the box is to be cut down (higher implying more likely)
52 int start
; // index in PaletteGenContext->refs
53 int len
; // number of referenced colors
54 int sorted_by
; // whether range of colors is sorted by red (0), green (1) or blue (2)
58 struct color_ref
*entries
;
63 STATS_MODE_ALL_FRAMES
,
64 STATS_MODE_DIFF_FRAMES
,
65 STATS_MODE_SINGLE_FRAMES
,
69 #define HIST_SIZE (1<<15)
71 typedef struct PaletteGenContext
{
75 int reserve_transparent
;
78 AVFrame
*prev_frame
; // previous frame used for the diff stats_mode
79 struct hist_node histogram
[HIST_SIZE
]; // histogram/hashtable of the colors
80 struct color_ref
**refs
; // references of all the colors used in the stream
81 int nb_refs
; // number of color references (or number of different colors)
82 struct range_box boxes
[256]; // define the segmentation of the colorspace (the final palette)
83 int nb_boxes
; // number of boxes (increase will segmenting them)
84 int palette_pushed
; // if the palette frame is pushed into the outlink or not
85 uint8_t transparency_color
[4]; // background color for transparency
88 #define OFFSET(x) offsetof(PaletteGenContext, x)
89 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
90 static const AVOption palettegen_options
[] = {
91 { "max_colors", "set the maximum number of colors to use in the palette", OFFSET(max_colors
), AV_OPT_TYPE_INT
, {.i64
=256}, 2, 256, FLAGS
},
92 { "reserve_transparent", "reserve a palette entry for transparency", OFFSET(reserve_transparent
), AV_OPT_TYPE_BOOL
, {.i64
=1}, 0, 1, FLAGS
},
93 { "transparency_color", "set a background color for transparency", OFFSET(transparency_color
), AV_OPT_TYPE_COLOR
, {.str
="lime"}, 0, 0, FLAGS
},
94 { "stats_mode", "set statistics mode", OFFSET(stats_mode
), AV_OPT_TYPE_INT
, {.i64
=STATS_MODE_ALL_FRAMES
}, 0, NB_STATS_MODE
-1, FLAGS
, .unit
= "mode" },
95 { "full", "compute full frame histograms", 0, AV_OPT_TYPE_CONST
, {.i64
=STATS_MODE_ALL_FRAMES
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "mode" },
96 { "diff", "compute histograms only for the part that differs from previous frame", 0, AV_OPT_TYPE_CONST
, {.i64
=STATS_MODE_DIFF_FRAMES
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "mode" },
97 { "single", "compute new histogram for each frame", 0, AV_OPT_TYPE_CONST
, {.i64
=STATS_MODE_SINGLE_FRAMES
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "mode" },
101 AVFILTER_DEFINE_CLASS(palettegen
);
103 static int query_formats(const AVFilterContext
*ctx
,
104 AVFilterFormatsConfig
**cfg_in
,
105 AVFilterFormatsConfig
**cfg_out
)
107 static const enum AVPixelFormat in_fmts
[] = {AV_PIX_FMT_RGB32
, AV_PIX_FMT_NONE
};
108 static const enum AVPixelFormat out_fmts
[] = {AV_PIX_FMT_RGB32
, AV_PIX_FMT_NONE
};
111 if ((ret
= ff_formats_ref(ff_make_format_list(in_fmts
) , &cfg_in
[0]->formats
)) < 0)
113 if ((ret
= ff_formats_ref(ff_make_format_list(out_fmts
), &cfg_out
[0]->formats
)) < 0)
118 typedef int (*cmp_func
)(const void *, const void *);
120 #define DECLARE_CMP_FUNC(k0, k1, k2) \
121 static int cmp_##k0##k1##k2(const void *pa, const void *pb) \
123 const struct color_ref * const *a = pa; \
124 const struct color_ref * const *b = pb; \
125 const int c0 = FFDIFFSIGN((*a)->lab.k0, (*b)->lab.k0); \
126 const int c1 = FFDIFFSIGN((*a)->lab.k1, (*b)->lab.k1); \
127 const int c2 = FFDIFFSIGN((*a)->lab.k2, (*b)->lab.k2); \
128 return c0 ? c0 : c1 ? c1 : c2; \
131 DECLARE_CMP_FUNC(L
, a
, b
)
132 DECLARE_CMP_FUNC(L
, b
, a
)
133 DECLARE_CMP_FUNC(a
, L
, b
)
134 DECLARE_CMP_FUNC(a
, b
, L
)
135 DECLARE_CMP_FUNC(b
, L
, a
)
136 DECLARE_CMP_FUNC(b
, a
, L
)
138 enum { ID_XYZ
, ID_XZY
, ID_ZXY
, ID_YXZ
, ID_ZYX
, ID_YZX
};
139 static const char * const sortstr
[] = { "Lab", "Lba", "bLa", "aLb", "baL", "abL" };
141 static const cmp_func cmp_funcs
[] = {
151 * Return an identifier for the order of x, y, z (from higher to lower),
152 * preferring x over y and y over z in case of equality.
154 static int sort3id(int64_t x
, int64_t y
, int64_t z
)
157 if (y
>= z
) return ID_XYZ
;
158 if (x
>= z
) return ID_XZY
;
161 if (x
>= z
) return ID_YXZ
;
162 if (y
>= z
) return ID_YZX
;
167 * Simple color comparison for sorting the final palette
169 static int cmp_color(const void *a
, const void *b
)
171 const struct range_box
*box1
= a
;
172 const struct range_box
*box2
= b
;
173 return FFDIFFSIGN(box1
->color
, box2
->color
);
176 static void compute_box_stats(PaletteGenContext
*s
, struct range_box
*box
)
178 int64_t er2
[3] = {0};
180 /* Compute average color */
181 int64_t sL
= 0, sa
= 0, sb
= 0;
183 for (int i
= box
->start
; i
< box
->start
+ box
->len
; i
++) {
184 const struct color_ref
*ref
= s
->refs
[i
];
185 sL
+= ref
->lab
.L
* ref
->count
;
186 sa
+= ref
->lab
.a
* ref
->count
;
187 sb
+= ref
->lab
.b
* ref
->count
;
188 box
->weight
+= ref
->count
;
190 box
->avg
.L
= sL
/ box
->weight
;
191 box
->avg
.a
= sa
/ box
->weight
;
192 box
->avg
.b
= sb
/ box
->weight
;
194 /* Compute squared error of each color channel */
195 for (int i
= box
->start
; i
< box
->start
+ box
->len
; i
++) {
196 const struct color_ref
*ref
= s
->refs
[i
];
197 const int64_t dL
= ref
->lab
.L
- box
->avg
.L
;
198 const int64_t da
= ref
->lab
.a
- box
->avg
.a
;
199 const int64_t db
= ref
->lab
.b
- box
->avg
.b
;
200 er2
[0] += dL
* dL
* ref
->count
;
201 er2
[1] += da
* da
* ref
->count
;
202 er2
[2] += db
* db
* ref
->count
;
205 /* Define the best axis candidate for cutting the box */
206 box
->major_axis
= sort3id(er2
[0], er2
[1], er2
[2]);
208 /* The box that has the axis with the biggest error amongst all boxes will but cut down */
209 box
->cut_score
= FFMAX3(er2
[0], er2
[1], er2
[2]);
213 * Find the next box to split: pick the one with the highest cut score
215 static int get_next_box_id_to_split(PaletteGenContext
*s
)
217 int best_box_id
= -1;
218 int64_t max_score
= -1;
220 if (s
->nb_boxes
== s
->max_colors
- s
->reserve_transparent
)
223 for (int box_id
= 0; box_id
< s
->nb_boxes
; box_id
++) {
224 const struct range_box
*box
= &s
->boxes
[box_id
];
225 if (s
->boxes
[box_id
].len
>= 2 && box
->cut_score
> max_score
) {
226 best_box_id
= box_id
;
227 max_score
= box
->cut_score
;
234 * Split given box in two at position n. The original box becomes the left part
235 * of the split, and the new index box is the right part.
237 static void split_box(PaletteGenContext
*s
, struct range_box
*box
, int n
)
239 struct range_box
*new_box
= &s
->boxes
[s
->nb_boxes
++];
240 new_box
->start
= n
+ 1;
241 new_box
->len
= box
->start
+ box
->len
- new_box
->start
;
242 new_box
->sorted_by
= box
->sorted_by
;
243 box
->len
-= new_box
->len
;
245 av_assert0(box
->len
>= 1);
246 av_assert0(new_box
->len
>= 1);
248 compute_box_stats(s
, box
);
249 compute_box_stats(s
, new_box
);
253 * Write the palette into the output frame.
255 static void write_palette(AVFilterContext
*ctx
, AVFrame
*out
)
257 const PaletteGenContext
*s
= ctx
->priv
;
259 uint32_t *pal
= (uint32_t *)out
->data
[0];
260 const int pal_linesize
= out
->linesize
[0] >> 2;
261 uint32_t last_color
= 0;
263 for (int y
= 0; y
< out
->height
; y
++) {
264 for (int x
= 0; x
< out
->width
; x
++) {
265 if (box_id
< s
->nb_boxes
) {
266 pal
[x
] = s
->boxes
[box_id
++].color
;
267 if ((x
|| y
) && pal
[x
] == last_color
)
268 av_log(ctx
, AV_LOG_WARNING
, "Duped color: %08"PRIX32
"\n", pal
[x
]);
271 pal
[x
] = last_color
; // pad with last color
277 if (s
->reserve_transparent
) {
278 av_assert0(s
->nb_boxes
< 256);
279 pal
[out
->width
- pal_linesize
- 1] = AV_RB32(&s
->transparency_color
) >> 8;
284 * Crawl the histogram to get all the defined colors, and create a linear list
285 * of them (each color reference entry is a pointer to the value in the
286 * histogram/hash table).
288 static struct color_ref
**load_color_refs(const struct hist_node
*hist
, int nb_refs
)
291 struct color_ref
**refs
= av_malloc_array(nb_refs
, sizeof(*refs
));
296 for (int j
= 0; j
< HIST_SIZE
; j
++) {
297 const struct hist_node
*node
= &hist
[j
];
299 for (int i
= 0; i
< node
->nb_entries
; i
++)
300 refs
[k
++] = &node
->entries
[i
];
306 static double set_colorquant_ratio_meta(AVFrame
*out
, int nb_out
, int nb_in
)
309 const double ratio
= (double)nb_out
/ nb_in
;
310 snprintf(buf
, sizeof(buf
), "%f", ratio
);
311 av_dict_set(&out
->metadata
, "lavfi.color_quant_ratio", buf
, 0);
316 * Main function implementing the Median Cut Algorithm defined by Paul Heckbert
317 * in Color Image Quantization for Frame Buffer Display (1982)
319 static AVFrame
*get_palette_frame(AVFilterContext
*ctx
)
322 PaletteGenContext
*s
= ctx
->priv
;
323 AVFilterLink
*outlink
= ctx
->outputs
[0];
326 struct range_box
*box
;
328 /* reference only the used colors from histogram */
329 s
->refs
= load_color_refs(s
->histogram
, s
->nb_refs
);
331 av_log(ctx
, AV_LOG_ERROR
, "Unable to allocate references for %d different colors\n", s
->nb_refs
);
335 /* create the palette frame */
336 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
341 /* set first box for 0..nb_refs */
342 box
= &s
->boxes
[box_id
];
343 box
->len
= s
->nb_refs
;
345 compute_box_stats(s
, box
);
348 while (box
&& box
->len
> 1) {
350 int64_t median
, weight
;
352 ff_dlog(ctx
, "box #%02X [%6d..%-6d] (%6d) w:%-6"PRIu64
" sort by %s (already sorted:%c) ",
353 box_id
, box
->start
, box
->start
+ box
->len
- 1, box
->len
, box
->weight
,
354 sortstr
[box
->major_axis
], box
->sorted_by
== box
->major_axis
? 'y':'n');
356 /* sort the range by its major axis if it's not already sorted */
357 if (box
->sorted_by
!= box
->major_axis
) {
358 cmp_func cmpf
= cmp_funcs
[box
->major_axis
];
359 qsort(&s
->refs
[box
->start
], box
->len
, sizeof(struct color_ref
*), cmpf
);
360 box
->sorted_by
= box
->major_axis
;
363 /* locate the median where to split */
364 median
= (box
->weight
+ 1) >> 1;
366 /* if you have 2 boxes, the maximum is actually #0: you must have at
367 * least 1 color on each side of the split, hence the -2 */
368 for (i
= box
->start
; i
< box
->start
+ box
->len
- 2; i
++) {
369 weight
+= s
->refs
[i
]->count
;
373 ff_dlog(ctx
, "split @ i=%-6d with w=%-6"PRIu64
" (target=%6"PRIu64
")\n", i
, weight
, median
);
374 split_box(s
, box
, i
);
376 box_id
= get_next_box_id_to_split(s
);
377 box
= box_id
>= 0 ? &s
->boxes
[box_id
] : NULL
;
380 ratio
= set_colorquant_ratio_meta(out
, s
->nb_boxes
, s
->nb_refs
);
381 av_log(ctx
, AV_LOG_INFO
, "%d%s colors generated out of %d colors; ratio=%f\n",
382 s
->nb_boxes
, s
->reserve_transparent
? "(+1)" : "", s
->nb_refs
, ratio
);
384 for (int i
= 0; i
< s
->nb_boxes
; i
++)
385 s
->boxes
[i
].color
= 0xffU
<<24 | ff_oklab_int_to_srgb_u8(s
->boxes
[i
].avg
);
387 qsort(s
->boxes
, s
->nb_boxes
, sizeof(*s
->boxes
), cmp_color
);
389 write_palette(ctx
, out
);
395 * Locate the color in the hash table and increment its counter.
397 static int color_inc(struct hist_node
*hist
, uint32_t color
)
399 const uint32_t hash
= ff_lowbias32(color
) & (HIST_SIZE
- 1);
400 struct hist_node
*node
= &hist
[hash
];
403 for (int i
= 0; i
< node
->nb_entries
; i
++) {
404 e
= &node
->entries
[i
];
405 if (e
->color
== color
) {
411 e
= av_dynarray2_add((void**)&node
->entries
, &node
->nb_entries
,
412 sizeof(*node
->entries
), NULL
);
414 return AVERROR(ENOMEM
);
416 e
->lab
= ff_srgb_u8_to_oklab_int(color
);
422 * Update histogram when pixels differ from previous frame.
424 static int update_histogram_diff(struct hist_node
*hist
,
425 const AVFrame
*f1
, const AVFrame
*f2
)
427 int x
, y
, ret
, nb_diff_colors
= 0;
429 for (y
= 0; y
< f1
->height
; y
++) {
430 const uint32_t *p
= (const uint32_t *)(f1
->data
[0] + y
*f1
->linesize
[0]);
431 const uint32_t *q
= (const uint32_t *)(f2
->data
[0] + y
*f2
->linesize
[0]);
433 for (x
= 0; x
< f1
->width
; x
++) {
436 ret
= color_inc(hist
, p
[x
]);
439 nb_diff_colors
+= ret
;
442 return nb_diff_colors
;
446 * Simple histogram of the frame.
448 static int update_histogram_frame(struct hist_node
*hist
, const AVFrame
*f
)
450 int x
, y
, ret
, nb_diff_colors
= 0;
452 for (y
= 0; y
< f
->height
; y
++) {
453 const uint32_t *p
= (const uint32_t *)(f
->data
[0] + y
*f
->linesize
[0]);
455 for (x
= 0; x
< f
->width
; x
++) {
456 ret
= color_inc(hist
, p
[x
]);
459 nb_diff_colors
+= ret
;
462 return nb_diff_colors
;
466 * Update the histogram for each passing frame. No frame will be pushed here.
468 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
470 AVFilterContext
*ctx
= inlink
->dst
;
471 PaletteGenContext
*s
= ctx
->priv
;
474 if (in
->color_trc
!= AVCOL_TRC_UNSPECIFIED
&& in
->color_trc
!= AVCOL_TRC_IEC61966_2_1
)
475 av_log(ctx
, AV_LOG_WARNING
, "The input frame is not in sRGB, colors may be off\n");
477 ret
= s
->prev_frame
? update_histogram_diff(s
->histogram
, s
->prev_frame
, in
)
478 : update_histogram_frame(s
->histogram
, in
);
482 if (s
->stats_mode
== STATS_MODE_DIFF_FRAMES
) {
483 av_frame_free(&s
->prev_frame
);
485 } else if (s
->stats_mode
== STATS_MODE_SINGLE_FRAMES
&& s
->nb_refs
> 0) {
489 out
= get_palette_frame(ctx
);
492 ret
= ff_filter_frame(ctx
->outputs
[0], out
);
493 for (i
= 0; i
< HIST_SIZE
; i
++)
494 av_freep(&s
->histogram
[i
].entries
);
498 memset(s
->boxes
, 0, sizeof(s
->boxes
));
499 memset(s
->histogram
, 0, sizeof(s
->histogram
));
508 * Returns only one frame at the end containing the full palette.
510 static int request_frame(AVFilterLink
*outlink
)
512 AVFilterContext
*ctx
= outlink
->src
;
513 AVFilterLink
*inlink
= ctx
->inputs
[0];
514 PaletteGenContext
*s
= ctx
->priv
;
517 r
= ff_request_frame(inlink
);
518 if (r
== AVERROR_EOF
&& !s
->palette_pushed
&& s
->nb_refs
&& s
->stats_mode
!= STATS_MODE_SINGLE_FRAMES
) {
519 r
= ff_filter_frame(outlink
, get_palette_frame(ctx
));
520 s
->palette_pushed
= 1;
527 * The output is one simple 16x16 squared-pixels palette.
529 static int config_output(AVFilterLink
*outlink
)
531 outlink
->w
= outlink
->h
= 16;
532 outlink
->sample_aspect_ratio
= av_make_q(1, 1);
536 static int init(AVFilterContext
*ctx
)
538 PaletteGenContext
* s
= ctx
->priv
;
540 if (s
->max_colors
- s
->reserve_transparent
< 2) {
541 av_log(ctx
, AV_LOG_ERROR
, "max_colors=2 is only allowed without reserving a transparent color slot\n");
542 return AVERROR(EINVAL
);
548 static av_cold
void uninit(AVFilterContext
*ctx
)
551 PaletteGenContext
*s
= ctx
->priv
;
553 for (i
= 0; i
< HIST_SIZE
; i
++)
554 av_freep(&s
->histogram
[i
].entries
);
556 av_frame_free(&s
->prev_frame
);
559 static const AVFilterPad palettegen_inputs
[] = {
562 .type
= AVMEDIA_TYPE_VIDEO
,
563 .filter_frame
= filter_frame
,
567 static const AVFilterPad palettegen_outputs
[] = {
570 .type
= AVMEDIA_TYPE_VIDEO
,
571 .config_props
= config_output
,
572 .request_frame
= request_frame
,
576 const FFFilter ff_vf_palettegen
= {
577 .p
.name
= "palettegen",
578 .p
.description
= NULL_IF_CONFIG_SMALL("Find the optimal palette for a given stream."),
579 .p
.priv_class
= &palettegen_class
,
580 .priv_size
= sizeof(PaletteGenContext
),
583 FILTER_INPUTS(palettegen_inputs
),
584 FILTER_OUTPUTS(palettegen_outputs
),
585 FILTER_QUERY_FUNC2(query_formats
),