From 3a975db1603e7917189cd76ea0eddc5d97d85c0d Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Wed, 15 Jun 2022 01:24:43 +0200 Subject: [PATCH 1/7] 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); From 675ab95edcf82f54893ede1dc0aca628c9a765b6 Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Wed, 24 Jul 2024 14:33:36 +1000 Subject: [PATCH 2/7] Pass start offset when reading Exif --- Source/com/drew/metadata/exif/ExifReader.java | 10 +++++----- Source/com/drew/metadata/heif/HeifPictureHandler.java | 2 +- .../com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java | 2 +- .../com/drew/metadata/photoshop/PhotoshopReader.java | 2 +- Source/com/drew/metadata/webp/WebpRiffHandler.java | 2 +- Tests/com/drew/metadata/exif/ExifReaderTest.java | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/com/drew/metadata/exif/ExifReader.java b/Source/com/drew/metadata/exif/ExifReader.java index 4d90f7e7b..1c81e0ca9 100644 --- a/Source/com/drew/metadata/exif/ExifReader.java +++ b/Source/com/drew/metadata/exif/ExifReader.java @@ -63,7 +63,7 @@ public void readJpegSegments(@NotNull final Iterable segments, @NotNull for (byte[] segmentBytes : segments) { // Segment must have the expected preamble if (startsWithJpegExifPreamble(segmentBytes)) { - extract(new ByteArrayReader(segmentBytes, JPEG_SEGMENT_PREAMBLE.length()), metadata); + extract(new ByteArrayReader(segmentBytes, JPEG_SEGMENT_PREAMBLE.length()), metadata, JPEG_SEGMENT_PREAMBLE.length()); } } } @@ -76,15 +76,15 @@ public static boolean startsWithJpegExifPreamble(byte[] bytes) } /** Reads TIFF formatted Exif data a specified offset within a {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int preambleLength) { - extract(reader, metadata, null); + extract(reader, metadata, null, preambleLength); } /** Reads TIFF formatted Exif data at a specified offset within a {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory) + public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory, int exifStartOffset) { - ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, /*readerOffset*/ 0); // FIXME what to do? + ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, exifStartOffset); try { // Read the TIFF-formatted Exif data diff --git a/Source/com/drew/metadata/heif/HeifPictureHandler.java b/Source/com/drew/metadata/heif/HeifPictureHandler.java index 088a9d636..e60ee9370 100644 --- a/Source/com/drew/metadata/heif/HeifPictureHandler.java +++ b/Source/com/drew/metadata/heif/HeifPictureHandler.java @@ -151,7 +151,7 @@ private void handleItem(@NotNull ItemInfoBox.ItemInfoEntry entry, } payloadReader.skip(tiffHeaderOffset); ByteArrayInputStream tiffStream = new ByteArrayInputStream(payloadReader.getBytes(payloadReader.available())); - new ExifReader().extract(new RandomAccessStreamReader(tiffStream), metadata); + new ExifReader().extract(new RandomAccessStreamReader(tiffStream), metadata, 0); } } diff --git a/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java b/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java index 7d6b369ba..4ed68c28d 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java @@ -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, directory); + new ExifReader().extract(new ByteArrayReader(payload, 16), metadata, directory, 0); 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 c494e17c2..79488cb17 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopReader.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopReader.java @@ -151,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, directory); + new ExifReader().extract(new ByteArrayReader(tagBytes), metadata, directory, 0); 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/webp/WebpRiffHandler.java b/Source/com/drew/metadata/webp/WebpRiffHandler.java index d1ac57c16..510834af2 100644 --- a/Source/com/drew/metadata/webp/WebpRiffHandler.java +++ b/Source/com/drew/metadata/webp/WebpRiffHandler.java @@ -83,7 +83,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) ByteArrayReader reader = ExifReader.startsWithJpegExifPreamble(payload) ? new ByteArrayReader(payload, ExifReader.JPEG_SEGMENT_PREAMBLE.length()) : new ByteArrayReader(payload); - new ExifReader().extract(reader, _metadata); + new ExifReader().extract(reader, _metadata, 0); } else if (fourCC.equals(WebpDirectory.CHUNK_ICCP)) { new IccReader().extract(new ByteArrayReader(payload), _metadata); } else if (fourCC.equals(WebpDirectory.CHUNK_XMP)) { diff --git a/Tests/com/drew/metadata/exif/ExifReaderTest.java b/Tests/com/drew/metadata/exif/ExifReaderTest.java index 3545bbb91..8cc4c7012 100644 --- a/Tests/com/drew/metadata/exif/ExifReaderTest.java +++ b/Tests/com/drew/metadata/exif/ExifReaderTest.java @@ -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, ExifReader.JPEG_SEGMENT_PREAMBLE.length()), metadata, null); + new ExifReader().extract(new ByteArrayReader(bytes, ExifReader.JPEG_SEGMENT_PREAMBLE.length()), metadata, null, ExifReader.JPEG_SEGMENT_PREAMBLE.length()); return metadata; } From f5ddfcd9e2a6235312436a7123a4e82a03fb43ed Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sat, 18 Jun 2022 21:54:07 +0200 Subject: [PATCH 3/7] Add IndexedReader.GetUInt64 --- Source/com/drew/lang/RandomAccessReader.java | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Source/com/drew/lang/RandomAccessReader.java b/Source/com/drew/lang/RandomAccessReader.java index 2e5c66b98..8424d6917 100644 --- a/Source/com/drew/lang/RandomAccessReader.java +++ b/Source/com/drew/lang/RandomAccessReader.java @@ -332,6 +332,41 @@ public long getInt64(int index) throws IOException } } + /** + * Get an unsigned 64-bit integer from the buffer. + * + * @param index position within the data buffer to read first byte + * @return the 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF + * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative + */ + public long getUInt64(int index) throws IOException + { + validateIndex(index, 8); + if (_isMotorolaByteOrder) + { + // Motorola - MSB first + return + (long)getByte(index ) << 56 | + (long)getByte(index + 1) << 48 | + (long)getByte(index + 2) << 40 | + (long)getByte(index + 3) << 32 | + (long)getByte(index + 4) << 24 | + (long)getByte(index + 5) << 16 | + (long)getByte(index + 6) << 8 | + getByte(index + 7); + } + // Intel ordering - LSB first + return + (long)getByte(index + 7) << 56 | + (long)getByte(index + 6) << 48 | + (long)getByte(index + 5) << 40 | + (long)getByte(index + 4) << 32 | + (long)getByte(index + 3) << 24 | + (long)getByte(index + 2) << 16 | + (long)getByte(index + 1) << 8 | + getByte(index ); + } + /** * Gets a s15.16 fixed point float from the buffer. *

From 6964deb304b8dceff9be682e6de99bd97e490652 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sat, 18 Jun 2022 22:57:02 +0200 Subject: [PATCH 4/7] Support BigTIFF encoded TIFF data Note that while BigTIFF supports files greater than 2 GiB in size, our current implementation does not due to the pervasive use of Int32 throughout the code to represent offsets into the data. --- Source/com/drew/imaging/FileTypeDetector.java | 4 +- .../com/drew/imaging/tiff/TiffDataFormat.java | 16 +- Source/com/drew/imaging/tiff/TiffHandler.java | 10 +- Source/com/drew/imaging/tiff/TiffReader.java | 202 +++++++++++++----- .../com/drew/imaging/tiff/TiffStandard.java | 34 +++ .../drew/metadata/exif/ExifTiffHandler.java | 85 ++++---- .../photoshop/PhotoshopTiffHandler.java | 5 +- .../metadata/tiff/DirectoryTiffHandler.java | 49 ++++- 8 files changed, 309 insertions(+), 96 deletions(-) create mode 100644 Source/com/drew/imaging/tiff/TiffStandard.java diff --git a/Source/com/drew/imaging/FileTypeDetector.java b/Source/com/drew/imaging/FileTypeDetector.java index fda22575e..e41d79a3b 100644 --- a/Source/com/drew/imaging/FileTypeDetector.java +++ b/Source/com/drew/imaging/FileTypeDetector.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. @@ -54,6 +54,8 @@ public class FileTypeDetector _root.addPath(FileType.Jpeg, new byte[]{(byte)0xff, (byte)0xd8}); _root.addPath(FileType.Tiff, "II".getBytes(), new byte[]{0x2a, 0x00}); _root.addPath(FileType.Tiff, "MM".getBytes(), new byte[]{0x00, 0x2a}); + _root.addPath(FileType.Tiff, "II".getBytes(), new byte[]{0x2b, 0x00}); // BigTIFF + _root.addPath(FileType.Tiff, "MM".getBytes(), new byte[]{0x00, 0x2b}); // BigTIFF _root.addPath(FileType.Psd, "8BPS".getBytes()); _root.addPath(FileType.Png, new byte[]{(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52}); _root.addPath(FileType.Bmp, "BM".getBytes()); // Standard Bitmap Windows and OS/2 diff --git a/Source/com/drew/imaging/tiff/TiffDataFormat.java b/Source/com/drew/imaging/tiff/TiffDataFormat.java index 83176b7fc..30c15985b 100644 --- a/Source/com/drew/imaging/tiff/TiffDataFormat.java +++ b/Source/com/drew/imaging/tiff/TiffDataFormat.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. @@ -42,6 +42,10 @@ public class TiffDataFormat public static final int CODE_RATIONAL_S = 10; public static final int CODE_SINGLE = 11; public static final int CODE_DOUBLE = 12; + // From BigTIFF + public static final int CODE_INT64_U = 16; + public static final int CODE_INT64_S = 17; + public static final int CODE_IFD8 = 18; @NotNull public static final TiffDataFormat INT8_U = new TiffDataFormat("BYTE", CODE_INT8_U, 1); @NotNull public static final TiffDataFormat STRING = new TiffDataFormat("STRING", CODE_STRING, 1); @@ -55,6 +59,10 @@ public class TiffDataFormat @NotNull public static final TiffDataFormat RATIONAL_S = new TiffDataFormat("SRATIONAL", CODE_RATIONAL_S, 8); @NotNull public static final TiffDataFormat SINGLE = new TiffDataFormat("SINGLE", CODE_SINGLE, 4); @NotNull public static final TiffDataFormat DOUBLE = new TiffDataFormat("DOUBLE", CODE_DOUBLE, 8); + // From BigTIFF + @NotNull public static final TiffDataFormat INT64_U = new TiffDataFormat("ULONG8", CODE_INT64_U, 8); + @NotNull public static final TiffDataFormat INT64_S = new TiffDataFormat("SLONG8", CODE_INT64_S, 8); + @NotNull public static final TiffDataFormat IFD8 = new TiffDataFormat("IFD8", CODE_IFD8, 8); @NotNull private final String _name; @@ -62,7 +70,7 @@ public class TiffDataFormat private final int _componentSizeBytes; @Nullable - public static TiffDataFormat fromTiffFormatCode(int tiffFormatCode) + public static TiffDataFormat fromTiffFormatCode(int tiffFormatCode, boolean isBigTiff) { switch (tiffFormatCode) { case 1: return INT8_U; @@ -77,6 +85,10 @@ public static TiffDataFormat fromTiffFormatCode(int tiffFormatCode) case 10: return RATIONAL_S; case 11: return SINGLE; case 12: return DOUBLE; + // From BigTIFF + case 16: return isBigTiff ? INT64_U : null; + case 17: return isBigTiff ? INT64_S : null; + case 18: return isBigTiff ? IFD8 : null; } return null; } diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index 5349dccba..6e3f33948 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -44,8 +44,9 @@ public interface TiffHandler * validation or perhaps differentiating the type of mapping to use for observed tags and IFDs. * * @param marker the 2-byte value found at position 2 of the TIFF header + * @return The TIFF standard via which to interpret the data stream. */ - void setTiffMarker(int marker) throws TiffProcessingException; + TiffStandard processTiffMarker(int marker) throws TiffProcessingException; boolean tryEnterSubIfd(int tagId); boolean hasFollowerIfd(); @@ -59,7 +60,8 @@ boolean customProcessTag(int tagOffset, @NotNull Set processedIfdOffsets, @NotNull RandomAccessReader reader, int tagId, - int byteCount) throws IOException; + int byteCount, + boolean isBigTiff) throws IOException; void warn(@NotNull String message); void error(@NotNull String message); @@ -84,4 +86,8 @@ boolean customProcessTag(int tagOffset, void setInt32sArray(int tagId, @NotNull int[] array); void setInt32u(int tagId, long int32u); void setInt32uArray(int tagId, @NotNull long[] array); + void setInt64S(int tagId, long int64S); + void setInt64SArray(int tagId, @NotNull long[] array); + void setInt64U(int tagId, long int64U); + void setInt64UArray(int tagId, @NotNull long[] array); } diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index 1a0b8a77a..d01abb986 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -47,6 +47,22 @@ public class TiffReader public void processTiff(@NotNull final RandomAccessReader reader, @NotNull final TiffHandler handler) throws TiffProcessingException, IOException { + // Standard TIFF + // + // TIFF Header: + // - 2 bytes: byte order (MM or II) + // - 2 bytes: version (always 42) + // - 4 bytes: offset to first IFD + + // Big TIFF + // + // TIFF Header: + // - 2 bytes: byte order (MM or II) + // - 2 bytes: version (always 43) + // - 2 bytes: byte size of offsets (always 8) + // - 2 bytes: reserved (always 0) + // - 8 bytes: offset to first IFD + // This must be either "MM" or "II". short byteOrderIdentifier = reader.getInt16(0); @@ -60,49 +76,91 @@ public void processTiff(@NotNull final RandomAccessReader reader, // Check the next two values for correctness. final int tiffMarker = reader.getUInt16(2); - handler.setTiffMarker(tiffMarker); + final TiffStandard tiffStandard = handler.processTiffMarker(tiffMarker); + + boolean isBigTiff; 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 = 2 + 2 + 4; + switch (tiffStandard) + { + case TIFF: + isBigTiff = false; + firstIfdOffset = (int) reader.getUInt32(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 = 2 + 2 + 4; + } + + break; + + case BIG_TIFF: + isBigTiff = true; + short offsetByteSize = reader.getInt16(4); + + if (offsetByteSize != 8) + { + handler.error("Unsupported offset byte size: {offsetByteSize}"); + return; + } + + // There are two reserved bytes at offset 6, which are expected to have zero value. + // We skip without validation for now, but may change this in future. + + firstIfdOffset = (int) reader.getUInt64(8); + break; + + default: + handler.error("Unsupported TiffStandard {tiffStandard}."); + return; } Set processedIfdOffsets = new HashSet(); - processIfd(handler, reader, processedIfdOffsets, firstIfdOffset); + processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, isBigTiff); } /** * Processes a TIFF IFD. * - * IFD Header: - *

    - *
  • 2 bytes number of tags
  • - *
- * Tag structure: - *
    - *
  • 2 bytes tag type
  • - *
  • 2 bytes format code (values 1 to 12, inclusive)
  • - *
  • 4 bytes component count
  • - *
  • 4 bytes inline value, or offset pointer if too large to fit in four bytes
  • - *
- * - * * @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 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 isBigTiff Whether the IFD uses the BigTIFF data format. * @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 processedGlobalIfdOffsets, - final int ifdOffset) throws IOException + final int ifdOffset, + final boolean isBigTiff) throws IOException { + // Standard TIFF + // + // IFD Header: + // - 2 bytes: number of tags + // + // Tag structure: + // - 2 bytes: tag type + // - 2 bytes: format code (values 1 to 12, inclusive) + // - 4 bytes: component count + // - 4 bytes: inline value, or offset pointer if too large to fit in four bytes + + // BigTIFF + // + // IFD Header: + // - 8 bytes: number of tags + // + // Tag structure: + // - 2 bytes: tag type + // - 2 bytes: format code (values 1 to 12, inclusive) + // - 8 bytes: component count + // - 8 bytes: inline value, or offset pointer if too large to fit in eight bytes + Boolean resetByteOrder = null; try { // Check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist. @@ -121,41 +179,46 @@ public static void processIfd(@NotNull final TiffHandler handler, return; } - // First two bytes in the IFD are the number of tags in this directory - int dirTagCount = reader.getUInt16(ifdOffset); + // The number of tags in this directory + int dirTagCount = isBigTiff + ? (int) reader.getUInt64(ifdOffset) + : reader.getUInt16(ifdOffset); // Some software modifies the byte order of the file, but misses some IFDs (such as makernotes). // The entire test image repository doesn't contain a single IFD with more than 255 entries. // Here we detect switched bytes that suggest this problem, and temporarily swap the byte order. // This was discussed in GitHub issue #136. - if (dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { + if (!isBigTiff && dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { resetByteOrder = reader.isMotorolaByteOrder(); dirTagCount >>= 8; reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); } - int dirLength = (2 + (12 * dirTagCount) + 4); + int dirLength = isBigTiff + ? 8 + 20 * dirTagCount + 8 + : 2 + 12 * dirTagCount + 4; if (dirLength + ifdOffset > reader.getLength()) { handler.error("Illegally sized IFD"); return; } + int inlineValueSize = isBigTiff ? 8 : 4; + // // Handle each tag in this directory // int invalidTiffFormatCodeCount = 0; for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) { - final int tagOffset = calculateTagOffset(ifdOffset, tagNumber); + final int tagOffset = calculateTagOffset(ifdOffset, tagNumber, isBigTiff); - // 2 bytes for the tag id final int tagId = reader.getUInt16(tagOffset); - // 2 bytes for the format code final int formatCode = reader.getUInt16(tagOffset + 2); - final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode); + final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode, isBigTiff); - // 4 bytes dictate the number of components in this tag's data - final long componentCount = reader.getUInt32(tagOffset + 4); + final long componentCount = isBigTiff + ? reader.getUInt64(tagOffset + 4) + : reader.getUInt32(tagOffset + 4); final long byteCount; if (format == null) { @@ -177,54 +240,64 @@ 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. - tagValueOffset = reader.getUInt32(tagOffset + 8); + if (byteCount > inlineValueSize) { + // Value(s) are too big to fit inline. Follow the pointer. + tagValueOffset = isBigTiff + ? reader.getUInt64(tagOffset + 12) + : reader.getUInt32(tagOffset + 8); if (tagValueOffset + byteCount > reader.getLength()) { - // Bogus pointer offset and / or byteCount value + // Bogus pointer offset and/or byteCount value handler.error("Illegal TIFF tag pointer offset"); continue; } } else { - // 4 bytes or less and value is in the dir entry itself. - tagValueOffset = tagOffset + 8; + // Value(s) can fit inline. + tagValueOffset = isBigTiff + ? tagOffset + 12 + : tagOffset + 8; } - if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) { + if (tagValueOffset > reader.getLength()) { handler.error("Illegal TIFF tag pointer offset"); continue; } // Check that this tag isn't going to allocate outside the bounds of the data array. // This addresses an uncommon OutOfMemoryError. - if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) { + if (tagValueOffset + byteCount > reader.getLength()) { handler.error("Illegal number of bytes for TIFF tag data: " + byteCount); continue; } // Some tags point to one or more additional IFDs to process boolean isIfdPointer = false; - if (byteCount == 4 * componentCount) { + if (byteCount == 4 * componentCount || formatCode == TiffDataFormat.CODE_IFD8) { for (int i = 0; i < componentCount; i++) { if (handler.tryEnterSubIfd(tagId)) { isIfdPointer = true; long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4)); - processIfd(handler, reader, processedGlobalIfdOffsets, (int) subDirOffset); + processIfd(handler, reader, processedGlobalIfdOffsets, (int) subDirOffset, isBigTiff); } } } // If it wasn't an IFD pointer, allow custom tag processing to occur - if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int) byteCount)) { + if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int) byteCount, isBigTiff)) { // If no custom processing occurred, process the tag in the standard fashion processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader); } } // at the end of each IFD is an optional link to the next IFD - final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount); - int nextIfdOffset = reader.getInt32(finalTagOffset); - if (nextIfdOffset != 0) { + final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount, isBigTiff); + + long nextIfdOffsetLong = isBigTiff + ? reader.getUInt64(finalTagOffset) + : reader.getUInt32(finalTagOffset); + + if (nextIfdOffsetLong != 0 && nextIfdOffsetLong <= Integer.MAX_VALUE) { + int nextIfdOffset = (int) nextIfdOffsetLong; + if (nextIfdOffset >= reader.getLength()) { // Last 4 bytes of IFD reference another IFD with an address that is out of bounds return; @@ -235,7 +308,7 @@ public static void processIfd(@NotNull final TiffHandler handler, } if (handler.hasFollowerIfd()) { - processIfd(handler, reader, processedGlobalIfdOffsets, nextIfdOffset); + processIfd(handler, reader, processedGlobalIfdOffsets, nextIfdOffset, isBigTiff); } } } finally { @@ -362,6 +435,32 @@ private static void processTag(@NotNull final TiffHandler handler, handler.setInt32uArray(tagId, array); } break; + case TiffDataFormat.CODE_INT64_S: + if (componentCount == 1) + { + handler.setInt64S(tagId, reader.getInt64(tagValueOffset)); + } + else + { + long[] array = new long[componentCount]; + for (int i = 0; i < componentCount; i++) + array[i] = reader.getInt64(tagValueOffset + i * 8); + handler.setInt64SArray(tagId, array); + } + break; + case TiffDataFormat.CODE_INT64_U: + if (componentCount == 1) + { + handler.setInt64U(tagId, reader.getUInt64(tagValueOffset)); + } + else + { + long[] array = new long[componentCount]; + for (int i = 0; i < componentCount; i++) + array[i] = reader.getUInt64(tagValueOffset + i * 8); + handler.setInt64UArray(tagId, array); + } + break; default: handler.error(String.format("Invalid TIFF tag format code %d for tag 0x%04X", formatCode, tagId)); } @@ -372,11 +471,14 @@ private static void processTag(@NotNull final TiffHandler handler, * * @param ifdStartOffset the offset at which the IFD starts * @param entryNumber the zero-based entry number + * @param isBigTiff Whether we are using BigTIFF encoding */ - private static int calculateTagOffset(int ifdStartOffset, int entryNumber) + private static int calculateTagOffset(int ifdStartOffset, int entryNumber, boolean isBigTiff) { // Add 2 bytes for the tag count. - // Each entry is 12 bytes. - return ifdStartOffset + 2 + (12 * entryNumber); + // Each entry is 12 bytes for regular TIFF, or 20 bytes for BigTIFF. + return !isBigTiff + ? ifdStartOffset + 2 + 12 * entryNumber + : ifdStartOffset + 8 + 20 * entryNumber; } } diff --git a/Source/com/drew/imaging/tiff/TiffStandard.java b/Source/com/drew/imaging/tiff/TiffStandard.java new file mode 100644 index 000000000..5973395c0 --- /dev/null +++ b/Source/com/drew/imaging/tiff/TiffStandard.java @@ -0,0 +1,34 @@ +/* + * 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.tiff; + +public enum TiffStandard { + + /** + * Regular TIFF. + */ + TIFF, + /** + * The "BigTIFF" standard, which supports greater than 4GB files, more entries + * in IFDs, and larger tag value arrays. + */ + BIG_TIFF +} diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index dcf535be2..2867ca21c 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -29,6 +29,7 @@ import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.imaging.tiff.TiffProcessingException; import com.drew.imaging.tiff.TiffReader; +import com.drew.imaging.tiff.TiffStandard; import com.drew.lang.BufferBoundsException; import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; @@ -66,15 +67,17 @@ public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDir } @Override - public void setTiffMarker(int marker) throws TiffProcessingException + public TiffStandard processTiffMarker(int marker) throws TiffProcessingException { final int standardTiffMarker = 0x002A; + final int bigTiffMarker = 0x002B; final int olympusRawTiffMarker = 0x4F52; // for ORF files final int olympusRawTiffMarker2 = 0x5352; // for ORF files final int panasonicRawTiffMarker = 0x0055; // for RW2 files switch (marker) { case standardTiffMarker: + case bigTiffMarker: case olympusRawTiffMarker: // TODO implement an IFD0, if there is one case olympusRawTiffMarker2: // TODO implement an IFD0, if there is one pushDirectory(ExifIFD0Directory.class); @@ -85,6 +88,10 @@ public void setTiffMarker(int marker) throws TiffProcessingException default: throw new TiffProcessingException(String.format("Unexpected TIFF marker: 0x%X", marker)); } + + return marker == bigTiffMarker + ? TiffStandard.BIG_TIFF + : TiffStandard.TIFF; } @Override @@ -187,7 +194,8 @@ public boolean customProcessTag(final int tagOffset, final @NotNull Set processedIfdOffsets, final @NotNull RandomAccessReader reader, final int tagId, - final int byteCount) throws IOException + final int byteCount, + final boolean isBigTiff) throws IOException { assert(_currentDirectory != null); @@ -205,7 +213,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, reader); + return processMakernote(tagOffset, processedIfdOffsets, reader, isBigTiff); } // Custom processing for embedded IPTC data @@ -261,35 +269,35 @@ public boolean customProcessTag(final int tagOffset, switch (tagId) { case OlympusMakernoteDirectory.TAG_EQUIPMENT: pushDirectory(OlympusEquipmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS: pushDirectory(OlympusCameraSettingsMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT: pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2: pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING: pushDirectory(OlympusImageProcessingMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_FOCUS_INFO: pushDirectory(OlympusFocusInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_RAW_INFO: pushDirectory(OlympusRawInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_MAIN_INFO: pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; } } @@ -429,7 +437,8 @@ private static String getReaderString(final @NotNull RandomAccessReader reader, private boolean processMakernote(final int makernoteOffset, final @NotNull Set processedIfdOffsets, - final @NotNull RandomAccessReader reader) throws IOException + final @NotNull RandomAccessReader reader, + final boolean isBigTiff) throws IOException { assert(_currentDirectory != null); @@ -456,24 +465,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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); } 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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 12); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 12, isBigTiff); } 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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); } 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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) { if ("Nikon".equals(firstFiveChars)) { /* There are two scenarios here: @@ -487,11 +496,11 @@ private boolean processMakernote(final int makernoteOffset, switch (reader.getUInt8(makernoteOffset + 6)) { case 1: pushDirectory(NikonType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); break; case 2: pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset + 10), processedIfdOffsets, 8); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset + 10), processedIfdOffsets, 8, isBigTiff); break; default: _currentDirectory.addError("Unsupported Nikon makernote data ignored."); @@ -500,26 +509,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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) { pushDirectory(SonyType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, isBigTiff); // 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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } 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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, isBigTiff); } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) { pushDirectory(SigmaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, isBigTiff); } else if ("KDK".equals(firstThreeChars)) { reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO")); KodakMakernoteDirectory directory = new KodakMakernoteDirectory(); @@ -527,14 +536,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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } 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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, isBigTiff); } else { pushDirectory(CasioType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) { // Note that this also applies to certain Leica cameras, such as the Digilux-4.3 @@ -545,11 +554,11 @@ private boolean processMakernote(final int makernoteOffset, RandomAccessReader makernoteReader = reader.withShiftedBaseOffset(makernoteOffset); int ifdStart = makernoteReader.getInt32(8); pushDirectory(FujifilmMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, ifdStart); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, ifdStart, isBigTiff); } 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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22, isBigTiff); } else if ("LEICA".equals(firstFiveChars)) { reader.setMotorolaByteOrder(false); @@ -567,14 +576,14 @@ private boolean processMakernote(final int makernoteOffset, "LEICA\0\u0007\0".equals(firstEightChars)) { pushDirectory(LeicaType5MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } else if ("Leica Camera AG".equals(cameraMake)) { pushDirectory(LeicaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else if ("LEICA".equals(cameraMake)) { // Some Leica cameras use Panasonic makernote tags pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else { return false; } @@ -583,7 +592,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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, isBigTiff); } 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 @@ -591,7 +600,7 @@ private boolean processMakernote(final int makernoteOffset, // Observed for: // - Pentax ist D pushDirectory(CasioType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 6); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 6, isBigTiff); } 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 @@ -600,7 +609,7 @@ private boolean processMakernote(final int makernoteOffset, // - PENTAX Optio 330 // - PENTAX Optio 430 pushDirectory(PentaxMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 0); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 0, isBigTiff); // } 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 @@ -608,7 +617,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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } 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: @@ -621,14 +630,14 @@ private boolean processMakernote(final int makernoteOffset, // Always in Motorola byte order reader.setMotorolaByteOrder(true); pushDirectory(RicohMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } } 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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); reader.setMotorolaByteOrder(orderBefore); } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory(); @@ -645,7 +654,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); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } 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/photoshop/PhotoshopTiffHandler.java b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java index aa65eecb3..26522493e 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java @@ -41,7 +41,8 @@ public boolean customProcessTag(final int tagOffset, final int tiffHeaderOffset, final @NotNull RandomAccessReader reader, final int tagId, - final int byteCount) throws IOException + final int byteCount, + final boolean isBigTiff) throws IOException { switch (tagId) { case TAG_XMP: @@ -55,6 +56,6 @@ public boolean customProcessTag(final int tagOffset, return true; } - return super.customProcessTag(tagOffset, processedIfdOffsets, reader, tagId, byteCount); + return super.customProcessTag(tagOffset, processedIfdOffsets, reader, tagId, byteCount, isBigTiff); } } diff --git a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java index 21ed52917..3e06b2537 100644 --- a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java +++ b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.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. @@ -50,6 +50,7 @@ protected DirectoryTiffHandler(Metadata metadata, @Nullable Directory parentDire _rootParentDirectory = parentDirectory; } + @Override public void endingIFD() { _currentDirectory = _directoryStack.empty() ? null : _directoryStack.pop(); @@ -90,11 +91,13 @@ protected void pushDirectory(@NotNull Directory directory) _metadata.addDirectory(_currentDirectory); } + @Override public void warn(@NotNull String message) { getCurrentOrErrorDirectory().addError(message); } + @Override public void error(@NotNull String message) { getCurrentOrErrorDirectory().addError(message); @@ -112,112 +115,156 @@ private Directory getCurrentOrErrorDirectory() return _currentDirectory; } + @Override public void setByteArray(int tagId, @NotNull byte[] bytes) { _currentDirectory.setByteArray(tagId, bytes); } + @Override public void setString(int tagId, @NotNull StringValue string) { _currentDirectory.setStringValue(tagId, string); } + @Override public void setRational(int tagId, @NotNull Rational rational) { _currentDirectory.setRational(tagId, rational); } + @Override public void setRationalArray(int tagId, @NotNull Rational[] array) { _currentDirectory.setRationalArray(tagId, array); } + @Override public void setFloat(int tagId, float float32) { _currentDirectory.setFloat(tagId, float32); } + @Override public void setFloatArray(int tagId, @NotNull float[] array) { _currentDirectory.setFloatArray(tagId, array); } + @Override public void setDouble(int tagId, double double64) { _currentDirectory.setDouble(tagId, double64); } + @Override public void setDoubleArray(int tagId, @NotNull double[] array) { _currentDirectory.setDoubleArray(tagId, array); } + @Override public void setInt8s(int tagId, byte int8s) { // NOTE Directory stores all integral types as int32s, except for int32u and long _currentDirectory.setInt(tagId, int8s); } + @Override public void setInt8sArray(int tagId, @NotNull byte[] array) { // NOTE Directory stores all integral types as int32s, except for int32u and long _currentDirectory.setByteArray(tagId, array); } + @Override public void setInt8u(int tagId, short int8u) { // NOTE Directory stores all integral types as int32s, except for int32u and long _currentDirectory.setInt(tagId, int8u); } + @Override public void setInt8uArray(int tagId, @NotNull short[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + @Override public void setInt16s(int tagId, int int16s) { // TODO create and use a proper setter for int16u? _currentDirectory.setInt(tagId, int16s); } + @Override public void setInt16sArray(int tagId, @NotNull short[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + @Override public void setInt16u(int tagId, int int16u) { // TODO create and use a proper setter for _currentDirectory.setInt(tagId, int16u); } + @Override public void setInt16uArray(int tagId, @NotNull int[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + @Override public void setInt32s(int tagId, int int32s) { _currentDirectory.setInt(tagId, int32s); } + @Override public void setInt32sArray(int tagId, @NotNull int[] array) { _currentDirectory.setIntArray(tagId, array); } + @Override public void setInt32u(int tagId, long int32u) { _currentDirectory.setLong(tagId, int32u); } + @Override public void setInt32uArray(int tagId, @NotNull long[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + + @Override + public void setInt64S(int tagId, long int64S) + { + _currentDirectory.setLong(tagId, int64S); + } + + @Override + public void setInt64SArray(int tagId, @NotNull long[] array) + { + _currentDirectory.setObjectArray(tagId, array); + } + + @Override + public void setInt64U(int tagId, long int64U) + { + _currentDirectory.setLong(tagId, int64U); + } + + @Override + public void setInt64UArray(int tagId, @NotNull long[] array) + { + _currentDirectory.setObjectArray(tagId, array); + } } From dec9b6b23e187828fb449030c31c1918f06b9626 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sat, 18 Jun 2022 23:07:55 +0200 Subject: [PATCH 5/7] Change ITiffHandler.SetTiffMarker to accept ushort This will only ever be a 16-bit value. --- Source/com/drew/imaging/tiff/TiffHandler.java | 2 +- Source/com/drew/imaging/tiff/TiffReader.java | 2 +- Source/com/drew/metadata/exif/ExifTiffHandler.java | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index 6e3f33948..6f99ae922 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -46,7 +46,7 @@ public interface TiffHandler * @param marker the 2-byte value found at position 2 of the TIFF header * @return The TIFF standard via which to interpret the data stream. */ - TiffStandard processTiffMarker(int marker) throws TiffProcessingException; + TiffStandard processTiffMarker(short marker) throws TiffProcessingException; boolean tryEnterSubIfd(int tagId); boolean hasFollowerIfd(); diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index d01abb986..1182f9afd 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -75,7 +75,7 @@ public void processTiff(@NotNull final RandomAccessReader reader, } // Check the next two values for correctness. - final int tiffMarker = reader.getUInt16(2); + final short tiffMarker = (short) reader.getUInt16(2); final TiffStandard tiffStandard = handler.processTiffMarker(tiffMarker); boolean isBigTiff; diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index 2867ca21c..379628cc1 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -67,13 +67,13 @@ public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDir } @Override - public TiffStandard processTiffMarker(int marker) throws TiffProcessingException + public TiffStandard processTiffMarker(short marker) throws TiffProcessingException { - final int standardTiffMarker = 0x002A; - final int bigTiffMarker = 0x002B; - final int olympusRawTiffMarker = 0x4F52; // for ORF files - final int olympusRawTiffMarker2 = 0x5352; // for ORF files - final int panasonicRawTiffMarker = 0x0055; // for RW2 files + final short standardTiffMarker = 0x002A; + final short bigTiffMarker = 0x002B; + final short olympusRawTiffMarker = 0x4F52; // for ORF files + final short olympusRawTiffMarker2 = 0x5352; // for ORF files + final short panasonicRawTiffMarker = 0x0055; // for RW2 files switch (marker) { case standardTiffMarker: From 499ea8ac64f9496b17e647abbedc7ab9857492fd Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sat, 18 Jun 2022 23:12:38 +0200 Subject: [PATCH 6/7] Pass Set for processed IFD offsets This allows combining the add and test operations into a single lookup. --- Source/com/drew/imaging/tiff/TiffReader.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index 1182f9afd..00ec1d1d3 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -128,14 +128,14 @@ 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 processedGlobalIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop + * @param processedIfdOffsets 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 isBigTiff Whether the IFD uses the BigTIFF data format. * @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 processedGlobalIfdOffsets, + @NotNull final Set processedIfdOffsets, final int ifdOffset, final boolean isBigTiff) throws IOException { @@ -166,12 +166,11 @@ public static void processIfd(@NotNull final TiffHandler handler, // 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 - processedGlobalIfdOffsets.add(globalIfdOffset); + if (!processedIfdOffsets.add(globalIfdOffset)) { + return; + } // Validate IFD offset if (ifdOffset >= reader.getLength() || ifdOffset < 0) { @@ -276,13 +275,13 @@ public static void processIfd(@NotNull final TiffHandler handler, if (handler.tryEnterSubIfd(tagId)) { isIfdPointer = true; long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4)); - processIfd(handler, reader, processedGlobalIfdOffsets, (int) subDirOffset, isBigTiff); + processIfd(handler, reader, processedIfdOffsets, (int) subDirOffset, isBigTiff); } } } // If it wasn't an IFD pointer, allow custom tag processing to occur - if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int) byteCount, isBigTiff)) { + if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, reader, tagId, (int) byteCount, isBigTiff)) { // If no custom processing occurred, process the tag in the standard fashion processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader); } @@ -308,7 +307,7 @@ public static void processIfd(@NotNull final TiffHandler handler, } if (handler.hasFollowerIfd()) { - processIfd(handler, reader, processedGlobalIfdOffsets, nextIfdOffset, isBigTiff); + processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, isBigTiff); } } } finally { From f1e1ea564de3f0c4d706c4f079c3a4ec2c553a85 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 19 Jun 2022 01:27:49 +0200 Subject: [PATCH 7/7] Make RandomAccessReader.isMotorolaByteOrder read only. If the byte order changes during extraction, a new reader is made via WithByteOrder. That new reader is naturally scoped to the sub-reading operation, and the byte order does not need to be reverted. --- Source/com/drew/imaging/tiff/TiffReader.java | 16 ++---- Source/com/drew/lang/ByteArrayReader.java | 23 ++++++-- .../com/drew/lang/RandomAccessFileReader.java | 23 ++++++-- Source/com/drew/lang/RandomAccessReader.java | 23 +++----- .../drew/lang/RandomAccessStreamReader.java | 37 +++++++++--- .../com/drew/metadata/avi/AviRiffHandler.java | 12 ++-- Source/com/drew/metadata/eps/EpsReader.java | 4 +- .../metadata/exif/ExifDescriptorBase.java | 4 +- .../drew/metadata/exif/ExifTiffHandler.java | 56 +++++++------------ .../PanasonicMakernoteDirectory.java | 8 +-- .../makernotes/SonyTag9050bDirectory.java | 4 +- .../com/drew/metadata/wav/WavRiffHandler.java | 7 ++- .../drew/metadata/webp/WebpRiffHandler.java | 14 ++--- Tests/com/drew/lang/RandomAccessTestBase.java | 13 ++--- 14 files changed, 134 insertions(+), 110 deletions(-) diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index 00ec1d1d3..f3998ddd5 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -44,7 +44,7 @@ public class TiffReader * ignored or recovered from * @throws IOException an error occurred while accessing the required data */ - public void processTiff(@NotNull final RandomAccessReader reader, + public void processTiff(@NotNull RandomAccessReader reader, @NotNull final TiffHandler handler) throws TiffProcessingException, IOException { // Standard TIFF @@ -67,9 +67,9 @@ public void processTiff(@NotNull final RandomAccessReader reader, short byteOrderIdentifier = reader.getInt16(0); if (byteOrderIdentifier == 0x4d4d) { // "MM" - reader.setMotorolaByteOrder(true); + reader = reader.withByteOrder(true); } else if (byteOrderIdentifier == 0x4949) { // "II" - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); } else { throw new TiffProcessingException("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier); } @@ -134,7 +134,7 @@ public void processTiff(@NotNull final RandomAccessReader reader, * @throws IOException an error occurred while accessing the required data */ public static void processIfd(@NotNull final TiffHandler handler, - @NotNull final RandomAccessReader reader, + @NotNull RandomAccessReader reader, @NotNull final Set processedIfdOffsets, final int ifdOffset, final boolean isBigTiff) throws IOException @@ -161,7 +161,6 @@ public static void processIfd(@NotNull final TiffHandler handler, // - 8 bytes: component count // - 8 bytes: inline value, or offset pointer if too large to fit in eight bytes - Boolean resetByteOrder = null; try { // 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. @@ -169,7 +168,7 @@ public static void processIfd(@NotNull final TiffHandler handler, // remember that we've visited this directory so that we don't visit it again later if (!processedIfdOffsets.add(globalIfdOffset)) { - return; + return; } // Validate IFD offset @@ -188,9 +187,8 @@ public static void processIfd(@NotNull final TiffHandler handler, // Here we detect switched bytes that suggest this problem, and temporarily swap the byte order. // This was discussed in GitHub issue #136. if (!isBigTiff && dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { - resetByteOrder = reader.isMotorolaByteOrder(); dirTagCount >>= 8; - reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); + reader = reader.withByteOrder(!reader.isMotorolaByteOrder()); } int dirLength = isBigTiff @@ -312,8 +310,6 @@ public static void processIfd(@NotNull final TiffHandler handler, } } finally { handler.endingIFD(); - if (resetByteOrder != null) - reader.setMotorolaByteOrder(resetByteOrder); } } diff --git a/Source/com/drew/lang/ByteArrayReader.java b/Source/com/drew/lang/ByteArrayReader.java index 6a47d78c0..2e4f88667 100644 --- a/Source/com/drew/lang/ByteArrayReader.java +++ b/Source/com/drew/lang/ByteArrayReader.java @@ -51,6 +51,14 @@ public ByteArrayReader(@NotNull byte[] buffer) @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") public ByteArrayReader(@NotNull byte[] buffer, int baseOffset) { + this(buffer, baseOffset, true); + } + + @SuppressWarnings({ "ConstantConditions" }) + @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") + public ByteArrayReader(@NotNull byte[] buffer, int baseOffset, boolean isMotorolaByteOrder) + { + super(isMotorolaByteOrder); if (buffer == null) throw new NullPointerException(); if (baseOffset < 0) @@ -61,13 +69,20 @@ public ByteArrayReader(@NotNull byte[] buffer, int baseOffset) } @Override - public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException { + public ByteArrayReader withByteOrder(boolean isMotorolaByteOrder) { + if (isMotorolaByteOrder == isMotorolaByteOrder()) { + return this; + } else { + return new ByteArrayReader(_buffer, _baseOffset, isMotorolaByteOrder); + } + } + + @Override + public ByteArrayReader withShiftedBaseOffset(int shift) throws IOException { if (shift == 0) { return this; } else { - RandomAccessReader reader = new ByteArrayReader(_buffer, _baseOffset + shift); - reader.setMotorolaByteOrder(isMotorolaByteOrder()); - return reader; + return new ByteArrayReader(_buffer, _baseOffset + shift, isMotorolaByteOrder()); } } diff --git a/Source/com/drew/lang/RandomAccessFileReader.java b/Source/com/drew/lang/RandomAccessFileReader.java index 513bb0e34..37b2fa6c2 100644 --- a/Source/com/drew/lang/RandomAccessFileReader.java +++ b/Source/com/drew/lang/RandomAccessFileReader.java @@ -52,6 +52,14 @@ public RandomAccessFileReader(@NotNull RandomAccessFile file) throws IOException @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) throws IOException { + this(file, baseOffset, true); + } + + @SuppressWarnings({ "ConstantConditions" }) + @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") + public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset, boolean isMotorolaByteOrder) throws IOException + { + super(isMotorolaByteOrder); if (file == null) throw new NullPointerException(); @@ -61,13 +69,20 @@ public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) th } @Override - public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException { + public RandomAccessFileReader withByteOrder(boolean isMotorolaByteOrder) throws IOException { + if (isMotorolaByteOrder == isMotorolaByteOrder()) { + return this; + } else { + return new RandomAccessFileReader(_file, _baseOffset, isMotorolaByteOrder); + } + } + + @Override + public RandomAccessFileReader withShiftedBaseOffset(int shift) throws IOException { if (shift == 0) { return this; } else { - RandomAccessReader reader = new RandomAccessFileReader(_file, _baseOffset + shift); - reader.setMotorolaByteOrder(isMotorolaByteOrder()); - return reader; + return new RandomAccessFileReader(_file, _baseOffset + shift, isMotorolaByteOrder()); } } diff --git a/Source/com/drew/lang/RandomAccessReader.java b/Source/com/drew/lang/RandomAccessReader.java index 8424d6917..338d363bb 100644 --- a/Source/com/drew/lang/RandomAccessReader.java +++ b/Source/com/drew/lang/RandomAccessReader.java @@ -45,7 +45,14 @@ */ public abstract class RandomAccessReader { - private boolean _isMotorolaByteOrder = true; + private final boolean _isMotorolaByteOrder; + + protected RandomAccessReader(boolean isMotorolaByteOrder) + { + _isMotorolaByteOrder = isMotorolaByteOrder; + } + + public abstract RandomAccessReader withByteOrder(boolean isMotorolaByteOrder) throws IOException; public abstract RandomAccessReader withShiftedBaseOffset(int shift) throws IOException; @@ -106,20 +113,6 @@ public abstract class RandomAccessReader */ public abstract long getLength() throws IOException; - /** - * Sets the endianness of this reader. - *
    - *
  • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
  • - *
  • false for Intel (or little) endianness, with LSB before MSB.
  • - *
- * - * @param motorolaByteOrder true for Motorola/big endian, false for Intel/little endian - */ - public void setMotorolaByteOrder(boolean motorolaByteOrder) - { - _isMotorolaByteOrder = motorolaByteOrder; - } - /** * Gets the endianness of this reader. *
    diff --git a/Source/com/drew/lang/RandomAccessStreamReader.java b/Source/com/drew/lang/RandomAccessStreamReader.java index 3f4aae628..2ecc16b78 100644 --- a/Source/com/drew/lang/RandomAccessStreamReader.java +++ b/Source/com/drew/lang/RandomAccessStreamReader.java @@ -55,6 +55,12 @@ public RandomAccessStreamReader(@NotNull InputStream stream, int chunkLength) public RandomAccessStreamReader(@NotNull InputStream stream, int chunkLength, long streamLength) { + this(stream, chunkLength, streamLength, true); + } + + public RandomAccessStreamReader(@NotNull InputStream stream, int chunkLength, long streamLength, boolean isMotorolaByteOrder) + { + super(isMotorolaByteOrder); if (stream == null) throw new NullPointerException(); if (chunkLength <= 0) @@ -213,14 +219,21 @@ public byte[] getBytes(int index, int count) throws IOException return bytes; } + @Override + public RandomAccessReader withByteOrder(boolean isMotorolaByteOrder) { + if (isMotorolaByteOrder == isMotorolaByteOrder()) { + return this; + } else { + return new ShiftedRandomAccessStreamReader(this, 0, isMotorolaByteOrder); + } + } + @Override public RandomAccessReader withShiftedBaseOffset(int shift) { if (shift == 0) { return this; } else { - RandomAccessReader reader = new ShiftedRandomAccessStreamReader(this, shift); - reader.setMotorolaByteOrder(isMotorolaByteOrder()); - return reader; + return new ShiftedRandomAccessStreamReader(this, shift, isMotorolaByteOrder()); } } @@ -229,8 +242,9 @@ private static class ShiftedRandomAccessStreamReader extends RandomAccessReader private final RandomAccessStreamReader _baseReader; private final int _baseOffset; - public ShiftedRandomAccessStreamReader(RandomAccessStreamReader baseReader, int baseOffset) + public ShiftedRandomAccessStreamReader(RandomAccessStreamReader baseReader, int baseOffset, boolean isMotorolaByteOrder) { + super(isMotorolaByteOrder); if (baseOffset < 0) throw new IllegalArgumentException("Must be zero or greater."); @@ -239,13 +253,20 @@ public ShiftedRandomAccessStreamReader(RandomAccessStreamReader baseReader, int } @Override - public RandomAccessReader withShiftedBaseOffset(int shift) { + public ShiftedRandomAccessStreamReader withByteOrder(boolean isMotorolaByteOrder) throws IOException { + if (isMotorolaByteOrder == isMotorolaByteOrder()) { + return this; + } else { + return new ShiftedRandomAccessStreamReader(_baseReader, _baseOffset, isMotorolaByteOrder); + } + } + + @Override + public ShiftedRandomAccessStreamReader withShiftedBaseOffset(int shift) { if (shift == 0) { return this; } else { - RandomAccessReader reader = new ShiftedRandomAccessStreamReader(_baseReader, _baseOffset + shift); - reader.setMotorolaByteOrder(isMotorolaByteOrder()); - return reader; + return new ShiftedRandomAccessStreamReader(_baseReader, _baseOffset + shift, isMotorolaByteOrder()); } } diff --git a/Source/com/drew/metadata/avi/AviRiffHandler.java b/Source/com/drew/metadata/avi/AviRiffHandler.java index 49e7ad1ee..be4a7576d 100644 --- a/Source/com/drew/metadata/avi/AviRiffHandler.java +++ b/Source/com/drew/metadata/avi/AviRiffHandler.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. @@ -54,11 +54,13 @@ public AviRiffHandler(@NotNull Metadata metadata) metadata.addDirectory(_directory); } + @Override public boolean shouldAcceptRiffIdentifier(@NotNull String identifier) { return identifier.equals(AviDirectory.FORMAT); } + @Override public boolean shouldAcceptChunk(@NotNull String fourCC) { return fourCC.equals(AviDirectory.CHUNK_STREAM_HEADER) @@ -66,6 +68,7 @@ public boolean shouldAcceptChunk(@NotNull String fourCC) || fourCC.equals(AviDirectory.CHUNK_DATETIME_ORIGINAL); } + @Override public boolean shouldAcceptList(@NotNull String fourCC) { return fourCC.equals(AviDirectory.LIST_HEADER) @@ -73,12 +76,12 @@ public boolean shouldAcceptList(@NotNull String fourCC) || fourCC.equals(AviDirectory.FORMAT); } + @Override public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) { try { if (fourCC.equals(AviDirectory.CHUNK_STREAM_HEADER)) { - ByteArrayReader reader = new ByteArrayReader(payload); - reader.setMotorolaByteOrder(false); + ByteArrayReader reader = new ByteArrayReader(payload, 0, false); String fccType = new String(reader.getBytes(0, 4)); String fccHandler = new String(reader.getBytes(4, 4)); @@ -114,8 +117,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) } } } else if (fourCC.equals(AviDirectory.CHUNK_MAIN_HEADER)) { - ByteArrayReader reader = new ByteArrayReader(payload); - reader.setMotorolaByteOrder(false); + ByteArrayReader reader = new ByteArrayReader(payload, 0, false); // int dwMicroSecPerFrame = reader.getInt32(0); // int dwMaxBytesPerSec = reader.getInt32(4); diff --git a/Source/com/drew/metadata/eps/EpsReader.java b/Source/com/drew/metadata/eps/EpsReader.java index d816253dd..a1083cd10 100644 --- a/Source/com/drew/metadata/eps/EpsReader.java +++ b/Source/com/drew/metadata/eps/EpsReader.java @@ -53,7 +53,7 @@ public class EpsReader */ public void extract(@NotNull final InputStream inputStream, @NotNull final Metadata metadata) throws IOException { - RandomAccessStreamReader reader = new RandomAccessStreamReader(inputStream); + RandomAccessReader reader = new RandomAccessStreamReader(inputStream); EpsDirectory directory = new EpsDirectory(); metadata.addDirectory(directory); @@ -64,7 +64,7 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad */ switch (reader.getInt32(0)) { case 0xC5D0D3C6: - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); int postScriptOffset = reader.getInt32(4); int postScriptLength = reader.getInt32(8); int wmfOffset = reader.getInt32(12); diff --git a/Source/com/drew/metadata/exif/ExifDescriptorBase.java b/Source/com/drew/metadata/exif/ExifDescriptorBase.java index 2b2499c25..f21866826 100644 --- a/Source/com/drew/metadata/exif/ExifDescriptorBase.java +++ b/Source/com/drew/metadata/exif/ExifDescriptorBase.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. @@ -1158,7 +1158,7 @@ private int[] decodeCfaPattern(int tagType) if (end > values.length) // sanity check in case of byte order problems; calculated 'end' should be <= length of the values { // try swapping byte order (I have seen this order different than in EXIF) - reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); + reader = reader.withByteOrder(!reader.isMotorolaByteOrder()); item0 = reader.getInt16(0); item1 = reader.getInt16(2); diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index 379628cc1..f2c3f44d3 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -459,8 +459,6 @@ private boolean processMakernote(final int makernoteOffset, final String firstTwelveChars = getReaderString(reader, makernoteOffset, 12); final String firstFourteenChars = getReaderString(reader, makernoteOffset, 14); - boolean byteOrderBefore = reader.isMotorolaByteOrder(); - if ("OLYMP\0".equals(firstSixChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) { // Olympus Makernote // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ @@ -521,19 +519,17 @@ private boolean processMakernote(final int makernoteOffset, pushDirectory(SonyType1MakernoteDirectory.class); TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) { - // force MM for this directory - reader.setMotorolaByteOrder(true); + // force Motorola byte order for this directory // skip 12 byte header + 2 for "MM" + 6 pushDirectory(SonyType6MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, isBigTiff); + TiffReader.processIfd(this, reader.withByteOrder(true), processedIfdOffsets, makernoteOffset + 20, isBigTiff); } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) { pushDirectory(SigmaMakernoteDirectory.class); TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, isBigTiff); } else if ("KDK".equals(firstThreeChars)) { - reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO")); KodakMakernoteDirectory directory = new KodakMakernoteDirectory(); _metadata.addDirectory(directory); - processKodakMakernote(directory, makernoteOffset, reader); + processKodakMakernote(directory, makernoteOffset, reader.withByteOrder(firstSevenChars.equals("KDK INFO"))); } else if ("Canon".equalsIgnoreCase(cameraMake)) { pushDirectory(CanonMakernoteDirectory.class); TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); @@ -546,22 +542,18 @@ private boolean processMakernote(final int makernoteOffset, TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) { - // Note that this also applies to certain Leica cameras, such as the Digilux-4.3 - reader.setMotorolaByteOrder(false); - // 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) - RandomAccessReader makernoteReader = reader.withShiftedBaseOffset(makernoteOffset); + // Note that this also applies to certain Leica cameras, such as the Digilux-4.3. + // 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 + RandomAccessReader makernoteReader = reader.withShiftedBaseOffset(makernoteOffset).withByteOrder(false); int ifdStart = makernoteReader.getInt32(8); pushDirectory(FujifilmMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, ifdStart, isBigTiff); + TiffReader.processIfd(this, makernoteReader, processedIfdOffsets, ifdStart, isBigTiff); } 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, isBigTiff); } else if ("LEICA".equals(firstFiveChars)) { - reader.setMotorolaByteOrder(false); - // used by the X1/X2/X VARIO/T // (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG") // (X2 starts with "LEICA\0\x05\0", Make is "LEICA CAMERA AG") @@ -576,14 +568,14 @@ private boolean processMakernote(final int makernoteOffset, "LEICA\0\u0007\0".equals(firstEightChars)) { pushDirectory(LeicaType5MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); + TiffReader.processIfd(this, reader.withByteOrder(false).withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } else if ("Leica Camera AG".equals(cameraMake)) { pushDirectory(LeicaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); + TiffReader.processIfd(this, reader.withByteOrder(false), processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else if ("LEICA".equals(cameraMake)) { // Some Leica cameras use Panasonic makernote tags pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); + TiffReader.processIfd(this, reader.withByteOrder(false), processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else { return false; } @@ -628,17 +620,13 @@ private boolean processMakernote(final int makernoteOffset, return false; } else if (firstFiveChars.equalsIgnoreCase("Ricoh")) { // Always in Motorola byte order - reader.setMotorolaByteOrder(true); pushDirectory(RicohMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); + TiffReader.processIfd(this, reader.withByteOrder(true).withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } } 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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); - reader.setMotorolaByteOrder(orderBefore); + TiffReader.processIfd(this, reader.withByteOrder(true).withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory(); _metadata.addDirectory(directory); @@ -661,7 +649,6 @@ private boolean processMakernote(final int makernoteOffset, return false; } - reader.setMotorolaByteOrder(byteOrderBefore); return true; } @@ -696,8 +683,6 @@ private static boolean handlePrintIM(@NotNull final Directory directory, final i */ private static void processPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException { - Boolean resetByteOrder = null; - if (byteCount == 0) { directory.addError("Empty PrintIM data"); return; @@ -715,14 +700,14 @@ private static void processPrintIM(@NotNull final PrintIMDirectory directory, fi return; } + RandomAccessReader localReader = reader; // check size of PrintIM block - int num = reader.getUInt16(tagValueOffset + 14); + int num = localReader.getUInt16(tagValueOffset + 14); if (byteCount < 16 + num * 6) { // size is too big, maybe byte ordering is wrong - resetByteOrder = reader.isMotorolaByteOrder(); - reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); - num = reader.getUInt16(tagValueOffset + 14); + localReader = reader.withByteOrder(!reader.isMotorolaByteOrder()); + num = localReader.getUInt16(tagValueOffset + 14); if (byteCount < 16 + num * 6) { directory.addError("Bad PrintIM size"); return; @@ -735,14 +720,11 @@ private static void processPrintIM(@NotNull final PrintIMDirectory directory, fi for (int n = 0; n < num; n++) { int pos = tagValueOffset + 16 + n * 6; - int tag = reader.getUInt16(pos); - long val = reader.getUInt32(pos + 2); + int tag = localReader.getUInt16(pos); + long val = localReader.getUInt32(pos + 2); directory.setObject(tag, val); } - - if (resetByteOrder != null) - reader.setMotorolaByteOrder(resetByteOrder); } private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader) diff --git a/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java b/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java index ce17249c5..e3dfd5eee 100644 --- a/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java +++ b/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.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. @@ -637,8 +637,7 @@ public Face[] getDetectedFaces() if (bytes==null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); - reader.setMotorolaByteOrder(false); + RandomAccessReader reader = new ByteArrayReader(bytes, 0, false); try { int faceCount = reader.getUInt16(0); @@ -668,8 +667,7 @@ public Face[] getRecognizedFaces() if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); - reader.setMotorolaByteOrder(false); + RandomAccessReader reader = new ByteArrayReader(bytes, 0, false); try { int faceCount = reader.getUInt16(0); diff --git a/Source/com/drew/metadata/exif/makernotes/SonyTag9050bDirectory.java b/Source/com/drew/metadata/exif/makernotes/SonyTag9050bDirectory.java index 74f74d49f..814d1cdd7 100644 --- a/Source/com/drew/metadata/exif/makernotes/SonyTag9050bDirectory.java +++ b/Source/com/drew/metadata/exif/makernotes/SonyTag9050bDirectory.java @@ -1,6 +1,7 @@ package com.drew.metadata.exif.makernotes; import com.drew.lang.ByteArrayReader; +import com.drew.lang.RandomAccessReader; import com.drew.lang.annotations.NotNull; import java.io.IOException; @@ -72,8 +73,7 @@ public static SonyTag9050bDirectory read(byte[] bytes) // First, decipher the bytes decipherInPlace(bytes); - ByteArrayReader reader = new ByteArrayReader(bytes); - reader.setMotorolaByteOrder(false); + ByteArrayReader reader = new ByteArrayReader(bytes).withByteOrder(false); // Shutter int offset = TAG_SHUTTER; diff --git a/Source/com/drew/metadata/wav/WavRiffHandler.java b/Source/com/drew/metadata/wav/WavRiffHandler.java index 6f73e1e8f..73dd1c695 100644 --- a/Source/com/drew/metadata/wav/WavRiffHandler.java +++ b/Source/com/drew/metadata/wav/WavRiffHandler.java @@ -2,6 +2,7 @@ import com.drew.imaging.riff.RiffHandler; import com.drew.lang.ByteArrayReader; +import com.drew.lang.RandomAccessReader; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.MetadataException; @@ -39,11 +40,13 @@ public WavRiffHandler(@NotNull Metadata metadata) metadata.addDirectory(_directory); } + @Override public boolean shouldAcceptRiffIdentifier(@NotNull String identifier) { return identifier.equals(WavDirectory.FORMAT); } + @Override public boolean shouldAcceptChunk(@NotNull String fourCC) { return fourCC.equals(WavDirectory.CHUNK_FORMAT) @@ -63,12 +66,12 @@ public boolean shouldAcceptList(@NotNull String fourCC) } } + @Override public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) { try { if (fourCC.equals(WavDirectory.CHUNK_FORMAT)) { - ByteArrayReader reader = new ByteArrayReader(payload); - reader.setMotorolaByteOrder(false); + ByteArrayReader reader = new ByteArrayReader(payload, 0, false); int wFormatTag = reader.getInt16(0); int wChannels = reader.getInt16(2); int dwSamplesPerSec = reader.getInt32(4); diff --git a/Source/com/drew/metadata/webp/WebpRiffHandler.java b/Source/com/drew/metadata/webp/WebpRiffHandler.java index 510834af2..0148e3c54 100644 --- a/Source/com/drew/metadata/webp/WebpRiffHandler.java +++ b/Source/com/drew/metadata/webp/WebpRiffHandler.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. @@ -53,11 +53,13 @@ public WebpRiffHandler(@NotNull Metadata metadata) _metadata = metadata; } + @Override public boolean shouldAcceptRiffIdentifier(@NotNull String identifier) { return identifier.equals(WebpDirectory.FORMAT); } + @Override public boolean shouldAcceptChunk(@NotNull String fourCC) { return fourCC.equals(WebpDirectory.CHUNK_VP8X) @@ -74,6 +76,7 @@ public boolean shouldAcceptList(@NotNull String fourCC) return false; } + @Override public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) { WebpDirectory directory = new WebpDirectory(); @@ -89,8 +92,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) } else if (fourCC.equals(WebpDirectory.CHUNK_XMP)) { new XmpReader().extract(payload, _metadata); } else if (fourCC.equals(WebpDirectory.CHUNK_VP8X) && payload.length == 10) { - RandomAccessReader reader = new ByteArrayReader(payload); - reader.setMotorolaByteOrder(false); + RandomAccessReader reader = new ByteArrayReader(payload, 0, false); try { // Flags @@ -116,8 +118,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) directory.addError(e.getMessage()); } } else if (fourCC.equals(WebpDirectory.CHUNK_VP8L) && payload.length > 4) { - RandomAccessReader reader = new ByteArrayReader(payload); - reader.setMotorolaByteOrder(false); + RandomAccessReader reader = new ByteArrayReader(payload, 0, false); try { // https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#2_riff_header @@ -143,8 +144,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) directory.addError(e.getMessage()); } } else if (fourCC.equals(WebpDirectory.CHUNK_VP8) && payload.length > 9) { - RandomAccessReader reader = new ByteArrayReader(payload); - reader.setMotorolaByteOrder(false); + RandomAccessReader reader = new ByteArrayReader(payload, 0, false); try { // https://tools.ietf.org/html/rfc6386#section-9.1 diff --git a/Tests/com/drew/lang/RandomAccessTestBase.java b/Tests/com/drew/lang/RandomAccessTestBase.java index 68ee674a7..d7a69e92c 100644 --- a/Tests/com/drew/lang/RandomAccessTestBase.java +++ b/Tests/com/drew/lang/RandomAccessTestBase.java @@ -94,7 +94,7 @@ public void testGetInt16() throws Exception assertEquals((short)0x017F, reader.getInt16(1)); assertEquals((short)0x7FFF, reader.getInt16(2)); - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); assertEquals((short)0x0100, reader.getInt16(0)); assertEquals((short)0x7F01, reader.getInt16(1)); @@ -111,7 +111,7 @@ public void testGetUInt16() throws Exception assertEquals(0x017F, reader.getUInt16(1)); assertEquals(0x7FFF, reader.getUInt16(2)); - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); assertEquals(0x0100, reader.getUInt16(0)); assertEquals(0x7F01, reader.getUInt16(1)); @@ -143,7 +143,7 @@ public void testGetInt32() throws Exception assertEquals(0x7FFF0203, reader.getInt32(2)); assertEquals(0xFF020304, reader.getInt32(3)); - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); assertEquals(0xFF7F0100, reader.getInt32(0)); assertEquals(0x02FF7F01, reader.getInt32(1)); @@ -164,7 +164,7 @@ public void testGetUInt32() throws Exception assertEquals(0x7FFF0203L, reader.getUInt32(2)); assertEquals(0xFF020304L, reader.getUInt32(3)); - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); assertEquals(4286513408L, reader.getUInt32(0)); assertEquals(0x02FF7F01L, reader.getUInt32(1)); @@ -193,7 +193,7 @@ public void testGetInt64() throws IOException assertEquals(0x0001020304050607L, reader.getInt64(0)); assertEquals(0x01020304050607FFL, reader.getInt64(1)); - reader.setMotorolaByteOrder(false); + reader = reader.withByteOrder(false); assertEquals(0x0706050403020100L, reader.getInt64(0)); assertEquals(0xFF07060504030201L, reader.getInt64(1)); @@ -342,8 +342,7 @@ public void testGetInt8EOF() throws Exception @Test public void testWithShiftedBaseOffset() throws Exception { - RandomAccessReader reader = createReader(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); - reader.setMotorolaByteOrder(false); + RandomAccessReader reader = createReader(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}).withByteOrder(false); assertEquals(10, reader.getLength()); assertEquals(0, reader.getByte(0));