@ -31,6 +31,7 @@
# include "libavutil/avstring.h"
# include "avfilter.h"
# include "drawutils.h"
# include "dualinput.h"
# include "formats.h"
# include "internal.h"
# include "video.h"
@ -51,7 +52,9 @@ struct rgbvec {
float r , g , b ;
} ;
# define MAX_LEVEL 36
/* 3D LUT don't often go up to level 32, but it is common to have a Hald CLUT
* of 512 x512 ( 64 x64x64 ) */
# define MAX_LEVEL 64
typedef struct LUT3DContext {
const AVClass * class ;
@ -64,20 +67,23 @@ typedef struct LUT3DContext {
struct rgbvec ( * interp_16 ) ( const struct LUT3DContext * , uint16_t , uint16_t , uint16_t ) ;
struct rgbvec lut [ MAX_LEVEL ] [ MAX_LEVEL ] [ MAX_LEVEL ] ;
int lutsize ;
# if CONFIG_HALDCLUT_FILTER
uint8_t clut_rgba_map [ 4 ] ;
int clut_step ;
int clut_is16bit ;
int clut_width ;
FFDualInputContext dinput ;
# endif
} LUT3DContext ;
# define OFFSET(x) offsetof(LUT3DContext, x)
# define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static const AVOption lut3d_options [ ] = {
{ " file " , " set 3D LUT file name " , OFFSET ( file ) , AV_OPT_TYPE_STRING , { . str = NULL } , . flags = FLAGS } ,
{ " interp " , " select interpolation mode " , OFFSET ( interpolation ) , AV_OPT_TYPE_INT , { . i64 = INTERPOLATE_TETRAHEDRAL } , 0 , NB_INTERP_MODE - 1 , FLAGS , " interp_mode " } ,
{ " nearest " , " use values from the nearest defined points " , 0 , AV_OPT_TYPE_CONST , { . i64 = INTERPOLATE_NEAREST } , INT_MIN , INT_MAX , FLAGS , " interp_mode " } ,
{ " trilinear " , " interpolate values using the 8 points defining a cube " , 0 , AV_OPT_TYPE_CONST , { . i64 = INTERPOLATE_TRILINEAR } , INT_MIN , INT_MAX , FLAGS , " interp_mode " } ,
{ " tetrahedral " , " interpolate values using a tetrahedron " , 0 , AV_OPT_TYPE_CONST , { . i64 = INTERPOLATE_TETRAHEDRAL } , INT_MIN , INT_MAX , FLAGS , " interp_mode " } ,
# define COMMON_OPTIONS \
{ " interp " , " select interpolation mode " , OFFSET ( interpolation ) , AV_OPT_TYPE_INT , { . i64 = INTERPOLATE_TETRAHEDRAL } , 0 , NB_INTERP_MODE - 1 , FLAGS , " interp_mode " } , \
{ " nearest " , " use values from the nearest defined points " , 0 , AV_OPT_TYPE_CONST , { . i64 = INTERPOLATE_NEAREST } , INT_MIN , INT_MAX , FLAGS , " interp_mode " } , \
{ " trilinear " , " interpolate values using the 8 points defining a cube " , 0 , AV_OPT_TYPE_CONST , { . i64 = INTERPOLATE_TRILINEAR } , INT_MIN , INT_MAX , FLAGS , " interp_mode " } , \
{ " tetrahedral " , " interpolate values using a tetrahedron " , 0 , AV_OPT_TYPE_CONST , { . i64 = INTERPOLATE_TETRAHEDRAL } , INT_MIN , INT_MAX , FLAGS , " interp_mode " } , \
{ NULL }
} ;
AVFILTER_DEFINE_CLASS ( lut3d ) ;
static inline float lerpf ( float v0 , float v1 , float f )
{
@ -404,7 +410,9 @@ static void set_identity_matrix(LUT3DContext *lut3d, int size)
}
}
static av_cold int init ( AVFilterContext * ctx )
# if CONFIG_LUT3D_FILTER
/* TODO: move to the CONFIG_LUT3D_FILTER definition scope at the bottom */
static av_cold int lut3d_init ( AVFilterContext * ctx )
{
int ret ;
FILE * f ;
@ -454,6 +462,7 @@ end:
fclose ( f ) ;
return ret ;
}
# endif
static int query_formats ( AVFilterContext * ctx )
{
@ -523,7 +532,7 @@ static int config_input(AVFilterLink *inlink)
} \
} while ( 0 )
static int filter_frame ( AVFilterLink * inlink , AVFrame * in )
static AVFrame * apply_lut ( AVFilterLink * inlink , AVFrame * in )
{
int x , y , direct = 0 ;
AVFilterContext * ctx = inlink - > dst ;
@ -543,7 +552,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
out = ff_get_video_buffer ( outlink , outlink - > w , outlink - > h ) ;
if ( ! out ) {
av_frame_free ( & in ) ;
return AVERROR ( ENOMEM ) ;
return NULL ;
}
av_frame_copy_props ( out , in ) ;
}
@ -554,9 +563,26 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
if ( ! direct )
av_frame_free ( & in ) ;
return out ;
}
static int filter_frame ( AVFilterLink * inlink , AVFrame * in )
{
AVFilterLink * outlink = inlink - > dst - > outputs [ 0 ] ;
AVFrame * out = apply_lut ( inlink , in ) ;
if ( ! out )
return AVERROR ( ENOMEM ) ;
return ff_filter_frame ( outlink , out ) ;
}
# if CONFIG_LUT3D_FILTER
static const AVOption lut3d_options [ ] = {
{ " file " , " set 3D LUT file name " , OFFSET ( file ) , AV_OPT_TYPE_STRING , { . str = NULL } , . flags = FLAGS } ,
COMMON_OPTIONS
} ;
AVFILTER_DEFINE_CLASS ( lut3d ) ;
static const AVFilterPad lut3d_inputs [ ] = {
{
. name = " default " ,
@ -579,10 +605,192 @@ AVFilter avfilter_vf_lut3d = {
. name = " lut3d " ,
. description = NULL_IF_CONFIG_SMALL ( " Adjust colors using a 3D LUT. " ) ,
. priv_size = sizeof ( LUT3DContext ) ,
. init = init ,
. init = lut3d_ init,
. query_formats = query_formats ,
. inputs = lut3d_inputs ,
. outputs = lut3d_outputs ,
. priv_class = & lut3d_class ,
. flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC ,
} ;
# endif
# if CONFIG_HALDCLUT_FILTER
static void update_clut ( LUT3DContext * lut3d , const AVFrame * frame )
{
const uint8_t * data = frame - > data [ 0 ] ;
const int linesize = frame - > linesize [ 0 ] ;
const int w = lut3d - > clut_width ;
const int step = lut3d - > clut_step ;
const uint8_t * rgba_map = lut3d - > clut_rgba_map ;
const int level = lut3d - > lutsize ;
# define LOAD_CLUT(nbits) do { \
int i , j , k , x = 0 , y = 0 ; \
\
for ( k = 0 ; k < level ; k + + ) { \
for ( j = 0 ; j < level ; j + + ) { \
for ( i = 0 ; i < level ; i + + ) { \
const uint # # nbits # # _t * src = ( const uint # # nbits # # _t * ) \
( data + y * linesize + x * step ) ; \
struct rgbvec * vec = & lut3d - > lut [ k ] [ j ] [ i ] ; \
vec - > r = src [ rgba_map [ 0 ] ] / ( float ) ( ( 1 < < ( nbits ) ) - 1 ) ; \
vec - > g = src [ rgba_map [ 1 ] ] / ( float ) ( ( 1 < < ( nbits ) ) - 1 ) ; \
vec - > b = src [ rgba_map [ 2 ] ] / ( float ) ( ( 1 < < ( nbits ) ) - 1 ) ; \
if ( + + x = = w ) { \
x = 0 ; \
y + + ; \
} \
} \
} \
} \
} while ( 0 )
if ( ! lut3d - > clut_is16bit ) LOAD_CLUT ( 8 ) ;
else LOAD_CLUT ( 16 ) ;
}
static int config_output ( AVFilterLink * outlink )
{
AVFilterContext * ctx = outlink - > src ;
outlink - > w = ctx - > inputs [ 0 ] - > w ;
outlink - > h = ctx - > inputs [ 0 ] - > h ;
outlink - > time_base = ctx - > inputs [ 0 ] - > time_base ;
return 0 ;
}
static int filter_frame_main ( AVFilterLink * inlink , AVFrame * inpicref )
{
LUT3DContext * s = inlink - > dst - > priv ;
return ff_dualinput_filter_frame_main ( & s - > dinput , inlink , inpicref ) ;
}
static int filter_frame_clut ( AVFilterLink * inlink , AVFrame * inpicref )
{
LUT3DContext * s = inlink - > dst - > priv ;
return ff_dualinput_filter_frame_second ( & s - > dinput , inlink , inpicref ) ;
}
static int request_frame ( AVFilterLink * outlink )
{
LUT3DContext * s = outlink - > src - > priv ;
return ff_dualinput_request_frame ( & s - > dinput , outlink ) ;
}
static int config_clut ( AVFilterLink * inlink )
{
int size , level , w , h ;
AVFilterContext * ctx = inlink - > dst ;
LUT3DContext * lut3d = ctx - > priv ;
const AVPixFmtDescriptor * desc = av_pix_fmt_desc_get ( inlink - > format ) ;
lut3d - > clut_is16bit = 0 ;
switch ( inlink - > format ) {
case AV_PIX_FMT_RGB48 :
case AV_PIX_FMT_BGR48 :
case AV_PIX_FMT_RGBA64 :
case AV_PIX_FMT_BGRA64 :
lut3d - > clut_is16bit = 1 ;
}
lut3d - > clut_step = av_get_padded_bits_per_pixel ( desc ) > > 3 ;
ff_fill_rgba_map ( lut3d - > clut_rgba_map , inlink - > format ) ;
if ( inlink - > w > inlink - > h )
av_log ( ctx , AV_LOG_INFO , " Padding on the right (%dpx) of the "
" Hald CLUT will be ignored \n " , inlink - > w - inlink - > h ) ;
else if ( inlink - > w < inlink - > h )
av_log ( ctx , AV_LOG_INFO , " Padding at the bottom (%dpx) of the "
" Hald CLUT will be ignored \n " , inlink - > h - inlink - > w ) ;
lut3d - > clut_width = w = h = FFMIN ( inlink - > w , inlink - > h ) ;
for ( level = 1 ; level * level * level < w ; level + + ) ;
size = level * level * level ;
if ( size ! = w ) {
av_log ( ctx , AV_LOG_WARNING , " The Hald CLUT width does not match the level \n " ) ;
return AVERROR_INVALIDDATA ;
}
av_assert0 ( w = = h & & w = = size ) ;
level * = level ;
if ( level > MAX_LEVEL ) {
const int max_clut_level = sqrt ( MAX_LEVEL ) ;
const int max_clut_size = max_clut_level * max_clut_level * max_clut_level ;
av_log ( ctx , AV_LOG_ERROR , " Too large Hald CLUT "
" (maximum level is %d, or %dx%d CLUT) \n " ,
max_clut_level , max_clut_size , max_clut_size ) ;
return AVERROR ( EINVAL ) ;
}
lut3d - > lutsize = level ;
return 0 ;
}
static AVFrame * update_apply_clut ( AVFilterContext * ctx , AVFrame * main ,
const AVFrame * second )
{
AVFilterLink * inlink = ctx - > inputs [ 0 ] ;
update_clut ( ctx - > priv , second ) ;
return apply_lut ( inlink , main ) ;
}
static av_cold int haldclut_init ( AVFilterContext * ctx )
{
LUT3DContext * lut3d = ctx - > priv ;
lut3d - > dinput . process = update_apply_clut ;
return 0 ;
}
static av_cold void haldclut_uninit ( AVFilterContext * ctx )
{
LUT3DContext * lut3d = ctx - > priv ;
ff_dualinput_uninit ( & lut3d - > dinput ) ;
}
static const AVOption haldclut_options [ ] = {
{ " shortest " , " force termination when the shortest input terminates " , OFFSET ( dinput . shortest ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , 1 , FLAGS } ,
{ " repeatlast " , " continue applying the last clut after eos " , OFFSET ( dinput . repeatlast ) , AV_OPT_TYPE_INT , { . i64 = 1 } , 0 , 1 , FLAGS } ,
COMMON_OPTIONS
} ;
AVFILTER_DEFINE_CLASS ( haldclut ) ;
static const AVFilterPad haldclut_inputs [ ] = {
{
. name = " main " ,
. type = AVMEDIA_TYPE_VIDEO ,
. filter_frame = filter_frame_main ,
. config_props = config_input ,
} , {
. name = " clut " ,
. type = AVMEDIA_TYPE_VIDEO ,
. filter_frame = filter_frame_clut ,
. config_props = config_clut ,
} ,
{ NULL }
} ;
static const AVFilterPad haldclut_outputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
. request_frame = request_frame ,
. config_props = config_output ,
} ,
{ NULL }
} ;
AVFilter avfilter_vf_haldclut = {
. name = " haldclut " ,
. description = NULL_IF_CONFIG_SMALL ( " Adjust colors using a Hald CLUT. " ) ,
. priv_size = sizeof ( LUT3DContext ) ,
. init = haldclut_init ,
. uninit = haldclut_uninit ,
. query_formats = query_formats ,
. inputs = haldclut_inputs ,
. outputs = haldclut_outputs ,
. priv_class = & haldclut_class ,
. flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL ,
} ;
# endif