Skip to content

Commit

Permalink
Simplify TIFF processing by 'shifting' base of indexed reader, rather…
Browse files Browse the repository at this point in the history
… than passing TIFF header offsets around everywhere.
  • Loading branch information
don-vip committed Jul 20, 2024
1 parent 9f4ad4c commit 3a975db
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 106 deletions.
4 changes: 2 additions & 2 deletions Source/com/drew/imaging/jpeg/JpegMetadataReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData)
public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData) throws IOException
{
// Pass the appropriate byte arrays to each reader.
for (JpegSegmentMetadataReader reader : readers) {
Expand Down
25 changes: 24 additions & 1 deletion Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType);
void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) throws IOException;
}
4 changes: 2 additions & 2 deletions Source/com/drew/imaging/png/PngMetadataReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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());
Expand Down
3 changes: 1 addition & 2 deletions Source/com/drew/imaging/tiff/TiffHandler.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -57,7 +57,6 @@ public interface TiffHandler

boolean customProcessTag(int tagOffset,
@NotNull Set<Integer> processedIfdOffsets,
int tiffHeaderOffset,
@NotNull RandomAccessReader reader,
int tagId,
int byteCount) throws IOException;
Expand Down
4 changes: 2 additions & 2 deletions Source/com/drew/imaging/tiff/TiffMetadataReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
}
}
50 changes: 23 additions & 27 deletions Source/com/drew/imaging/tiff/TiffReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 <code>reader</code> 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);
Expand All @@ -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<Integer> processedIfdOffsets = new HashSet<Integer>();
processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset);
processIfd(handler, reader, processedIfdOffsets, firstIfdOffset);
}

/**
Expand All @@ -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 <code>reader</code> at which the IFD data starts
* @param tiffHeaderOffset the offset within <code>reader</code> 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<Integer> processedIfdOffsets,
final int ifdOffset,
final int tiffHeaderOffset) throws IOException
@NotNull final Set<Integer> 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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand All @@ -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?
Expand All @@ -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 {
Expand Down Expand Up @@ -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++)
Expand Down
13 changes: 12 additions & 1 deletion Source/com/drew/lang/ByteArrayReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
{
Expand Down
17 changes: 14 additions & 3 deletions Source/com/drew/lang/RandomAccessFileReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
{
Expand All @@ -69,7 +80,7 @@ public int toUnshiftedOffset(int localOffset)
@Override
public long getLength()
{
return _length;
return _length - _baseOffset;
}

@Override
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 3 additions & 1 deletion Source/com/drew/lang/RandomAccessReader.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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);

/**
Expand Down
Loading

0 comments on commit 3a975db

Please sign in to comment.