From b4a2422ae38e825f73e3f7ee2a955b4d3641afa9 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Mon, 8 May 2023 14:37:45 +1000 Subject: [PATCH] Fix Exif thumbnail offset Fixes #578 The offset is currently reported relative to the start of the Exif data. However, most users will need the value relative to the start of the outermost data stream. This change attempts to provide that value. Should work for JPEG and RAW files. Won't work for Exif data embedded within other formats such as PNG, QuickTime, WebP or HEIF. Such support could be added in future. --- .../drew/imaging/png/PngMetadataReader.java | 2 +- .../drew/imaging/tiff/TiffMetadataReader.java | 2 +- Source/com/drew/metadata/exif/ExifReader.java | 2 +- .../exif/ExifThumbnailDescriptor.java | 5 ++- .../metadata/exif/ExifThumbnailDirectory.java | 42 ++++++++++++++++++- .../drew/metadata/exif/ExifTiffHandler.java | 7 +++- .../photoshop/PhotoshopTiffHandler.java | 2 +- .../metadata/tiff/DirectoryTiffHandler.java | 11 +++-- Tests/com/drew/metadata/MetadataTest.java | 2 +- .../drew/metadata/exif/ExifDirectoryTest.java | 2 +- .../exif/ExifThumbnailDescriptorTest.java | 2 +- 11 files changed, 63 insertions(+), 16 deletions(-) diff --git a/Source/com/drew/imaging/png/PngMetadataReader.java b/Source/com/drew/imaging/png/PngMetadataReader.java index c276e9bfa..1a0447451 100644 --- a/Source/com/drew/imaging/png/PngMetadataReader.java +++ b/Source/com/drew/imaging/png/PngMetadataReader.java @@ -330,7 +330,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.eXIf)) { try { - ExifTiffHandler handler = new ExifTiffHandler(metadata, null); + ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0); new TiffReader().processTiff(new ByteArrayReader(bytes), handler, 0); } catch (TiffProcessingException ex) { PngDirectory directory = new PngDirectory(PngChunkType.eXIf); diff --git a/Source/com/drew/imaging/tiff/TiffMetadataReader.java b/Source/com/drew/imaging/tiff/TiffMetadataReader.java index 7137a0384..5db7883d4 100644 --- a/Source/com/drew/imaging/tiff/TiffMetadataReader.java +++ b/Source/com/drew/imaging/tiff/TiffMetadataReader.java @@ -67,7 +67,7 @@ public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOE public static Metadata readMetadata(@NotNull RandomAccessReader reader) throws IOException, TiffProcessingException { Metadata metadata = new Metadata(); - ExifTiffHandler handler = new ExifTiffHandler(metadata, null); + ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0); new TiffReader().processTiff(reader, handler, 0); return metadata; } diff --git a/Source/com/drew/metadata/exif/ExifReader.java b/Source/com/drew/metadata/exif/ExifReader.java index ff2e9a954..d343597f1 100644 --- a/Source/com/drew/metadata/exif/ExifReader.java +++ b/Source/com/drew/metadata/exif/ExifReader.java @@ -87,7 +87,7 @@ public void extract(@NotNull final RandomAccessReader reader, @NotNull final Met /** Reads TIFF formatted Exif data at a specified offset within a {@link RandomAccessReader}. */ public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset, @Nullable Directory parentDirectory) { - ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory); + ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, readerOffset); try { // Read the TIFF-formatted Exif data diff --git a/Source/com/drew/metadata/exif/ExifThumbnailDescriptor.java b/Source/com/drew/metadata/exif/ExifThumbnailDescriptor.java index 430491772..15525e87f 100644 --- a/Source/com/drew/metadata/exif/ExifThumbnailDescriptor.java +++ b/Source/com/drew/metadata/exif/ExifThumbnailDescriptor.java @@ -63,7 +63,8 @@ public String getThumbnailLengthDescription() @Nullable public String getThumbnailOffsetDescription() { - String value = _directory.getString(TAG_THUMBNAIL_OFFSET); - return value == null ? null : value + " bytes"; + Integer offset = _directory.getAdjustedThumbnailOffset(); + + return offset == null ? null : offset.intValue() + " bytes"; } } diff --git a/Source/com/drew/metadata/exif/ExifThumbnailDirectory.java b/Source/com/drew/metadata/exif/ExifThumbnailDirectory.java index 85b106f7e..572a0505f 100644 --- a/Source/com/drew/metadata/exif/ExifThumbnailDirectory.java +++ b/Source/com/drew/metadata/exif/ExifThumbnailDirectory.java @@ -34,7 +34,11 @@ public class ExifThumbnailDirectory extends ExifDirectoryBase { /** - * The offset to thumbnail image bytes. + * The offset to thumbnail image bytes, relative to the start of the IFD. + * + * To obtain the offset relative to the start of the TIFF data stream, use + * getAdjustedThumbnailOffset, which includes the value of + * getExifStartOffset. */ public static final int TAG_THUMBNAIL_OFFSET = 0x0201; /** @@ -59,8 +63,11 @@ public class ExifThumbnailDirectory extends ExifDirectoryBase _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length"); } - public ExifThumbnailDirectory() + private final int _exifStartOffset; + + public ExifThumbnailDirectory(int exifStartOffset) { + _exifStartOffset = exifStartOffset; this.setDescriptor(new ExifThumbnailDescriptor(this)); } @@ -77,4 +84,35 @@ protected HashMap getTagNameMap() { return _tagNameMap; } + + /** + * Gets the offset at which the Exif data stream commenced within any containing stream. + */ + public int getExifStartOffset() + { + return _exifStartOffset; + } + + /** + * Returns the offset to thumbnail data within the outermost data stream. + * + * The value for TagThumbnailOffset is relative to the Exif data stream. + * Generally, consumers of thumbnail data need this value relative to the outermost stream, + * so that the thumbnail data may be extracted from that stream. + * + * This property adds the value of ExifStartOffset to this tag's value in order + * to produce that value. + * + * Returns null when the tag is not defined in this directory. + */ + public Integer getAdjustedThumbnailOffset() + { + Integer offset = this.getInteger(TAG_THUMBNAIL_OFFSET); + + if (offset == null) { + return null; + } + + return offset.intValue() + _exifStartOffset; + } } diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index 2acf88294..39857b440 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -57,9 +57,12 @@ */ public class ExifTiffHandler extends DirectoryTiffHandler { - public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory) + private final int _exifStartOffset; + + public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory, int exifStartOffset) { super(metadata, parentDirectory); + _exifStartOffset = exifStartOffset; } public void setTiffMarker(int marker) throws TiffProcessingException @@ -149,7 +152,7 @@ public boolean hasFollowerIfd() if (_currentDirectory.containsTag(ExifDirectoryBase.TAG_PAGE_NUMBER)) pushDirectory(ExifImageDirectory.class); else - pushDirectory(ExifThumbnailDirectory.class); + pushDirectory(new ExifThumbnailDirectory(_exifStartOffset)); return true; } diff --git a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java index b5de35ea8..a90e52827 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java @@ -33,7 +33,7 @@ public class PhotoshopTiffHandler extends ExifTiffHandler public PhotoshopTiffHandler(Metadata metadata, Directory parentDirectory) { - super(metadata, parentDirectory); + super(metadata, parentDirectory, 0); } public boolean customProcessTag(final int tagOffset, diff --git a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java index a327bd808..21ed52917 100644 --- a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java +++ b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java @@ -67,21 +67,26 @@ protected void pushDirectory(@NotNull Class directoryClass) throw new RuntimeException(e); } + pushDirectory(newDirectory); + } + + protected void pushDirectory(@NotNull Directory directory) + { // If this is the first directory, don't add to the stack if (_currentDirectory == null) { // Apply any pending root parent to this new directory if (_rootParentDirectory != null) { - newDirectory.setParent(_rootParentDirectory); + directory.setParent(_rootParentDirectory); _rootParentDirectory = null; } } else { // The current directory is pushed onto the stack, and set as the new directory's parent _directoryStack.push(_currentDirectory); - newDirectory.setParent(_currentDirectory); + directory.setParent(_currentDirectory); } - _currentDirectory = newDirectory; + _currentDirectory = directory; _metadata.addDirectory(_currentDirectory); } diff --git a/Tests/com/drew/metadata/MetadataTest.java b/Tests/com/drew/metadata/MetadataTest.java index 7a10e52a2..05b5239cb 100644 --- a/Tests/com/drew/metadata/MetadataTest.java +++ b/Tests/com/drew/metadata/MetadataTest.java @@ -96,7 +96,7 @@ public void testOrderOfDifferentTypes() { Metadata metadata = new Metadata(); Directory directory1 = new ExifSubIFDDirectory(); - Directory directory2 = new ExifThumbnailDirectory(); + Directory directory2 = new ExifThumbnailDirectory(0); Directory directory3 = new ExifIFD0Directory(); metadata.addDirectory(directory1); diff --git a/Tests/com/drew/metadata/exif/ExifDirectoryTest.java b/Tests/com/drew/metadata/exif/ExifDirectoryTest.java index d818dcbac..d96822b23 100644 --- a/Tests/com/drew/metadata/exif/ExifDirectoryTest.java +++ b/Tests/com/drew/metadata/exif/ExifDirectoryTest.java @@ -46,7 +46,7 @@ public void testGetDirectoryName() throws Exception { Directory subIFDDirectory = new ExifSubIFDDirectory(); Directory ifd0Directory = new ExifIFD0Directory(); - Directory thumbDirectory = new ExifThumbnailDirectory(); + Directory thumbDirectory = new ExifThumbnailDirectory(0); Directory gpsDirectory = new GpsDirectory(); assertFalse(subIFDDirectory.hasErrors()); diff --git a/Tests/com/drew/metadata/exif/ExifThumbnailDescriptorTest.java b/Tests/com/drew/metadata/exif/ExifThumbnailDescriptorTest.java index a223eab42..1c3bc304f 100644 --- a/Tests/com/drew/metadata/exif/ExifThumbnailDescriptorTest.java +++ b/Tests/com/drew/metadata/exif/ExifThumbnailDescriptorTest.java @@ -36,7 +36,7 @@ public class ExifThumbnailDescriptorTest @Test public void testGetYCbCrSubsamplingDescription() throws Exception { - ExifThumbnailDirectory directory = new ExifThumbnailDirectory(); + ExifThumbnailDirectory directory = new ExifThumbnailDirectory(0); directory.setIntArray(TAG_YCBCR_SUBSAMPLING, new int[]{2, 1}); ExifThumbnailDescriptor descriptor = new ExifThumbnailDescriptor(directory);