2015-01-24 22:37:59 +02:00
/*
2015-02-23 15:00:13 +02:00
* Copyright ( c ) 2015 Stupeflix
*
2015-01-24 22:37:59 +02:00
* This file is part of FFmpeg .
*
* FFmpeg is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* FFmpeg is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
* @ file
* Generate one palette for a whole video stream .
*/
# include "libavutil/avassert.h"
2015-08-18 02:45:35 +02:00
# include "libavutil/internal.h"
2015-01-24 22:37:59 +02:00
# include "libavutil/opt.h"
2015-02-25 17:06:34 +02:00
# include "libavutil/qsort.h"
2017-10-17 23:39:59 +02:00
# include "libavutil/intreadwrite.h"
2015-01-24 22:37:59 +02:00
# include "avfilter.h"
# include "internal.h"
2022-12-27 16:33:15 +02:00
# include "palette.h"
2015-01-24 22:37:59 +02:00
/* Reference a color and how much it's used */
struct color_ref {
uint32_t color ;
2022-12-27 16:33:15 +02:00
struct Lab lab ;
2022-12-27 16:05:01 +02:00
int64_t count ;
2015-01-24 22:37:59 +02:00
} ;
/* Store a range of colors */
struct range_box {
uint32_t color ; // average color
2022-12-27 16:33:15 +02:00
struct Lab avg ; // average color in perceptual OkLab space
2022-12-27 14:58:07 +02:00
int major_axis ; // best axis candidate for cutting the box
2022-12-27 16:05:01 +02:00
int64_t weight ; // sum of all the weights of the colors
2022-12-27 15:47:20 +02:00
int64_t cut_score ; // how likely the box is to be cut down (higher implying more likely)
2015-01-24 22:37:59 +02:00
int start ; // index in PaletteGenContext->refs
int len ; // number of referenced colors
int sorted_by ; // whether range of colors is sorted by red (0), green (1) or blue (2)
} ;
struct hist_node {
struct color_ref * entries ;
int nb_entries ;
} ;
enum {
STATS_MODE_ALL_FRAMES ,
STATS_MODE_DIFF_FRAMES ,
2016-09-02 22:15:10 +02:00
STATS_MODE_SINGLE_FRAMES ,
2015-01-24 22:37:59 +02:00
NB_STATS_MODE
} ;
2015-02-19 12:17:43 +02:00
# define NBITS 5
2022-10-30 19:38:23 +02:00
# define HIST_SIZE (1<<(3*NBITS))
2015-01-24 22:37:59 +02:00
2017-05-12 20:00:49 +02:00
typedef struct PaletteGenContext {
2015-01-24 22:37:59 +02:00
const AVClass * class ;
int max_colors ;
int reserve_transparent ;
int stats_mode ;
AVFrame * prev_frame ; // previous frame used for the diff stats_mode
struct hist_node histogram [ HIST_SIZE ] ; // histogram/hashtable of the colors
struct color_ref * * refs ; // references of all the colors used in the stream
int nb_refs ; // number of color references (or number of different colors)
struct range_box boxes [ 256 ] ; // define the segmentation of the colorspace (the final palette)
int nb_boxes ; // number of boxes (increase will segmenting them)
int palette_pushed ; // if the palette frame is pushed into the outlink or not
2017-10-29 02:35:36 +02:00
uint8_t transparency_color [ 4 ] ; // background color for transparency
2015-01-24 22:37:59 +02:00
} PaletteGenContext ;
# define OFFSET(x) offsetof(PaletteGenContext, x)
# define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static const AVOption palettegen_options [ ] = {
2022-12-27 23:06:23 +02:00
{ " 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 } ,
2015-09-09 00:34:56 +02:00
{ " reserve_transparent " , " reserve a palette entry for transparency " , OFFSET ( reserve_transparent ) , AV_OPT_TYPE_BOOL , { . i64 = 1 } , 0 , 1 , FLAGS } ,
2020-03-17 23:46:36 +02:00
{ " transparency_color " , " set a background color for transparency " , OFFSET ( transparency_color ) , AV_OPT_TYPE_COLOR , { . str = " lime " } , 0 , 0 , FLAGS } ,
2016-09-02 22:15:10 +02:00
{ " stats_mode " , " set statistics mode " , OFFSET ( stats_mode ) , AV_OPT_TYPE_INT , { . i64 = STATS_MODE_ALL_FRAMES } , 0 , NB_STATS_MODE - 1 , FLAGS , " mode " } ,
2015-01-24 22:37:59 +02:00
{ " full " , " compute full frame histograms " , 0 , AV_OPT_TYPE_CONST , { . i64 = STATS_MODE_ALL_FRAMES } , INT_MIN , INT_MAX , FLAGS , " mode " } ,
{ " 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 , " mode " } ,
2016-09-02 22:15:10 +02:00
{ " single " , " compute new histogram for each frame " , 0 , AV_OPT_TYPE_CONST , { . i64 = STATS_MODE_SINGLE_FRAMES } , INT_MIN , INT_MAX , FLAGS , " mode " } ,
2015-01-24 22:37:59 +02:00
{ NULL }
} ;
AVFILTER_DEFINE_CLASS ( palettegen ) ;
static int query_formats ( AVFilterContext * ctx )
{
static const enum AVPixelFormat in_fmts [ ] = { AV_PIX_FMT_RGB32 , AV_PIX_FMT_NONE } ;
static const enum AVPixelFormat out_fmts [ ] = { AV_PIX_FMT_RGB32 , AV_PIX_FMT_NONE } ;
2015-10-05 05:39:25 +02:00
int ret ;
2017-01-21 23:09:03 +02:00
2020-08-24 12:16:34 +02:00
if ( ( ret = ff_formats_ref ( ff_make_format_list ( in_fmts ) , & ctx - > inputs [ 0 ] - > outcfg . formats ) ) < 0 )
2017-01-21 23:09:03 +02:00
return ret ;
2020-08-24 12:16:34 +02:00
if ( ( ret = ff_formats_ref ( ff_make_format_list ( out_fmts ) , & ctx - > outputs [ 0 ] - > incfg . formats ) ) < 0 )
2015-10-05 05:39:25 +02:00
return ret ;
2015-01-24 22:37:59 +02:00
return 0 ;
}
typedef int ( * cmp_func ) ( const void * , const void * ) ;
2022-12-27 20:23:23 +02:00
# define DECLARE_CMP_FUNC(k0, k1, k2) \
static int cmp_ # # k0 # # k1 # # k2 ( const void * pa , const void * pb ) \
{ \
const struct color_ref * const * a = pa ; \
const struct color_ref * const * b = pb ; \
const int c0 = FFDIFFSIGN ( ( * a ) - > lab . k0 , ( * b ) - > lab . k0 ) ; \
const int c1 = FFDIFFSIGN ( ( * a ) - > lab . k1 , ( * b ) - > lab . k1 ) ; \
const int c2 = FFDIFFSIGN ( ( * a ) - > lab . k2 , ( * b ) - > lab . k2 ) ; \
return c0 ? c0 : c1 ? c1 : c2 ; \
2015-01-24 22:37:59 +02:00
}
2022-12-27 20:23:23 +02:00
DECLARE_CMP_FUNC ( L , a , b )
DECLARE_CMP_FUNC ( L , b , a )
DECLARE_CMP_FUNC ( a , L , b )
DECLARE_CMP_FUNC ( a , b , L )
DECLARE_CMP_FUNC ( b , L , a )
DECLARE_CMP_FUNC ( b , a , L )
enum { ID_XYZ , ID_XZY , ID_ZXY , ID_YXZ , ID_ZYX , ID_YZX } ;
static const char * const sortstr [ ] = { " Lab " , " Lba " , " bLa " , " aLb " , " baL " , " abL " } ;
static const cmp_func cmp_funcs [ ] = {
[ ID_XYZ ] = cmp_Lab ,
[ ID_XZY ] = cmp_Lba ,
[ ID_ZXY ] = cmp_bLa ,
[ ID_YXZ ] = cmp_aLb ,
[ ID_ZYX ] = cmp_baL ,
[ ID_YZX ] = cmp_abL ,
} ;
2015-01-24 22:37:59 +02:00
2022-12-27 20:23:23 +02:00
/*
* Return an identifier for the order of x , y , z ( from higher to lower ) ,
* preferring x over y and y over z in case of equality .
*/
static int sort3id ( int64_t x , int64_t y , int64_t z )
{
if ( x > = y ) {
if ( y > = z ) return ID_XYZ ;
if ( x > = z ) return ID_XZY ;
return ID_ZXY ;
}
if ( x > = z ) return ID_YXZ ;
if ( y > = z ) return ID_YZX ;
return ID_ZYX ;
}
2015-01-24 22:37:59 +02:00
/**
* Simple color comparison for sorting the final palette
*/
static int cmp_color ( const void * a , const void * b )
{
const struct range_box * box1 = a ;
const struct range_box * box2 = b ;
2022-12-27 15:55:18 +02:00
return FFDIFFSIGN ( box1 - > color , box2 - > color ) ;
2015-01-24 22:37:59 +02:00
}
2022-12-27 14:58:07 +02:00
static void compute_box_stats ( PaletteGenContext * s , struct range_box * box )
{
2022-12-27 19:25:18 +02:00
int64_t er2 [ 3 ] = { 0 } ;
2022-12-27 14:58:07 +02:00
2022-12-27 19:25:18 +02:00
/* Compute average color */
2022-12-27 16:33:15 +02:00
int64_t sL = 0 , sa = 0 , sb = 0 ;
2022-12-27 14:58:07 +02:00
box - > weight = 0 ;
for ( int i = box - > start ; i < box - > start + box - > len ; i + + ) {
const struct color_ref * ref = s - > refs [ i ] ;
2022-12-27 16:33:15 +02:00
sL + = ref - > lab . L * ref - > count ;
sa + = ref - > lab . a * ref - > count ;
sb + = ref - > lab . b * ref - > count ;
2022-12-27 14:58:07 +02:00
box - > weight + = ref - > count ;
}
2022-12-27 16:33:15 +02:00
box - > avg . L = sL / box - > weight ;
box - > avg . a = sa / box - > weight ;
box - > avg . b = sb / box - > weight ;
2022-12-27 14:58:07 +02:00
2022-12-27 19:25:18 +02:00
/* Compute squared error of each color channel */
for ( int i = box - > start ; i < box - > start + box - > len ; i + + ) {
const struct color_ref * ref = s - > refs [ i ] ;
2022-12-27 16:33:15 +02:00
const int64_t dL = ref - > lab . L - box - > avg . L ;
const int64_t da = ref - > lab . a - box - > avg . a ;
const int64_t db = ref - > lab . b - box - > avg . b ;
er2 [ 0 ] + = dL * dL * ref - > count ;
er2 [ 1 ] + = da * da * ref - > count ;
2022-12-27 19:25:18 +02:00
er2 [ 2 ] + = db * db * ref - > count ;
}
/* Define the best axis candidate for cutting the box */
2022-12-27 20:23:23 +02:00
box - > major_axis = sort3id ( er2 [ 0 ] , er2 [ 1 ] , er2 [ 2 ] ) ;
2022-12-27 15:41:41 +02:00
2022-12-27 15:48:58 +02:00
/* The box that has the axis with the biggest error amongst all boxes will but cut down */
box - > cut_score = FFMAX3 ( er2 [ 0 ] , er2 [ 1 ] , er2 [ 2 ] ) ;
2022-12-27 14:58:07 +02:00
}
2015-01-24 22:37:59 +02:00
/**
2022-12-27 15:47:20 +02:00
* Find the next box to split : pick the one with the highest cut score
2015-01-24 22:37:59 +02:00
*/
static int get_next_box_id_to_split ( PaletteGenContext * s )
{
2022-12-27 15:41:41 +02:00
int box_id , best_box_id = - 1 ;
2022-12-27 15:47:20 +02:00
int64_t max_score = - 1 ;
2015-01-24 22:37:59 +02:00
if ( s - > nb_boxes = = s - > max_colors - s - > reserve_transparent )
return - 1 ;
for ( box_id = 0 ; box_id < s - > nb_boxes ; box_id + + ) {
2022-12-27 15:55:18 +02:00
const struct range_box * box = & s - > boxes [ box_id ] ;
2022-12-27 15:47:20 +02:00
if ( s - > boxes [ box_id ] . len > = 2 & & box - > cut_score > max_score ) {
2022-12-27 15:41:41 +02:00
best_box_id = box_id ;
2022-12-27 15:47:20 +02:00
max_score = box - > cut_score ;
2015-01-24 22:37:59 +02:00
}
}
return best_box_id ;
}
/**
* Split given box in two at position n . The original box becomes the left part
* of the split , and the new index box is the right part .
*/
static void split_box ( PaletteGenContext * s , struct range_box * box , int n )
{
struct range_box * new_box = & s - > boxes [ s - > nb_boxes + + ] ;
new_box - > start = n + 1 ;
new_box - > len = box - > start + box - > len - new_box - > start ;
new_box - > sorted_by = box - > sorted_by ;
box - > len - = new_box - > len ;
av_assert0 ( box - > len > = 1 ) ;
av_assert0 ( new_box - > len > = 1 ) ;
2022-12-27 15:41:41 +02:00
compute_box_stats ( s , box ) ;
compute_box_stats ( s , new_box ) ;
2015-01-24 22:37:59 +02:00
}
/**
* Write the palette into the output frame .
*/
2015-02-26 11:19:45 +02:00
static void write_palette ( AVFilterContext * ctx , AVFrame * out )
2015-01-24 22:37:59 +02:00
{
2015-02-26 11:19:45 +02:00
const PaletteGenContext * s = ctx - > priv ;
2015-01-24 22:37:59 +02:00
int x , y , box_id = 0 ;
uint32_t * pal = ( uint32_t * ) out - > data [ 0 ] ;
const int pal_linesize = out - > linesize [ 0 ] > > 2 ;
uint32_t last_color = 0 ;
for ( y = 0 ; y < out - > height ; y + + ) {
for ( x = 0 ; x < out - > width ; x + + ) {
if ( box_id < s - > nb_boxes ) {
pal [ x ] = s - > boxes [ box_id + + ] . color ;
if ( ( x | | y ) & & pal [ x ] = = last_color )
2021-09-26 19:23:01 +02:00
av_log ( ctx , AV_LOG_WARNING , " Duped color: %08 " PRIX32 " \n " , pal [ x ] ) ;
2015-01-24 22:37:59 +02:00
last_color = pal [ x ] ;
} else {
2019-01-16 14:07:48 +02:00
pal [ x ] = last_color ; // pad with last color
2015-01-24 22:37:59 +02:00
}
}
pal + = pal_linesize ;
}
2022-10-30 19:38:23 +02:00
if ( s - > reserve_transparent ) {
2015-01-24 22:37:59 +02:00
av_assert0 ( s - > nb_boxes < 256 ) ;
2017-10-17 23:39:59 +02:00
pal [ out - > width - pal_linesize - 1 ] = AV_RB32 ( & s - > transparency_color ) > > 8 ;
2015-01-24 22:37:59 +02:00
}
}
/**
* Crawl the histogram to get all the defined colors , and create a linear list
* of them ( each color reference entry is a pointer to the value in the
* histogram / hash table ) .
*/
static struct color_ref * * load_color_refs ( const struct hist_node * hist , int nb_refs )
{
int i , j , k = 0 ;
struct color_ref * * refs = av_malloc_array ( nb_refs , sizeof ( * refs ) ) ;
if ( ! refs )
return NULL ;
for ( j = 0 ; j < HIST_SIZE ; j + + ) {
const struct hist_node * node = & hist [ j ] ;
for ( i = 0 ; i < node - > nb_entries ; i + + )
refs [ k + + ] = & node - > entries [ i ] ;
}
return refs ;
}
2015-02-25 16:34:13 +02:00
static double set_colorquant_ratio_meta ( AVFrame * out , int nb_out , int nb_in )
{
char buf [ 32 ] ;
const double ratio = ( double ) nb_out / nb_in ;
snprintf ( buf , sizeof ( buf ) , " %f " , ratio ) ;
av_dict_set ( & out - > metadata , " lavfi.color_quant_ratio " , buf , 0 ) ;
return ratio ;
}
2015-01-24 22:37:59 +02:00
/**
* Main function implementing the Median Cut Algorithm defined by Paul Heckbert
* in Color Image Quantization for Frame Buffer Display ( 1982 )
*/
static AVFrame * get_palette_frame ( AVFilterContext * ctx )
{
AVFrame * out ;
PaletteGenContext * s = ctx - > priv ;
AVFilterLink * outlink = ctx - > outputs [ 0 ] ;
2015-02-25 16:34:13 +02:00
double ratio ;
2015-01-24 22:37:59 +02:00
int box_id = 0 ;
struct range_box * box ;
/* reference only the used colors from histogram */
s - > refs = load_color_refs ( s - > histogram , s - > nb_refs ) ;
if ( ! s - > refs ) {
av_log ( ctx , AV_LOG_ERROR , " Unable to allocate references for %d different colors \n " , s - > nb_refs ) ;
return NULL ;
}
/* create the palette frame */
out = ff_get_video_buffer ( outlink , outlink - > w , outlink - > h ) ;
if ( ! out )
return NULL ;
out - > pts = 0 ;
/* set first box for 0..nb_refs */
box = & s - > boxes [ box_id ] ;
box - > len = s - > nb_refs ;
box - > sorted_by = - 1 ;
2022-12-27 15:41:41 +02:00
compute_box_stats ( s , box ) ;
2015-01-24 22:37:59 +02:00
s - > nb_boxes = 1 ;
while ( box & & box - > len > 1 ) {
2022-12-27 19:27:14 +02:00
int i ;
2022-12-27 16:05:01 +02:00
int64_t median , weight ;
2022-10-30 19:38:23 +02:00
2022-12-27 20:23:23 +02:00
ff_dlog ( ctx , " box #%02X [%6d..%-6d] (%6d) w:%-6 " PRIu64 " sort by %s (already sorted:%c) " ,
2022-12-27 15:56:58 +02:00
box_id , box - > start , box - > start + box - > len - 1 , box - > len , box - > weight ,
2022-12-27 20:23:23 +02:00
sortstr [ box - > major_axis ] , box - > sorted_by = = box - > major_axis ? ' y ' : ' n ' ) ;
2015-01-24 22:37:59 +02:00
2022-12-27 19:27:14 +02:00
/* sort the range by its major axis if it's not already sorted */
if ( box - > sorted_by ! = box - > major_axis ) {
cmp_func cmpf = cmp_funcs [ box - > major_axis ] ;
2015-02-25 17:06:34 +02:00
AV_QSORT ( & s - > refs [ box - > start ] , box - > len , const struct color_ref * , cmpf ) ;
2022-12-27 19:27:14 +02:00
box - > sorted_by = box - > major_axis ;
2015-01-24 22:37:59 +02:00
}
/* locate the median where to split */
2022-12-27 15:56:58 +02:00
median = ( box - > weight + 1 ) > > 1 ;
weight = 0 ;
2015-01-24 22:37:59 +02:00
/* if you have 2 boxes, the maximum is actually #0: you must have at
* least 1 color on each side of the split , hence the - 2 */
for ( i = box - > start ; i < box - > start + box - > len - 2 ; i + + ) {
2022-12-27 15:56:58 +02:00
weight + = s - > refs [ i ] - > count ;
if ( weight > median )
2015-01-24 22:37:59 +02:00
break ;
}
2022-12-27 15:56:58 +02:00
ff_dlog ( ctx , " split @ i=%-6d with w=%-6 " PRIu64 " (target=%6 " PRIu64 " ) \n " , i , weight , median ) ;
2015-01-24 22:37:59 +02:00
split_box ( s , box , i ) ;
box_id = get_next_box_id_to_split ( s ) ;
box = box_id > = 0 ? & s - > boxes [ box_id ] : NULL ;
}
2015-02-25 16:34:13 +02:00
ratio = set_colorquant_ratio_meta ( out , s - > nb_boxes , s - > nb_refs ) ;
av_log ( ctx , AV_LOG_INFO , " %d%s colors generated out of %d colors; ratio=%f \n " ,
s - > nb_boxes , s - > reserve_transparent ? " (+1) " : " " , s - > nb_refs , ratio ) ;
2015-01-24 22:37:59 +02:00
2022-12-27 16:33:15 +02:00
for ( int i = 0 ; i < s - > nb_boxes ; i + + )
s - > boxes [ i ] . color = 0xffU < < 24 | ff_oklab_int_to_srgb_u8 ( s - > boxes [ i ] . avg ) ;
2015-01-24 22:37:59 +02:00
qsort ( s - > boxes , s - > nb_boxes , sizeof ( * s - > boxes ) , cmp_color ) ;
2015-02-26 11:19:45 +02:00
write_palette ( ctx , out ) ;
2015-01-24 22:37:59 +02:00
return out ;
}
/**
* Hashing function for the color .
* It keeps the NBITS least significant bit of each component to make it
* " random " even if the scene doesn ' t have much different colors .
*/
2022-10-30 19:38:23 +02:00
static inline unsigned color_hash ( uint32_t color )
2015-01-24 22:37:59 +02:00
{
const uint8_t r = color > > 16 & ( ( 1 < < NBITS ) - 1 ) ;
const uint8_t g = color > > 8 & ( ( 1 < < NBITS ) - 1 ) ;
const uint8_t b = color & ( ( 1 < < NBITS ) - 1 ) ;
2021-10-13 18:33:05 +02:00
2021-09-26 19:23:01 +02:00
return r < < ( NBITS * 2 ) | g < < NBITS | b ;
2015-01-24 22:37:59 +02:00
}
/**
* Locate the color in the hash table and increment its counter .
*/
2022-10-30 19:38:23 +02:00
static int color_inc ( struct hist_node * hist , uint32_t color )
2015-01-24 22:37:59 +02:00
{
int i ;
2022-10-30 19:38:23 +02:00
const unsigned hash = color_hash ( color ) ;
2015-01-24 22:37:59 +02:00
struct hist_node * node = & hist [ hash ] ;
struct color_ref * e ;
for ( i = 0 ; i < node - > nb_entries ; i + + ) {
e = & node - > entries [ i ] ;
if ( e - > color = = color ) {
e - > count + + ;
return 0 ;
}
}
e = av_dynarray2_add ( ( void * * ) & node - > entries , & node - > nb_entries ,
sizeof ( * node - > entries ) , NULL ) ;
if ( ! e )
return AVERROR ( ENOMEM ) ;
e - > color = color ;
2022-12-27 16:33:15 +02:00
e - > lab = ff_srgb_u8_to_oklab_int ( color ) ;
2015-01-24 22:37:59 +02:00
e - > count = 1 ;
return 1 ;
}
/**
* Update histogram when pixels differ from previous frame .
*/
static int update_histogram_diff ( struct hist_node * hist ,
2022-10-30 19:38:23 +02:00
const AVFrame * f1 , const AVFrame * f2 )
2015-01-24 22:37:59 +02:00
{
int x , y , ret , nb_diff_colors = 0 ;
for ( y = 0 ; y < f1 - > height ; y + + ) {
const uint32_t * p = ( const uint32_t * ) ( f1 - > data [ 0 ] + y * f1 - > linesize [ 0 ] ) ;
const uint32_t * q = ( const uint32_t * ) ( f2 - > data [ 0 ] + y * f2 - > linesize [ 0 ] ) ;
2015-02-26 11:25:53 +02:00
for ( x = 0 ; x < f1 - > width ; x + + ) {
2015-01-24 22:37:59 +02:00
if ( p [ x ] = = q [ x ] )
continue ;
2022-10-30 19:38:23 +02:00
ret = color_inc ( hist , p [ x ] ) ;
2015-01-24 22:37:59 +02:00
if ( ret < 0 )
return ret ;
nb_diff_colors + = ret ;
}
}
return nb_diff_colors ;
}
/**
* Simple histogram of the frame .
*/
2022-10-30 19:38:23 +02:00
static int update_histogram_frame ( struct hist_node * hist , const AVFrame * f )
2015-01-24 22:37:59 +02:00
{
int x , y , ret , nb_diff_colors = 0 ;
for ( y = 0 ; y < f - > height ; y + + ) {
const uint32_t * p = ( const uint32_t * ) ( f - > data [ 0 ] + y * f - > linesize [ 0 ] ) ;
for ( x = 0 ; x < f - > width ; x + + ) {
2022-10-30 19:38:23 +02:00
ret = color_inc ( hist , p [ x ] ) ;
2015-01-24 22:37:59 +02:00
if ( ret < 0 )
return ret ;
nb_diff_colors + = ret ;
}
}
return nb_diff_colors ;
}
/**
* Update the histogram for each passing frame . No frame will be pushed here .
*/
static int filter_frame ( AVFilterLink * inlink , AVFrame * in )
{
AVFilterContext * ctx = inlink - > dst ;
PaletteGenContext * s = ctx - > priv ;
2022-12-27 22:24:18 +02:00
int ret ;
if ( in - > color_trc ! = AVCOL_TRC_UNSPECIFIED & & in - > color_trc ! = AVCOL_TRC_IEC61966_2_1 )
av_log ( ctx , AV_LOG_WARNING , " The input frame is not in sRGB, colors may be off \n " ) ;
2015-01-24 22:37:59 +02:00
2022-12-27 22:24:18 +02:00
ret = s - > prev_frame ? update_histogram_diff ( s - > histogram , s - > prev_frame , in )
: update_histogram_frame ( s - > histogram , in ) ;
2015-01-24 22:37:59 +02:00
if ( ret > 0 )
s - > nb_refs + = ret ;
if ( s - > stats_mode = = STATS_MODE_DIFF_FRAMES ) {
av_frame_free ( & s - > prev_frame ) ;
s - > prev_frame = in ;
2021-05-15 07:54:35 +02:00
} else if ( s - > stats_mode = = STATS_MODE_SINGLE_FRAMES & & s - > nb_refs > 0 ) {
2016-09-02 22:15:10 +02:00
AVFrame * out ;
int i ;
out = get_palette_frame ( ctx ) ;
out - > pts = in - > pts ;
av_frame_free ( & in ) ;
ret = ff_filter_frame ( ctx - > outputs [ 0 ] , out ) ;
for ( i = 0 ; i < HIST_SIZE ; i + + )
av_freep ( & s - > histogram [ i ] . entries ) ;
av_freep ( & s - > refs ) ;
s - > nb_refs = 0 ;
s - > nb_boxes = 0 ;
memset ( s - > boxes , 0 , sizeof ( s - > boxes ) ) ;
memset ( s - > histogram , 0 , sizeof ( s - > histogram ) ) ;
2015-01-24 22:37:59 +02:00
} else {
av_frame_free ( & in ) ;
}
return ret ;
}
/**
* Returns only one frame at the end containing the full palette .
*/
static int request_frame ( AVFilterLink * outlink )
{
AVFilterContext * ctx = outlink - > src ;
AVFilterLink * inlink = ctx - > inputs [ 0 ] ;
PaletteGenContext * s = ctx - > priv ;
int r ;
r = ff_request_frame ( inlink ) ;
2016-09-02 22:15:10 +02:00
if ( r = = AVERROR_EOF & & ! s - > palette_pushed & & s - > nb_refs & & s - > stats_mode ! = STATS_MODE_SINGLE_FRAMES ) {
2015-01-24 22:37:59 +02:00
r = ff_filter_frame ( outlink , get_palette_frame ( ctx ) ) ;
s - > palette_pushed = 1 ;
return r ;
}
return r ;
}
/**
* The output is one simple 16 x16 squared - pixels palette .
*/
static int config_output ( AVFilterLink * outlink )
{
outlink - > w = outlink - > h = 16 ;
outlink - > sample_aspect_ratio = av_make_q ( 1 , 1 ) ;
return 0 ;
}
2021-10-13 18:33:05 +02:00
static int init ( AVFilterContext * ctx )
{
PaletteGenContext * s = ctx - > priv ;
2022-12-27 23:06:23 +02:00
if ( s - > max_colors - s - > reserve_transparent < 2 ) {
av_log ( ctx , AV_LOG_ERROR , " max_colors=2 is only allowed without reserving a transparent color slot \n " ) ;
return AVERROR ( EINVAL ) ;
}
2021-10-13 18:33:05 +02:00
return 0 ;
}
2015-01-24 22:37:59 +02:00
static av_cold void uninit ( AVFilterContext * ctx )
{
int i ;
PaletteGenContext * s = ctx - > priv ;
for ( i = 0 ; i < HIST_SIZE ; i + + )
av_freep ( & s - > histogram [ i ] . entries ) ;
av_freep ( & s - > refs ) ;
2015-02-27 15:18:53 +02:00
av_frame_free ( & s - > prev_frame ) ;
2015-01-24 22:37:59 +02:00
}
static const AVFilterPad palettegen_inputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
. filter_frame = filter_frame ,
} ,
} ;
static const AVFilterPad palettegen_outputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
. config_props = config_output ,
. request_frame = request_frame ,
} ,
} ;
2021-04-19 18:33:56 +02:00
const AVFilter ff_vf_palettegen = {
2015-01-24 22:37:59 +02:00
. name = " palettegen " ,
. description = NULL_IF_CONFIG_SMALL ( " Find the optimal palette for a given stream. " ) ,
. priv_size = sizeof ( PaletteGenContext ) ,
2021-10-13 18:33:05 +02:00
. init = init ,
2015-01-24 22:37:59 +02:00
. uninit = uninit ,
2021-08-12 13:05:31 +02:00
FILTER_INPUTS ( palettegen_inputs ) ,
FILTER_OUTPUTS ( palettegen_outputs ) ,
2021-09-27 12:07:35 +02:00
FILTER_QUERY_FUNC ( query_formats ) ,
2015-01-24 22:37:59 +02:00
. priv_class = & palettegen_class ,
} ;