2011-07-09 18:13:10 +02:00
/*
* Copyright ( c ) 2002 Michael Niedermayer < michaelni @ gmx . at >
* Copyright ( c ) 2011 Stefano Sabatini
*
* This file is part of FFmpeg .
*
* FFmpeg is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 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 General Public License for more details .
*
* You should have received a copy of the GNU 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
* Apply a boxblur filter to the input video .
* Ported from MPlayer libmpcodecs / vf_boxblur . c .
*/
# include "libavutil/avstring.h"
2012-08-06 15:49:32 +02:00
# include "libavutil/common.h"
2011-07-09 18:13:10 +02:00
# include "libavutil/eval.h"
2013-02-20 15:45:02 +01:00
# include "libavutil/opt.h"
2011-07-09 18:13:10 +02:00
# include "libavutil/pixdesc.h"
# include "avfilter.h"
2012-05-30 10:12:55 +02:00
# include "formats.h"
2012-06-12 20:12:42 +02:00
# include "internal.h"
2012-05-30 11:20:32 +02:00
# include "video.h"
2011-07-09 18:13:10 +02:00
2012-02-20 09:42:33 +01:00
static const char * const var_names [ ] = {
2011-07-09 18:13:10 +02:00
" w " ,
" h " ,
" cw " ,
" ch " ,
" hsub " ,
" vsub " ,
NULL
} ;
enum var_name {
VAR_W ,
VAR_H ,
VAR_CW ,
VAR_CH ,
VAR_HSUB ,
VAR_VSUB ,
VARS_NB
} ;
2014-04-11 11:54:15 +02:00
typedef struct FilterParam {
2011-07-09 18:13:10 +02:00
int radius ;
int power ;
2013-02-20 15:45:02 +01:00
char * radius_expr ;
2011-07-09 18:13:10 +02:00
} FilterParam ;
2014-04-11 11:54:15 +02:00
typedef struct BoxBlurContext {
2013-02-20 15:45:02 +01:00
const AVClass * class ;
2011-07-09 18:13:10 +02:00
FilterParam luma_param ;
FilterParam chroma_param ;
FilterParam alpha_param ;
int hsub , vsub ;
int radius [ 4 ] ;
int power [ 4 ] ;
uint8_t * temp [ 2 ] ; ///< temporary buffer used in blur_power()
} BoxBlurContext ;
# define Y 0
# define U 1
# define V 2
# define A 3
2013-03-13 08:26:39 +01:00
static av_cold int init ( AVFilterContext * ctx )
2011-07-09 18:13:10 +02:00
{
2013-03-18 20:44:36 +01:00
BoxBlurContext * s = ctx - > priv ;
2011-07-09 18:13:10 +02:00
2013-05-16 10:08:17 +02:00
if ( ! s - > luma_param . radius_expr ) {
2013-02-25 21:21:29 +01:00
av_log ( ctx , AV_LOG_ERROR , " Luma radius expression is not set. \n " ) ;
2011-07-09 18:13:10 +02:00
return AVERROR ( EINVAL ) ;
}
2013-02-20 15:45:02 +01:00
/* fill missing params */
2013-05-16 10:08:17 +02:00
if ( ! s - > chroma_param . radius_expr ) {
s - > chroma_param . radius_expr = av_strdup ( s - > luma_param . radius_expr ) ;
if ( ! s - > chroma_param . radius_expr )
2013-02-20 15:45:02 +01:00
return AVERROR ( ENOMEM ) ;
2011-07-09 18:13:10 +02:00
}
2013-05-16 10:08:17 +02:00
if ( s - > chroma_param . power < 0 )
s - > chroma_param . power = s - > luma_param . power ;
2013-02-20 15:45:02 +01:00
2013-05-16 10:08:17 +02:00
if ( ! s - > alpha_param . radius_expr ) {
s - > alpha_param . radius_expr = av_strdup ( s - > luma_param . radius_expr ) ;
if ( ! s - > alpha_param . radius_expr )
2013-02-20 15:45:02 +01:00
return AVERROR ( ENOMEM ) ;
2011-07-09 18:13:10 +02:00
}
2013-05-16 10:08:17 +02:00
if ( s - > alpha_param . power < 0 )
s - > alpha_param . power = s - > luma_param . power ;
2011-07-09 18:13:10 +02:00
return 0 ;
}
static av_cold void uninit ( AVFilterContext * ctx )
{
2013-03-18 20:44:36 +01:00
BoxBlurContext * s = ctx - > priv ;
2011-07-09 18:13:10 +02:00
2013-03-18 20:44:36 +01:00
av_freep ( & s - > temp [ 0 ] ) ;
av_freep ( & s - > temp [ 1 ] ) ;
2011-07-09 18:13:10 +02:00
}
static int query_formats ( AVFilterContext * ctx )
{
2014-12-21 17:38:17 +01:00
AVFilterFormats * formats = NULL ;
2015-10-05 05:39:25 +02:00
int fmt , ret ;
2014-12-21 17:38:17 +01:00
for ( fmt = 0 ; av_pix_fmt_desc_get ( fmt ) ; fmt + + ) {
const AVPixFmtDescriptor * desc = av_pix_fmt_desc_get ( fmt ) ;
if ( ! ( desc - > flags & ( AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_BITSTREAM | AV_PIX_FMT_FLAG_PAL ) ) & &
( desc - > flags & AV_PIX_FMT_FLAG_PLANAR | | desc - > nb_components = = 1 ) & &
2015-10-05 05:39:25 +02:00
( ! ( desc - > flags & AV_PIX_FMT_FLAG_BE ) = = ! HAVE_BIGENDIAN | | desc - > comp [ 0 ] . depth = = 8 ) & &
( ret = ff_add_format ( & formats , fmt ) ) < 0 )
return ret ;
2014-12-21 17:38:17 +01:00
}
2015-04-03 19:55:18 +02:00
return ff_set_common_formats ( ctx , formats ) ;
2011-07-09 18:13:10 +02:00
}
static int config_input ( AVFilterLink * inlink )
{
2012-10-06 13:29:37 +02:00
const AVPixFmtDescriptor * desc = av_pix_fmt_desc_get ( inlink - > format ) ;
2011-07-09 18:13:10 +02:00
AVFilterContext * ctx = inlink - > dst ;
2013-03-18 20:44:36 +01:00
BoxBlurContext * s = ctx - > priv ;
2011-07-09 18:13:10 +02:00
int w = inlink - > w , h = inlink - > h ;
int cw , ch ;
double var_values [ VARS_NB ] , res ;
char * expr ;
int ret ;
2014-12-21 14:59:05 +01:00
if ( ! ( s - > temp [ 0 ] = av_malloc ( 2 * FFMAX ( w , h ) ) ) | |
! ( s - > temp [ 1 ] = av_malloc ( 2 * FFMAX ( w , h ) ) ) )
2011-07-09 18:13:10 +02:00
return AVERROR ( ENOMEM ) ;
2013-03-18 20:44:36 +01:00
s - > hsub = desc - > log2_chroma_w ;
s - > vsub = desc - > log2_chroma_h ;
2011-07-09 18:13:10 +02:00
2012-11-28 19:12:42 +01:00
var_values [ VAR_W ] = inlink - > w ;
var_values [ VAR_H ] = inlink - > h ;
2013-03-18 20:44:36 +01:00
var_values [ VAR_CW ] = cw = w > > s - > hsub ;
var_values [ VAR_CH ] = ch = h > > s - > vsub ;
var_values [ VAR_HSUB ] = 1 < < s - > hsub ;
var_values [ VAR_VSUB ] = 1 < < s - > vsub ;
2011-07-09 18:13:10 +02:00
# define EVAL_RADIUS_EXPR(comp) \
2013-05-16 10:08:17 +02:00
expr = s - > comp # # _param . radius_expr ; \
2011-07-09 18:13:10 +02:00
ret = av_expr_parse_and_eval ( & res , expr , var_names , var_values , \
NULL , NULL , NULL , NULL , NULL , 0 , ctx ) ; \
2013-03-18 20:44:36 +01:00
s - > comp # # _param . radius = res ; \
2011-07-09 18:13:10 +02:00
if ( ret < 0 ) { \
av_log ( NULL , AV_LOG_ERROR , \
" Error when evaluating " # comp " radius expression '%s' \n " , expr ) ; \
return ret ; \
}
EVAL_RADIUS_EXPR ( luma ) ;
EVAL_RADIUS_EXPR ( chroma ) ;
EVAL_RADIUS_EXPR ( alpha ) ;
2012-07-14 18:26:04 +02:00
av_log ( ctx , AV_LOG_VERBOSE ,
2011-07-09 18:13:10 +02:00
" luma_radius:%d luma_power:%d "
" chroma_radius:%d chroma_power:%d "
" alpha_radius:%d alpha_power:%d "
" w:%d chroma_w:%d h:%d chroma_h:%d \n " ,
2013-03-18 20:44:36 +01:00
s - > luma_param . radius , s - > luma_param . power ,
s - > chroma_param . radius , s - > chroma_param . power ,
s - > alpha_param . radius , s - > alpha_param . power ,
2011-07-09 18:13:10 +02:00
w , cw , h , ch ) ;
# define CHECK_RADIUS_VAL(w_, h_, comp) \
2013-03-18 20:44:36 +01:00
if ( s - > comp # # _param . radius < 0 | | \
2 * s - > comp # # _param . radius > FFMIN ( w_ , h_ ) ) { \
2011-07-09 18:13:10 +02:00
av_log ( ctx , AV_LOG_ERROR , \
" Invalid " # comp " radius value %d, must be >= 0 and <= %d \n " , \
2013-03-18 20:44:36 +01:00
s - > comp # # _param . radius , FFMIN ( w_ , h_ ) / 2 ) ; \
2011-07-09 18:13:10 +02:00
return AVERROR ( EINVAL ) ; \
}
CHECK_RADIUS_VAL ( w , h , luma ) ;
CHECK_RADIUS_VAL ( cw , ch , chroma ) ;
CHECK_RADIUS_VAL ( w , h , alpha ) ;
2013-03-18 20:44:36 +01:00
s - > radius [ Y ] = s - > luma_param . radius ;
s - > radius [ U ] = s - > radius [ V ] = s - > chroma_param . radius ;
s - > radius [ A ] = s - > alpha_param . radius ;
2011-07-09 18:13:10 +02:00
2013-03-18 20:44:36 +01:00
s - > power [ Y ] = s - > luma_param . power ;
s - > power [ U ] = s - > power [ V ] = s - > chroma_param . power ;
s - > power [ A ] = s - > alpha_param . power ;
2011-07-09 18:13:10 +02:00
return 0 ;
}
2015-11-01 19:20:58 +01:00
/* Naive boxblur would sum source pixels from x-radius .. x+radius
* for destination pixel x . That would be O ( radius * width ) .
* If you now look at what source pixels represent 2 consecutive
* output pixels , then you see they are almost identical and only
* differ by 2 pixels , like :
* src0 111111111
* dst0 1
* src1 111111111
* dst1 1
* src0 - src1 1 - 1
* so when you know one output pixel you can find the next by just adding
* and subtracting 1 input pixel .
* The following code adopts this faster variant .
*/
# define BLUR(type, depth) \
static inline void blur # # depth ( type * dst , int dst_step , const type * src , \
int src_step , int len , int radius ) \
{ \
const int length = radius * 2 + 1 ; \
const int inv = ( ( 1 < < 16 ) + length / 2 ) / length ; \
int x , sum = src [ radius * src_step ] ; \
\
for ( x = 0 ; x < radius ; x + + ) \
sum + = src [ x * src_step ] < < 1 ; \
\
sum = sum * inv + ( 1 < < 15 ) ; \
\
for ( x = 0 ; x < = radius ; x + + ) { \
sum + = ( src [ ( radius + x ) * src_step ] - src [ ( radius - x ) * src_step ] ) * inv ; \
dst [ x * dst_step ] = sum > > 16 ; \
} \
\
for ( ; x < len - radius ; x + + ) { \
sum + = ( src [ ( radius + x ) * src_step ] - src [ ( x - radius - 1 ) * src_step ] ) * inv ; \
dst [ x * dst_step ] = sum > > 16 ; \
} \
\
for ( ; x < len ; x + + ) { \
sum + = ( src [ ( 2 * len - radius - x - 1 ) * src_step ] - src [ ( x - radius - 1 ) * src_step ] ) * inv ; \
dst [ x * dst_step ] = sum > > 16 ; \
} \
2011-07-09 18:13:10 +02:00
}
2015-11-01 19:20:58 +01:00
BLUR ( uint8_t , 8 )
BLUR ( uint16_t , 16 )
2014-12-21 14:59:05 +01:00
2015-11-01 19:20:58 +01:00
# undef BLUR
2014-12-21 14:59:05 +01:00
static inline void blur ( uint8_t * dst , int dst_step , const uint8_t * src , int src_step ,
int len , int radius , int pixsize )
{
if ( pixsize = = 1 ) blur8 ( dst , dst_step , src , src_step , len , radius ) ;
else blur16 ( ( uint16_t * ) dst , dst_step > > 1 , ( const uint16_t * ) src , src_step > > 1 , len , radius ) ;
}
2011-07-09 18:13:10 +02:00
static inline void blur_power ( uint8_t * dst , int dst_step , const uint8_t * src , int src_step ,
2014-12-21 14:59:05 +01:00
int len , int radius , int power , uint8_t * temp [ 2 ] , int pixsize )
2011-07-09 18:13:10 +02:00
{
uint8_t * a = temp [ 0 ] , * b = temp [ 1 ] ;
if ( radius & & power ) {
2014-12-21 14:59:05 +01:00
blur ( a , pixsize , src , src_step , len , radius , pixsize ) ;
2011-07-09 18:13:10 +02:00
for ( ; power > 2 ; power - - ) {
uint8_t * c ;
2014-12-21 14:59:05 +01:00
blur ( b , pixsize , a , pixsize , len , radius , pixsize ) ;
2011-07-09 18:13:10 +02:00
c = a ; a = b ; b = c ;
}
if ( power > 1 ) {
2014-12-21 14:59:05 +01:00
blur ( dst , dst_step , a , pixsize , len , radius , pixsize ) ;
2011-07-09 18:13:10 +02:00
} else {
int i ;
2014-12-21 14:59:05 +01:00
if ( pixsize = = 1 ) {
for ( i = 0 ; i < len ; i + + )
dst [ i * dst_step ] = a [ i ] ;
} else
for ( i = 0 ; i < len ; i + + )
* ( uint16_t * ) ( dst + i * dst_step ) = ( ( uint16_t * ) a ) [ i ] ;
2011-07-09 18:13:10 +02:00
}
} else {
int i ;
2014-12-21 14:59:05 +01:00
if ( pixsize = = 1 ) {
for ( i = 0 ; i < len ; i + + )
dst [ i * dst_step ] = src [ i * src_step ] ;
} else
for ( i = 0 ; i < len ; i + + )
* ( uint16_t * ) ( dst + i * dst_step ) = * ( uint16_t * ) ( src + i * src_step ) ;
2011-07-09 18:13:10 +02:00
}
}
static void hblur ( uint8_t * dst , int dst_linesize , const uint8_t * src , int src_linesize ,
2014-12-21 14:59:05 +01:00
int w , int h , int radius , int power , uint8_t * temp [ 2 ] , int pixsize )
2011-07-09 18:13:10 +02:00
{
int y ;
if ( radius = = 0 & & dst = = src )
return ;
for ( y = 0 ; y < h ; y + + )
2014-12-21 14:59:05 +01:00
blur_power ( dst + y * dst_linesize , pixsize , src + y * src_linesize , pixsize ,
w , radius , power , temp , pixsize ) ;
2011-07-09 18:13:10 +02:00
}
static void vblur ( uint8_t * dst , int dst_linesize , const uint8_t * src , int src_linesize ,
2014-12-21 14:59:05 +01:00
int w , int h , int radius , int power , uint8_t * temp [ 2 ] , int pixsize )
2011-07-09 18:13:10 +02:00
{
int x ;
if ( radius = = 0 & & dst = = src )
return ;
for ( x = 0 ; x < w ; x + + )
2014-12-21 14:59:05 +01:00
blur_power ( dst + x * pixsize , dst_linesize , src + x * pixsize , src_linesize ,
h , radius , power , temp , pixsize ) ;
2011-07-09 18:13:10 +02:00
}
2012-11-28 08:41:07 +01:00
static int filter_frame ( AVFilterLink * inlink , AVFrame * in )
2011-07-09 18:13:10 +02:00
{
AVFilterContext * ctx = inlink - > dst ;
2013-03-18 20:44:36 +01:00
BoxBlurContext * s = ctx - > priv ;
2011-07-09 18:13:10 +02:00
AVFilterLink * outlink = inlink - > dst - > outputs [ 0 ] ;
2012-11-28 08:41:07 +01:00
AVFrame * out ;
2011-07-09 18:13:10 +02:00
int plane ;
2013-05-16 10:08:17 +02:00
int cw = FF_CEIL_RSHIFT ( inlink - > w , s - > hsub ) , ch = FF_CEIL_RSHIFT ( in - > height , s - > vsub ) ;
2011-07-09 18:13:10 +02:00
int w [ 4 ] = { inlink - > w , cw , cw , inlink - > w } ;
2012-11-28 08:41:07 +01:00
int h [ 4 ] = { in - > height , ch , ch , in - > height } ;
2014-12-21 14:59:05 +01:00
const AVPixFmtDescriptor * desc = av_pix_fmt_desc_get ( inlink - > format ) ;
2015-09-08 17:10:48 +02:00
const int depth = desc - > comp [ 0 ] . depth ;
2014-12-21 14:59:05 +01:00
const int pixsize = ( depth + 7 ) / 8 ;
2011-07-09 18:13:10 +02:00
2012-11-28 08:41:07 +01:00
out = ff_get_video_buffer ( outlink , outlink - > w , outlink - > h ) ;
2012-11-28 17:33:07 +01:00
if ( ! out ) {
2012-11-28 08:41:07 +01:00
av_frame_free ( & in ) ;
2012-11-28 17:33:07 +01:00
return AVERROR ( ENOMEM ) ;
}
2012-11-28 08:41:07 +01:00
av_frame_copy_props ( out , in ) ;
2012-11-28 17:33:07 +01:00
2013-08-03 18:54:43 +02:00
for ( plane = 0 ; plane < 4 & & in - > data [ plane ] & & in - > linesize [ plane ] ; plane + + )
2012-11-28 17:33:07 +01:00
hblur ( out - > data [ plane ] , out - > linesize [ plane ] ,
in - > data [ plane ] , in - > linesize [ plane ] ,
2013-03-18 20:44:36 +01:00
w [ plane ] , h [ plane ] , s - > radius [ plane ] , s - > power [ plane ] ,
2014-12-21 14:59:05 +01:00
s - > temp , pixsize ) ;
2011-07-09 18:13:10 +02:00
2013-08-03 18:54:43 +02:00
for ( plane = 0 ; plane < 4 & & in - > data [ plane ] & & in - > linesize [ plane ] ; plane + + )
2012-11-28 17:33:07 +01:00
vblur ( out - > data [ plane ] , out - > linesize [ plane ] ,
out - > data [ plane ] , out - > linesize [ plane ] ,
2013-03-18 20:44:36 +01:00
w [ plane ] , h [ plane ] , s - > radius [ plane ] , s - > power [ plane ] ,
2014-12-21 14:59:05 +01:00
s - > temp , pixsize ) ;
2011-08-02 23:58:19 +02:00
2012-11-28 08:41:07 +01:00
av_frame_free ( & in ) ;
2012-11-28 17:33:07 +01:00
return ff_filter_frame ( outlink , out ) ;
2011-07-09 18:13:10 +02:00
}
2013-02-25 21:21:29 +01:00
# define OFFSET(x) offsetof(BoxBlurContext, x)
2013-04-10 16:28:23 +02:00
# define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption boxblur_options [ ] = {
{ " luma_radius " , " Radius of the luma blurring box " , OFFSET ( luma_param . radius_expr ) , AV_OPT_TYPE_STRING , { . str = " 2 " } , . flags = FLAGS } ,
{ " lr " , " Radius of the luma blurring box " , OFFSET ( luma_param . radius_expr ) , AV_OPT_TYPE_STRING , { . str = " 2 " } , . flags = FLAGS } ,
{ " luma_power " , " How many times should the boxblur be applied to luma " , OFFSET ( luma_param . power ) , AV_OPT_TYPE_INT , { . i64 = 2 } , 0 , INT_MAX , . flags = FLAGS } ,
{ " lp " , " How many times should the boxblur be applied to luma " , OFFSET ( luma_param . power ) , AV_OPT_TYPE_INT , { . i64 = 2 } , 0 , INT_MAX , . flags = FLAGS } ,
{ " chroma_radius " , " Radius of the chroma blurring box " , OFFSET ( chroma_param . radius_expr ) , AV_OPT_TYPE_STRING , { . str = NULL } , . flags = FLAGS } ,
{ " cr " , " Radius of the chroma blurring box " , OFFSET ( chroma_param . radius_expr ) , AV_OPT_TYPE_STRING , { . str = NULL } , . flags = FLAGS } ,
{ " chroma_power " , " How many times should the boxblur be applied to chroma " , OFFSET ( chroma_param . power ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , INT_MAX , . flags = FLAGS } ,
{ " cp " , " How many times should the boxblur be applied to chroma " , OFFSET ( chroma_param . power ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , INT_MAX , . flags = FLAGS } ,
2013-02-25 21:21:29 +01:00
2013-04-10 16:28:23 +02:00
{ " alpha_radius " , " Radius of the alpha blurring box " , OFFSET ( alpha_param . radius_expr ) , AV_OPT_TYPE_STRING , { . str = NULL } , . flags = FLAGS } ,
{ " ar " , " Radius of the alpha blurring box " , OFFSET ( alpha_param . radius_expr ) , AV_OPT_TYPE_STRING , { . str = NULL } , . flags = FLAGS } ,
{ " alpha_power " , " How many times should the boxblur be applied to alpha " , OFFSET ( alpha_param . power ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , INT_MAX , . flags = FLAGS } ,
{ " ap " , " How many times should the boxblur be applied to alpha " , OFFSET ( alpha_param . power ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , INT_MAX , . flags = FLAGS } ,
{ NULL }
2013-02-25 21:21:29 +01:00
} ;
2013-04-10 16:28:23 +02:00
AVFILTER_DEFINE_CLASS ( boxblur ) ;
2012-07-24 15:14:01 +02:00
static const AVFilterPad avfilter_vf_boxblur_inputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
. config_props = config_input ,
2012-11-28 17:33:07 +01:00
. filter_frame = filter_frame ,
2012-07-24 15:14:01 +02:00
} ,
{ NULL }
} ;
static const AVFilterPad avfilter_vf_boxblur_outputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
} ,
{ NULL }
} ;
2013-10-28 07:44:24 +01:00
AVFilter ff_vf_boxblur = {
2011-07-09 18:13:10 +02:00
. name = " boxblur " ,
. description = NULL_IF_CONFIG_SMALL ( " Blur the input. " ) ,
. priv_size = sizeof ( BoxBlurContext ) ,
2013-02-25 21:21:29 +01:00
. priv_class = & boxblur_class ,
2011-07-09 18:13:10 +02:00
. init = init ,
. uninit = uninit ,
. query_formats = query_formats ,
2013-09-07 14:13:50 +02:00
. inputs = avfilter_vf_boxblur_inputs ,
. outputs = avfilter_vf_boxblur_outputs ,
. flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC ,
2011-10-23 18:57:07 +02:00
} ;