@ -30,10 +30,12 @@
# endif
# include "lzw.h"
# include "tiff.h"
# include "tiff_data.h"
# include "faxcompr.h"
# include "libavutil/common.h"
# include "libavutil/intreadwrite.h"
# include "libavutil/imgutils.h"
# include "libavutil/avstring.h"
typedef struct TiffContext {
AVCodecContext * avctx ;
@ -56,6 +58,9 @@ typedef struct TiffContext {
const uint8_t * stripsizes ;
int stripsize , stripoff ;
LZWState * lzw ;
int geotag_count ;
TiffGeoTag * geotags ;
} TiffContext ;
static unsigned tget_short ( const uint8_t * * p , int le )
@ -72,6 +77,13 @@ static unsigned tget_long(const uint8_t **p, int le)
return v ;
}
static double tget_double ( const uint8_t * * p , int le )
{
av_alias64 i = { . u64 = le ? AV_RL64 ( * p ) : AV_RB64 ( * p ) } ;
* p + = 8 ;
return i . f64 ;
}
static unsigned tget ( const uint8_t * * p , int type , int le )
{
switch ( type ) {
@ -82,6 +94,202 @@ static unsigned tget(const uint8_t **p, int type, int le)
}
}
static void free_geotags ( TiffContext * const s )
{
int i ;
for ( i = 0 ; i < s - > geotag_count ; i + + ) {
if ( s - > geotags [ i ] . val )
av_freep ( & s - > geotags [ i ] . val ) ;
}
av_freep ( & s - > geotags ) ;
}
# define RET_GEOKEY(TYPE, array, element)\
if ( key > = TIFF_ # # TYPE # # _KEY_ID_OFFSET & & \
key - TIFF_ # # TYPE # # _KEY_ID_OFFSET < FF_ARRAY_ELEMS ( ff_tiff_ # # array # # _name_type_map ) ) \
return ff_tiff_ # # array # # _name_type_map [ key - TIFF_ # # TYPE # # _KEY_ID_OFFSET ] . element ;
static const char * get_geokey_name ( int key )
{
RET_GEOKEY ( VERT , vert , name ) ;
RET_GEOKEY ( PROJ , proj , name ) ;
RET_GEOKEY ( GEOG , geog , name ) ;
RET_GEOKEY ( CONF , conf , name ) ;
return NULL ;
}
static int get_geokey_type ( int key )
{
RET_GEOKEY ( VERT , vert , type ) ;
RET_GEOKEY ( PROJ , proj , type ) ;
RET_GEOKEY ( GEOG , geog , type ) ;
RET_GEOKEY ( CONF , conf , type ) ;
return AVERROR_INVALIDDATA ;
}
static int cmp_id_key ( const void * id , const void * k )
{
return * ( const int * ) id - ( ( const TiffGeoTagKeyName * ) k ) - > key ;
}
static const char * search_keyval ( const TiffGeoTagKeyName * keys , int n , int id )
{
return ( ( TiffGeoTagKeyName * ) bsearch ( & id , keys , n , sizeof ( keys [ 0 ] ) , cmp_id_key ) ) - > name ;
}
static char * get_geokey_val ( int key , int val )
{
char * ap ;
if ( val = = TIFF_GEO_KEY_UNDEFINED )
return av_strdup ( " undefined " ) ;
if ( val = = TIFF_GEO_KEY_USER_DEFINED )
return av_strdup ( " User-Defined " ) ;
# define RET_GEOKEY_VAL(TYPE, array)\
if ( val > = TIFF_ # # TYPE # # _OFFSET & & \
val - TIFF_ # # TYPE # # _OFFSET < FF_ARRAY_ELEMS ( ff_tiff_ # # array # # _codes ) ) \
return av_strdup ( ff_tiff_ # # array # # _codes [ val - TIFF_ # # TYPE # # _OFFSET ] ) ;
switch ( key ) {
case TIFF_GT_MODEL_TYPE_GEOKEY :
RET_GEOKEY_VAL ( GT_MODEL_TYPE , gt_model_type ) ;
break ;
case TIFF_GT_RASTER_TYPE_GEOKEY :
RET_GEOKEY_VAL ( GT_RASTER_TYPE , gt_raster_type ) ;
break ;
case TIFF_GEOG_LINEAR_UNITS_GEOKEY :
case TIFF_PROJ_LINEAR_UNITS_GEOKEY :
case TIFF_VERTICAL_UNITS_GEOKEY :
RET_GEOKEY_VAL ( LINEAR_UNIT , linear_unit ) ;
break ;
case TIFF_GEOG_ANGULAR_UNITS_GEOKEY :
case TIFF_GEOG_AZIMUTH_UNITS_GEOKEY :
RET_GEOKEY_VAL ( ANGULAR_UNIT , angular_unit ) ;
break ;
case TIFF_GEOGRAPHIC_TYPE_GEOKEY :
RET_GEOKEY_VAL ( GCS_TYPE , gcs_type ) ;
RET_GEOKEY_VAL ( GCSE_TYPE , gcse_type ) ;
break ;
case TIFF_GEOG_GEODETIC_DATUM_GEOKEY :
RET_GEOKEY_VAL ( GEODETIC_DATUM , geodetic_datum ) ;
RET_GEOKEY_VAL ( GEODETIC_DATUM_E , geodetic_datum_e ) ;
break ;
case TIFF_GEOG_ELLIPSOID_GEOKEY :
RET_GEOKEY_VAL ( ELLIPSOID , ellipsoid ) ;
break ;
case TIFF_GEOG_PRIME_MERIDIAN_GEOKEY :
RET_GEOKEY_VAL ( PRIME_MERIDIAN , prime_meridian ) ;
break ;
case TIFF_PROJECTED_CS_TYPE_GEOKEY :
return av_strdup ( search_keyval ( ff_tiff_proj_cs_type_codes , FF_ARRAY_ELEMS ( ff_tiff_proj_cs_type_codes ) , val ) ) ;
break ;
case TIFF_PROJECTION_GEOKEY :
return av_strdup ( search_keyval ( ff_tiff_projection_codes , FF_ARRAY_ELEMS ( ff_tiff_projection_codes ) , val ) ) ;
break ;
case TIFF_PROJ_COORD_TRANS_GEOKEY :
RET_GEOKEY_VAL ( COORD_TRANS , coord_trans ) ;
break ;
case TIFF_VERTICAL_CS_TYPE_GEOKEY :
RET_GEOKEY_VAL ( VERT_CS , vert_cs ) ;
RET_GEOKEY_VAL ( ORTHO_VERT_CS , ortho_vert_cs ) ;
break ;
}
ap = av_malloc ( 14 ) ;
if ( ap )
snprintf ( ap , 14 , " Unknown-%d " , val ) ;
return ap ;
}
static char * doubles2str ( double * dp , int count , const char * sep )
{
int i ;
char * ap , * ap0 ;
if ( ! sep ) sep = " , " ;
ap = av_malloc ( ( 15 + strlen ( sep ) ) * count ) ;
if ( ! ap )
return NULL ;
ap0 = ap ;
ap [ 0 ] = ' \0 ' ;
for ( i = 0 ; i < count ; i + + ) {
int l = snprintf ( ap , 15 + strlen ( sep ) , " %f%s " , dp [ i ] , sep ) ;
ap + = l ;
}
ap0 [ strlen ( ap0 ) - strlen ( sep ) ] = ' \0 ' ;
return ap0 ;
}
static char * shorts2str ( int * sp , int count , const char * sep )
{
int i ;
char * ap , * ap0 ;
if ( ! sep ) sep = " , " ;
ap = av_malloc ( ( 5 + strlen ( sep ) ) * count ) ;
if ( ! ap )
return NULL ;
ap0 = ap ;
ap [ 0 ] = ' \0 ' ;
for ( i = 0 ; i < count ; i + + ) {
int l = snprintf ( ap , 5 + strlen ( sep ) , " %d%s " , sp [ i ] , sep ) ;
ap + = l ;
}
ap0 [ strlen ( ap0 ) - strlen ( sep ) ] = ' \0 ' ;
return ap0 ;
}
static int add_doubles_metadata ( const uint8_t * * buf , int count ,
const char * name , const char * sep ,
TiffContext * s )
{
char * ap ;
int i ;
double * dp = av_malloc ( count * sizeof ( double ) ) ;
if ( ! dp )
return AVERROR ( ENOMEM ) ;
for ( i = 0 ; i < count ; i + + )
dp [ i ] = tget_double ( buf , s - > le ) ;
ap = doubles2str ( dp , count , sep ) ;
av_freep ( & dp ) ;
if ( ! ap )
return AVERROR ( ENOMEM ) ;
av_dict_set ( & s - > picture . metadata , name , ap , AV_DICT_DONT_STRDUP_VAL ) ;
return 0 ;
}
static int add_shorts_metadata ( const uint8_t * * buf , int count , const char * name ,
const char * sep , TiffContext * s )
{
char * ap ;
int i ;
int * sp = av_malloc ( count * sizeof ( int ) ) ;
if ( ! sp )
return AVERROR ( ENOMEM ) ;
for ( i = 0 ; i < count ; i + + )
sp [ i ] = tget_short ( buf , s - > le ) ;
ap = shorts2str ( sp , count , sep ) ;
av_freep ( & sp ) ;
if ( ! ap )
return AVERROR ( ENOMEM ) ;
av_dict_set ( & s - > picture . metadata , name , ap , AV_DICT_DONT_STRDUP_VAL ) ;
return 0 ;
}
static int add_metadata ( const uint8_t * * buf , int count , int type ,
const char * name , const char * sep , TiffContext * s )
{
switch ( type ) {
case TIFF_DOUBLE : return add_doubles_metadata ( buf , count , name , sep , s ) ;
case TIFF_SHORT : return add_shorts_metadata ( buf , count , name , sep , s ) ;
default : return AVERROR_INVALIDDATA ;
} ;
}
# if CONFIG_ZLIB
static int tiff_uncompress ( uint8_t * dst , unsigned long * len , const uint8_t * src ,
int size )
@ -372,8 +580,10 @@ static int tiff_decode_tag(TiffContext *s, const uint8_t *start,
{
unsigned tag , type , count , off , value = 0 ;
int i , j ;
int ret ;
uint32_t * pal ;
const uint8_t * rp , * gp , * bp ;
double * dp ;
if ( end_buf - buf < 12 )
return - 1 ;
@ -608,6 +818,89 @@ static int tiff_decode_tag(TiffContext *s, const uint8_t *start,
if ( s - > compr = = TIFF_G4 )
s - > fax_opts = value ;
break ;
# define ADD_METADATA(count, name, sep)\
if ( ret = add_metadata ( & buf , count , type , name , sep , s ) < 0 ) { \
av_log ( s - > avctx , AV_LOG_ERROR , " Error allocating temporary buffer \n " ) ; \
return ret ; \
}
case TIFF_MODEL_PIXEL_SCALE :
ADD_METADATA ( count , " ModelPixelScaleTag " , NULL ) ;
break ;
case TIFF_MODEL_TRANSFORMATION :
ADD_METADATA ( count , " ModelTransformationTag " , NULL ) ;
break ;
case TIFF_MODEL_TIEPOINT :
ADD_METADATA ( count , " ModelTiepointTag " , NULL ) ;
break ;
case TIFF_GEO_KEY_DIRECTORY :
ADD_METADATA ( 1 , " GeoTIFF_Version " , NULL ) ;
ADD_METADATA ( 2 , " GeoTIFF_Key_Revision " , " . " ) ;
s - > geotag_count = tget_short ( & buf , s - > le ) ;
if ( s - > geotag_count > count / 4 - 1 ) {
s - > geotag_count = count / 4 - 1 ;
av_log ( s - > avctx , AV_LOG_WARNING , " GeoTIFF key directory buffer shorter than specified \n " ) ;
}
s - > geotags = av_mallocz ( sizeof ( TiffGeoTag ) * s - > geotag_count ) ;
if ( ! s - > geotags ) {
av_log ( s - > avctx , AV_LOG_ERROR , " Error allocating temporary buffer \n " ) ;
return AVERROR ( ENOMEM ) ;
}
for ( i = 0 ; i < s - > geotag_count ; i + + ) {
s - > geotags [ i ] . key = tget_short ( & buf , s - > le ) ;
s - > geotags [ i ] . type = tget_short ( & buf , s - > le ) ;
s - > geotags [ i ] . count = tget_short ( & buf , s - > le ) ;
if ( ! s - > geotags [ i ] . type )
s - > geotags [ i ] . val = get_geokey_val ( s - > geotags [ i ] . key , tget_short ( & buf , s - > le ) ) ;
else
s - > geotags [ i ] . offset = tget_short ( & buf , s - > le ) ;
}
break ;
case TIFF_GEO_DOUBLE_PARAMS :
dp = av_malloc ( count * sizeof ( double ) ) ;
if ( ! dp ) {
av_log ( s - > avctx , AV_LOG_ERROR , " Error allocating temporary buffer \n " ) ;
return AVERROR ( ENOMEM ) ;
}
for ( i = 0 ; i < count ; i + + )
dp [ i ] = tget_double ( & buf , s - > le ) ;
for ( i = 0 ; i < s - > geotag_count ; i + + ) {
if ( s - > geotags [ i ] . type = = TIFF_GEO_DOUBLE_PARAMS ) {
if ( s - > geotags [ i ] . count = = 0
| | s - > geotags [ i ] . offset + s - > geotags [ i ] . count > count ) {
av_log ( s - > avctx , AV_LOG_WARNING , " Invalid GeoTIFF key %d \n " , s - > geotags [ i ] . key ) ;
} else {
char * ap = doubles2str ( & dp [ s - > geotags [ i ] . offset ] , s - > geotags [ i ] . count , " , " ) ;
if ( ! ap ) {
av_log ( s - > avctx , AV_LOG_ERROR , " Error allocating temporary buffer \n " ) ;
av_freep ( & dp ) ;
return AVERROR ( ENOMEM ) ;
}
s - > geotags [ i ] . val = ap ;
}
}
}
av_freep ( & dp ) ;
break ;
case TIFF_GEO_ASCII_PARAMS :
for ( i = 0 ; i < s - > geotag_count ; i + + ) {
if ( s - > geotags [ i ] . type = = TIFF_GEO_ASCII_PARAMS ) {
if ( s - > geotags [ i ] . count = = 0
| | s - > geotags [ i ] . offset + s - > geotags [ i ] . count > count ) {
av_log ( s - > avctx , AV_LOG_WARNING , " Invalid GeoTIFF key %d \n " , s - > geotags [ i ] . key ) ;
} else {
char * ap = av_malloc ( s - > geotags [ i ] . count ) ;
if ( ! ap ) {
av_log ( s - > avctx , AV_LOG_ERROR , " Error allocating temporary buffer \n " ) ;
return AVERROR ( ENOMEM ) ;
}
memcpy ( ap , & buf [ s - > geotags [ i ] . offset ] , s - > geotags [ i ] . count ) ;
ap [ s - > geotags [ i ] . count - 1 ] = ' \0 ' ; //replace the "|" delimiter with a 0 byte
s - > geotags [ i ] . val = ap ;
}
}
}
break ;
default :
av_log ( s - > avctx , AV_LOG_DEBUG , " Unknown or unsupported tag %d/0X%0X \n " ,
tag , tag ) ;
@ -648,6 +941,10 @@ static int decode_frame(AVCodecContext *avctx,
s - > invert = 0 ;
s - > compr = TIFF_RAW ;
s - > fill_order = 0 ;
free_geotags ( s ) ;
/* free existing metadata */
av_dict_free ( & s - > picture . metadata ) ;
// As TIFF 6.0 specification puts it "An arbitrary but carefully chosen number
// that further identifies the file as a TIFF file"
if ( tget_short ( & buf , le ) ! = 42 ) {
@ -670,6 +967,24 @@ static int decode_frame(AVCodecContext *avctx,
return - 1 ;
buf + = 12 ;
}
for ( i = 0 ; i < s - > geotag_count ; i + + ) {
const char * keyname = get_geokey_name ( s - > geotags [ i ] . key ) ;
if ( ! keyname ) {
av_log ( avctx , AV_LOG_WARNING , " Unknown or unsupported GeoTIFF key %d \n " , s - > geotags [ i ] . key ) ;
continue ;
}
if ( get_geokey_type ( s - > geotags [ i ] . key ) ! = s - > geotags [ i ] . type ) {
av_log ( avctx , AV_LOG_WARNING , " Type of GeoTIFF key %d is wrong \n " , s - > geotags [ i ] . key ) ;
continue ;
}
ret = av_dict_set ( & s - > picture . metadata , keyname , s - > geotags [ i ] . val , 0 ) ;
if ( ret < 0 ) {
av_log ( avctx , AV_LOG_ERROR , " Writing metadata with key '%s' failed \n " , keyname ) ;
return ret ;
}
}
if ( ! s - > stripdata & & ! s - > stripoff ) {
av_log ( avctx , AV_LOG_ERROR , " Image data is missing \n " ) ;
return - 1 ;
@ -768,6 +1083,10 @@ static av_cold int tiff_end(AVCodecContext *avctx)
{
TiffContext * const s = avctx - > priv_data ;
free_geotags ( s ) ;
if ( avctx - > coded_frame & & avctx - > coded_frame - > metadata )
av_dict_free ( & avctx - > coded_frame - > metadata ) ;
ff_lzw_decode_close ( & s - > lzw ) ;
if ( s - > picture . data [ 0 ] )
avctx - > release_buffer ( avctx , & s - > picture ) ;