Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2015 Stupeflix | ||
3 | * Copyright (c) 2022 Clément Bœsch <u pkh me> | ||
4 | * | ||
5 | * This file is part of FFmpeg. | ||
6 | * | ||
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. | ||
11 | * | ||
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. | ||
16 | * | ||
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 | ||
20 | */ | ||
21 | |||
22 | /** | ||
23 | * @file | ||
24 | * Generate one palette for a whole video stream. | ||
25 | */ | ||
26 | |||
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" | ||
32 | #include "avfilter.h" | ||
33 | #include "filters.h" | ||
34 | #include "formats.h" | ||
35 | #include "palette.h" | ||
36 | #include "video.h" | ||
37 | |||
38 | /* Reference a color and how much it's used */ | ||
39 | struct color_ref { | ||
40 | uint32_t color; | ||
41 | struct Lab lab; | ||
42 | int64_t count; | ||
43 | }; | ||
44 | |||
45 | /* Store a range of colors */ | ||
46 | struct range_box { | ||
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) | ||
55 | }; | ||
56 | |||
57 | struct hist_node { | ||
58 | struct color_ref *entries; | ||
59 | int nb_entries; | ||
60 | }; | ||
61 | |||
62 | enum { | ||
63 | STATS_MODE_ALL_FRAMES, | ||
64 | STATS_MODE_DIFF_FRAMES, | ||
65 | STATS_MODE_SINGLE_FRAMES, | ||
66 | NB_STATS_MODE | ||
67 | }; | ||
68 | |||
69 | #define HIST_SIZE (1<<15) | ||
70 | |||
71 | typedef struct PaletteGenContext { | ||
72 | const AVClass *class; | ||
73 | |||
74 | int max_colors; | ||
75 | int reserve_transparent; | ||
76 | int stats_mode; | ||
77 | |||
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 | ||
86 | } PaletteGenContext; | ||
87 | |||
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" }, | ||
98 | { NULL } | ||
99 | }; | ||
100 | |||
101 | AVFILTER_DEFINE_CLASS(palettegen); | ||
102 | |||
103 | 2 | static int query_formats(const AVFilterContext *ctx, | |
104 | AVFilterFormatsConfig **cfg_in, | ||
105 | AVFilterFormatsConfig **cfg_out) | ||
106 | { | ||
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}; | ||
109 | int ret; | ||
110 | |||
111 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if ((ret = ff_formats_ref(ff_make_format_list(in_fmts) , &cfg_in[0]->formats)) < 0) |
112 | ✗ | return ret; | |
113 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if ((ret = ff_formats_ref(ff_make_format_list(out_fmts), &cfg_out[0]->formats)) < 0) |
114 | ✗ | return ret; | |
115 | 2 | return 0; | |
116 | } | ||
117 | |||
118 | typedef int (*cmp_func)(const void *, const void *); | ||
119 | |||
120 | #define DECLARE_CMP_FUNC(k0, k1, k2) \ | ||
121 | static int cmp_##k0##k1##k2(const void *pa, const void *pb) \ | ||
122 | { \ | ||
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; \ | ||
129 | } | ||
130 | |||
131 |
4/4✓ Branch 0 taken 19743 times.
✓ Branch 1 taken 872344 times.
✓ Branch 2 taken 19711 times.
✓ Branch 3 taken 32 times.
|
892087 | DECLARE_CMP_FUNC(L, a, b) |
132 |
4/4✓ Branch 0 taken 14185 times.
✓ Branch 1 taken 385091 times.
✓ Branch 2 taken 14183 times.
✓ Branch 3 taken 2 times.
|
399276 | DECLARE_CMP_FUNC(L, b, a) |
133 |
4/4✓ Branch 0 taken 2244 times.
✓ Branch 1 taken 127244 times.
✓ Branch 2 taken 2240 times.
✓ Branch 3 taken 4 times.
|
129488 | DECLARE_CMP_FUNC(a, L, b) |
134 |
4/4✓ Branch 0 taken 3596 times.
✓ Branch 1 taken 221279 times.
✓ Branch 2 taken 2886 times.
✓ Branch 3 taken 710 times.
|
224875 | DECLARE_CMP_FUNC(a, b, L) |
135 |
3/4✓ Branch 0 taken 3083 times.
✓ Branch 1 taken 91466 times.
✓ Branch 2 taken 3083 times.
✗ Branch 3 not taken.
|
94549 | DECLARE_CMP_FUNC(b, L, a) |
136 |
4/4✓ Branch 0 taken 9979 times.
✓ Branch 1 taken 319698 times.
✓ Branch 2 taken 9052 times.
✓ Branch 3 taken 927 times.
|
329677 | DECLARE_CMP_FUNC(b, a, L) |
137 | |||
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" }; | ||
140 | |||
141 | static const cmp_func cmp_funcs[] = { | ||
142 | [ID_XYZ] = cmp_Lab, | ||
143 | [ID_XZY] = cmp_Lba, | ||
144 | [ID_ZXY] = cmp_bLa, | ||
145 | [ID_YXZ] = cmp_aLb, | ||
146 | [ID_ZYX] = cmp_baL, | ||
147 | [ID_YZX] = cmp_abL, | ||
148 | }; | ||
149 | |||
150 | /* | ||
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. | ||
153 | */ | ||
154 | 764 | static int sort3id(int64_t x, int64_t y, int64_t z) | |
155 | { | ||
156 |
2/2✓ Branch 0 taken 400 times.
✓ Branch 1 taken 364 times.
|
764 | if (x >= y) { |
157 |
2/2✓ Branch 0 taken 209 times.
✓ Branch 1 taken 191 times.
|
400 | if (y >= z) return ID_XYZ; |
158 |
2/2✓ Branch 0 taken 130 times.
✓ Branch 1 taken 61 times.
|
191 | if (x >= z) return ID_XZY; |
159 | 61 | return ID_ZXY; | |
160 | } | ||
161 |
2/2✓ Branch 0 taken 118 times.
✓ Branch 1 taken 246 times.
|
364 | if (x >= z) return ID_YXZ; |
162 |
2/2✓ Branch 0 taken 138 times.
✓ Branch 1 taken 108 times.
|
246 | if (y >= z) return ID_YZX; |
163 | 108 | return ID_ZYX; | |
164 | } | ||
165 | |||
166 | /** | ||
167 | * Simple color comparison for sorting the final palette | ||
168 | */ | ||
169 | 2462 | static int cmp_color(const void *a, const void *b) | |
170 | { | ||
171 | 2462 | const struct range_box *box1 = a; | |
172 | 2462 | const struct range_box *box2 = b; | |
173 | 2462 | return FFDIFFSIGN(box1->color, box2->color); | |
174 | } | ||
175 | |||
176 | 764 | static void compute_box_stats(PaletteGenContext *s, struct range_box *box) | |
177 | { | ||
178 | 764 | int64_t er2[3] = {0}; | |
179 | |||
180 | /* Compute average color */ | ||
181 | 764 | int64_t sL = 0, sa = 0, sb = 0; | |
182 | 764 | box->weight = 0; | |
183 |
2/2✓ Branch 0 taken 418470 times.
✓ Branch 1 taken 764 times.
|
419234 | for (int i = box->start; i < box->start + box->len; i++) { |
184 | 418470 | const struct color_ref *ref = s->refs[i]; | |
185 | 418470 | sL += ref->lab.L * ref->count; | |
186 | 418470 | sa += ref->lab.a * ref->count; | |
187 | 418470 | sb += ref->lab.b * ref->count; | |
188 | 418470 | box->weight += ref->count; | |
189 | } | ||
190 | 764 | box->avg.L = sL / box->weight; | |
191 | 764 | box->avg.a = sa / box->weight; | |
192 | 764 | box->avg.b = sb / box->weight; | |
193 | |||
194 | /* Compute squared error of each color channel */ | ||
195 |
2/2✓ Branch 0 taken 418470 times.
✓ Branch 1 taken 764 times.
|
419234 | for (int i = box->start; i < box->start + box->len; i++) { |
196 | 418470 | const struct color_ref *ref = s->refs[i]; | |
197 | 418470 | const int64_t dL = ref->lab.L - box->avg.L; | |
198 | 418470 | const int64_t da = ref->lab.a - box->avg.a; | |
199 | 418470 | const int64_t db = ref->lab.b - box->avg.b; | |
200 | 418470 | er2[0] += dL * dL * ref->count; | |
201 | 418470 | er2[1] += da * da * ref->count; | |
202 | 418470 | er2[2] += db * db * ref->count; | |
203 | } | ||
204 | |||
205 | /* Define the best axis candidate for cutting the box */ | ||
206 | 764 | box->major_axis = sort3id(er2[0], er2[1], er2[2]); | |
207 | |||
208 | /* The box that has the axis with the biggest error amongst all boxes will but cut down */ | ||
209 | 764 | box->cut_score = FFMAX3(er2[0], er2[1], er2[2]); | |
210 | 764 | } | |
211 | |||
212 | /** | ||
213 | * Find the next box to split: pick the one with the highest cut score | ||
214 | */ | ||
215 | 381 | static int get_next_box_id_to_split(PaletteGenContext *s) | |
216 | { | ||
217 | 381 | int best_box_id = -1; | |
218 | 381 | int64_t max_score = -1; | |
219 | |||
220 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 379 times.
|
381 | if (s->nb_boxes == s->max_colors - s->reserve_transparent) |
221 | 2 | return -1; | |
222 | |||
223 |
2/2✓ Branch 0 taken 40511 times.
✓ Branch 1 taken 379 times.
|
40890 | for (int box_id = 0; box_id < s->nb_boxes; box_id++) { |
224 | 40511 | const struct range_box *box = &s->boxes[box_id]; | |
225 |
3/4✓ Branch 0 taken 40511 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1490 times.
✓ Branch 3 taken 39021 times.
|
40511 | if (s->boxes[box_id].len >= 2 && box->cut_score > max_score) { |
226 | 1490 | best_box_id = box_id; | |
227 | 1490 | max_score = box->cut_score; | |
228 | } | ||
229 | } | ||
230 | 379 | return best_box_id; | |
231 | } | ||
232 | |||
233 | /** | ||
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. | ||
236 | */ | ||
237 | 381 | static void split_box(PaletteGenContext *s, struct range_box *box, int n) | |
238 | { | ||
239 | 381 | struct range_box *new_box = &s->boxes[s->nb_boxes++]; | |
240 | 381 | new_box->start = n + 1; | |
241 | 381 | new_box->len = box->start + box->len - new_box->start; | |
242 | 381 | new_box->sorted_by = box->sorted_by; | |
243 | 381 | box->len -= new_box->len; | |
244 | |||
245 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 381 times.
|
381 | av_assert0(box->len >= 1); |
246 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 381 times.
|
381 | av_assert0(new_box->len >= 1); |
247 | |||
248 | 381 | compute_box_stats(s, box); | |
249 | 381 | compute_box_stats(s, new_box); | |
250 | 381 | } | |
251 | |||
252 | /** | ||
253 | * Write the palette into the output frame. | ||
254 | */ | ||
255 | 2 | static void write_palette(AVFilterContext *ctx, AVFrame *out) | |
256 | { | ||
257 | 2 | const PaletteGenContext *s = ctx->priv; | |
258 | 2 | int box_id = 0; | |
259 | 2 | uint32_t *pal = (uint32_t *)out->data[0]; | |
260 | 2 | const int pal_linesize = out->linesize[0] >> 2; | |
261 | 2 | uint32_t last_color = 0; | |
262 | |||
263 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 2 times.
|
34 | for (int y = 0; y < out->height; y++) { |
264 |
2/2✓ Branch 0 taken 512 times.
✓ Branch 1 taken 32 times.
|
544 | for (int x = 0; x < out->width; x++) { |
265 |
2/2✓ Branch 0 taken 383 times.
✓ Branch 1 taken 129 times.
|
512 | if (box_id < s->nb_boxes) { |
266 | 383 | pal[x] = s->boxes[box_id++].color; | |
267 |
5/6✓ Branch 0 taken 24 times.
✓ Branch 1 taken 359 times.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 381 times.
|
383 | if ((x || y) && pal[x] == last_color) |
268 | ✗ | av_log(ctx, AV_LOG_WARNING, "Duped color: %08"PRIX32"\n", pal[x]); | |
269 | 383 | last_color = pal[x]; | |
270 | } else { | ||
271 | 129 | pal[x] = last_color; // pad with last color | |
272 | } | ||
273 | } | ||
274 | 32 | pal += pal_linesize; | |
275 | } | ||
276 | |||
277 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (s->reserve_transparent) { |
278 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | av_assert0(s->nb_boxes < 256); |
279 | 1 | pal[out->width - pal_linesize - 1] = AV_RB32(&s->transparency_color) >> 8; | |
280 | } | ||
281 | 2 | } | |
282 | |||
283 | /** | ||
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). | ||
287 | */ | ||
288 | 2 | static struct color_ref **load_color_refs(const struct hist_node *hist, int nb_refs) | |
289 | { | ||
290 | 2 | int k = 0; | |
291 | 2 | struct color_ref **refs = av_malloc_array(nb_refs, sizeof(*refs)); | |
292 | |||
293 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!refs) |
294 | ✗ | return NULL; | |
295 | |||
296 |
2/2✓ Branch 0 taken 65536 times.
✓ Branch 1 taken 2 times.
|
65538 | for (int j = 0; j < HIST_SIZE; j++) { |
297 | 65536 | const struct hist_node *node = &hist[j]; | |
298 | |||
299 |
2/2✓ Branch 0 taken 44830 times.
✓ Branch 1 taken 65536 times.
|
110366 | for (int i = 0; i < node->nb_entries; i++) |
300 | 44830 | refs[k++] = &node->entries[i]; | |
301 | } | ||
302 | |||
303 | 2 | return refs; | |
304 | } | ||
305 | |||
306 | 2 | static double set_colorquant_ratio_meta(AVFrame *out, int nb_out, int nb_in) | |
307 | { | ||
308 | char buf[32]; | ||
309 | 2 | const double ratio = (double)nb_out / nb_in; | |
310 | 2 | snprintf(buf, sizeof(buf), "%f", ratio); | |
311 | 2 | av_dict_set(&out->metadata, "lavfi.color_quant_ratio", buf, 0); | |
312 | 2 | return ratio; | |
313 | } | ||
314 | |||
315 | /** | ||
316 | * Main function implementing the Median Cut Algorithm defined by Paul Heckbert | ||
317 | * in Color Image Quantization for Frame Buffer Display (1982) | ||
318 | */ | ||
319 | 2 | static AVFrame *get_palette_frame(AVFilterContext *ctx) | |
320 | { | ||
321 | AVFrame *out; | ||
322 | 2 | PaletteGenContext *s = ctx->priv; | |
323 | 2 | AVFilterLink *outlink = ctx->outputs[0]; | |
324 | double ratio; | ||
325 | 2 | int box_id = 0; | |
326 | struct range_box *box; | ||
327 | |||
328 | /* reference only the used colors from histogram */ | ||
329 | 2 | s->refs = load_color_refs(s->histogram, s->nb_refs); | |
330 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!s->refs) { |
331 | ✗ | av_log(ctx, AV_LOG_ERROR, "Unable to allocate references for %d different colors\n", s->nb_refs); | |
332 | ✗ | return NULL; | |
333 | } | ||
334 | |||
335 | /* create the palette frame */ | ||
336 | 2 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
337 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!out) |
338 | ✗ | return NULL; | |
339 | 2 | out->pts = 0; | |
340 | |||
341 | /* set first box for 0..nb_refs */ | ||
342 | 2 | box = &s->boxes[box_id]; | |
343 | 2 | box->len = s->nb_refs; | |
344 | 2 | box->sorted_by = -1; | |
345 | 2 | compute_box_stats(s, box); | |
346 | 2 | s->nb_boxes = 1; | |
347 | |||
348 |
3/4✓ Branch 0 taken 381 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 381 times.
✗ Branch 3 not taken.
|
383 | while (box && box->len > 1) { |
349 | int i; | ||
350 | int64_t median, weight; | ||
351 | |||
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'); | ||
355 | |||
356 | /* sort the range by its major axis if it's not already sorted */ | ||
357 |
2/2✓ Branch 0 taken 248 times.
✓ Branch 1 taken 133 times.
|
381 | if (box->sorted_by != box->major_axis) { |
358 | 248 | cmp_func cmpf = cmp_funcs[box->major_axis]; | |
359 | 248 | qsort(&s->refs[box->start], box->len, sizeof(struct color_ref *), cmpf); | |
360 | 248 | box->sorted_by = box->major_axis; | |
361 | } | ||
362 | |||
363 | /* locate the median where to split */ | ||
364 | 381 | median = (box->weight + 1) >> 1; | |
365 | 381 | weight = 0; | |
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 |
1/2✓ Branch 0 taken 200876 times.
✗ Branch 1 not taken.
|
200876 | for (i = box->start; i < box->start + box->len - 2; i++) { |
369 | 200876 | weight += s->refs[i]->count; | |
370 |
2/2✓ Branch 0 taken 381 times.
✓ Branch 1 taken 200495 times.
|
200876 | if (weight > median) |
371 | 381 | break; | |
372 | } | ||
373 | ff_dlog(ctx, "split @ i=%-6d with w=%-6"PRIu64" (target=%6"PRIu64")\n", i, weight, median); | ||
374 | 381 | split_box(s, box, i); | |
375 | |||
376 | 381 | box_id = get_next_box_id_to_split(s); | |
377 |
2/2✓ Branch 0 taken 379 times.
✓ Branch 1 taken 2 times.
|
381 | box = box_id >= 0 ? &s->boxes[box_id] : NULL; |
378 | } | ||
379 | |||
380 | 2 | ratio = set_colorquant_ratio_meta(out, s->nb_boxes, s->nb_refs); | |
381 | 2 | av_log(ctx, AV_LOG_INFO, "%d%s colors generated out of %d colors; ratio=%f\n", | |
382 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | s->nb_boxes, s->reserve_transparent ? "(+1)" : "", s->nb_refs, ratio); |
383 | |||
384 |
2/2✓ Branch 0 taken 383 times.
✓ Branch 1 taken 2 times.
|
385 | for (int i = 0; i < s->nb_boxes; i++) |
385 | 383 | s->boxes[i].color = 0xffU<<24 | ff_oklab_int_to_srgb_u8(s->boxes[i].avg); | |
386 | |||
387 | 2 | qsort(s->boxes, s->nb_boxes, sizeof(*s->boxes), cmp_color); | |
388 | |||
389 | 2 | write_palette(ctx, out); | |
390 | |||
391 | 2 | return out; | |
392 | } | ||
393 | |||
394 | /** | ||
395 | * Locate the color in the hash table and increment its counter. | ||
396 | */ | ||
397 | 4329458 | static int color_inc(struct hist_node *hist, uint32_t color) | |
398 | { | ||
399 | 4329458 | const uint32_t hash = ff_lowbias32(color) & (HIST_SIZE - 1); | |
400 | 4329458 | struct hist_node *node = &hist[hash]; | |
401 | struct color_ref *e; | ||
402 | |||
403 |
2/2✓ Branch 0 taken 4990540 times.
✓ Branch 1 taken 44830 times.
|
5035370 | for (int i = 0; i < node->nb_entries; i++) { |
404 | 4990540 | e = &node->entries[i]; | |
405 |
2/2✓ Branch 0 taken 4284628 times.
✓ Branch 1 taken 705912 times.
|
4990540 | if (e->color == color) { |
406 | 4284628 | e->count++; | |
407 | 4284628 | return 0; | |
408 | } | ||
409 | } | ||
410 | |||
411 | 44830 | e = av_dynarray2_add((void**)&node->entries, &node->nb_entries, | |
412 | sizeof(*node->entries), NULL); | ||
413 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44830 times.
|
44830 | if (!e) |
414 | ✗ | return AVERROR(ENOMEM); | |
415 | 44830 | e->color = color; | |
416 | 44830 | e->lab = ff_srgb_u8_to_oklab_int(color); | |
417 | 44830 | e->count = 1; | |
418 | 44830 | return 1; | |
419 | } | ||
420 | |||
421 | /** | ||
422 | * Update histogram when pixels differ from previous frame. | ||
423 | */ | ||
424 | 70 | static int update_histogram_diff(struct hist_node *hist, | |
425 | const AVFrame *f1, const AVFrame *f2) | ||
426 | { | ||
427 | 70 | int x, y, ret, nb_diff_colors = 0; | |
428 | |||
429 |
2/2✓ Branch 0 taken 12600 times.
✓ Branch 1 taken 70 times.
|
12670 | for (y = 0; y < f1->height; y++) { |
430 | 12600 | const uint32_t *p = (const uint32_t *)(f1->data[0] + y*f1->linesize[0]); | |
431 | 12600 | const uint32_t *q = (const uint32_t *)(f2->data[0] + y*f2->linesize[0]); | |
432 | |||
433 |
2/2✓ Branch 0 taken 4032000 times.
✓ Branch 1 taken 12600 times.
|
4044600 | for (x = 0; x < f1->width; x++) { |
434 |
2/2✓ Branch 0 taken 3849742 times.
✓ Branch 1 taken 182258 times.
|
4032000 | if (p[x] == q[x]) |
435 | 3849742 | continue; | |
436 | 182258 | ret = color_inc(hist, p[x]); | |
437 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 182258 times.
|
182258 | if (ret < 0) |
438 | ✗ | return ret; | |
439 | 182258 | nb_diff_colors += ret; | |
440 | } | ||
441 | } | ||
442 | 70 | return nb_diff_colors; | |
443 | } | ||
444 | |||
445 | /** | ||
446 | * Simple histogram of the frame. | ||
447 | */ | ||
448 | 72 | static int update_histogram_frame(struct hist_node *hist, const AVFrame *f) | |
449 | { | ||
450 | 72 | int x, y, ret, nb_diff_colors = 0; | |
451 | |||
452 |
2/2✓ Branch 0 taken 12960 times.
✓ Branch 1 taken 72 times.
|
13032 | for (y = 0; y < f->height; y++) { |
453 | 12960 | const uint32_t *p = (const uint32_t *)(f->data[0] + y*f->linesize[0]); | |
454 | |||
455 |
2/2✓ Branch 0 taken 4147200 times.
✓ Branch 1 taken 12960 times.
|
4160160 | for (x = 0; x < f->width; x++) { |
456 | 4147200 | ret = color_inc(hist, p[x]); | |
457 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4147200 times.
|
4147200 | if (ret < 0) |
458 | ✗ | return ret; | |
459 | 4147200 | nb_diff_colors += ret; | |
460 | } | ||
461 | } | ||
462 | 72 | return nb_diff_colors; | |
463 | } | ||
464 | |||
465 | /** | ||
466 | * Update the histogram for each passing frame. No frame will be pushed here. | ||
467 | */ | ||
468 | 142 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
469 | { | ||
470 | 142 | AVFilterContext *ctx = inlink->dst; | |
471 | 142 | PaletteGenContext *s = ctx->priv; | |
472 | int ret; | ||
473 | |||
474 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
142 | 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"); | |
476 | |||
477 | 70 | ret = s->prev_frame ? update_histogram_diff(s->histogram, s->prev_frame, in) | |
478 |
2/2✓ Branch 0 taken 70 times.
✓ Branch 1 taken 72 times.
|
142 | : update_histogram_frame(s->histogram, in); |
479 |
2/2✓ Branch 0 taken 137 times.
✓ Branch 1 taken 5 times.
|
142 | if (ret > 0) |
480 | 137 | s->nb_refs += ret; | |
481 | |||
482 |
2/2✓ Branch 0 taken 71 times.
✓ Branch 1 taken 71 times.
|
142 | if (s->stats_mode == STATS_MODE_DIFF_FRAMES) { |
483 | 71 | av_frame_free(&s->prev_frame); | |
484 | 71 | s->prev_frame = in; | |
485 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 71 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
71 | } else if (s->stats_mode == STATS_MODE_SINGLE_FRAMES && s->nb_refs > 0) { |
486 | AVFrame *out; | ||
487 | int i; | ||
488 | |||
489 | ✗ | out = get_palette_frame(ctx); | |
490 | ✗ | out->pts = in->pts; | |
491 | ✗ | av_frame_free(&in); | |
492 | ✗ | ret = ff_filter_frame(ctx->outputs[0], out); | |
493 | ✗ | for (i = 0; i < HIST_SIZE; i++) | |
494 | ✗ | av_freep(&s->histogram[i].entries); | |
495 | ✗ | av_freep(&s->refs); | |
496 | ✗ | s->nb_refs = 0; | |
497 | ✗ | s->nb_boxes = 0; | |
498 | ✗ | memset(s->boxes, 0, sizeof(s->boxes)); | |
499 | ✗ | memset(s->histogram, 0, sizeof(s->histogram)); | |
500 | } else { | ||
501 | 71 | av_frame_free(&in); | |
502 | } | ||
503 | |||
504 | 142 | return ret; | |
505 | } | ||
506 | |||
507 | /** | ||
508 | * Returns only one frame at the end containing the full palette. | ||
509 | */ | ||
510 | 146 | static int request_frame(AVFilterLink *outlink) | |
511 | { | ||
512 | 146 | AVFilterContext *ctx = outlink->src; | |
513 | 146 | AVFilterLink *inlink = ctx->inputs[0]; | |
514 | 146 | PaletteGenContext *s = ctx->priv; | |
515 | int r; | ||
516 | |||
517 | 146 | r = ff_request_frame(inlink); | |
518 |
6/8✓ Branch 0 taken 4 times.
✓ Branch 1 taken 142 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
146 | if (r == AVERROR_EOF && !s->palette_pushed && s->nb_refs && s->stats_mode != STATS_MODE_SINGLE_FRAMES) { |
519 | 2 | r = ff_filter_frame(outlink, get_palette_frame(ctx)); | |
520 | 2 | s->palette_pushed = 1; | |
521 | 2 | return r; | |
522 | } | ||
523 | 144 | return r; | |
524 | } | ||
525 | |||
526 | /** | ||
527 | * The output is one simple 16x16 squared-pixels palette. | ||
528 | */ | ||
529 | 2 | static int config_output(AVFilterLink *outlink) | |
530 | { | ||
531 | 2 | outlink->w = outlink->h = 16; | |
532 | 2 | outlink->sample_aspect_ratio = av_make_q(1, 1); | |
533 | 2 | return 0; | |
534 | } | ||
535 | |||
536 | 4 | static int init(AVFilterContext *ctx) | |
537 | { | ||
538 | 4 | PaletteGenContext* s = ctx->priv; | |
539 | |||
540 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | 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); | |
543 | } | ||
544 | |||
545 | 4 | return 0; | |
546 | } | ||
547 | |||
548 | 4 | static av_cold void uninit(AVFilterContext *ctx) | |
549 | { | ||
550 | int i; | ||
551 | 4 | PaletteGenContext *s = ctx->priv; | |
552 | |||
553 |
2/2✓ Branch 0 taken 131072 times.
✓ Branch 1 taken 4 times.
|
131076 | for (i = 0; i < HIST_SIZE; i++) |
554 | 131072 | av_freep(&s->histogram[i].entries); | |
555 | 4 | av_freep(&s->refs); | |
556 | 4 | av_frame_free(&s->prev_frame); | |
557 | 4 | } | |
558 | |||
559 | static const AVFilterPad palettegen_inputs[] = { | ||
560 | { | ||
561 | .name = "default", | ||
562 | .type = AVMEDIA_TYPE_VIDEO, | ||
563 | .filter_frame = filter_frame, | ||
564 | }, | ||
565 | }; | ||
566 | |||
567 | static const AVFilterPad palettegen_outputs[] = { | ||
568 | { | ||
569 | .name = "default", | ||
570 | .type = AVMEDIA_TYPE_VIDEO, | ||
571 | .config_props = config_output, | ||
572 | .request_frame = request_frame, | ||
573 | }, | ||
574 | }; | ||
575 | |||
576 | const AVFilter ff_vf_palettegen = { | ||
577 | .name = "palettegen", | ||
578 | .description = NULL_IF_CONFIG_SMALL("Find the optimal palette for a given stream."), | ||
579 | .priv_size = sizeof(PaletteGenContext), | ||
580 | .init = init, | ||
581 | .uninit = uninit, | ||
582 | FILTER_INPUTS(palettegen_inputs), | ||
583 | FILTER_OUTPUTS(palettegen_outputs), | ||
584 | FILTER_QUERY_FUNC2(query_formats), | ||
585 | .priv_class = &palettegen_class, | ||
586 | }; | ||
587 |