From 3a975db1603e7917189cd76ea0eddc5d97d85c0d Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Wed, 15 Jun 2022 01:24:43 +0200 Subject: [PATCH] Simplify TIFF processing by 'shifting' base of indexed reader, rather than passing TIFF header offsets around everywhere. --- .../drew/imaging/jpeg/JpegMetadataReader.java | 4 +- .../jpeg/JpegSegmentMetadataReader.java | 25 +++++- .../drew/imaging/png/PngMetadataReader.java | 4 +- Source/com/drew/imaging/tiff/TiffHandler.java | 3 +- .../drew/imaging/tiff/TiffMetadataReader.java | 4 +- Source/com/drew/imaging/tiff/TiffReader.java | 50 ++++++----- Source/com/drew/lang/ByteArrayReader.java | 13 ++- .../com/drew/lang/RandomAccessFileReader.java | 17 +++- Source/com/drew/lang/RandomAccessReader.java | 4 +- .../drew/lang/RandomAccessStreamReader.java | 71 +++++++++++++++- Source/com/drew/metadata/eps/EpsReader.java | 2 +- Source/com/drew/metadata/exif/ExifReader.java | 24 +++--- .../drew/metadata/exif/ExifTiffHandler.java | 82 ++++++++++--------- .../metadata/mp4/media/Mp4UuidBoxHandler.java | 4 +- .../metadata/photoshop/PhotoshopReader.java | 6 +- .../photoshop/PhotoshopTiffHandler.java | 3 +- Tests/com/drew/lang/RandomAccessTestBase.java | 35 +++++++- .../drew/metadata/exif/ExifReaderTest.java | 4 +- .../exif/NikonType1MakernoteTest.java | 6 ++ 19 files changed, 255 insertions(+), 106 deletions(-) diff --git a/Source/com/drew/imaging/jpeg/JpegMetadataReader.java b/Source/com/drew/imaging/jpeg/JpegMetadataReader.java index 4d92f09a7..0d0df967d 100644 --- a/Source/com/drew/imaging/jpeg/JpegMetadataReader.java +++ b/Source/com/drew/imaging/jpeg/JpegMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,7 +126,7 @@ public static void process(@NotNull Metadata metadata, @NotNull InputStream inpu processJpegSegmentData(metadata, readers, segmentData); } - public static void processJpegSegmentData(Metadata metadata, Iterable readers, JpegSegmentData segmentData) + public static void processJpegSegmentData(Metadata metadata, Iterable readers, JpegSegmentData segmentData) throws IOException { // Pass the appropriate byte arrays to each reader. for (JpegSegmentMetadataReader reader : readers) { diff --git a/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java b/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java index e1d2aef34..e6b0679d0 100644 --- a/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java +++ b/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java @@ -1,5 +1,27 @@ +/* + * Copyright 2002-2022 Drew Noakes and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * https://drewnoakes.com/code/exif/ + * https://github.com/drewnoakes/metadata-extractor + */ package com.drew.imaging.jpeg; +import java.io.IOException; + import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -21,6 +43,7 @@ public interface JpegSegmentMetadataReader * encountered in the original file. * @param metadata The {@link Metadata} object into which extracted values should be merged. * @param segmentType The {@link JpegSegmentType} being read. + * @throws IOException an error occurred while accessing the required data */ - void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType); + void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) throws IOException; } diff --git a/Source/com/drew/imaging/png/PngMetadataReader.java b/Source/com/drew/imaging/png/PngMetadataReader.java index c2dd8b9e9..68c206391 100644 --- a/Source/com/drew/imaging/png/PngMetadataReader.java +++ b/Source/com/drew/imaging/png/PngMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -331,7 +331,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c } else if (chunkType.equals(PngChunkType.eXIf)) { try { ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0); - new TiffReader().processTiff(new ByteArrayReader(bytes), handler, 0); + new TiffReader().processTiff(new ByteArrayReader(bytes), handler); } catch (TiffProcessingException ex) { PngDirectory directory = new PngDirectory(PngChunkType.eXIf); directory.addError(ex.getMessage()); diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index bada4224a..5349dccba 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,6 @@ public interface TiffHandler boolean customProcessTag(int tagOffset, @NotNull Set processedIfdOffsets, - int tiffHeaderOffset, @NotNull RandomAccessReader reader, int tagId, int byteCount) throws IOException; diff --git a/Source/com/drew/imaging/tiff/TiffMetadataReader.java b/Source/com/drew/imaging/tiff/TiffMetadataReader.java index 5db7883d4..c67be81d5 100644 --- a/Source/com/drew/imaging/tiff/TiffMetadataReader.java +++ b/Source/com/drew/imaging/tiff/TiffMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,7 +68,7 @@ public static Metadata readMetadata(@NotNull RandomAccessReader reader) throws I { Metadata metadata = new Metadata(); ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0); - new TiffReader().processTiff(reader, handler, 0); + new TiffReader().processTiff(reader, handler); return metadata; } } diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index 1215d9287..1a0b8a77a 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,17 +40,15 @@ public class TiffReader * * @param reader the {@link RandomAccessReader} from which the data should be read * @param handler the {@link TiffHandler} that will coordinate processing and accept read values - * @param tiffHeaderOffset the offset within reader at which the TIFF header starts * @throws TiffProcessingException if an error occurred during the processing of TIFF data that could not be * ignored or recovered from * @throws IOException an error occurred while accessing the required data */ public void processTiff(@NotNull final RandomAccessReader reader, - @NotNull final TiffHandler handler, - final int tiffHeaderOffset) throws TiffProcessingException, IOException + @NotNull final TiffHandler handler) throws TiffProcessingException, IOException { // This must be either "MM" or "II". - short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset); + short byteOrderIdentifier = reader.getInt16(0); if (byteOrderIdentifier == 0x4d4d) { // "MM" reader.setMotorolaByteOrder(true); @@ -61,21 +59,21 @@ public void processTiff(@NotNull final RandomAccessReader reader, } // Check the next two values for correctness. - final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset); + final int tiffMarker = reader.getUInt16(2); handler.setTiffMarker(tiffMarker); - int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset; + int firstIfdOffset = reader.getInt32(4); // David Ekholm sent a digital camera image that has this problem // TODO getLength should be avoided as it causes RandomAccessStreamReader to read to the end of the stream if (firstIfdOffset >= reader.getLength() - 1) { handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset"); // First directory normally starts immediately after the offset bytes, so try that - firstIfdOffset = tiffHeaderOffset + 2 + 2 + 4; + firstIfdOffset = 2 + 2 + 4; } Set processedIfdOffsets = new HashSet(); - processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset); + processIfd(handler, reader, processedIfdOffsets, firstIfdOffset); } /** @@ -96,27 +94,28 @@ public void processTiff(@NotNull final RandomAccessReader reader, * * @param handler the {@link com.drew.imaging.tiff.TiffHandler} that will coordinate processing and accept read values * @param reader the {@link com.drew.lang.RandomAccessReader} from which the data should be read - * @param processedIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop + * @param processedGlobalIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop * @param ifdOffset the offset within reader at which the IFD data starts - * @param tiffHeaderOffset the offset within reader at which the TIFF header starts * @throws IOException an error occurred while accessing the required data */ public static void processIfd(@NotNull final TiffHandler handler, @NotNull final RandomAccessReader reader, - @NotNull final Set processedIfdOffsets, - final int ifdOffset, - final int tiffHeaderOffset) throws IOException + @NotNull final Set processedGlobalIfdOffsets, + final int ifdOffset) throws IOException { Boolean resetByteOrder = null; try { - // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist - if (processedIfdOffsets.contains(Integer.valueOf(ifdOffset))) { + // Check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist. + // Note that we track these offsets in the global frame, not the reader's local frame. + int globalIfdOffset = reader.toUnshiftedOffset(ifdOffset); + if (processedGlobalIfdOffsets.contains(Integer.valueOf(globalIfdOffset))) { return; } // remember that we've visited this directory so that we don't visit it again later - processedIfdOffsets.add(ifdOffset); + processedGlobalIfdOffsets.add(globalIfdOffset); + // Validate IFD offset if (ifdOffset >= reader.getLength() || ifdOffset < 0) { handler.error("Ignored IFD marked to start outside data segment"); return; @@ -180,13 +179,12 @@ public static void processIfd(@NotNull final TiffHandler handler, final long tagValueOffset; if (byteCount > 4) { // If it's bigger than 4 bytes, the dir entry contains an offset. - final long offsetVal = reader.getUInt32(tagOffset + 8); - if (offsetVal + byteCount > reader.getLength()) { + tagValueOffset = reader.getUInt32(tagOffset + 8); + if (tagValueOffset + byteCount > reader.getLength()) { // Bogus pointer offset and / or byteCount value handler.error("Illegal TIFF tag pointer offset"); continue; } - tagValueOffset = tiffHeaderOffset + offsetVal; } else { // 4 bytes or less and value is in the dir entry itself. tagValueOffset = tagOffset + 8; @@ -210,14 +208,14 @@ public static void processIfd(@NotNull final TiffHandler handler, for (int i = 0; i < componentCount; i++) { if (handler.tryEnterSubIfd(tagId)) { isIfdPointer = true; - int subDirOffset = tiffHeaderOffset + reader.getInt32((int) (tagValueOffset + i * 4)); - processIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset); + long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4)); + processIfd(handler, reader, processedGlobalIfdOffsets, (int) subDirOffset); } } } // If it wasn't an IFD pointer, allow custom tag processing to occur - if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, (int) byteCount)) { + if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int) byteCount)) { // If no custom processing occurred, process the tag in the standard fashion processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader); } @@ -227,10 +225,8 @@ public static void processIfd(@NotNull final TiffHandler handler, final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount); int nextIfdOffset = reader.getInt32(finalTagOffset); if (nextIfdOffset != 0) { - nextIfdOffset += tiffHeaderOffset; if (nextIfdOffset >= reader.getLength()) { // Last 4 bytes of IFD reference another IFD with an address that is out of bounds - // Note this could have been caused by jhead 1.3 cropping too much return; } else if (nextIfdOffset < ifdOffset) { // TODO is this a valid restriction? @@ -239,7 +235,7 @@ public static void processIfd(@NotNull final TiffHandler handler, } if (handler.hasFollowerIfd()) { - processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset); + processIfd(handler, reader, processedGlobalIfdOffsets, nextIfdOffset); } } } finally { @@ -326,7 +322,7 @@ private static void processTag(@NotNull final TiffHandler handler, break; case TiffDataFormat.CODE_INT16_S: if (componentCount == 1) { - handler.setInt16s(tagId, (int)reader.getInt16(tagValueOffset)); + handler.setInt16s(tagId, reader.getInt16(tagValueOffset)); } else { short[] array = new short[componentCount]; for (int i = 0; i < componentCount; i++) diff --git a/Source/com/drew/lang/ByteArrayReader.java b/Source/com/drew/lang/ByteArrayReader.java index 3429192af..6a47d78c0 100644 --- a/Source/com/drew/lang/ByteArrayReader.java +++ b/Source/com/drew/lang/ByteArrayReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,17 @@ public ByteArrayReader(@NotNull byte[] buffer, int baseOffset) _baseOffset = baseOffset; } + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new ByteArrayReader(_buffer, _baseOffset + shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + @Override public int toUnshiftedOffset(int localOffset) { diff --git a/Source/com/drew/lang/RandomAccessFileReader.java b/Source/com/drew/lang/RandomAccessFileReader.java index 502feb55e..513bb0e34 100644 --- a/Source/com/drew/lang/RandomAccessFileReader.java +++ b/Source/com/drew/lang/RandomAccessFileReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,17 @@ public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) th _length = _file.length(); } + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new RandomAccessFileReader(_file, _baseOffset + shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + @Override public int toUnshiftedOffset(int localOffset) { @@ -69,7 +80,7 @@ public int toUnshiftedOffset(int localOffset) @Override public long getLength() { - return _length; + return _length - _baseOffset; } @Override @@ -108,7 +119,7 @@ private void seek(final int index) throws IOException if (index == _currentIndex) return; - _file.seek(index); + _file.seek(index + _baseOffset); _currentIndex = index; } diff --git a/Source/com/drew/lang/RandomAccessReader.java b/Source/com/drew/lang/RandomAccessReader.java index 0eb430a0d..2e5c66b98 100644 --- a/Source/com/drew/lang/RandomAccessReader.java +++ b/Source/com/drew/lang/RandomAccessReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,8 @@ public abstract class RandomAccessReader { private boolean _isMotorolaByteOrder = true; + public abstract RandomAccessReader withShiftedBaseOffset(int shift) throws IOException; + public abstract int toUnshiftedOffset(int localOffset); /** diff --git a/Source/com/drew/lang/RandomAccessStreamReader.java b/Source/com/drew/lang/RandomAccessStreamReader.java index 133bb4bf1..3f4aae628 100644 --- a/Source/com/drew/lang/RandomAccessStreamReader.java +++ b/Source/com/drew/lang/RandomAccessStreamReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,7 +107,7 @@ protected void validateIndex(int index, int bytesRequested) throws IOException if (!isValidIndex(index, bytesRequested)) { assert(_isStreamFinished); // TODO test that can continue using an instance of this type after this exception - throw new BufferBoundsException(index, bytesRequested, _streamLength); + throw new BufferBoundsException(toUnshiftedOffset(index), bytesRequested, _streamLength); } } @@ -212,4 +212,71 @@ public byte[] getBytes(int index, int count) throws IOException return bytes; } + + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new ShiftedRandomAccessStreamReader(this, shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + + private static class ShiftedRandomAccessStreamReader extends RandomAccessReader + { + private final RandomAccessStreamReader _baseReader; + private final int _baseOffset; + + public ShiftedRandomAccessStreamReader(RandomAccessStreamReader baseReader, int baseOffset) + { + if (baseOffset < 0) + throw new IllegalArgumentException("Must be zero or greater."); + + _baseReader = baseReader; + _baseOffset = baseOffset; + } + + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new ShiftedRandomAccessStreamReader(_baseReader, _baseOffset + shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + + @Override + public int toUnshiftedOffset(int localOffset) { + return localOffset + _baseOffset; + } + + @Override + public byte getByte(int index) throws IOException { + return _baseReader.getByte(_baseOffset + index); + } + + @Override + public byte[] getBytes(int index, int count) throws IOException { + return _baseReader.getBytes(_baseOffset + index, count); + } + + @Override + protected void validateIndex(int index, int bytesRequested) throws IOException { + _baseReader.validateIndex(index + _baseOffset, bytesRequested); + } + + @Override + protected boolean isValidIndex(int index, int bytesRequested) throws IOException { + return _baseReader.isValidIndex(index + _baseOffset, bytesRequested); + } + + @Override + public long getLength() throws IOException { + return _baseReader.getLength() - _baseOffset; + } + } } diff --git a/Source/com/drew/metadata/eps/EpsReader.java b/Source/com/drew/metadata/eps/EpsReader.java index 24d8233c5..d816253dd 100644 --- a/Source/com/drew/metadata/eps/EpsReader.java +++ b/Source/com/drew/metadata/eps/EpsReader.java @@ -80,7 +80,7 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad // Get Tiff metadata try { ByteArrayReader byteArrayReader = new ByteArrayReader(reader.getBytes(tifOffset, tifSize)); - new TiffReader().processTiff(byteArrayReader, new PhotoshopTiffHandler(metadata, null), 0); + new TiffReader().processTiff(byteArrayReader, new PhotoshopTiffHandler(metadata, null)); } catch (TiffProcessingException ex) { directory.addError("Unable to process TIFF data: " + ex.getMessage()); } diff --git a/Source/com/drew/metadata/exif/ExifReader.java b/Source/com/drew/metadata/exif/ExifReader.java index d343597f1..4d90f7e7b 100644 --- a/Source/com/drew/metadata/exif/ExifReader.java +++ b/Source/com/drew/metadata/exif/ExifReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,20 +47,23 @@ public class ExifReader implements JpegSegmentMetadataReader /** Exif data stored in JPEG files' APP1 segment are preceded by this six character preamble "Exif\0\0". */ public static final String JPEG_SEGMENT_PREAMBLE = "Exif\0\0"; + @Override @NotNull public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.APP1); } + @Override public void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) + throws IOException { assert(segmentType == JpegSegmentType.APP1); for (byte[] segmentBytes : segments) { // Segment must have the expected preamble if (startsWithJpegExifPreamble(segmentBytes)) { - extract(new ByteArrayReader(segmentBytes), metadata, JPEG_SEGMENT_PREAMBLE.length()); + extract(new ByteArrayReader(segmentBytes, JPEG_SEGMENT_PREAMBLE.length()), metadata); } } } @@ -72,29 +75,22 @@ public static boolean startsWithJpegExifPreamble(byte[] bytes) new String(bytes, 0, JPEG_SEGMENT_PREAMBLE.length()).equals(JPEG_SEGMENT_PREAMBLE); } - /** Reads TIFF formatted Exif data from start of the specified {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) - { - extract(reader, metadata, 0); - } - /** Reads TIFF formatted Exif data a specified offset within a {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset) + public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) { - extract(reader, metadata, readerOffset, null); + extract(reader, metadata, null); } /** 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) + public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory) { - ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, readerOffset); + ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, /*readerOffset*/ 0); // FIXME what to do? try { // Read the TIFF-formatted Exif data new TiffReader().processTiff( reader, - exifTiffHandler, - readerOffset + exifTiffHandler ); } catch (TiffProcessingException e) { exifTiffHandler.error("Exception processing TIFF data: " + e.getMessage()); diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index 6189588f6..dcf535be2 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,7 @@ public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDir _exifStartOffset = exifStartOffset; } + @Override public void setTiffMarker(int marker) throws TiffProcessingException { final int standardTiffMarker = 0x002A; @@ -86,6 +87,7 @@ public void setTiffMarker(int marker) throws TiffProcessingException } } + @Override public boolean tryEnterSubIfd(int tagId) { if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) { @@ -142,6 +144,7 @@ public boolean tryEnterSubIfd(int tagId) return false; } + @Override public boolean hasFollowerIfd() { // In Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case. @@ -165,6 +168,7 @@ public boolean hasFollowerIfd() return false; } + @Override @Nullable public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount) { @@ -178,9 +182,9 @@ public Long tryCustomProcessFormat(final int tagId, final int formatCode, final return null; } + @Override public boolean customProcessTag(final int tagOffset, final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, final @NotNull RandomAccessReader reader, final int tagId, final int byteCount) throws IOException @@ -201,7 +205,7 @@ public boolean customProcessTag(final int tagOffset, // Custom processing for the Makernote tag if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) { - return processMakernote(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader); + return processMakernote(tagOffset, processedIfdOffsets, reader); } // Custom processing for embedded IPTC data @@ -257,35 +261,35 @@ public boolean customProcessTag(final int tagOffset, switch (tagId) { case OlympusMakernoteDirectory.TAG_EQUIPMENT: pushDirectory(OlympusEquipmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS: pushDirectory(OlympusCameraSettingsMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT: pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2: pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING: pushDirectory(OlympusImageProcessingMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_FOCUS_INFO: pushDirectory(OlympusFocusInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_INFO: pushDirectory(OlympusRawInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_MAIN_INFO: pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; } } @@ -425,7 +429,6 @@ private static String getReaderString(final @NotNull RandomAccessReader reader, private boolean processMakernote(final int makernoteOffset, final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, final @NotNull RandomAccessReader reader) throws IOException { assert(_currentDirectory != null); @@ -453,24 +456,24 @@ private boolean processMakernote(final int makernoteOffset, // Olympus Makernote // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); } else if ("OLYMPUS\0II".equals(firstTenChars)) { // Olympus Makernote (alternate) // Note that data is relative to the beginning of the makernote // http://exiv2.org/makernote.html pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 12); } else if ("OM SYSTEM\0\0\0II".equals(firstFourteenChars)) { // Olympus Makernote (OM SYSTEM) // Note that data is relative to the beginning of the makernote // http://exiv2.org/makernote.html pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14); } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("MINOLTA")) { // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote // area that commences immediately. pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) { if ("Nikon".equals(firstFiveChars)) { /* There are two scenarios here: @@ -484,11 +487,11 @@ private boolean processMakernote(final int makernoteOffset, switch (reader.getUInt8(makernoteOffset + 6)) { case 1: pushDirectory(NikonType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); break; case 2: pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 18, makernoteOffset + 10); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset + 10), processedIfdOffsets, 8); break; default: _currentDirectory.addError("Unsupported Nikon makernote data ignored."); @@ -497,26 +500,26 @@ private boolean processMakernote(final int makernoteOffset, } else { // The IFD begins with the first Makernote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models. pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); } } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) { pushDirectory(SonyType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12); // Do this check LAST after most other Sony checks } else if (cameraMake != null && cameraMake.startsWith("SONY") && !Arrays.equals(reader.getBytes(makernoteOffset, 2), new byte[]{ 0x01, 0x00 }) ) { // The IFD begins with the first Makernote byte (no ASCII name). Used in SR2 and ARW images pushDirectory(SonyType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) { // force MM for this directory reader.setMotorolaByteOrder(true); // skip 12 byte header + 2 for "MM" + 6 pushDirectory(SonyType6MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20); } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) { pushDirectory(SigmaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10); } else if ("KDK".equals(firstThreeChars)) { reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO")); KodakMakernoteDirectory directory = new KodakMakernoteDirectory(); @@ -524,14 +527,14 @@ private boolean processMakernote(final int makernoteOffset, processKodakMakernote(directory, makernoteOffset, reader); } else if ("Canon".equalsIgnoreCase(cameraMake)) { pushDirectory(CanonMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("CASIO")) { if ("QVC\u0000\u0000\u0000".equals(firstSixChars)) { pushDirectory(CasioType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6); } else { pushDirectory(CasioType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); } } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) { // Note that this also applies to certain Leica cameras, such as the Digilux-4.3 @@ -539,13 +542,14 @@ private boolean processMakernote(final int makernoteOffset, // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote // IFD, though the offset is relative to the start of the makernote, not the TIFF // header (like everywhere else) - int ifdStart = makernoteOffset + reader.getInt32(makernoteOffset + 8); + RandomAccessReader makernoteReader = reader.withShiftedBaseOffset(makernoteOffset); + int ifdStart = makernoteReader.getInt32(8); pushDirectory(FujifilmMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, ifdStart, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, ifdStart); } else if ("KYOCERA".equals(firstSevenChars)) { // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html pushDirectory(KyoceraMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22); } else if ("LEICA".equals(firstFiveChars)) { reader.setMotorolaByteOrder(false); @@ -563,14 +567,14 @@ private boolean processMakernote(final int makernoteOffset, "LEICA\0\u0007\0".equals(firstEightChars)) { pushDirectory(LeicaType5MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); } else if ("Leica Camera AG".equals(cameraMake)) { pushDirectory(LeicaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); } else if ("LEICA".equals(cameraMake)) { // Some Leica cameras use Panasonic makernote tags pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); } else { return false; } @@ -579,7 +583,7 @@ private boolean processMakernote(final int makernoteOffset, // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12); } else if ("AOC\u0000".equals(firstFourChars)) { // NON-Standard TIFF IFD Data using Casio Type 2 Tags // IFD has no Next-IFD pointer at end of IFD, and @@ -587,7 +591,7 @@ private boolean processMakernote(final int makernoteOffset, // Observed for: // - Pentax ist D pushDirectory(CasioType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 6); } else if (cameraMake != null && (cameraMake.toUpperCase().startsWith("PENTAX") || cameraMake.toUpperCase().startsWith("ASAHI"))) { // NON-Standard TIFF IFD Data using Pentax Tags // IFD has no Next-IFD pointer at end of IFD, and @@ -596,7 +600,7 @@ private boolean processMakernote(final int makernoteOffset, // - PENTAX Optio 330 // - PENTAX Optio 430 pushDirectory(PentaxMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 0); // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) { // // This Konica data is not understood. Header identified in accordance with information at this site: // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html @@ -604,7 +608,7 @@ private boolean processMakernote(final int makernoteOffset, // exifDirectory.addError("Unsupported Konica/Minolta data ignored."); } else if ("SANYO\0\1\0".equals(firstEightChars)) { pushDirectory(SanyoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); } else if (cameraMake != null && cameraMake.toLowerCase().startsWith("ricoh")) { if (firstTwoChars.equals("Rv") || firstThreeChars.equals("Rev")) { // This is a textual format, where the makernote bytes look like: @@ -617,14 +621,14 @@ private boolean processMakernote(final int makernoteOffset, // Always in Motorola byte order reader.setMotorolaByteOrder(true); pushDirectory(RicohMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); } } else if (firstTenChars.equals("Apple iOS\0")) { // Always in Motorola byte order boolean orderBefore = reader.isMotorolaByteOrder(); reader.setMotorolaByteOrder(true); pushDirectory(AppleMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14); reader.setMotorolaByteOrder(orderBefore); } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory(); @@ -641,7 +645,7 @@ private boolean processMakernote(final int makernoteOffset, } else if ("SAMSUNG".equalsIgnoreCase(cameraMake)) { // Only handles Type2 notes correctly. Others aren't implemented, and it's complex to determine which ones to use pushDirectory(SamsungType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); } else { // The makernote is not comprehended by this library. // If you are reading this and believe a particular camera's image should be processed, get in touch. diff --git a/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java b/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java index b3dcdc1ec..7d6b369ba 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ public Mp4Handler processBox(@NotNull String type, byte[] payload, long boxSi switch (uuidType) { case Exif: - new ExifReader().extract(new ByteArrayReader(payload, 16), metadata, 0, directory); + new ExifReader().extract(new ByteArrayReader(payload, 16), metadata, directory); break; case IptcIim: new IptcReader().extract(new SequentialByteArrayReader(payload, 16), metadata, payload.length - 16, directory); diff --git a/Source/com/drew/metadata/photoshop/PhotoshopReader.java b/Source/com/drew/metadata/photoshop/PhotoshopReader.java index 90ef156f7..c494e17c2 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopReader.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,12 +52,14 @@ public class PhotoshopReader implements JpegSegmentMetadataReader @NotNull private static final String JPEG_SEGMENT_PREAMBLE = "Photoshop 3.0"; + @Override @NotNull public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.APPD); } + @Override public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) { final int preambleLength = JPEG_SEGMENT_PREAMBLE.length(); @@ -149,7 +151,7 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull else if (tagType == PhotoshopDirectory.TAG_ICC_PROFILE_BYTES) new IccReader().extract(new ByteArrayReader(tagBytes), metadata, directory); else if (tagType == PhotoshopDirectory.TAG_EXIF_DATA_1 || tagType == PhotoshopDirectory.TAG_EXIF_DATA_3) - new ExifReader().extract(new ByteArrayReader(tagBytes), metadata, 0, directory); + new ExifReader().extract(new ByteArrayReader(tagBytes), metadata, directory); else if (tagType == PhotoshopDirectory.TAG_XMP_DATA) new XmpReader().extract(tagBytes, metadata, directory); else if (tagType >= 0x07D0 && tagType <= 0x0BB6) { diff --git a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java index a90e52827..aa65eecb3 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java @@ -55,7 +55,6 @@ public boolean customProcessTag(final int tagOffset, return true; } - - return super.customProcessTag(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, byteCount); + return super.customProcessTag(tagOffset, processedIfdOffsets, reader, tagId, byteCount); } } diff --git a/Tests/com/drew/lang/RandomAccessTestBase.java b/Tests/com/drew/lang/RandomAccessTestBase.java index 7d9ac418f..68ee674a7 100644 --- a/Tests/com/drew/lang/RandomAccessTestBase.java +++ b/Tests/com/drew/lang/RandomAccessTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,9 @@ import java.io.IOException; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -336,4 +338,35 @@ public void testGetInt8EOF() throws Exception fail("Expecting exception"); } catch (IOException ignored) {} } + + @Test + public void testWithShiftedBaseOffset() throws Exception + { + RandomAccessReader reader = createReader(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + reader.setMotorolaByteOrder(false); + + assertEquals(10, reader.getLength()); + assertEquals(0, reader.getByte(0)); + assertEquals(1, reader.getByte(1)); + assertArrayEquals(new byte[] { 0, 1 }, reader.getBytes(0, 2)); + assertEquals(4, reader.toUnshiftedOffset(4)); + + reader = reader.withShiftedBaseOffset(2); + + assertFalse(reader.isMotorolaByteOrder()); + assertEquals(8, reader.getLength()); + assertEquals(2, reader.getByte(0)); + assertEquals(3, reader.getByte(1)); + assertArrayEquals(new byte[] { 2, 3 }, reader.getBytes(0, 2)); + assertEquals(6, reader.toUnshiftedOffset(4)); + + reader = reader.withShiftedBaseOffset(2); + + assertFalse(reader.isMotorolaByteOrder()); + assertEquals(6, reader.getLength()); + assertEquals(4, reader.getByte(0)); + assertEquals(5, reader.getByte(1)); + assertArrayEquals(new byte[] { 4, 5 }, reader.getBytes(0, 2)); + assertEquals(8, reader.toUnshiftedOffset(4)); + } } diff --git a/Tests/com/drew/metadata/exif/ExifReaderTest.java b/Tests/com/drew/metadata/exif/ExifReaderTest.java index 3cd95547b..3545bbb91 100644 --- a/Tests/com/drew/metadata/exif/ExifReaderTest.java +++ b/Tests/com/drew/metadata/exif/ExifReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public static Metadata processBytes(@NotNull String filePath) throws IOException { Metadata metadata = new Metadata(); byte[] bytes = FileUtil.readBytes(filePath); - new ExifReader().extract(new ByteArrayReader(bytes), metadata, ExifReader.JPEG_SEGMENT_PREAMBLE.length(), null); + new ExifReader().extract(new ByteArrayReader(bytes, ExifReader.JPEG_SEGMENT_PREAMBLE.length()), metadata, null); return metadata; } diff --git a/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java b/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java index 302c27d6a..fcd842e8c 100644 --- a/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java +++ b/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; import com.drew.lang.Rational; +import com.drew.metadata.ErrorDirectory; import com.drew.metadata.Metadata; import com.drew.metadata.exif.makernotes.NikonType1MakernoteDirectory; import org.junit.Before; @@ -28,6 +29,8 @@ import static org.junit.Assert.*; +import java.util.Objects; + /** * @author Drew Noakes https://drewnoakes.com */ @@ -55,6 +58,9 @@ public void setUp() throws Exception { Metadata metadata = ExifReaderTest.processBytes("Tests/Data/nikonMakernoteType1.jpg.app1"); + ErrorDirectory error = metadata.getFirstDirectoryOfType(ErrorDirectory.class); + assertNull(error != null ? Objects.toString(error.getErrors()) : "", error); + _nikonDirectory = metadata.getFirstDirectoryOfType(NikonType1MakernoteDirectory.class); _exifSubIFDDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); _exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);