Skip to content

Commit a18b83d

Browse files
committed
Simplify TIFF processing by 'shifting' base of indexed reader, rather than passing TIFF header offsets around everywhere.
1 parent ae792e7 commit a18b83d

19 files changed

+253
-104
lines changed

Source/com/drew/imaging/jpeg/JpegMetadataReader.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* 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
126126
processJpegSegmentData(metadata, readers, segmentData);
127127
}
128128

129-
public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData)
129+
public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData) throws IOException
130130
{
131131
// Pass the appropriate byte arrays to each reader.
132132
for (JpegSegmentMetadataReader reader : readers) {

Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
1+
/*
2+
* Copyright 2002-2022 Drew Noakes and contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* More information about this project is available at:
17+
*
18+
* https://drewnoakes.com/code/exif/
19+
* https://github.com/drewnoakes/metadata-extractor
20+
*/
121
package com.drew.imaging.jpeg;
222

23+
import java.io.IOException;
24+
325
import com.drew.lang.annotations.NotNull;
426
import com.drew.metadata.Metadata;
527

@@ -21,6 +43,7 @@ public interface JpegSegmentMetadataReader
2143
* encountered in the original file.
2244
* @param metadata The {@link Metadata} object into which extracted values should be merged.
2345
* @param segmentType The {@link JpegSegmentType} being read.
46+
* @throws IOException an error occurred while accessing the required data
2447
*/
25-
void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType);
48+
void readJpegSegments(@NotNull final Iterable<byte[]> segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) throws IOException;
2649
}

Source/com/drew/imaging/png/PngMetadataReader.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -330,7 +330,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c
330330
} else if (chunkType.equals(PngChunkType.eXIf)) {
331331
try {
332332
ExifTiffHandler handler = new ExifTiffHandler(metadata, null);
333-
new TiffReader().processTiff(new ByteArrayReader(bytes), handler, 0);
333+
new TiffReader().processTiff(new ByteArrayReader(bytes), handler);
334334
} catch (TiffProcessingException ex) {
335335
PngDirectory directory = new PngDirectory(PngChunkType.eXIf);
336336
directory.addError(ex.getMessage());

Source/com/drew/imaging/tiff/TiffHandler.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,7 +57,6 @@ public interface TiffHandler
5757

5858
boolean customProcessTag(int tagOffset,
5959
@NotNull Set<Integer> processedIfdOffsets,
60-
int tiffHeaderOffset,
6160
@NotNull RandomAccessReader reader,
6261
int tagId,
6362
int byteCount) throws IOException;

Source/com/drew/imaging/tiff/TiffMetadataReader.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* 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
6868
{
6969
Metadata metadata = new Metadata();
7070
ExifTiffHandler handler = new ExifTiffHandler(metadata, null);
71-
new TiffReader().processTiff(reader, handler, 0);
71+
new TiffReader().processTiff(reader, handler);
7272
return metadata;
7373
}
7474
}

Source/com/drew/imaging/tiff/TiffReader.java

+23-27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,17 +40,15 @@ public class TiffReader
4040
*
4141
* @param reader the {@link RandomAccessReader} from which the data should be read
4242
* @param handler the {@link TiffHandler} that will coordinate processing and accept read values
43-
* @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
4443
* @throws TiffProcessingException if an error occurred during the processing of TIFF data that could not be
4544
* ignored or recovered from
4645
* @throws IOException an error occurred while accessing the required data
4746
*/
4847
public void processTiff(@NotNull final RandomAccessReader reader,
49-
@NotNull final TiffHandler handler,
50-
final int tiffHeaderOffset) throws TiffProcessingException, IOException
48+
@NotNull final TiffHandler handler) throws TiffProcessingException, IOException
5149
{
5250
// This must be either "MM" or "II".
53-
short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset);
51+
short byteOrderIdentifier = reader.getInt16(0);
5452

5553
if (byteOrderIdentifier == 0x4d4d) { // "MM"
5654
reader.setMotorolaByteOrder(true);
@@ -61,21 +59,21 @@ public void processTiff(@NotNull final RandomAccessReader reader,
6159
}
6260

6361
// Check the next two values for correctness.
64-
final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);
62+
final int tiffMarker = reader.getUInt16(2);
6563
handler.setTiffMarker(tiffMarker);
6664

67-
int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
65+
int firstIfdOffset = reader.getInt32(4);
6866

6967
// David Ekholm sent a digital camera image that has this problem
7068
// TODO getLength should be avoided as it causes RandomAccessStreamReader to read to the end of the stream
7169
if (firstIfdOffset >= reader.getLength() - 1) {
7270
handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset");
7371
// First directory normally starts immediately after the offset bytes, so try that
74-
firstIfdOffset = tiffHeaderOffset + 2 + 2 + 4;
72+
firstIfdOffset = 2 + 2 + 4;
7573
}
7674

7775
Set<Integer> processedIfdOffsets = new HashSet<Integer>();
78-
processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset);
76+
processIfd(handler, reader, processedIfdOffsets, firstIfdOffset);
7977
}
8078

8179
/**
@@ -96,27 +94,28 @@ public void processTiff(@NotNull final RandomAccessReader reader,
9694
*
9795
* @param handler the {@link com.drew.imaging.tiff.TiffHandler} that will coordinate processing and accept read values
9896
* @param reader the {@link com.drew.lang.RandomAccessReader} from which the data should be read
99-
* @param processedIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop
97+
* @param processedGlobalIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop
10098
* @param ifdOffset the offset within <code>reader</code> at which the IFD data starts
101-
* @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
10299
* @throws IOException an error occurred while accessing the required data
103100
*/
104101
public static void processIfd(@NotNull final TiffHandler handler,
105102
@NotNull final RandomAccessReader reader,
106-
@NotNull final Set<Integer> processedIfdOffsets,
107-
final int ifdOffset,
108-
final int tiffHeaderOffset) throws IOException
103+
@NotNull final Set<Integer> processedGlobalIfdOffsets,
104+
final int ifdOffset) throws IOException
109105
{
110106
Boolean resetByteOrder = null;
111107
try {
112-
// check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
113-
if (processedIfdOffsets.contains(Integer.valueOf(ifdOffset))) {
108+
// Check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist.
109+
// Note that we track these offsets in the global frame, not the reader's local frame.
110+
int globalIfdOffset = reader.toUnshiftedOffset(ifdOffset);
111+
if (processedGlobalIfdOffsets.contains(Integer.valueOf(globalIfdOffset))) {
114112
return;
115113
}
116114

117115
// remember that we've visited this directory so that we don't visit it again later
118-
processedIfdOffsets.add(ifdOffset);
116+
processedGlobalIfdOffsets.add(globalIfdOffset);
119117

118+
// Validate IFD offset
120119
if (ifdOffset >= reader.getLength() || ifdOffset < 0) {
121120
handler.error("Ignored IFD marked to start outside data segment");
122121
return;
@@ -180,13 +179,12 @@ public static void processIfd(@NotNull final TiffHandler handler,
180179
final long tagValueOffset;
181180
if (byteCount > 4) {
182181
// If it's bigger than 4 bytes, the dir entry contains an offset.
183-
final long offsetVal = reader.getUInt32(tagOffset + 8);
184-
if (offsetVal + byteCount > reader.getLength()) {
182+
tagValueOffset = reader.getUInt32(tagOffset + 8);
183+
if (tagValueOffset + byteCount > reader.getLength()) {
185184
// Bogus pointer offset and / or byteCount value
186185
handler.error("Illegal TIFF tag pointer offset");
187186
continue;
188187
}
189-
tagValueOffset = tiffHeaderOffset + offsetVal;
190188
} else {
191189
// 4 bytes or less and value is in the dir entry itself.
192190
tagValueOffset = tagOffset + 8;
@@ -210,14 +208,14 @@ public static void processIfd(@NotNull final TiffHandler handler,
210208
for (int i = 0; i < componentCount; i++) {
211209
if (handler.tryEnterSubIfd(tagId)) {
212210
isIfdPointer = true;
213-
int subDirOffset = tiffHeaderOffset + reader.getInt32((int) (tagValueOffset + i * 4));
214-
processIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset);
211+
long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4));
212+
processIfd(handler, reader, processedGlobalIfdOffsets, (int) subDirOffset);
215213
}
216214
}
217215
}
218216

219217
// If it wasn't an IFD pointer, allow custom tag processing to occur
220-
if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, (int) byteCount)) {
218+
if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int) byteCount)) {
221219
// If no custom processing occurred, process the tag in the standard fashion
222220
processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader);
223221
}
@@ -227,10 +225,8 @@ public static void processIfd(@NotNull final TiffHandler handler,
227225
final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount);
228226
int nextIfdOffset = reader.getInt32(finalTagOffset);
229227
if (nextIfdOffset != 0) {
230-
nextIfdOffset += tiffHeaderOffset;
231228
if (nextIfdOffset >= reader.getLength()) {
232229
// Last 4 bytes of IFD reference another IFD with an address that is out of bounds
233-
// Note this could have been caused by jhead 1.3 cropping too much
234230
return;
235231
} else if (nextIfdOffset < ifdOffset) {
236232
// TODO is this a valid restriction?
@@ -239,7 +235,7 @@ public static void processIfd(@NotNull final TiffHandler handler,
239235
}
240236

241237
if (handler.hasFollowerIfd()) {
242-
processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset);
238+
processIfd(handler, reader, processedGlobalIfdOffsets, nextIfdOffset);
243239
}
244240
}
245241
} finally {
@@ -326,7 +322,7 @@ private static void processTag(@NotNull final TiffHandler handler,
326322
break;
327323
case TiffDataFormat.CODE_INT16_S:
328324
if (componentCount == 1) {
329-
handler.setInt16s(tagId, (int)reader.getInt16(tagValueOffset));
325+
handler.setInt16s(tagId, reader.getInt16(tagValueOffset));
330326
} else {
331327
short[] array = new short[componentCount];
332328
for (int i = 0; i < componentCount; i++)

Source/com/drew/lang/ByteArrayReader.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,6 +60,17 @@ public ByteArrayReader(@NotNull byte[] buffer, int baseOffset)
6060
_baseOffset = baseOffset;
6161
}
6262

63+
@Override
64+
public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException {
65+
if (shift == 0) {
66+
return this;
67+
} else {
68+
RandomAccessReader reader = new ByteArrayReader(_buffer, _baseOffset + shift);
69+
reader.setMotorolaByteOrder(isMotorolaByteOrder());
70+
return reader;
71+
}
72+
}
73+
6374
@Override
6475
public int toUnshiftedOffset(int localOffset)
6576
{

Source/com/drew/lang/RandomAccessFileReader.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,6 +60,17 @@ public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) th
6060
_length = _file.length();
6161
}
6262

63+
@Override
64+
public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException {
65+
if (shift == 0) {
66+
return this;
67+
} else {
68+
RandomAccessReader reader = new RandomAccessFileReader(_file, _baseOffset + shift);
69+
reader.setMotorolaByteOrder(isMotorolaByteOrder());
70+
return reader;
71+
}
72+
}
73+
6374
@Override
6475
public int toUnshiftedOffset(int localOffset)
6576
{
@@ -69,7 +80,7 @@ public int toUnshiftedOffset(int localOffset)
6980
@Override
7081
public long getLength()
7182
{
72-
return _length;
83+
return _length - _baseOffset;
7384
}
7485

7586
@Override
@@ -108,7 +119,7 @@ private void seek(final int index) throws IOException
108119
if (index == _currentIndex)
109120
return;
110121

111-
_file.seek(index);
122+
_file.seek(index + _baseOffset);
112123
_currentIndex = index;
113124
}
114125

Source/com/drew/lang/RandomAccessReader.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 Drew Noakes and contributors
2+
* Copyright 2002-2022 Drew Noakes and contributors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,6 +47,8 @@ public abstract class RandomAccessReader
4747
{
4848
private boolean _isMotorolaByteOrder = true;
4949

50+
public abstract RandomAccessReader withShiftedBaseOffset(int shift) throws IOException;
51+
5052
public abstract int toUnshiftedOffset(int localOffset);
5153

5254
/**

0 commit comments

Comments
 (0)