You've already forked FFmpeg
							
							
				mirror of
				https://github.com/FFmpeg/FFmpeg.git
				synced 2025-10-30 23:18:11 +02:00 
			
		
		
		
	avfilter/paletteuse: add diff_mode
This commit is contained in:
		| @@ -7026,6 +7026,21 @@ visible pattern for less banding, and higher value means less visible pattern | |||||||
| at the cost of more banding. | at the cost of more banding. | ||||||
|  |  | ||||||
| The option must be an integer value in the range [0,5]. Default is @var{2}. | The option must be an integer value in the range [0,5]. Default is @var{2}. | ||||||
|  |  | ||||||
|  | @item diff_mode | ||||||
|  | If set, define the zone to process | ||||||
|  |  | ||||||
|  | @table @samp | ||||||
|  | @item rectangle | ||||||
|  | Only the changing rectangle will be reprocessed. This is similar to GIF | ||||||
|  | cropping/offsetting compression mechanism. This option can be useful for speed | ||||||
|  | if only a part of the image is changing, and has use cases such as limiting the | ||||||
|  | scope of the error diffusal @option{dither} to the rectangle that bounds the | ||||||
|  | moving scene (it leads to more deterministic output if the scene doesn't change | ||||||
|  | much, and as a result less moving noise and better GIF compression). | ||||||
|  | @end table | ||||||
|  |  | ||||||
|  | Default is @var{none}. | ||||||
| @end table | @end table | ||||||
|  |  | ||||||
| @subsection Examples | @subsection Examples | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ | |||||||
|  |  | ||||||
| #define LIBAVFILTER_VERSION_MAJOR  5 | #define LIBAVFILTER_VERSION_MAJOR  5 | ||||||
| #define LIBAVFILTER_VERSION_MINOR  11 | #define LIBAVFILTER_VERSION_MINOR  11 | ||||||
| #define LIBAVFILTER_VERSION_MICRO 100 | #define LIBAVFILTER_VERSION_MICRO 101 | ||||||
|  |  | ||||||
| #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ | #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ | ||||||
|                                                LIBAVFILTER_VERSION_MINOR, \ |                                                LIBAVFILTER_VERSION_MINOR, \ | ||||||
|   | |||||||
| @@ -43,6 +43,12 @@ enum color_search_method { | |||||||
|     NB_COLOR_SEARCHES |     NB_COLOR_SEARCHES | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum diff_mode { | ||||||
|  |     DIFF_MODE_NONE, | ||||||
|  |     DIFF_MODE_RECTANGLE, | ||||||
|  |     NB_DIFF_MODE | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct color_node { | struct color_node { | ||||||
|     uint8_t val[3]; |     uint8_t val[3]; | ||||||
|     uint8_t palette_id; |     uint8_t palette_id; | ||||||
| @@ -65,7 +71,8 @@ struct cache_node { | |||||||
|  |  | ||||||
| struct PaletteUseContext; | struct PaletteUseContext; | ||||||
|  |  | ||||||
| typedef int (*set_frame_func)(struct PaletteUseContext *s, AVFrame *out, AVFrame *in); | typedef int (*set_frame_func)(struct PaletteUseContext *s, AVFrame *out, AVFrame *in, | ||||||
|  |                               int x_start, int y_start, int width, int height); | ||||||
|  |  | ||||||
| typedef struct PaletteUseContext { | typedef struct PaletteUseContext { | ||||||
|     const AVClass *class; |     const AVClass *class; | ||||||
| @@ -78,6 +85,9 @@ typedef struct PaletteUseContext { | |||||||
|     set_frame_func set_frame; |     set_frame_func set_frame; | ||||||
|     int bayer_scale; |     int bayer_scale; | ||||||
|     int ordered_dither[8*8]; |     int ordered_dither[8*8]; | ||||||
|  |     int diff_mode; | ||||||
|  |     AVFrame *last_in; | ||||||
|  |     AVFrame *last_out; | ||||||
|  |  | ||||||
|     /* debug options */ |     /* debug options */ | ||||||
|     char *dot_filename; |     char *dot_filename; | ||||||
| @@ -97,6 +107,8 @@ static const AVOption paletteuse_options[] = { | |||||||
|         { "sierra2",         "Frankie Sierra dithering v2 (error diffusion)",                          0, AV_OPT_TYPE_CONST, {.i64=DITHERING_SIERRA2},         INT_MIN, INT_MAX, FLAGS, "dithering_mode" }, |         { "sierra2",         "Frankie Sierra dithering v2 (error diffusion)",                          0, AV_OPT_TYPE_CONST, {.i64=DITHERING_SIERRA2},         INT_MIN, INT_MAX, FLAGS, "dithering_mode" }, | ||||||
|         { "sierra2_4a",      "Frankie Sierra dithering v2 \"Lite\" (error diffusion)",                 0, AV_OPT_TYPE_CONST, {.i64=DITHERING_SIERRA2_4A},      INT_MIN, INT_MAX, FLAGS, "dithering_mode" }, |         { "sierra2_4a",      "Frankie Sierra dithering v2 \"Lite\" (error diffusion)",                 0, AV_OPT_TYPE_CONST, {.i64=DITHERING_SIERRA2_4A},      INT_MIN, INT_MAX, FLAGS, "dithering_mode" }, | ||||||
|     { "bayer_scale", "set scale for bayer dithering", OFFSET(bayer_scale), AV_OPT_TYPE_INT, {.i64=2}, 0, 5, FLAGS }, |     { "bayer_scale", "set scale for bayer dithering", OFFSET(bayer_scale), AV_OPT_TYPE_INT, {.i64=2}, 0, 5, FLAGS }, | ||||||
|  |     { "diff_mode",   "set frame difference mode",     OFFSET(diff_mode),   AV_OPT_TYPE_INT, {.i64=DIFF_MODE_NONE}, 0, NB_DIFF_MODE-1, FLAGS, "diff_mode" }, | ||||||
|  |         { "rectangle", "process smallest different rectangle", 0, AV_OPT_TYPE_CONST, {.i64=DIFF_MODE_RECTANGLE}, INT_MIN, INT_MAX, FLAGS, "diff_mode" }, | ||||||
|  |  | ||||||
|     /* following are the debug options, not part of the official API */ |     /* following are the debug options, not part of the official API */ | ||||||
|     { "debug_kdtree", "save Graphviz graph of the kdtree in specified file", OFFSET(dot_filename), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, |     { "debug_kdtree", "save Graphviz graph of the kdtree in specified file", OFFSET(dot_filename), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, | ||||||
| @@ -349,6 +361,7 @@ static av_always_inline uint8_t get_dst_color_err(struct cache_node *cache, | |||||||
| } | } | ||||||
|  |  | ||||||
| static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFrame *in, | static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFrame *in, | ||||||
|  |                                       int x_start, int y_start, int w, int h, | ||||||
|                                       enum dithering_mode dither, |                                       enum dithering_mode dither, | ||||||
|                                       const enum color_search_method search_method) |                                       const enum color_search_method search_method) | ||||||
| { | { | ||||||
| @@ -356,13 +369,16 @@ static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFram | |||||||
|     const struct color_node *map = s->map; |     const struct color_node *map = s->map; | ||||||
|     struct cache_node *cache = s->cache; |     struct cache_node *cache = s->cache; | ||||||
|     const uint32_t *palette = s->palette; |     const uint32_t *palette = s->palette; | ||||||
|     uint32_t *src = (uint32_t *)in ->data[0]; |  | ||||||
|     uint8_t  *dst =             out->data[0]; |  | ||||||
|     const int src_linesize = in ->linesize[0] >> 2; |     const int src_linesize = in ->linesize[0] >> 2; | ||||||
|     const int dst_linesize = out->linesize[0]; |     const int dst_linesize = out->linesize[0]; | ||||||
|  |     uint32_t *src = ((uint32_t *)in ->data[0]) + y_start*src_linesize; | ||||||
|  |     uint8_t  *dst =              out->data[0]  + y_start*dst_linesize; | ||||||
|  |  | ||||||
|     for (y = 0; y < in->height; y++) { |     w += x_start; | ||||||
|         for (x = 0; x < in->width; x++) { |     h += y_start; | ||||||
|  |  | ||||||
|  |     for (y = y_start; y < h; y++) { | ||||||
|  |         for (x = x_start; x < w; x++) { | ||||||
|             int er, eg, eb; |             int er, eg, eb; | ||||||
|  |  | ||||||
|             if (dither == DITHERING_BAYER) { |             if (dither == DITHERING_BAYER) { | ||||||
| @@ -381,7 +397,7 @@ static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFram | |||||||
|                 dst[x] = color; |                 dst[x] = color; | ||||||
|  |  | ||||||
|             } else if (dither == DITHERING_HECKBERT) { |             } else if (dither == DITHERING_HECKBERT) { | ||||||
|                 const int right = x < in->width - 1, down = y < in->height - 1; |                 const int right = x < w - 1, down = y < h - 1; | ||||||
|                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); |                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); | ||||||
|  |  | ||||||
|                 if (color < 0) |                 if (color < 0) | ||||||
| @@ -393,7 +409,7 @@ static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFram | |||||||
|                 if (right && down) src[src_linesize + x + 1] = dither_color(src[src_linesize + x + 1], er, eg, eb, 2, 3); |                 if (right && down) src[src_linesize + x + 1] = dither_color(src[src_linesize + x + 1], er, eg, eb, 2, 3); | ||||||
|  |  | ||||||
|             } else if (dither == DITHERING_FLOYD_STEINBERG) { |             } else if (dither == DITHERING_FLOYD_STEINBERG) { | ||||||
|                 const int right = x < in->width - 1, down = y < in->height - 1, left = x > 0; |                 const int right = x < w - 1, down = y < h - 1, left = x > x_start; | ||||||
|                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); |                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); | ||||||
|  |  | ||||||
|                 if (color < 0) |                 if (color < 0) | ||||||
| @@ -406,8 +422,8 @@ static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFram | |||||||
|                 if (right && down) src[src_linesize + x + 1] = dither_color(src[src_linesize + x + 1], er, eg, eb, 1, 4); |                 if (right && down) src[src_linesize + x + 1] = dither_color(src[src_linesize + x + 1], er, eg, eb, 1, 4); | ||||||
|  |  | ||||||
|             } else if (dither == DITHERING_SIERRA2) { |             } else if (dither == DITHERING_SIERRA2) { | ||||||
|                 const int right  = x < in->width - 1, down  = y < in->height - 1, left  = x > 0; |                 const int right  = x < w - 1, down  = y < h - 1, left  = x > x_start; | ||||||
|                 const int right2 = x < in->width - 2,                             left2 = x > 1; |                 const int right2 = x < w - 2,                    left2 = x > x_start + 1; | ||||||
|                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); |                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); | ||||||
|  |  | ||||||
|                 if (color < 0) |                 if (color < 0) | ||||||
| @@ -426,7 +442,7 @@ static av_always_inline int set_frame(PaletteUseContext *s, AVFrame *out, AVFram | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|             } else if (dither == DITHERING_SIERRA2_4A) { |             } else if (dither == DITHERING_SIERRA2_4A) { | ||||||
|                 const int right = x < in->width - 1, down = y < in->height - 1, left = x > 0; |                 const int right = x < w - 1, down = y < h - 1, left = x > x_start; | ||||||
|                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); |                 const int color = get_dst_color_err(cache, src[x], map, palette, &er, &eg, &eb, search_method); | ||||||
|  |  | ||||||
|                 if (color < 0) |                 if (color < 0) | ||||||
| @@ -738,8 +754,98 @@ static void debug_mean_error(PaletteUseContext *s, const AVFrame *in1, | |||||||
|            mean_err / div, s->total_mean_err / (div * frame_count)); |            mean_err / div, s->total_mean_err / (div * frame_count)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void set_processing_window(enum diff_mode diff_mode, | ||||||
|  |                                   const AVFrame *prv_src, const AVFrame *cur_src, | ||||||
|  |                                   const AVFrame *prv_dst,       AVFrame *cur_dst, | ||||||
|  |                                   int *xp, int *yp, int *wp, int *hp) | ||||||
|  | { | ||||||
|  |     int x_start = 0, y_start = 0; | ||||||
|  |     int width  = cur_src->width; | ||||||
|  |     int height = cur_src->height; | ||||||
|  |  | ||||||
|  |     if (prv_src && diff_mode == DIFF_MODE_RECTANGLE) { | ||||||
|  |         int y; | ||||||
|  |         int x_end = cur_src->width  - 1, | ||||||
|  |             y_end = cur_src->height - 1; | ||||||
|  |         const uint32_t *prv_srcp = (const uint32_t *)prv_src->data[0]; | ||||||
|  |         const uint32_t *cur_srcp = (const uint32_t *)cur_src->data[0]; | ||||||
|  |         const uint8_t  *prv_dstp = prv_dst->data[0]; | ||||||
|  |         uint8_t        *cur_dstp = cur_dst->data[0]; | ||||||
|  |  | ||||||
|  |         const int prv_src_linesize = prv_src->linesize[0] >> 2; | ||||||
|  |         const int cur_src_linesize = cur_src->linesize[0] >> 2; | ||||||
|  |         const int prv_dst_linesize = prv_dst->linesize[0]; | ||||||
|  |         const int cur_dst_linesize = cur_dst->linesize[0]; | ||||||
|  |  | ||||||
|  |         /* skip common lines */ | ||||||
|  |         while (y_start < y_end && !memcmp(prv_srcp + y_start*prv_src_linesize, | ||||||
|  |                                           cur_srcp + y_start*cur_src_linesize, | ||||||
|  |                                           cur_src->width * 4)) { | ||||||
|  |             memcpy(cur_dstp + y_start*cur_dst_linesize, | ||||||
|  |                    prv_dstp + y_start*prv_dst_linesize, | ||||||
|  |                    cur_dst->width); | ||||||
|  |             y_start++; | ||||||
|  |         } | ||||||
|  |         while (y_end > y_start && !memcmp(prv_srcp + y_end*prv_src_linesize, | ||||||
|  |                                           cur_srcp + y_end*cur_src_linesize, | ||||||
|  |                                           cur_src->width * 4)) { | ||||||
|  |             memcpy(cur_dstp + y_end*cur_dst_linesize, | ||||||
|  |                    prv_dstp + y_end*prv_dst_linesize, | ||||||
|  |                    cur_dst->width); | ||||||
|  |             y_end--; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         height = y_end + 1 - y_start; | ||||||
|  |  | ||||||
|  |         /* skip common columns */ | ||||||
|  |         while (x_start < x_end) { | ||||||
|  |             int same_column = 1; | ||||||
|  |             for (y = y_start; y <= y_end; y++) { | ||||||
|  |                 if (prv_srcp[y*prv_src_linesize + x_start] != cur_srcp[y*cur_src_linesize + x_start]) { | ||||||
|  |                     same_column = 0; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (!same_column) | ||||||
|  |                 break; | ||||||
|  |             x_start++; | ||||||
|  |         } | ||||||
|  |         while (x_end > x_start) { | ||||||
|  |             int same_column = 1; | ||||||
|  |             for (y = y_start; y <= y_end; y++) { | ||||||
|  |                 if (prv_srcp[y*prv_src_linesize + x_end] != cur_srcp[y*cur_src_linesize + x_end]) { | ||||||
|  |                     same_column = 0; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (!same_column) | ||||||
|  |                 break; | ||||||
|  |             x_end--; | ||||||
|  |         } | ||||||
|  |         width = x_end + 1 - x_start; | ||||||
|  |  | ||||||
|  |         if (x_start) { | ||||||
|  |             for (y = y_start; y <= y_end; y++) | ||||||
|  |                 memcpy(cur_dstp + y*cur_dst_linesize, | ||||||
|  |                        prv_dstp + y*prv_dst_linesize, x_start); | ||||||
|  |         } | ||||||
|  |         if (x_end != cur_src->width - 1) { | ||||||
|  |             const int copy_len = cur_src->width - 1 - x_end; | ||||||
|  |             for (y = y_start; y <= y_end; y++) | ||||||
|  |                 memcpy(cur_dstp + y*cur_dst_linesize + x_end + 1, | ||||||
|  |                        prv_dstp + y*prv_dst_linesize + x_end + 1, | ||||||
|  |                        copy_len); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     *xp = x_start; | ||||||
|  |     *yp = y_start; | ||||||
|  |     *wp = width; | ||||||
|  |     *hp = height; | ||||||
|  | } | ||||||
|  |  | ||||||
| static AVFrame *apply_palette(AVFilterLink *inlink, AVFrame *in) | static AVFrame *apply_palette(AVFilterLink *inlink, AVFrame *in) | ||||||
| { | { | ||||||
|  |     int x, y, w, h; | ||||||
|     AVFilterContext *ctx = inlink->dst; |     AVFilterContext *ctx = inlink->dst; | ||||||
|     PaletteUseContext *s = ctx->priv; |     PaletteUseContext *s = ctx->priv; | ||||||
|     AVFilterLink *outlink = inlink->dst->outputs[0]; |     AVFilterLink *outlink = inlink->dst->outputs[0]; | ||||||
| @@ -750,11 +856,27 @@ static AVFrame *apply_palette(AVFilterLink *inlink, AVFrame *in) | |||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|     av_frame_copy_props(out, in); |     av_frame_copy_props(out, in); | ||||||
|     if (s->set_frame(s, out, in) < 0) { |  | ||||||
|  |     set_processing_window(s->diff_mode, s->last_in, in, | ||||||
|  |                           s->last_out, out, &x, &y, &w, &h); | ||||||
|  |     av_frame_free(&s->last_in); | ||||||
|  |     av_frame_free(&s->last_out); | ||||||
|  |     s->last_in  = av_frame_clone(in); | ||||||
|  |     s->last_out = av_frame_clone(out); | ||||||
|  |     if (!s->last_in || !s->last_out || | ||||||
|  |         av_frame_make_writable(s->last_in) < 0) { | ||||||
|         av_frame_free(&in); |         av_frame_free(&in); | ||||||
|         av_frame_free(&out); |         av_frame_free(&out); | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     av_dlog(ctx, "%dx%d rect: (%d;%d) -> (%d,%d) [area:%dx%d]\n", | ||||||
|  |             w, h, x, y, x+w, y+h, in->width, in->height); | ||||||
|  |  | ||||||
|  |     if (s->set_frame(s, out, in, x, y, w, h) < 0) { | ||||||
|  |         av_frame_free(&out); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|     memcpy(out->data[1], s->palette, AVPALETTE_SIZE); |     memcpy(out->data[1], s->palette, AVPALETTE_SIZE); | ||||||
|     if (s->calc_mean_err) |     if (s->calc_mean_err) | ||||||
|         debug_mean_error(s, in, out, inlink->frame_count); |         debug_mean_error(s, in, out, inlink->frame_count); | ||||||
| @@ -828,9 +950,10 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |||||||
| } | } | ||||||
|  |  | ||||||
| #define DEFINE_SET_FRAME(color_search, name, value)                             \ | #define DEFINE_SET_FRAME(color_search, name, value)                             \ | ||||||
| static int set_frame_##name(PaletteUseContext *s, AVFrame *out, AVFrame *in)    \ | static int set_frame_##name(PaletteUseContext *s, AVFrame *out, AVFrame *in,    \ | ||||||
|  |                             int x_start, int y_start, int w, int h)             \ | ||||||
| {                                                                               \ | {                                                                               \ | ||||||
|     return set_frame(s, out, in, value, color_search);                          \ |     return set_frame(s, out, in, x_start, y_start, w, h, value, color_search);  \ | ||||||
| } | } | ||||||
|  |  | ||||||
| #define DEFINE_SET_FRAME_COLOR_SEARCH(color_search, color_search_macro)                                 \ | #define DEFINE_SET_FRAME_COLOR_SEARCH(color_search, color_search_macro)                                 \ | ||||||
| @@ -901,6 +1024,8 @@ static av_cold void uninit(AVFilterContext *ctx) | |||||||
|     ff_dualinput_uninit(&s->dinput); |     ff_dualinput_uninit(&s->dinput); | ||||||
|     for (i = 0; i < CACHE_SIZE; i++) |     for (i = 0; i < CACHE_SIZE; i++) | ||||||
|         av_freep(&s->cache[i].entries); |         av_freep(&s->cache[i].entries); | ||||||
|  |     av_frame_free(&s->last_in); | ||||||
|  |     av_frame_free(&s->last_out); | ||||||
| } | } | ||||||
|  |  | ||||||
| static const AVFilterPad paletteuse_inputs[] = { | static const AVFilterPad paletteuse_inputs[] = { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user