summaryrefslogtreecommitdiff
path: root/libavcodec/tiff.c
diff options
context:
space:
mode:
Diffstat (limited to 'libavcodec/tiff.c')
-rw-r--r--libavcodec/tiff.c319
1 files changed, 319 insertions, 0 deletions
diff --git a/libavcodec/tiff.c b/libavcodec/tiff.c
index 103c44fdfb..88f5238779 100644
--- a/libavcodec/tiff.c
+++ b/libavcodec/tiff.c
@@ -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);