Skip to content

Commit 33c9999

Browse files
lgritzCopilot
andauthored
feat(tiff): Support GPS fields, and other metadata enhancements (#5050)
Most visible change: The TIFF reader and writer now correctly handle GPS tags. Fixes #5049 But along the way, and to get it completely right, a fairly extensive refactoring of our TIFF tag handling was needed. The libtiff behavior around different tags is extremely convoluted, and there were a bunch of cases we either didn't handle correctly, or were dropping on the floor. So there are also some other non-GPS tags that we missed all along but now read properly. Note that the support for separate IFDs for GPS tags is only supported via the API in libtiff 4.2 and newer, so this is disabled when bulding against older libtiff versions. (Our support of old libtiff versions extends extraordinarily far back in time.) --------- Signed-off-by: Larry Gritz <lg@larrygritz.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 43b8aa9 commit 33c9999

20 files changed

Lines changed: 522 additions & 106 deletions

File tree

src/cmake/externalpackages.cmake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ checked_find_package (libuhdr
9191
VERSION_MIN 1.3)
9292

9393
checked_find_package (TIFF REQUIRED
94-
VERSION_MIN 4.0)
94+
VERSION_MIN 4.0
95+
RECOMMEND_MIN 4.2
96+
RECOMMEND_MIN_REASON "4.2 for GPS support")
9597
alias_library_if_not_exists (TIFF::TIFF TIFF::tiff)
9698

9799
# JPEG XL

src/include/OpenImageIO/tiffutils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ OIIO_API void encode_exif (const ImageSpec &spec, std::vector<char> &blob,
196196
OIIO_API bool exif_tag_lookup (string_view name, int &tag,
197197
int &tifftype, int &count);
198198

199+
/// Helper: For the given OIIO metadata attribute name, look up the GPS tag
200+
/// ID, TIFFDataType (expressed as an int), and count. Return true and fill
201+
/// in the fields if found, return false if not found.
202+
OIIO_API bool gps_tag_lookup (string_view name, int &tag,
203+
int &tifftype, int &count);
204+
199205
/// Add metadata to spec based on raw IPTC (International Press
200206
/// Telecommunications Council) metadata in the form of an IIM
201207
/// (Information Interchange Model). Return true if all is ok, false if
@@ -286,6 +292,7 @@ using v3_1::decode_xmp;
286292
using v3_1::encode_iptc_iim;
287293
using v3_1::encode_xmp;
288294
using v3_1::exif_tag_lookup;
295+
using v3_1::gps_tag_lookup;
289296
using v3_1::tag_lookup;
290297
using v3_1::tag_table;
291298
using v3_1::TagInfo;

src/libOpenImageIO/exif.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ static const TagInfo exif_tag_table[] = {
426426
{ TIFFTAG_PHOTOMETRIC, "Exif:Photometric", TIFF_NOTYPE, 1 },
427427
{ TIFFTAG_SAMPLESPERPIXEL, "Exif:SamplesPerPixel", TIFF_NOTYPE, 1 },
428428
{ TIFFTAG_PLANARCONFIG, "Exif:PlanarConfig", TIFF_NOTYPE, 1 },
429-
{ TIFFTAG_YCBCRSUBSAMPLING, "Exif:YCbCrSubsampling",TIFF_SHORT, 1 },
429+
{ TIFFTAG_YCBCRSUBSAMPLING, "Exif:YCbCrSubsampling",TIFF_SHORT, 2 },
430430
{ TIFFTAG_YCBCRPOSITIONING, "Exif:YCbCrPositioning",TIFF_SHORT, 1 },
431431
// TIFF tags we may come across
432432
{ TIFFTAG_ORIENTATION, "Orientation", TIFF_SHORT, 1 },
@@ -525,7 +525,7 @@ static const TagInfo exif_tag_table[] = {
525525
{ EXIF_LENSMAKE, "Exif:LensMake", TIFF_ASCII, 0 },
526526
{ EXIF_LENSMODEL, "Exif:LensModel", TIFF_ASCII, 0 },
527527
{ EXIF_LENSSERIALNUMBER, "Exif:LensSerialNumber", TIFF_ASCII, 0 },
528-
{ EXIF_GAMMA, "Exif:Gamma", TIFF_RATIONAL, 0 },
528+
{ EXIF_GAMMA, "Exif:Gamma", TIFF_RATIONAL, 1 },
529529
// Exif 3.0 additions follow
530530
{ EXIF_IMAGETITLE, "Exif:ImageTitle", TIFF_ASCII, 0 },
531531
{ EXIF_PHOTOGRAPHER, "Exif:Photographer", TIFF_ASCII, 0 },
@@ -1559,4 +1559,19 @@ exif_tag_lookup(string_view name, int& tag, int& tifftype, int& count)
15591559
return true;
15601560
}
15611561

1562+
1563+
1564+
bool
1565+
gps_tag_lookup(string_view name, int& tag, int& tifftype, int& count)
1566+
{
1567+
const TagInfo* e = gps_tagmap_ref().find(name);
1568+
if (!e)
1569+
return false; // not found
1570+
1571+
tag = e->tifftag;
1572+
tifftype = e->tifftype;
1573+
count = e->tiffcount;
1574+
return true;
1575+
}
1576+
15621577
OIIO_NAMESPACE_3_1_END

src/tiff.imageio/tiffinput.cpp

Lines changed: 161 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -265,25 +265,37 @@ class TIFFInput final : public ImageInput {
265265
}
266266

267267
OIIO_NODISCARD
268-
TypeDesc tiffgetfieldtype(int tag)
268+
TypeDesc tiffgetfieldtype(int tag, OIIO_MAYBE_UNUSED string_view name = "",
269+
int* readcount_ = nullptr,
270+
int* passcount_ = nullptr,
271+
TIFFDataType* tiffdatatype_ = nullptr)
269272
{
270273
auto field = find_field(tag);
271274
if (!field)
272275
return TypeUnknown;
273-
TIFFDataType tiffdatatype = TIFFFieldDataType(field);
274-
int passcount = TIFFFieldPassCount(field);
275-
int readcount = TIFFFieldReadCount(field);
276-
if (!passcount && readcount > 0)
277-
return tiff_datatype_to_typedesc(tiffdatatype, readcount);
278-
return TypeUnknown;
276+
auto tiffdatatype = TIFFFieldDataType(field);
277+
int readcount = TIFFFieldReadCount(field);
278+
int passcount = TIFFFieldPassCount(field);
279+
TypeDesc type = tiff_datatype_to_typedesc(tiffdatatype,
280+
std::max(1, readcount));
281+
if (readcount == TIFF_SPP)
282+
type.arraylen = m_spec.nchannels;
283+
if (readcount_)
284+
*readcount_ = readcount;
285+
if (passcount_)
286+
*passcount_ = passcount;
287+
if (tiffdatatype_)
288+
*tiffdatatype_ = tiffdatatype;
289+
return type;
279290
}
280291

281292
OIIO_NODISCARD
282293
bool safe_tiffgetfield(string_view name OIIO_MAYBE_UNUSED, int tag,
283294
TypeDesc expected, void* dest,
284-
const uint32_t* count = nullptr)
295+
uint32_t* count = nullptr)
285296
{
286-
TypeDesc type = tiffgetfieldtype(tag);
297+
int readcount = 0, passcount = 0;
298+
TypeDesc type = tiffgetfieldtype(tag, name, &readcount, &passcount);
287299
// Caller expects a specific type and the tag doesn't match? Punt.
288300
if (expected != TypeUnknown && !equivalent(expected, type))
289301
return false;
@@ -292,8 +304,8 @@ class TIFFInput final : public ImageInput {
292304
return false;
293305

294306
// TIFFDataType tiffdatatype = TIFFFieldDataType(field);
295-
int passcount = TIFFFieldPassCount(field);
296-
int readcount = TIFFFieldReadCount(field);
307+
passcount = TIFFFieldPassCount(field);
308+
readcount = TIFFFieldReadCount(field);
297309
if (!passcount && readcount > 0) {
298310
return TIFFGetField(m_tif, tag, dest);
299311
} else if (passcount && readcount <= 0) {
@@ -370,31 +382,120 @@ class TIFFInput final : public ImageInput {
370382
m_spec.attribute(name, TypeMatrix, f);
371383
}
372384

373-
// Get a float tiff tag field and put it into extra_params
374-
void get_float_attribute(string_view name, int tag)
375-
{
376-
float f[16];
377-
if (safe_tiffgetfield(name, tag, TypeUnknown, f))
378-
m_spec.attribute(name, f[0]);
379-
}
380-
381-
// Get an int tiff tag field and put it into extra_params
382-
void get_int_attribute(string_view name, int tag)
383-
{
384-
int i = 0;
385-
if (safe_tiffgetfield(name, tag, TypeUnknown, &i))
386-
m_spec.attribute(name, i);
387-
}
388-
389-
// Get an int tiff tag field and put it into extra_params
390-
void get_short_attribute(string_view name, int tag)
385+
// Get a tiff tag field whose C type will match T, and put it into
386+
// extra_params. If it must be a very specific TypeDesc, it can be
387+
// supplied as expected_type. The hard part is that libtiff is horribly
388+
// complicated because some tags have variable lengths, and there are even
389+
// several different parameter passing conventions to the incredibly
390+
// unsafe TIFFGetField function.
391+
template<typename T>
392+
void get_attribute_from_tag(string_view name, int tag,
393+
TypeDesc expected_type = TypeUnknown)
391394
{
392-
// Make room for two shorts, in case the tag is not the type we
393-
// expect, and libtiff writes a long instead.
394-
unsigned short s[2] = { 0, 0 };
395-
if (safe_tiffgetfield(name, tag, TypeUInt16, &s)) {
396-
int i = s[0];
397-
m_spec.attribute(name, i);
395+
int readcount = 0, passcount = 0;
396+
TIFFDataType tiffdatatype = TIFF_NOTYPE;
397+
TypeDesc oiiotype = tiffgetfieldtype(tag, name, &readcount, &passcount,
398+
&tiffdatatype);
399+
if (oiiotype == TypeUnknown)
400+
return;
401+
OIIO_ASSERT((readcount > 0 && passcount == 0)
402+
|| (readcount == 0 && passcount == 0)
403+
|| (readcount < 0 && passcount > 0));
404+
oiiotype.basetype = BaseTypeFromC_v<T>;
405+
if (expected_type != TypeUnknown) {
406+
// If a specific OIIO type is demanded, skip if what we got
407+
// doesn't really match in base type and total number of elements.
408+
// But allow a ushort tag to make a int attribute.
409+
if (oiiotype.basetype != expected_type.basetype)
410+
return;
411+
if (oiiotype.basevalues() != expected_type.basevalues())
412+
return;
413+
oiiotype = expected_type;
414+
} else {
415+
oiiotype.aggregate = TypeDesc::SCALAR;
416+
oiiotype.vecsemantics = TypeDesc::NOSEMANTICS;
417+
}
418+
if (readcount > 0 && passcount == 0) {
419+
// We know how many are passed, so we pass TIFFGetField a pointer
420+
// to where we want the (already sized) data to go.
421+
OIIO_ASSERT(size_t(readcount) == oiiotype.basevalues());
422+
if constexpr (std::is_same_v<T, uint16_t>) {
423+
// Special case: we save a single tiff ushort as an int.
424+
if (tiffdatatype == TIFF_SHORT && readcount == 1) {
425+
T val;
426+
if (TIFFGetField(m_tif, tag, &val))
427+
m_spec.attribute(name, int(val));
428+
return;
429+
}
430+
// Fun, there are some quirky special cases in libtiff where
431+
// certain uint16[2] fields are retrieved with two pointers!
432+
if (tiffdatatype == TIFF_SHORT && readcount == 2
433+
&& (tag == TIFFTAG_PAGENUMBER
434+
|| tag == TIFFTAG_HALFTONEHINTS
435+
|| tag == TIFFTAG_DOTRANGE
436+
|| tag == TIFFTAG_YCBCRSUBSAMPLING)) {
437+
T vals[2] = { 0, 0 };
438+
if (TIFFGetField(m_tif, tag, &(vals[0]), &(vals[1]))) {
439+
constexpr TypeDesc TypeUInt16_2(TypeDesc::UINT16, 2);
440+
m_spec.attribute(name, TypeUInt16_2, vals);
441+
}
442+
return;
443+
}
444+
}
445+
if (readcount > 1) {
446+
// If there are multiple values, we pass a T** and it puts the
447+
// address of the real data in our pointer. There are very few
448+
// TIFF tags like this.
449+
const T* ptr = nullptr;
450+
if (TIFFGetField(m_tif, tag, &ptr) && ptr)
451+
m_spec.attribute(name, oiiotype, ptr);
452+
return;
453+
} else {
454+
// If there is just one value, we pass a pointer to our data,
455+
// and libtiff fills it in.
456+
T val;
457+
if (TIFFGetField(m_tif, tag, &val))
458+
m_spec.attribute(name, oiiotype, &val);
459+
}
460+
return;
461+
} else if (readcount == TIFF_VARIABLE) {
462+
// Must pass a uin16_t to find out how many, and we get a data
463+
// pointer instead of providing a pointer.
464+
uint16_t count = 0;
465+
const T* vals = nullptr;
466+
if (TIFFGetField(m_tif, tag, &count, &vals) && vals && count) {
467+
oiiotype.unarray();
468+
oiiotype.arraylen = count;
469+
m_spec.attribute(name, oiiotype, make_span(vals, count));
470+
}
471+
return;
472+
} else if (readcount == TIFF_VARIABLE2) {
473+
// Must pass a uin32t_t to find out how many, and we get a data
474+
// pointer instead of providing a pointer.
475+
uint32_t count = 0;
476+
const T* vals = nullptr;
477+
if (TIFFGetField(m_tif, tag, &count, &vals) && vals && count) {
478+
if (count > 1)
479+
oiiotype.arraylen = count;
480+
else
481+
oiiotype.unarray();
482+
m_spec.attribute(name, oiiotype, make_span(vals, count));
483+
}
484+
return;
485+
} else if (readcount == TIFF_SPP) {
486+
// The number of values is equal to the number of channels.
487+
uint32_t count = static_cast<uint32_t>(m_spec.nchannels);
488+
const T* vals = nullptr;
489+
if (TIFFGetField(m_tif, tag, &vals) && vals) {
490+
if (count > 1)
491+
oiiotype.arraylen = count;
492+
else
493+
oiiotype.unarray();
494+
m_spec.attribute(name, oiiotype, make_span(vals, count));
495+
}
496+
return;
497+
} else {
498+
// print("UNHANDLED CASE!\n");
398499
}
399500
}
400501

@@ -416,14 +517,14 @@ class TIFFInput final : public ImageInput {
416517
get_string_attribute(oiioname, tifftag);
417518
return;
418519
} else if (tifftype == TIFF_SHORT) {
419-
get_short_attribute(oiioname, tifftag);
520+
get_attribute_from_tag<uint16_t>(oiioname, tifftag);
420521
return;
421522
} else if (tifftype == TIFF_LONG) {
422-
get_int_attribute(oiioname, tifftag);
523+
get_attribute_from_tag<int>(oiioname, tifftag);
423524
return;
424525
} else if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL
425526
|| tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) {
426-
get_float_attribute(oiioname, tifftag);
527+
get_attribute_from_tag<float>(oiioname, tifftag);
427528
return;
428529
}
429530
// special cases follow
@@ -1295,11 +1396,13 @@ TIFFInput::readspec(bool read_meta)
12951396
if (xdensity && ydensity)
12961397
m_spec.attribute("PixelAspectRatio", ydensity / xdensity);
12971398

1298-
get_matrix_attribute("worldtocamera", TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA);
1299-
get_matrix_attribute("worldtoscreen", TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN);
1300-
get_int_attribute("tiff:subfiletype", TIFFTAG_SUBFILETYPE);
1301-
// FIXME -- should subfiletype be "conventionized" and used for all
1302-
// plugins uniformly?
1399+
get_attribute_from_tag<float>("worldtocamera",
1400+
TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA,
1401+
TypeMatrix);
1402+
get_attribute_from_tag<float>("worldtoscreen",
1403+
TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN,
1404+
TypeMatrix);
1405+
get_attribute_from_tag<int>("tiff:subfiletype", TIFFTAG_SUBFILETYPE);
13031406

13041407
// Special names for shadow maps
13051408
char* s = NULL;
@@ -1357,6 +1460,21 @@ TIFFInput::readspec(bool read_meta)
13571460
TIFFSetDirectory(m_tif, m_actual_subimage);
13581461
}
13591462

1463+
#if OIIO_TIFFLIB_VERSION >= 40200
1464+
// Search for an GPS IFD in the TIFF file, and if found, rummage
1465+
// around for GPS fields.
1466+
toff_t gpsoffset = 0;
1467+
if (TIFFGetField(m_tif, TIFFTAG_GPSIFD, &gpsoffset)) {
1468+
if (TIFFReadEXIFDirectory(m_tif, gpsoffset)) {
1469+
for (const auto& tag : tag_table("GPS"))
1470+
find_tag(tag.tifftag, tag.tifftype, tag.name);
1471+
}
1472+
// TIFFReadEXIFDirectory seems to do something to the internal state
1473+
// that requires a TIFFSetDirectory to set things straight again.
1474+
TIFFSetDirectory(m_tif, m_actual_subimage);
1475+
}
1476+
#endif
1477+
13601478
// Search for IPTC metadata in IIM form -- but older versions of
13611479
// libtiff botch the size, so ignore it for very old libtiff.
13621480
int iptcsize = 0;

0 commit comments

Comments
 (0)