From 3a975db1603e7917189cd76ea0eddc5d97d85c0d Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Wed, 15 Jun 2022 01:24:43 +0200 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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)); From 157b7ec15afe80721f991f208b8072b9d9dc976c Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 19 Jun 2022 00:12:11 +0200 Subject: [PATCH 08/13] Introduce TiffReaderContext We have been steadily adding parameters to members of ITiffHandler. We are about to add more to support GeoTIFF. Rather than do that, introduce a new type that encapsulates this common state. This makes the API simpler, and means we can add members in future without it being a breaking change. --- Source/com/drew/imaging/tiff/TiffHandler.java | 20 ++- Source/com/drew/imaging/tiff/TiffReader.java | 94 +++++++------- .../drew/imaging/tiff/TiffReaderContext.java | 109 ++++++++++++++++ .../drew/metadata/exif/ExifTiffHandler.java | 120 +++++++++--------- .../photoshop/PhotoshopTiffHandler.java | 20 +-- 5 files changed, 234 insertions(+), 129 deletions(-) create mode 100644 Source/com/drew/imaging/tiff/TiffReaderContext.java diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index 6f99ae922..266bcab8a 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -20,14 +20,12 @@ */ package com.drew.imaging.tiff; -import com.drew.lang.RandomAccessReader; import com.drew.lang.Rational; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.StringValue; import java.io.IOException; -import java.util.Set; /** * Interface of an class capable of handling events raised during the reading of a TIFF file @@ -56,12 +54,20 @@ public interface TiffHandler @Nullable Long tryCustomProcessFormat(int tagId, int formatCode, long componentCount); - boolean customProcessTag(int tagOffset, - @NotNull Set processedIfdOffsets, - @NotNull RandomAccessReader reader, + /** + * Allows handlers to provide custom logic for a given tag. + * + * @param context Context for the TIFF read operation. + * @param tagId The ID of the tag being processed. + * @param valueOffset The offset into the data stream at which the tag's value starts. + * @param byteCount The number of bytes that the tag's value spans. + * @return {@code true} if processing was successful and default processing should be suppressed, otherwise {@code false} + * @throws IOException if any I/O error occurs + */ + boolean customProcessTag(TiffReaderContext context, int tagId, - int byteCount, - boolean isBigTiff) throws IOException; + int valueOffset, + int byteCount) throws IOException; void warn(@NotNull String message); void error(@NotNull String message); diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index f3998ddd5..eecc7e0e9 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -25,8 +25,6 @@ import com.drew.lang.annotations.NotNull; import java.io.IOException; -import java.util.HashSet; -import java.util.Set; /** * Processes TIFF-formatted data, calling into client code via that {@link TiffHandler} interface. @@ -119,25 +117,22 @@ public void processTiff(@NotNull RandomAccessReader reader, return; } - Set processedIfdOffsets = new HashSet(); - processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, isBigTiff); + final TiffReaderContext context = new TiffReaderContext(reader, reader.isMotorolaByteOrder(), isBigTiff); + + processIfd(handler, context, firstIfdOffset); } /** * Processes a TIFF IFD. * - * @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 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 + * @param handler the {@link com.drew.imaging.tiff.TiffHandler} that will coordinate processing and accept read values. + * @param context Context for the TIFF read operation. + * @param ifdOffset the offset within reader at which the IFD data starts. + * @throws IOException an error occurred while accessing the required data. */ public static void processIfd(@NotNull final TiffHandler handler, - @NotNull RandomAccessReader reader, - @NotNull final Set processedIfdOffsets, - final int ifdOffset, - final boolean isBigTiff) throws IOException + @NotNull TiffReaderContext context, + final int ifdOffset) throws IOException { // Standard TIFF // @@ -163,59 +158,56 @@ public static void processIfd(@NotNull final TiffHandler handler, 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. - int globalIfdOffset = reader.toUnshiftedOffset(ifdOffset); - // remember that we've visited this directory so that we don't visit it again later - if (!processedIfdOffsets.add(globalIfdOffset)) { + if (!context.tryVisitIfd(ifdOffset)) { return; } // Validate IFD offset - if (ifdOffset >= reader.getLength() || ifdOffset < 0) { + if (ifdOffset >= context.getReader().getLength() || ifdOffset < 0) { handler.error("Ignored IFD marked to start outside data segment"); return; } // The number of tags in this directory - int dirTagCount = isBigTiff - ? (int) reader.getUInt64(ifdOffset) - : reader.getUInt16(ifdOffset); + int dirTagCount = context.isBigTiff() + ? (int) context.getReader().getUInt64(ifdOffset) + : context.getReader().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 (!isBigTiff && dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { + if (!context.isBigTiff() && dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { dirTagCount >>= 8; - reader = reader.withByteOrder(!reader.isMotorolaByteOrder()); + context = context.withByteOrder(!context.isMotorolaByteOrder()); } - int dirLength = isBigTiff + int dirLength = context.isBigTiff() ? 8 + 20 * dirTagCount + 8 : 2 + 12 * dirTagCount + 4; - if (dirLength + ifdOffset > reader.getLength()) { + if (dirLength + ifdOffset > context.getReader().getLength()) { handler.error("Illegally sized IFD"); return; } - int inlineValueSize = isBigTiff ? 8 : 4; + int inlineValueSize = context.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, isBigTiff); + final int tagOffset = calculateTagOffset(ifdOffset, tagNumber, context.isBigTiff()); - final int tagId = reader.getUInt16(tagOffset); + final int tagId = context.getReader().getUInt16(tagOffset); - final int formatCode = reader.getUInt16(tagOffset + 2); - final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode, isBigTiff); + final int formatCode = context.getReader().getUInt16(tagOffset + 2); + final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode, context.isBigTiff()); - final long componentCount = isBigTiff - ? reader.getUInt64(tagOffset + 4) - : reader.getUInt32(tagOffset + 4); + final long componentCount = context.isBigTiff() + ? context.getReader().getUInt64(tagOffset + 4) + : context.getReader().getUInt32(tagOffset + 4); final long byteCount; if (format == null) { @@ -239,29 +231,29 @@ public static void processIfd(@NotNull final TiffHandler handler, final long tagValueOffset; 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()) { + tagValueOffset = context.isBigTiff() + ? context.getReader().getUInt64(tagOffset + 12) + : context.getReader().getUInt32(tagOffset + 8); + if (tagValueOffset + byteCount > context.getReader().getLength()) { // Bogus pointer offset and/or byteCount value handler.error("Illegal TIFF tag pointer offset"); continue; } } else { // Value(s) can fit inline. - tagValueOffset = isBigTiff + tagValueOffset = context.isBigTiff() ? tagOffset + 12 : tagOffset + 8; } - if (tagValueOffset > reader.getLength()) { + if (tagValueOffset > context.getReader().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 (tagValueOffset + byteCount > reader.getLength()) { + if (tagValueOffset + byteCount > context.getReader().getLength()) { handler.error("Illegal number of bytes for TIFF tag data: " + byteCount); continue; } @@ -272,30 +264,30 @@ public static void processIfd(@NotNull final TiffHandler handler, for (int i = 0; i < componentCount; i++) { if (handler.tryEnterSubIfd(tagId)) { isIfdPointer = true; - long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4)); - processIfd(handler, reader, processedIfdOffsets, (int) subDirOffset, isBigTiff); + long subDirOffset = context.getReader().getUInt32((int) (tagValueOffset + i*4)); + processIfd(handler, context, (int) subDirOffset); } } } // If it wasn't an IFD pointer, allow custom tag processing to occur - if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, reader, tagId, (int) byteCount, isBigTiff)) { + if (!isIfdPointer && !handler.customProcessTag(context, tagId, (int) tagValueOffset, (int) byteCount)) { // If no custom processing occurred, process the tag in the standard fashion - processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader); + processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, context.getReader()); } } // at the end of each IFD is an optional link to the next IFD - final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount, isBigTiff); + final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount, context.isBigTiff()); - long nextIfdOffsetLong = isBigTiff - ? reader.getUInt64(finalTagOffset) - : reader.getUInt32(finalTagOffset); + long nextIfdOffsetLong = context.isBigTiff() + ? context.getReader().getUInt64(finalTagOffset) + : context.getReader().getUInt32(finalTagOffset); if (nextIfdOffsetLong != 0 && nextIfdOffsetLong <= Integer.MAX_VALUE) { int nextIfdOffset = (int) nextIfdOffsetLong; - if (nextIfdOffset >= reader.getLength()) { + if (nextIfdOffset >= context.getReader().getLength()) { // Last 4 bytes of IFD reference another IFD with an address that is out of bounds return; } else if (nextIfdOffset < ifdOffset) { @@ -305,7 +297,7 @@ public static void processIfd(@NotNull final TiffHandler handler, } if (handler.hasFollowerIfd()) { - processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, isBigTiff); + processIfd(handler, context, nextIfdOffset); } } } finally { diff --git a/Source/com/drew/imaging/tiff/TiffReaderContext.java b/Source/com/drew/imaging/tiff/TiffReaderContext.java new file mode 100644 index 000000000..acccb7fd6 --- /dev/null +++ b/Source/com/drew/imaging/tiff/TiffReaderContext.java @@ -0,0 +1,109 @@ +/* + * 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; + +import com.drew.lang.RandomAccessReader; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class TiffReaderContext { + + private final RandomAccessReader _reader; + private final boolean _isMotorolaByteOrder; + private final boolean _isBigTiff; + private final Set _visitedIfds; + + /** + * Gets an object via which TIFF data may be read by index. + * + * @return an object via which TIFF data may be read by index. + */ + public RandomAccessReader getReader() + { + return _reader; + } + + /** + * Gets whether the TIFF data advertises itself as having Motorola byte order. + * Note that this value may differ from the byte order of Reader + * if during reading it is determined that the byte order should change for some reason. + * + * @return whether the TIFF data advertises itself as having Motorola byte order. + */ + public boolean isMotorolaByteOrder() + { + return _isMotorolaByteOrder; + } + + /** + * Gets whether the TIFF data stream is encoded using the BigTIFF standard. + * + * @return whether the TIFF data stream is encoded using the BigTIFF standard. + */ + public boolean isBigTiff() + { + return _isBigTiff; + } + + public TiffReaderContext(RandomAccessReader reader, boolean isMotorolaByteOrder, boolean isBigTiff) + { + _reader = reader; + _isMotorolaByteOrder = isMotorolaByteOrder; + _isBigTiff = isBigTiff; + _visitedIfds = new HashSet(); + } + + /** + * Gets whether the specified IFD should be processed or not, based on whether it has + * been processed before. + * @param ifdOffset The offset at which the IFD starts. + * @return {@code true} if the IFD should be processed, otherwise {@code false}. + */ + public boolean tryVisitIfd(int ifdOffset) + { + // Note that we track these offsets in the global frame, not the reader's local frame. + int globalIfdOffset = _reader.toUnshiftedOffset(ifdOffset); + + return _visitedIfds.add(globalIfdOffset); + } + + /** + * Returns a copy of this context object with a reader observing the specified byte order. + * Note that this method does not change the value of {@code isMotorolaByteOrder} which + * represents the advertised byte order at the start of the TIFF data stream. + * @throws IOException if an I/O error occurs. + */ + public TiffReaderContext withByteOrder(boolean isMotorolaByteOrder) throws IOException + { + return new TiffReaderContext(_reader.withByteOrder(isMotorolaByteOrder), isMotorolaByteOrder, _isBigTiff); + } + + /** + * Returns a copy of this context object with a reader having a shifted base offset. + * @throws IOException if an I/O error occurs. + */ + public TiffReaderContext withShiftedBaseOffset(int baseOffset) throws IOException + { + return new TiffReaderContext(_reader.withShiftedBaseOffset(baseOffset), _isMotorolaByteOrder, _isBigTiff); + } +} diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index f2c3f44d3..cfe7212eb 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -23,12 +23,12 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; -import java.util.Set; import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.imaging.tiff.TiffProcessingException; import com.drew.imaging.tiff.TiffReader; +import com.drew.imaging.tiff.TiffReaderContext; import com.drew.imaging.tiff.TiffStandard; import com.drew.lang.BufferBoundsException; import com.drew.lang.ByteArrayReader; @@ -190,14 +190,13 @@ public Long tryCustomProcessFormat(final int tagId, final int formatCode, final } @Override - public boolean customProcessTag(final int tagOffset, - final @NotNull Set processedIfdOffsets, - final @NotNull RandomAccessReader reader, + public boolean customProcessTag(final TiffReaderContext context, final int tagId, - final int byteCount, - final boolean isBigTiff) throws IOException + final int valueOffset, + final int byteCount) throws IOException { assert(_currentDirectory != null); + final RandomAccessReader reader = context.getReader(); // Some 0x0000 tags have a 0 byteCount. Determine whether it's bad. if (tagId == 0) { @@ -213,14 +212,14 @@ 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, isBigTiff); + return processMakernote(context, valueOffset); } // Custom processing for embedded IPTC data if (tagId == ExifSubIFDDirectory.TAG_IPTC_NAA && _currentDirectory instanceof ExifIFD0Directory) { // NOTE Adobe sets type 4 for IPTC instead of 7 - if (reader.getInt8(tagOffset) == 0x1c) { - final byte[] iptcBytes = reader.getBytes(tagOffset, byteCount); + if (reader.getInt8(valueOffset) == 0x1c) { + final byte[] iptcBytes = reader.getBytes(valueOffset, byteCount); new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length, _currentDirectory); return true; } @@ -229,27 +228,27 @@ public boolean customProcessTag(final int tagOffset, // Custom processing for ICC Profile data if (tagId == ExifSubIFDDirectory.TAG_INTER_COLOR_PROFILE) { - final byte[] iccBytes = reader.getBytes(tagOffset, byteCount); + final byte[] iccBytes = reader.getBytes(valueOffset, byteCount); new IccReader().extract(new ByteArrayReader(iccBytes), _metadata, _currentDirectory); return true; } // Custom processing for Photoshop data if (tagId == ExifSubIFDDirectory.TAG_PHOTOSHOP_SETTINGS && _currentDirectory instanceof ExifIFD0Directory) { - final byte[] photoshopBytes = reader.getBytes(tagOffset, byteCount); + final byte[] photoshopBytes = reader.getBytes(valueOffset, byteCount); new PhotoshopReader().extract(new SequentialByteArrayReader(photoshopBytes), byteCount, _metadata, _currentDirectory); return true; } // Custom processing for embedded XMP data if (tagId == ExifSubIFDDirectory.TAG_APPLICATION_NOTES && (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof ExifSubIFDDirectory)) { - new XmpReader().extract(reader.getNullTerminatedBytes(tagOffset, byteCount), _metadata, _currentDirectory); + new XmpReader().extract(reader.getNullTerminatedBytes(valueOffset, byteCount), _metadata, _currentDirectory); return true; } // Custom processing for Apple RunTime tag if (tagId == AppleMakernoteDirectory.TAG_RUN_TIME && _currentDirectory instanceof AppleMakernoteDirectory) { - byte[] bytes = reader.getBytes(tagOffset, byteCount); + byte[] bytes = reader.getBytes(valueOffset, byteCount); new AppleRunTimeReader().extract(bytes, _metadata, _currentDirectory); return true; } @@ -259,7 +258,7 @@ public boolean customProcessTag(final int tagOffset, PrintIMDirectory printIMDirectory = new PrintIMDirectory(); printIMDirectory.setParent(_currentDirectory); _metadata.addDirectory(printIMDirectory); - processPrintIM(printIMDirectory, tagOffset, reader, byteCount); + processPrintIM(printIMDirectory, valueOffset, reader, byteCount); return true; } @@ -269,35 +268,35 @@ public boolean customProcessTag(final int tagOffset, switch (tagId) { case OlympusMakernoteDirectory.TAG_EQUIPMENT: pushDirectory(OlympusEquipmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS: pushDirectory(OlympusCameraSettingsMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT: pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2: pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING: pushDirectory(OlympusImageProcessingMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_FOCUS_INFO: pushDirectory(OlympusFocusInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_INFO: pushDirectory(OlympusRawInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; case OlympusMakernoteDirectory.TAG_MAIN_INFO: pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); + TiffReader.processIfd(this, context, valueOffset); return true; } } @@ -310,26 +309,26 @@ public boolean customProcessTag(final int tagOffset, PanasonicRawWbInfoDirectory dirWbInfo = new PanasonicRawWbInfoDirectory(); dirWbInfo.setParent(_currentDirectory); _metadata.addDirectory(dirWbInfo); - processBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2); + processBinary(dirWbInfo, valueOffset, reader, byteCount, false, 2); return true; case PanasonicRawIFD0Directory.TagWbInfo2: PanasonicRawWbInfo2Directory dirWbInfo2 = new PanasonicRawWbInfo2Directory(); dirWbInfo2.setParent(_currentDirectory); _metadata.addDirectory(dirWbInfo2); - processBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3); + processBinary(dirWbInfo2, valueOffset, reader, byteCount, false, 3); return true; case PanasonicRawIFD0Directory.TagDistortionInfo: PanasonicRawDistortionDirectory dirDistort = new PanasonicRawDistortionDirectory(); dirDistort.setParent(_currentDirectory); _metadata.addDirectory(dirDistort); - processBinary(dirDistort, tagOffset, reader, byteCount, true, 1); + processBinary(dirDistort, valueOffset, reader, byteCount, true, 1); return true; } } // Panasonic RAW sometimes contains an embedded version of the data as a JPG file. if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory) { - byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount); + byte[] jpegrawbytes = reader.getBytes(valueOffset, byteCount); // Extract information from embedded image since it is metadata-rich ByteArrayInputStream jpegmem = new ByteArrayInputStream(jpegrawbytes); @@ -349,7 +348,7 @@ public boolean customProcessTag(final int tagOffset, if (_currentDirectory instanceof SonyType1MakernoteDirectory) { if (tagId == SonyType1MakernoteDirectory.TAG_9050B) { - byte[] bytes = reader.getBytes(tagOffset, byteCount); + byte[] bytes = reader.getBytes(valueOffset, byteCount); SonyTag9050bDirectory directory = SonyTag9050bDirectory.read(bytes); directory.setParent(_currentDirectory); _metadata.addDirectory(directory); @@ -360,13 +359,13 @@ public boolean customProcessTag(final int tagOffset, if (_currentDirectory instanceof NikonType2MakernoteDirectory) { if (tagId == NikonType2MakernoteDirectory.TAG_PICTURE_CONTROL || tagId == NikonType2MakernoteDirectory.TAG_PICTURE_CONTROL_2) { if (byteCount == 58) { - byte[] bytes = reader.getBytes(tagOffset, byteCount); + byte[] bytes = reader.getBytes(valueOffset, byteCount); NikonPictureControl1Directory directory = NikonPictureControl1Directory.read(bytes); directory.setParent(_currentDirectory); _metadata.addDirectory(directory); return true; } else if (byteCount == 68) { - byte[] bytes = reader.getBytes(tagOffset, byteCount); + byte[] bytes = reader.getBytes(valueOffset, byteCount); NikonPictureControl2Directory directory = NikonPictureControl2Directory.read(bytes); directory.setParent(_currentDirectory); _metadata.addDirectory(directory); @@ -435,12 +434,11 @@ private static String getReaderString(final @NotNull RandomAccessReader reader, } } - private boolean processMakernote(final int makernoteOffset, - final @NotNull Set processedIfdOffsets, - final @NotNull RandomAccessReader reader, - final boolean isBigTiff) throws IOException + private boolean processMakernote(final TiffReaderContext context, + final int makernoteOffset) throws IOException { assert(_currentDirectory != null); + final RandomAccessReader reader = context.getReader(); // Determine the camera model and makernote format. Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); @@ -463,24 +461,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, isBigTiff); + TiffReader.processIfd(this, context, 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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 12, isBigTiff); + TiffReader.processIfd(this, context.withShiftedBaseOffset(makernoteOffset), 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.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); + TiffReader.processIfd(this, context.withShiftedBaseOffset(makernoteOffset), 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, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset); } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) { if ("Nikon".equals(firstFiveChars)) { /* There are two scenarios here: @@ -494,11 +492,11 @@ private boolean processMakernote(final int makernoteOffset, switch (reader.getUInt8(makernoteOffset + 6)) { case 1: pushDirectory(NikonType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset + 8); break; case 2: pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset + 10), processedIfdOffsets, 8, isBigTiff); + TiffReader.processIfd(this, context.withShiftedBaseOffset(makernoteOffset + 10), 8); break; default: _currentDirectory.addError("Unsupported Nikon makernote data ignored."); @@ -507,52 +505,52 @@ 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, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset); } } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) { pushDirectory(SonyType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, isBigTiff); + TiffReader.processIfd(this, context, 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, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset); } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) { // force Motorola byte order for this directory // skip 12 byte header + 2 for "MM" + 6 pushDirectory(SonyType6MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withByteOrder(true), processedIfdOffsets, makernoteOffset + 20, isBigTiff); + TiffReader.processIfd(this, context.withByteOrder(true), 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, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset + 10); } else if ("KDK".equals(firstThreeChars)) { KodakMakernoteDirectory directory = new KodakMakernoteDirectory(); _metadata.addDirectory(directory); processKodakMakernote(directory, makernoteOffset, reader.withByteOrder(firstSevenChars.equals("KDK INFO"))); } else if ("Canon".equalsIgnoreCase(cameraMake)) { pushDirectory(CanonMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); + TiffReader.processIfd(this, context, 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, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset + 6); } else { pushDirectory(CasioType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset); } } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) { // 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); + TiffReaderContext makernoteContext = context.withShiftedBaseOffset(makernoteOffset).withByteOrder(false); + int ifdStart = makernoteContext.getReader().getInt32(8); pushDirectory(FujifilmMakernoteDirectory.class); - TiffReader.processIfd(this, makernoteReader, processedIfdOffsets, ifdStart, isBigTiff); + TiffReader.processIfd(this, makernoteContext, 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, isBigTiff); + TiffReader.processIfd(this, context, makernoteOffset + 22); } else if ("LEICA".equals(firstFiveChars)) { // used by the X1/X2/X VARIO/T // (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG") @@ -568,14 +566,14 @@ private boolean processMakernote(final int makernoteOffset, "LEICA\0\u0007\0".equals(firstEightChars)) { pushDirectory(LeicaType5MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withByteOrder(false).withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); + TiffReader.processIfd(this, context.withByteOrder(false).withShiftedBaseOffset(makernoteOffset), 8); } else if ("Leica Camera AG".equals(cameraMake)) { pushDirectory(LeicaMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withByteOrder(false), processedIfdOffsets, makernoteOffset + 8, isBigTiff); + TiffReader.processIfd(this, context.withByteOrder(false), makernoteOffset + 8); } else if ("LEICA".equals(cameraMake)) { // Some Leica cameras use Panasonic makernote tags pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withByteOrder(false), processedIfdOffsets, makernoteOffset + 8, isBigTiff); + TiffReader.processIfd(this, context.withByteOrder(false), makernoteOffset + 8); } else { return false; } @@ -584,7 +582,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, isBigTiff); + TiffReader.processIfd(this, context, 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 @@ -592,7 +590,7 @@ private boolean processMakernote(final int makernoteOffset, // Observed for: // - Pentax ist D pushDirectory(CasioType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 6, isBigTiff); + TiffReader.processIfd(this, context.withShiftedBaseOffset(makernoteOffset), 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 @@ -601,7 +599,7 @@ private boolean processMakernote(final int makernoteOffset, // - PENTAX Optio 330 // - PENTAX Optio 430 pushDirectory(PentaxMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 0, isBigTiff); + TiffReader.processIfd(this, context.withShiftedBaseOffset(makernoteOffset), 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 @@ -609,7 +607,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, isBigTiff); + TiffReader.processIfd(this, context.withShiftedBaseOffset(makernoteOffset), 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: @@ -621,12 +619,12 @@ private boolean processMakernote(final int makernoteOffset, } else if (firstFiveChars.equalsIgnoreCase("Ricoh")) { // Always in Motorola byte order pushDirectory(RicohMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withByteOrder(true).withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); + TiffReader.processIfd(this, context.withByteOrder(true).withShiftedBaseOffset(makernoteOffset), 8); } } else if (firstTenChars.equals("Apple iOS\0")) { // Always in Motorola byte order pushDirectory(AppleMakernoteDirectory.class); - TiffReader.processIfd(this, reader.withByteOrder(true).withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); + TiffReader.processIfd(this, context.withByteOrder(true).withShiftedBaseOffset(makernoteOffset), 14); } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory(); _metadata.addDirectory(directory); @@ -642,7 +640,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, isBigTiff); + TiffReader.processIfd(this, context, 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/photoshop/PhotoshopTiffHandler.java b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java index 26522493e..4771aab1f 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java @@ -1,5 +1,6 @@ package com.drew.metadata.photoshop; +import com.drew.imaging.tiff.TiffReaderContext; import com.drew.lang.ByteArrayReader; import com.drew.lang.RandomAccessReader; import com.drew.lang.SequentialByteArrayReader; @@ -36,26 +37,25 @@ public PhotoshopTiffHandler(Metadata metadata, Directory parentDirectory) super(metadata, parentDirectory, 0); } - public boolean customProcessTag(final int tagOffset, - final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, - final @NotNull RandomAccessReader reader, + @Override + public boolean customProcessTag(final TiffReaderContext context, final int tagId, - final int byteCount, - final boolean isBigTiff) throws IOException + final int valueOffset, + final int byteCount) throws IOException { + final RandomAccessReader reader = context.getReader(); switch (tagId) { case TAG_XMP: - new XmpReader().extract(reader.getBytes(tagOffset, byteCount), _metadata); + new XmpReader().extract(reader.getBytes(valueOffset, byteCount), _metadata); return true; case TAG_PHOTOSHOP_IMAGE_RESOURCES: - new PhotoshopReader().extract(new SequentialByteArrayReader(reader.getBytes(tagOffset, byteCount)), byteCount, _metadata); + new PhotoshopReader().extract(new SequentialByteArrayReader(reader.getBytes(valueOffset, byteCount)), byteCount, _metadata); return true; case TAG_ICC_PROFILES: - new IccReader().extract(new ByteArrayReader(reader.getBytes(tagOffset, byteCount)), _metadata); + new IccReader().extract(new ByteArrayReader(reader.getBytes(valueOffset, byteCount)), _metadata); return true; } - return super.customProcessTag(tagOffset, processedIfdOffsets, reader, tagId, byteCount, isBigTiff); + return super.customProcessTag(context, tagId, valueOffset, byteCount); } } From 7b9b1ad69d187e8c83f1a448f0956d426695de05 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 19 Jun 2022 14:01:43 +0200 Subject: [PATCH 09/13] Support removing a tag from a directory --- Source/com/drew/metadata/Directory.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/com/drew/metadata/Directory.java b/Source/com/drew/metadata/Directory.java index 68575a840..d6524ae64 100644 --- a/Source/com/drew/metadata/Directory.java +++ b/Source/com/drew/metadata/Directory.java @@ -56,7 +56,7 @@ public abstract class Directory * defined tags. */ @NotNull - protected final Collection _definedTagList = new ArrayList(); + protected final List _definedTagList = new ArrayList(); @NotNull private final Collection _errorList = new ArrayList(4); @@ -1136,6 +1136,21 @@ public Object getObject(int tagType) // OTHER METHODS + public void removeTag(int tagId) + { + if (_tagMap.remove(tagId) != null) + { + for (int i = 0; i < _definedTagList.size(); i++) + { + if (_definedTagList.get(i).getTagType() == tagId) + { + _definedTagList.remove(i); + break; + } + } + } + } + /** * Returns the name of a specified tag as a String. * From e52f358250835ba1783489c17f5c07318f1cf5b3 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 19 Jun 2022 14:03:19 +0200 Subject: [PATCH 10/13] Support formatting a StringValue span --- Source/com/drew/metadata/StringValue.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/com/drew/metadata/StringValue.java b/Source/com/drew/metadata/StringValue.java index 8468cc298..efaf7093c 100644 --- a/Source/com/drew/metadata/StringValue.java +++ b/Source/com/drew/metadata/StringValue.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,4 +68,9 @@ public String toString(@Nullable Charset charset) return new String(_bytes); } + + public String toString(int index, int count) + { + return new String(_bytes, index, count, _charset != null ? _charset : Charset.defaultCharset()); + } } From da47f9d1dc9d29ed612ea79e5ddd831966d25c00 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 19 Jun 2022 14:07:35 +0200 Subject: [PATCH 11/13] Pass context to endingIfd --- Source/com/drew/imaging/tiff/TiffHandler.java | 2 +- Source/com/drew/imaging/tiff/TiffReader.java | 2 +- Source/com/drew/metadata/tiff/DirectoryTiffHandler.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index 266bcab8a..e7429f65b 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -49,7 +49,7 @@ public interface TiffHandler boolean tryEnterSubIfd(int tagId); boolean hasFollowerIfd(); - void endingIFD(); + void endingIFD(TiffReaderContext context); @Nullable Long tryCustomProcessFormat(int tagId, int formatCode, long componentCount); diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index eecc7e0e9..654a0a545 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -301,7 +301,7 @@ public static void processIfd(@NotNull final TiffHandler handler, } } } finally { - handler.endingIFD(); + handler.endingIFD(context); } } diff --git a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java index 3e06b2537..eb01a5aac 100644 --- a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java +++ b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java @@ -21,6 +21,7 @@ package com.drew.metadata.tiff; import com.drew.imaging.tiff.TiffHandler; +import com.drew.imaging.tiff.TiffReaderContext; import com.drew.lang.Rational; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; @@ -51,7 +52,7 @@ protected DirectoryTiffHandler(Metadata metadata, @Nullable Directory parentDire } @Override - public void endingIFD() + public void endingIFD(TiffReaderContext context) { _currentDirectory = _directoryStack.empty() ? null : _directoryStack.pop(); } From ad0e55511d4e9d010a93f4fb3ff1ba1e665681e9 Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Sun, 19 Jun 2022 15:57:47 +0200 Subject: [PATCH 12/13] Support GeoTIFF data in TIFF --- .../drew/metadata/exif/ExifDirectoryBase.java | 34 +- .../drew/metadata/exif/ExifTiffHandler.java | 100 + .../metadata/geotiff/GeoTiffDescriptor.java | 2564 +++++++++++++++++ .../metadata/geotiff/GeoTiffDirectory.java | 186 ++ 4 files changed, 2883 insertions(+), 1 deletion(-) create mode 100644 Source/com/drew/metadata/geotiff/GeoTiffDescriptor.java create mode 100644 Source/com/drew/metadata/geotiff/GeoTiffDirectory.java diff --git a/Source/com/drew/metadata/exif/ExifDirectoryBase.java b/Source/com/drew/metadata/exif/ExifDirectoryBase.java index 0d93c949c..f3a5eb8de 100644 --- a/Source/com/drew/metadata/exif/ExifDirectoryBase.java +++ b/Source/com/drew/metadata/exif/ExifDirectoryBase.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. @@ -137,6 +137,13 @@ public abstract class ExifDirectoryBase extends Directory public static final int TAG_WHITE_POINT = 0x013E; public static final int TAG_PRIMARY_CHROMATICITIES = 0x013F; + /** + * A color map for palette color images. + * This field defines a Red-Green-Blue color map (often called a lookup table) for palette-color images. + * In a palette-color image, a pixel value is used to index into an RGB lookup table. + */ + public static final int TAG_COLOR_MAP = 0x0140; + public static final int TAG_TILE_WIDTH = 0x0142; public static final int TAG_TILE_LENGTH = 0x0143; public static final int TAG_TILE_OFFSETS = 0x0144; @@ -191,9 +198,22 @@ public abstract class ExifDirectoryBase extends Directory * The actual F-number(F-stop) of lens when the image was taken. */ public static final int TAG_FNUMBER = 0x829D; + + public static final int TAG_PIXEL_SCALE = 0x830E; + public static final int TAG_IPTC_NAA = 0x83BB; + + public static final int TAG_MODEL_TIE_POINT = 0x8482; + public static final int TAG_PHOTOSHOP_SETTINGS = 0x8649; public static final int TAG_INTER_COLOR_PROFILE = 0x8773; + + public static final int TAG_GEOTIFF_GEO_KEYS = 0x87AF; + + public static final int TAG_GEOTIFF_GEO_DOUBLE_PARAMS = 0x87B0; + + public static final int TAG_GEOTIFF_GEO_ASCII_PARAMS = 0x87B1; + /** * Exposure program that the camera used when image was taken. '1' means * manual control, '2' program normal, '3' aperture priority, '4' shutter @@ -602,6 +622,10 @@ public abstract class ExifDirectoryBase extends Directory public static final int TAG_LENS_MODEL = 0xA434; /** String. */ public static final int TAG_LENS_SERIAL_NUMBER = 0xA435; + + public static final int TAG_GDAL_METADATA = 0xA480; + public static final int TAG_GDAL_NO_DATA = 0xA481; + /** Rational64u. */ public static final int TAG_GAMMA = 0xA500; @@ -652,6 +676,7 @@ protected static void addExifTagNames(HashMap map) map.put(TAG_HOST_COMPUTER, "Host Computer"); map.put(TAG_WHITE_POINT, "White Point"); map.put(TAG_PRIMARY_CHROMATICITIES, "Primary Chromaticities"); + map.put(TAG_COLOR_MAP, "Color Map"); map.put(TAG_TILE_WIDTH, "Tile Width"); map.put(TAG_TILE_LENGTH, "Tile Length"); map.put(TAG_TILE_OFFSETS, "Tile Offsets"); @@ -687,9 +712,14 @@ protected static void addExifTagNames(HashMap map) map.put(TAG_COPYRIGHT, "Copyright"); map.put(TAG_EXPOSURE_TIME, "Exposure Time"); map.put(TAG_FNUMBER, "F-Number"); + map.put(TAG_PIXEL_SCALE, "Pixel Scale"); map.put(TAG_IPTC_NAA, "IPTC/NAA"); + map.put(TAG_MODEL_TIE_POINT, "Model Tie Point"); map.put(TAG_PHOTOSHOP_SETTINGS, "Photoshop Settings"); map.put(TAG_INTER_COLOR_PROFILE, "Inter Color Profile"); + map.put(TAG_GEOTIFF_GEO_KEYS, "GeoTIFF Geo Keys"); + map.put(TAG_GEOTIFF_GEO_DOUBLE_PARAMS, "GeoTIFF Geo Double Params"); + map.put(TAG_GEOTIFF_GEO_ASCII_PARAMS, "GeoTIFF Geo ASCII Params"); map.put(TAG_EXPOSURE_PROGRAM, "Exposure Program"); map.put(TAG_SPECTRAL_SENSITIVITY, "Spectral Sensitivity"); map.put(TAG_ISO_EQUIVALENT, "ISO Speed Ratings"); @@ -783,6 +813,8 @@ protected static void addExifTagNames(HashMap map) map.put(TAG_LENS_MAKE, "Lens Make"); map.put(TAG_LENS_MODEL, "Lens Model"); map.put(TAG_LENS_SERIAL_NUMBER, "Lens Serial Number"); + map.put(TAG_GDAL_METADATA, "GDAL Metadata"); + map.put(TAG_GDAL_NO_DATA, "GDAL NoData"); map.put(TAG_GAMMA, "Gamma"); map.put(TAG_PRINT_IMAGE_MATCHING_INFO, "Print Image Matching (PIM) Info"); map.put(TAG_PANASONIC_TITLE, "Panasonic Title"); diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index cfe7212eb..97535426f 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -23,6 +23,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.imaging.jpeg.JpegProcessingException; @@ -42,6 +44,7 @@ import com.drew.metadata.StringValue; import com.drew.metadata.apple.AppleRunTimeReader; import com.drew.metadata.exif.makernotes.*; +import com.drew.metadata.geotiff.GeoTiffDirectory; import com.drew.metadata.icc.IccReader; import com.drew.metadata.iptc.IptcReader; import com.drew.metadata.photoshop.PhotoshopReader; @@ -175,6 +178,103 @@ public boolean hasFollowerIfd() return false; } + @Override + public void endingIFD(TiffReaderContext context) + { + if (_currentDirectory instanceof ExifIFD0Directory) + { + ExifIFD0Directory directory = (ExifIFD0Directory) _currentDirectory; + int[] geoKeys = directory.getIntArray(ExifDirectoryBase.TAG_GEOTIFF_GEO_KEYS); + if (geoKeys != null) + { + // GetTIFF stores data in its own format within TIFF. It is TIFF-like, but different. + // It can reference data from tags that have not been visited yet, so we must unpack it + // once the directory is complete. + processGeoTiff(geoKeys, directory); + } + } + + super.endingIFD(context); + } + + private void processGeoTiff(int[] geoKeys, ExifIFD0Directory sourceDirectory) + { + if (geoKeys.length < 4) + return; + + pushDirectory(GeoTiffDirectory.class); + + int i = 0; + + int directoryVersion = geoKeys[i++]; + int revision = geoKeys[i++]; + int minorRevision = geoKeys[i++]; + int numberOfKeys = geoKeys[i++]; + + // TODO store these values in negative tag IDs + + Set sourceTags = new HashSet(ExifDirectoryBase.TAG_GEOTIFF_GEO_KEYS); + + for (int j = 0; j < numberOfKeys; j++) + { + int keyId = geoKeys[i++]; + int tiffTagLocation = geoKeys[i++]; + int valueCount = geoKeys[i++]; + int valueOffset = geoKeys[i++]; + + if (tiffTagLocation == 0) + { + // Identifies the tag containing the value. If zero, then the value is ushort and stored + // in valueOffset directly, and the value count is implied as 1. + _currentDirectory.setInt(keyId, valueOffset); + } + else + { + // The value is stored in another tag. + int sourceTagId = tiffTagLocation; + sourceTags.add(sourceTagId); + Object sourceValue = sourceDirectory.getObject(sourceTagId); + if (sourceValue instanceof StringValue) + { + StringValue sourceString = (StringValue) sourceValue; + int sourceStringBytesLength = sourceString.getBytes().length; + if (valueOffset + valueCount <= sourceStringBytesLength) + { + // ASCII values appear to have a | character and the end, so we trim it off here + _currentDirectory.setString(keyId, sourceString.toString(valueOffset, valueCount).replaceAll("\\|$", "")); + } + else + { + _currentDirectory.addError("GeoTIFF key "+keyId+" with offset "+valueOffset+" and count "+valueCount+" extends beyond length of source value ("+sourceStringBytesLength+")"); + } + } + else if (sourceValue instanceof double[]) + { + double[] sourceArray = (double[]) sourceValue; + if (valueOffset + valueCount <= sourceArray.length) + { + double[] array = new double[valueCount]; + System.arraycopy(sourceArray, valueOffset, array, 0, valueCount); + _currentDirectory.setDoubleArray(keyId, array); + } + else + { + _currentDirectory.addError("GeoTIFF key "+keyId+" with offset "+valueOffset+" and count "+valueCount+" extends beyond length of source value ("+sourceArray.length+")"); + } + } + else + { + _currentDirectory.addError("GeoTIFF key "+keyId+" references tag "+sourceTagId+" which has unsupported type of "+sourceValue.getClass()); + } + } + } + + for (Integer sourceTag : sourceTags) + { + sourceDirectory.removeTag(sourceTag); + } + } + @Override @Nullable public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount) diff --git a/Source/com/drew/metadata/geotiff/GeoTiffDescriptor.java b/Source/com/drew/metadata/geotiff/GeoTiffDescriptor.java new file mode 100644 index 000000000..5b5541e70 --- /dev/null +++ b/Source/com/drew/metadata/geotiff/GeoTiffDescriptor.java @@ -0,0 +1,2564 @@ +/* + * 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.metadata.geotiff; + +import com.drew.metadata.TagDescriptor; + +public class GeoTiffDescriptor extends TagDescriptor { + + public GeoTiffDescriptor(GeoTiffDirectory directory) + { + super(directory); + } + + @Override + public String getDescription(int tagType) + { + switch(tagType) + { + case GeoTiffDirectory.TAG_MODEL_TYPE : return getModelTypeDescription(); + case GeoTiffDirectory.TAG_RASTER_TYPE : return getRasterTypeDescription(); + case GeoTiffDirectory.TAG_GEOGRAPHIC_TYPE : return getGeographicTypeDescription(); + case GeoTiffDirectory.TAG_GEODETIC_DATUM : return getGeodeticDatumDescription(); + case GeoTiffDirectory.TAG_GEOGRAPHIC_PRIME_MERIDIAN : return getPrimeMeridianDescription(); + case GeoTiffDirectory.TAG_GEOGRAPHIC_LINEAR_UNITS : return getGeoTiffUnitsDescription(GeoTiffDirectory.TAG_GEOGRAPHIC_LINEAR_UNITS); + case GeoTiffDirectory.TAG_GEOGRAPHIC_ANGULAR_UNITS : return getGeoTiffUnitsDescription(GeoTiffDirectory.TAG_GEOGRAPHIC_ANGULAR_UNITS); + case GeoTiffDirectory.TAG_GEOGRAPHIC_ELLIPSOID : return getGeographicEllipsoidDescription(); + case GeoTiffDirectory.TAG_PROJECTED_CS_TYPE : return getProjectedCoordinateSystemDescription(); + case GeoTiffDirectory.TAG_PROJECTION : return getProjectionDescription(); + case GeoTiffDirectory.TAG_PROJECTED_COORDINATE_TRANSFORM : return getProjectedCoordinateTransformDescription(); + case GeoTiffDirectory.TAG_PROJ_LINEAR_UNITS : return getGeoTiffUnitsDescription(GeoTiffDirectory.TAG_PROJ_LINEAR_UNITS); + case GeoTiffDirectory.TAG_VERTICAL_CS_TYPE : return getGeoTiffVerticalCSDescription(GeoTiffDirectory.TAG_VERTICAL_CS_TYPE); + case GeoTiffDirectory.TAG_VERTICAL_DATUM : return getGeoTiffVerticalCSDescription(GeoTiffDirectory.TAG_VERTICAL_DATUM); + case GeoTiffDirectory.TAG_VERTICAL_UNITS : return getGeoTiffUnitsDescription(GeoTiffDirectory.TAG_VERTICAL_UNITS); + case GeoTiffDirectory.TAG_CHART_FORMAT : return getChartFormatDescription(); + case GeoTiffDirectory.TAG_CHART_SOUNDING_DATUM : return getChartSoundingDatumDescription(); + case GeoTiffDirectory.TAG_CHART_DEPTH_UNITS : return getGeoTiffUnitsDescription(GeoTiffDirectory.TAG_CHART_DEPTH_UNITS); + default : return super.getDescription(tagType); + } + } + + private String getGeoTiffUnitsDescription(int tagType) + { + Integer value = _directory.getInteger(tagType); + if (value == null) + return null; + + // Requirement 16.4 + // GeogAngularUnitsGeoKey and GeogAzimuthUnitsGeoKey values in the range 1024-32766 SHALL be EPSG Unit Of Measure (UOM) codes with type = angle. + // NOTE: In GeoTIFF v1.0 the range was 9100-9199 + + // Requirement 16.5 + // GeogLinearUnitsGeoKey, ProjLinearUnitsGeoKey and VerticalUnitsGeoKey values in the range 1024-32766 SHALL be EPSG Unit Of Measure (UOM) codes with type = length. + // NOTE: In GeoTIFF v1.0 the range was 9000-9099. Several values in this range have been deprecated or deleted from the EPSG Dataset and should no longer be used. + + switch(value) + { + // Linear + case 9001 : return "Linear Meter"; + case 9002 : return "Linear Foot"; + case 9003 : return "Linear Foot US Survey"; + case 9004 : return "Linear Foot Modified American"; + case 9005 : return "Linear Foot Clarke"; + case 9006 : return "Linear Foot Indian"; + case 9007 : return "Linear Link"; + case 9008 : return "Linear Link Benoit"; + case 9009 : return "Linear Link Sears"; + case 9010 : return "Linear Chain Benoit"; + case 9011 : return "Linear Chain Sears"; + case 9012 : return "Linear Yard Sears"; + case 9013 : return "Linear Yard Indian"; + case 9014 : return "Linear Fathom"; + case 9015 : return "Linear Mile International Nautical"; + case 9030 : return "Linear Mile Nautical"; + case 9031 : return "Linear Meter German Legal"; + case 9033 : return "Linear Chain US Survey"; + case 9034 : return "Linear Link US Survey"; + case 9035 : return "Linear Mile US Survey"; + case 9036 : return "Linear Kilometer"; + case 9037 : return "Linear Yard Clarke"; + case 9038 : return "Linear Chain Clarke"; + case 9039 : return "Linear Link Clarke"; + case 9040 : return "Linear Yard British Sears 1922"; + case 9041 : return "Linear Foot British Sears 1922"; + case 9042 : return "Linear Chain British Sears 1922"; + case 9043 : return "Linear Link British Sears 1922"; + case 9050 : return "Linear Yard British Benoit 1895 A"; + case 9051 : return "Linear Foot British Benoit 1895 A"; + case 9052 : return "Linear Chain British Benoit 1895 A"; + case 9053 : return "Linear Link British Benoit 1895 A"; + case 9060 : return "Linear Yard British Benoit 1895 B"; + case 9061 : return "Linear Foot British Benoit 1895 B"; + case 9062 : return "Linear Chain British Benoit 1895 B"; + case 9063 : return "Linear Link British Benoit 1895 B"; + case 9070 : return "Linear Foot British 1865"; + case 9080 : return "Linear Foot Indian"; + case 9081 : return "Linear Foot Indian 1937"; + case 9082 : return "Linear Foot Indian 1962"; + case 9083 : return "Linear Foot Indian 1975"; + case 9084 : return "Linear Yard Indian"; + case 9085 : return "Linear Yard Indian 1937"; + case 9086 : return "Linear Yard Indian 1962"; + case 9087 : return "Linear Yard Indian 1975"; + case 9093 : return "Linear Mile Statute"; + case 9094 : return "Linear Foot Gold Coast"; + case 9095 : return "Linear Foot British 1936"; + case 9096 : return "Linear Yard"; + case 9097 : return "Linear Chain"; + case 9098 : return "Linear Link"; + // Angular + case 9101 : return "Angular Radian"; + case 9102 : return "Angular Degree"; + case 9103 : return "Angular Arc Minute"; + case 9104 : return "Angular Arc Second"; + case 9105 : return "Angular Grad"; + case 9106 : return "Angular Gon"; + case 9107 : return "Angular DMS"; + case 9108 : return "Angular DMS Hemisphere"; + case 9109 : return "Angular Microradian"; + case 9110 : return "Angular Sexagesimal DMS"; + case 9111 : return "Angular Sexagesimal DM"; + case 9112 : return "Angular Centesimal Minute"; + case 9113 : return "Angular Centesimal Second"; + case 9114 : return "Angular 6400 mil"; + case 9115 : return "Angular DM"; + case 9116 : return "Angular Degree Hemisphere"; + case 9117 : return "Angular Hemisphere Degree"; + case 9118 : return "Angular DM Hemisphere"; + case 9119 : return "Angular Hemisphere DM"; + case 9120 : return "Angular Hemisphere DMS"; + case 9121 : return "Angular Sexagesimal DMS.s"; + case 9122 : return "Angular Degree (supplier to define representation)"; + // Other + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + private String getGeoTiffVerticalCSDescription(int tagType) + { + Integer value = _directory.getInteger(tagType); + if (value == null) + return null; + + // Requirement 14.4 + // VerticalGeoKey values in the range 1024-32766 SHALL be either EPSG Vertical CRS Codes or EPSG geographic 3D CRS codes + // NOTE: In GeoTIFF v1.0 the ranges were 5000-5099 and 5200-5999. As at 2018-05-29 no EPSG vertical CRSs have been or are in this range. + // Values in this range have been and are used as EPSG vertical datum codes; in this document their use as codes for vertical CRSs is deprecated. + + // Requirement 25.4 + // VerticalDatumGeoKey values in the range 1024-32766 SHALL be EPSG vertical datum codes + // NOTE: In GeoTIFF v1.0 the range was given as 1-16383 but without reference to EPSG. + + switch(value) + { + case 0 : return "Undefined"; + case 5001 : return "Airy 1830 ellipsoid"; + case 5002 : return "Airy Modified 1849 ellipsoid"; + case 5003 : return "ANS ellipsoid"; + case 5004 : return "Bessel 1841 ellipsoid"; + case 5005 : return "Bessel Modified ellipsoid"; + case 5006 : return "Bessel Namibia ellipsoid"; + case 5007 : return "Clarke 1858 ellipsoid"; + case 5008 : return "Clarke 1866 ellipsoid"; + case 5010 : return "Clarke 1880 Benoit ellipsoid"; + case 5011 : return "Clarke 1880 IGN ellipsoid"; + case 5012 : return "Clarke 1880 RGS ellipsoid"; + case 5013 : return "Clarke 1880 Arc ellipsoid"; + case 5014 : return "Clarke 1880 SGA 1922 ellipsoid"; + case 5015 : return "Everest 1830 1937 Adjustment ellipsoid"; + case 5016 : return "Everest 1830 1967 Definition ellipsoid"; + case 5017 : return "Everest 1830 1975 Definition ellipsoid"; + case 5018 : return "Everest 1830 Modified ellipsoid"; + case 5019 : return "GRS 1980 ellipsoid"; + case 5020 : return "Helmert 1906 ellipsoid"; + case 5021 : return "INS ellipsoid"; + case 5022 : return "International 1924 ellipsoid"; + case 5023 : return "International 1967 ellipsoid"; + case 5024 : return "Krassowsky 1940 ellipsoid"; + case 5025 : return "NWL 9D ellipsoid"; + case 5026 : return "NWL 10D ellipsoid"; + case 5027 : return "Plessis 1817 ellipsoid"; + case 5028 : return "Struve 1860 ellipsoid"; + case 5029 : return "War Office ellipsoid"; + case 5030 : return "WGS 84 ellipsoid"; + case 5031 : return "GEM 10C ellipsoid"; + case 5032 : return "OSU86F ellipsoid"; + case 5033 : return "OSU91A ellipsoid"; + case 5101 : return "Newlyn"; + case 5102 : return "North American Vertical Datum 1929"; + case 5103 : return "North American Vertical Datum 1988"; + case 5104 : return "Yellow Sea 1956"; + case 5105 : return "Baltic Sea"; + case 5106 : return "Caspian Sea"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getModelTypeDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_MODEL_TYPE); + if (value == null) + return null; + + // Requirement 8.4 + // The GTModelTypeGeoKey value SHALL be: + // * 0 to indicate that the Model CRS in undefined or unknown; or + // * 1 to indicate that the Model CRS is a 2D projected coordinate reference system, indicated by the value of the ProjectedCRSGeoKey; or + // * 2 to indicate that the Model CRS is a geographic 2D coordinate reference system, indicated by the value of the GeodeticCRSGeoKey; or + // * 3 to indicate that the Model CRS is a geocentric Cartesian 3D coordinate reference system, indicated by the value of the GeodeticCRSGeoKey; or + // * 32767 to indicate that the Model CRS type is user-defined. + + switch(value) + { + case 0 : return "Undefined"; + case 1 : return "Projected"; + case 2 : return "Geographic"; + case 3 : return "Geocentric"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getRasterTypeDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_RASTER_TYPE); + if (value == null) + return null; + + // Requirement 7.3 + // The GTRasterTypeGeoKey value SHALL be: + // * 0 to indicate that the Raster type is undefined or unknown; or + // * 1 to indicate that the Raster type is PixelIsArea; or + // * 2 to indicate that the Raster type is PixelIsPoint; or + // * 32767 to indicate that the Raster type is user-defined. + // Recommendation: the use of 0 (undefined) or 32767 (user-defined) is not recommended + + switch(value) + { + case 0 : return "Undefined"; + case 1 : return "PixelIsArea"; + case 2 : return "PixelIsPoint"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getGeographicTypeDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_GEOGRAPHIC_TYPE); + if (value == null) + return null; + + // Requirement 13.4 + // GeodeticCRSGeoKey values in the range 1024-32766 SHALL be EPSG geographic 2D or geocentric CRS codes + // NOTE: In GeoTIFF v1.0 the range was 4000-4999. Several values in this range have been deprecated or deleted from the EPSG Dataset and should no longer be used. + + switch(value) + { + case 4001 : return "Airy 1830"; + case 4002 : return "Airy Modified 1849"; + case 4003 : return "Australian National Spheroid"; + case 4004 : return "Bessel 1841"; + case 4005 : return "Bessel Modified"; + case 4006 : return "Bessel Namibia"; + case 4007 : return "Clarke 1858"; + case 4008 : return "Clarke 1866"; + case 4009 : return "Clarke 1866 Michigan"; + case 4010 : return "Clarke 1880 Benoit"; + case 4011 : return "Clarke 1880 IGN"; + case 4012 : return "Clarke 1880 RGS"; + case 4013 : return "Clarke 1880 Arc"; + case 4014 : return "Clarke 1880 SGA 1922"; + case 4015 : return "Everest 1830 1937 Adjustment"; + case 4016 : return "Everest 1830 1967 Definition"; + case 4017 : return "Everest 1830 1975 Definition"; + case 4018 : return "Everest 1830 Modified"; + case 4019 : return "GRS 1980"; + case 4020 : return "Helmert 1906"; + case 4021 : return "Indonesian National Spheroid"; + case 4022 : return "International 1924"; + case 4023 : return "International 1967"; + case 4024 : return "Krassowsky 1940"; + case 4025 : return "NWL9D"; + case 4026 : return "NWL10D"; + case 4027 : return "Plessis 1817"; + case 4028 : return "Struve 1860"; + case 4029 : return "War Office"; + case 4030 : return "WGS84"; + case 4031 : return "GEM10C"; + case 4032 : return "OSU86F"; + case 4033 : return "OSU91A"; + case 4034 : return "Clarke 1880"; + case 4035 : return "Sphere"; + case 4053 : return "1924 Authalic Sphere"; + case 4120 : return "Greek"; + case 4121 : return "GGRS87"; + case 4123 : return "KKJ"; + case 4124 : return "RT90"; + case 4133 : return "EST92"; + case 4134 : return "PSD93"; + case 4135 : return "Old Hawaiian"; + case 4136 : return "St. Lawrence Island"; + case 4137 : return "St. Paul Island"; + case 4138 : return "St. George Island"; + case 4139 : return "Puerto Rico"; + case 4141 : return "Israel 1993"; + case 4142 : return "Locodjo 1965"; + case 4143 : return "Abidjan 1987"; + case 4144 : return "Kalianpur 1937"; + case 4145 : return "Kalianpur 1962"; + case 4146 : return "Kalianpur 1975"; + case 4147 : return "Hanoi 1972"; + case 4148 : return "Hartebeesthoek94"; + case 4149 : return "CH1903"; + case 4150 : return "CH1903+"; + case 4151 : return "CHTRF95"; + case 4152 : return "NAD83 HARN"; + case 4153 : return "Rassadiran"; + case 4154 : return "ED50 ED77"; + case 4155 : return "Dabola 1981"; + case 4201 : return "Adindan"; + case 4202 : return "AGD66"; + case 4203 : return "AGD84"; + case 4204 : return "Ain el Abd"; + case 4205 : return "Afgooye"; + case 4206 : return "Agadez"; + case 4207 : return "Lisbon"; + case 4208 : return "Aratu"; + case 4209 : return "Arc 1950"; + case 4210 : return "Arc 1960"; + case 4211 : return "Batavia"; + case 4212 : return "Barbados"; + case 4213 : return "Beduaram"; + case 4214 : return "Beijing 1954"; + case 4215 : return "Belge 1950"; + case 4216 : return "Bermuda 1957"; + case 4217 : return "Bern 1898"; + case 4218 : return "Bogota"; + case 4219 : return "Bukit Rimpah"; + case 4220 : return "Camacupa"; + case 4221 : return "Campo Inchauspe"; + case 4222 : return "Cape"; + case 4223 : return "Carthage"; + case 4224 : return "Chua"; + case 4225 : return "Corrego Alegre"; + case 4226 : return "Cote d Ivoire"; + case 4227 : return "Deir ez Zor"; + case 4228 : return "Douala"; + case 4229 : return "Egypt 1907"; + case 4230 : return "ED50"; + case 4231 : return "ED87"; + case 4232 : return "Fahud"; + case 4233 : return "Gandajika 1970"; + case 4234 : return "Garoua"; + case 4235 : return "Guyane Française"; + case 4236 : return "Hu Tzu Shan"; + case 4237 : return "HD72"; + case 4238 : return "ID74"; + case 4239 : return "Indian 1954"; + case 4240 : return "Indian 1975"; + case 4241 : return "Jamaica 1875"; + case 4242 : return "JAD69"; + case 4243 : return "Kalianpur"; + case 4244 : return "Kandawala"; + case 4245 : return "Kertau"; + case 4246 : return "KOC"; + case 4247 : return "La Canoa"; + case 4248 : return "PSAD56"; + case 4249 : return "Lake"; + case 4250 : return "Leigon"; + case 4251 : return "Liberia 1964"; + case 4252 : return "Lome"; + case 4253 : return "Luzon 1911"; + case 4254 : return "Hito XVIII 1963"; + case 4255 : return "Herat North"; + case 4256 : return "Mahe 1971"; + case 4257 : return "Makassar"; + case 4258 : return "EUREF89"; + case 4259 : return "Malongo 1987"; + case 4260 : return "Manoca"; + case 4261 : return "Merchich"; + case 4262 : return "Massawa"; + case 4263 : return "Minna"; + case 4264 : return "Mhast"; + case 4265 : return "Monte Mario"; + case 4266 : return "M poraloko"; + case 4267 : return "NAD27"; + case 4268 : return "NAD Michigan"; + case 4269 : return "NAD83"; + case 4270 : return "Nahrwan 1967"; + case 4271 : return "Naparima 1972"; + case 4272 : return "GD49"; + case 4273 : return "NGO 1948"; + case 4274 : return "73"; + case 4275 : return "NTF"; + case 4276 : return "NSWC 9Z 2"; + case 4277 : return "OSGB 1936"; + case 4278 : return "OSGB70"; + case 4279 : return "OS SN80"; + case 4280 : return "Padang"; + case 4281 : return "Palestine 1923"; + case 4282 : return "Pointe Noire"; + case 4283 : return "GDA94"; + case 4284 : return "Pulkovo 1942"; + case 4285 : return "Qatar"; + case 4286 : return "Qatar 1948"; + case 4287 : return "Qornoq"; + case 4288 : return "Loma Quintana"; + case 4289 : return "Amersfoort"; + case 4290 : return "RT38"; + case 4291 : return "SAD69"; + case 4292 : return "Sapper Hill 1943"; + case 4293 : return "Schwarzeck"; + case 4294 : return "Segora"; + case 4295 : return "Serindung"; + case 4296 : return "Sudan"; + case 4297 : return "Tananarive"; + case 4298 : return "Timbalai 1948"; + case 4299 : return "TM65"; + case 4300 : return "TM75"; + case 4301 : return "Tokyo"; + case 4302 : return "Trinidad 1903"; + case 4303 : return "TC 1948"; + case 4304 : return "Voirol 1875"; + case 4305 : return "Voirol Unifie"; + case 4306 : return "Bern 1938"; + case 4307 : return "Nord Sahara 1959"; + case 4308 : return "Stockholm 1938"; + case 4309 : return "Yacare"; + case 4310 : return "Yoff"; + case 4311 : return "Zanderij"; + case 4312 : return "MGI"; + case 4313 : return "Belge 1972"; + case 4314 : return "DHDN"; + case 4315 : return "Conakry 1905"; + case 4317 : return "Dealul Piscului 1970"; + case 4322 : return "WGS 72"; + case 4324 : return "WGS 72BE"; + case 4326 : return "WGS 84"; + case 4618 : return "NAD83 CSRS"; + case 4619 : return "SWEREF99"; + case 4620 : return "Point 58"; + case 4621 : return "Fort Marigot"; + case 4622 : return "Guadeloupe 1948"; + case 4623 : return "CSG67"; + case 4624 : return "RGFG95"; + case 4625 : return "Martinique 1938"; + case 4626 : return "Reunion 1947"; + case 4627 : return "RGR92"; + case 4628 : return "Tahiti 52"; + case 4629 : return "Tahaa 54"; + case 4630 : return "IGN72 Nuku Hiva"; + case 4632 : return "Combani 1950"; + case 4633 : return "IGN56 Lifou"; + case 4636 : return "Petrels 1972"; + case 4637 : return "Perroud 1950"; + case 4638 : return "Saint Pierre et Miquelon 1950"; + case 4639 : return "MOP78"; + case 4641 : return "IGN53 Mare"; + case 4642 : return "ST84 Ile des Pins"; + case 4643 : return "ST71 Belep"; + case 4644 : return "NEA74 Noumea"; + case 4646 : return "Grand Comoros"; + case 4657 : return "Reykjavik 1900"; + case 4658 : return "Hjorsey 1955"; + case 4659 : return "ISN93"; + case 4660 : return "Helle 1954"; + case 4661 : return "LKS92"; + case 4662 : return "IGN72 Grande Terre"; + case 4663 : return "Porto Santo 1995"; + case 4664 : return "Azores Oriental 1995"; + case 4665 : return "Azores Central 1995"; + case 4666 : return "Lisbon 1890"; + case 4667 : return "IKBD-92"; + case 4668 : return "ED79"; + case 4669 : return "LKS94"; + case 4670 : return "IGM95"; + case 4671 : return "Voirol 1879"; + case 4672 : return "Chatham Islands 1971"; + case 4673 : return "Chatham Islands 1979"; + case 4674 : return "SIRGAS 2000"; + case 4675 : return "Guam 1963"; + case 4676 : return "Vientiane 1982"; + case 4677 : return "Lao 1993"; + case 4678 : return "Lao 1997"; + case 4679 : return "Jouik 1961"; + case 4680 : return "Nouakchott 1965"; + case 4682 : return "Gulshan 303"; + case 4683 : return "PRS92"; + case 4684 : return "Gan 1970"; + case 4760 : return "WGS 66"; + case 4761 : return "HTRS96"; + case 4762 : return "BDA2000"; + case 4763 : return "Pitcairn 2006"; + case 4764 : return "RSRGD2000"; + case 4765 : return "Slovenia 1996"; + case 4801 : return "Bern 1898 Bern"; + case 4802 : return "Bogota Bogota"; + case 4803 : return "Lisbon Lisbon"; + case 4804 : return "Makassar Jakarta"; + case 4805 : return "MGI Ferro"; + case 4806 : return "Monte Mario Rome"; + case 4807 : return "NTF Paris"; + case 4808 : return "Padang Jakarta"; + case 4809 : return "Belge 1950 Brussels"; + case 4810 : return "Tananarive Paris"; + case 4811 : return "Voirol 1875 Paris"; + case 4812 : return "Voirol Unifie Paris"; + case 4813 : return "Batavia Jakarta"; + case 4814 : return "RT38 Stockholm"; + case 4815 : return "Greek Athens"; + case 4816 : return "Carthage Paris"; + case 4817 : return "NGO 1948 Oslo"; + case 4818 : return "S-JTSK Ferro"; + case 4820 : return "Segara Jakarta"; + case 4821 : return "Voirol 1879 Paris"; + case 4823 : return "Sao Tome"; + case 4824 : return "Principe"; + case 4901 : return "ATF Paris"; + case 4902 : return "NDG Paris"; + case 4903 : return "Madrid 1870 Madrid"; + case 4904 : return "Lisbon 1890 Lisbon"; + case 5451 : return "Ocotepeque 1935"; + case 6318 : return "NAD83 2011"; + case 6322 : return "NAD83 PA11"; + case 6325 : return "NAD83 MA11"; + case 6783 : return "NAD83 CORS96"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getGeodeticDatumDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_GEODETIC_DATUM); + if (value == null) + return null; + + // Requirement 18.4 + // GeodeticDatumGeoKey values in the range 1024-32766 SHALL be EPSG geodetic datum codes. + // NOTE: In GeoTIFF v1.0 the range was 6000-6999. Several values in this range have been deprecated or deleted from the EPSG Dataset and should no longer be used. + + switch(value) + { + case 6001 : return "Airy 1830"; + case 6002 : return "Airy Modified 1849"; + case 6003 : return "Australian National Spheroid"; + case 6004 : return "Bessel 1841"; + case 6005 : return "Bessel Modified"; + case 6006 : return "Bessel Namibia"; + case 6007 : return "Clarke 1858"; + case 6008 : return "Clarke 1866"; + case 6009 : return "Clarke 1866 Michigan"; + case 6010 : return "Clarke 1880 Benoit"; + case 6011 : return "Clarke 1880 IGN"; + case 6012 : return "Clarke 1880 RGS"; + case 6013 : return "Clarke 1880 Arc"; + case 6014 : return "Clarke 1880 SGA 1922"; + case 6015 : return "Everest 1830 1937 Adjustment"; + case 6016 : return "Everest 1830 1967 Definition"; + case 6017 : return "Everest 1830 1975 Definition"; + case 6018 : return "Everest 1830 Modified"; + case 6019 : return "GRS 1980"; + case 6020 : return "Helmert 1906"; + case 6021 : return "Indonesian National Spheroid"; + case 6022 : return "International 1924"; + case 6023 : return "International 1967"; + case 6024 : return "Krassowsky 1960"; + case 6025 : return "NWL9D"; + case 6026 : return "NWL10D"; + case 6027 : return "Plessis 1817"; + case 6028 : return "Struve 1860"; + case 6029 : return "War Office"; + case 6030 : return "WGS84"; + case 6031 : return "GEM10C"; + case 6032 : return "OSU86F"; + case 6033 : return "OSU91A"; + case 6034 : return "Clarke 1880"; + case 6035 : return "Sphere"; + case 6201 : return "Adindan"; + case 6202 : return "Australian Geodetic Datum 1966"; + case 6203 : return "Australian Geodetic Datum 1984"; + case 6204 : return "Ain el Abd 1970"; + case 6205 : return "Afgooye"; + case 6206 : return "Agadez"; + case 6207 : return "Lisbon"; + case 6208 : return "Aratu"; + case 6209 : return "Arc 1950"; + case 6210 : return "Arc 1960"; + case 6211 : return "Batavia"; + case 6212 : return "Barbados"; + case 6213 : return "Beduaram"; + case 6214 : return "Beijing 1954"; + case 6215 : return "Reseau National Belge 1950"; + case 6216 : return "Bermuda 1957"; + case 6217 : return "Bern 1898"; + case 6218 : return "Bogota"; + case 6219 : return "Bukit Rimpah"; + case 6220 : return "Camacupa"; + case 6221 : return "Campo Inchauspe"; + case 6222 : return "Cape"; + case 6223 : return "Carthage"; + case 6224 : return "Chua"; + case 6225 : return "Corrego Alegre"; + case 6226 : return "Cote d Ivoire"; + case 6227 : return "Deir ez Zor"; + case 6228 : return "Douala"; + case 6229 : return "Egypt 1907"; + case 6230 : return "European Datum 1950"; + case 6231 : return "European Datum 1987"; + case 6232 : return "Fahud"; + case 6233 : return "Gandajika 1970"; + case 6234 : return "Garoua"; + case 6235 : return "Guyane Française"; + case 6236 : return "Hu Tzu Shan"; + case 6237 : return "Hungarian Datum 1972"; + case 6238 : return "Indonesian Datum 1974"; + case 6239 : return "Indian 1954"; + case 6240 : return "Indian 1975"; + case 6241 : return "Jamaica 1875"; + case 6242 : return "Jamaica 1969"; + case 6243 : return "Kalianpur"; + case 6244 : return "Kandawala"; + case 6245 : return "Kertau"; + case 6246 : return "Kuwait Oil Company"; + case 6247 : return "La Canoa"; + case 6248 : return "Provisional S American Datum 1956"; + case 6249 : return "Lake"; + case 6250 : return "Leigon"; + case 6251 : return "Liberia 1964"; + case 6252 : return "Lome"; + case 6253 : return "Luzon 1911"; + case 6254 : return "Hito XVIII 1963"; + case 6255 : return "Herat North"; + case 6256 : return "Mahe 1971"; + case 6257 : return "Makassar"; + case 6258 : return "European Reference System 1989"; + case 6259 : return "Malongo 1987"; + case 6260 : return "Manoca"; + case 6261 : return "Merchich"; + case 6262 : return "Massawa"; + case 6263 : return "Minna"; + case 6264 : return "Mhast"; + case 6265 : return "Monte Mario"; + case 6266 : return "M poraloko"; + case 6267 : return "North American Datum 1927"; + case 6268 : return "NAD Michigan"; + case 6269 : return "North American Datum 1983"; + case 6270 : return "Nahrwan 1967"; + case 6271 : return "Naparima 1972"; + case 6272 : return "New Zealand Geodetic Datum 1949"; + case 6273 : return "NGO 1948"; + case 6274 : return "Datum 73"; + case 6275 : return "Nouvelle Triangulation Française"; + case 6276 : return "NSWC 9Z 2"; + case 6277 : return "OSGB 1936"; + case 6278 : return "OSGB 1970 SN"; + case 6279 : return "OS SN 1980"; + case 6280 : return "Padang 1884"; + case 6281 : return "Palestine 1923"; + case 6282 : return "Pointe Noire"; + case 6283 : return "Geocentric Datum of Australia 1994"; + case 6284 : return "Pulkovo 1942"; + case 6285 : return "Qatar"; + case 6286 : return "Qatar 1948"; + case 6287 : return "Qornoq"; + case 6288 : return "Loma Quintana"; + case 6289 : return "Amersfoort"; + case 6290 : return "RT38"; + case 6291 : return "South American Datum 1969"; + case 6292 : return "Sapper Hill 1943"; + case 6293 : return "Schwarzeck"; + case 6294 : return "Segora"; + case 6295 : return "Serindung"; + case 6296 : return "Sudan"; + case 6297 : return "Tananarive 1925"; + case 6298 : return "Timbalai 1948"; + case 6299 : return "TM65"; + case 6300 : return "TM75"; + case 6301 : return "Tokyo"; + case 6302 : return "Trinidad 1903"; + case 6303 : return "Trucial Coast 1948"; + case 6304 : return "Voirol 1875"; + case 6305 : return "Voirol Unifie 1960"; + case 6306 : return "Bern 1938"; + case 6307 : return "Nord Sahara 1959"; + case 6308 : return "Stockholm 1938"; + case 6309 : return "Yacare"; + case 6310 : return "Yoff"; + case 6311 : return "Zanderij"; + case 6312 : return "Militar Geographische Institut"; + case 6313 : return "Reseau National Belge 1972"; + case 6314 : return "Deutsche Hauptdreiecksnetz"; + case 6315 : return "Conakry 1905"; + case 6317 : return "Dealul Piscului 1970"; + case 6322 : return "WGS72"; + case 6324 : return "WGS72 Transit Broadcast Ephemeris"; + case 6326 : return "WGS84"; + case 6742 : return "Malaysia 2000"; + case 6901 : return "Ancienne Triangulation Française"; + case 6902 : return "Nord de Guerre"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getPrimeMeridianDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_GEOGRAPHIC_PRIME_MERIDIAN); + if (value == null) + return null; + + // Requirement 19.4 + // PrimeMeridianGeoKey values in the range 1024-32766 SHALL be EPSG Prime Meridian Codes + // NOTE: In GeoTIFF v1.0 the range was 8000-8999 + + switch(value) + { + case 8901 : return "Greenwich"; + case 8902 : return "Lisbon"; + case 8903 : return "Paris"; + case 8904 : return "Bogota"; + case 8905 : return "Madrid"; + case 8906 : return "Rome"; + case 8907 : return "Bern"; + case 8908 : return "Jakarta"; + case 8909 : return "Ferro"; + case 8910 : return "Brussels"; + case 8911 : return "Stockholm"; + case 8912 : return "Athens"; + case 8913 : return "Oslo"; + case 8914 : return "Paris RGS"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getGeographicEllipsoidDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_GEOGRAPHIC_ELLIPSOID); + if (value == null) + return null; + + // Requirement 21.4 + // EllipsoidGeoKey values in the range 1024-32766 SHALL be EPSG ellipsoid Codes + // NOTE: In GeoTIFF v1.0 the range was 7000-7999. Several values in this range have been deprecated or deleted from the EPSG Dataset and should no longer be used. + + switch(value) + { + case 7001 : return "Airy 1830"; + case 7002 : return "Airy Modified 1849"; + case 7003 : return "Australian National Spheroid"; + case 7004 : return "Bessel 1841"; + case 7005 : return "Bessel Modified"; + case 7006 : return "Bessel Namibia"; + case 7007 : return "Clarke 1858"; + case 7008 : return "Clarke 1866"; + case 7009 : return "Clarke 1866 Michigan"; + case 7010 : return "Clarke 1880 Benoit"; + case 7011 : return "Clarke 1880 IGN"; + case 7012 : return "Clarke 1880 RGS"; + case 7013 : return "Clarke 1880 Arc"; + case 7014 : return "Clarke 1880 SGA 1922"; + case 7015 : return "Everest 1830 1937 Adjustment"; + case 7016 : return "Everest 1830 1967 Definition"; + case 7017 : return "Everest 1830 1975 Definition"; + case 7018 : return "Everest 1830 Modified"; + case 7019 : return "GRS 1980"; + case 7020 : return "Helmert 1906"; + case 7021 : return "Indonesian National Spheroid"; + case 7022 : return "International 1924"; + case 7023 : return "International 1967"; + case 7024 : return "Krassowsky 1940"; + case 7025 : return "NWL 9D"; + case 7026 : return "NWL 10D"; + case 7027 : return "Plessis 1817"; + case 7028 : return "Struve 1860"; + case 7029 : return "War Office"; + case 7030 : return "WGS 84"; + case 7031 : return "GEM 10C"; + case 7032 : return "OSU86F"; + case 7033 : return "OSU91A"; + case 7034 : return "Clarke 1880"; + case 7035 : return "Sphere"; + case 7036 : return "GRS 1967"; + case 7041 : return "Average Terrestrial System 1977"; + case 7042 : return "Everest 1830"; + case 7043 : return "WGS 72"; + case 7044 : return "Everest 1830 1962 Definition"; + case 7045 : return "Everest 1830 1975 Definition"; + case 7046 : return "Bessel Namibia GLM"; + case 7047 : return "GRS 1980 Authalic Sphere"; + case 7048 : return "GRS 1980 Authalic Sphere"; + case 7049 : return "IAG 1975"; + case 7050 : return "GRS 1967 Modified"; + case 7051 : return "Danish 1876"; + case 7052 : return "Clarke 1866 Authalic Sphere"; + case 7053 : return "Hough 1960"; + case 7054 : return "PZ-90"; + case 7055 : return "Clarke 1880 International Foot"; + case 7056 : return "Everest 1830 RSO 1969"; + case 7057 : return "International 1924 Authalic Sphere"; + case 7058 : return "Hughes 1980"; + case 7059 : return "Popular Visualisation Sphere"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getProjectedCoordinateSystemDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_PROJECTED_CS_TYPE); + if (value == null) + return null; + + // Requirement 12.4 + // ProjectedCRSGeoKey values in the range 1024-32766 SHALL be EPSG Projected CRS Codes + // NOTE: In GeoTIFF v1.0 the range was 20000-32760. Several values in this range have been deprecated or deleted from the EPSG Dataset and should no longer be used. + + switch(value) + { + case 2046 : return "Hartebeesthoek94 Lo15"; + case 2047 : return "Hartebeesthoek94 Lo17"; + case 2048 : return "Hartebeesthoek94 Lo19"; + case 2049 : return "Hartebeesthoek94 Lo21"; + case 2050 : return "Hartebeesthoek94 Lo23"; + case 2051 : return "Hartebeesthoek94 Lo25"; + case 2052 : return "Hartebeesthoek94 Lo27"; + case 2053 : return "Hartebeesthoek94 Lo29"; + case 2054 : return "Hartebeesthoek94 Lo31"; + case 2055 : return "Hartebeesthoek94 Lo33"; + case 2100 : return "GGRS87 Greek Grid"; + case 2176 : return "ETRS89 Poland CS2000 zone 5"; + case 2177 : return "ETRS89 Poland CS2000 zone 7"; + case 2178 : return "ETRS89 Poland CS2000 zone 8"; + case 2180 : return "ETRS89 Poland CS92"; + case 2204 : return "NAD27 Tennessee"; + case 2205 : return "NAD83 Kentucky North"; + case 2391 : return "KKJ Finland zone 1"; + case 2392 : return "KKJ Finland zone 2"; + case 2393 : return "KKJ Finland zone 3"; + case 2394 : return "KKJ Finland zone 4"; + case 2400 : return "RT90 2 5 gon W"; + case 2600 : return "Lietuvos Koordinoei Sistema 1994"; + case 3031 : return "WGS 84 Antarctic Polar Stereographic"; + case 3032 : return "WGS 84 Australian Antarctic Polar Stereographic"; + case 3033 : return "WGS 84 Australian Antarctic Lambert"; + case 3034 : return "ETRS89-extended LCC Europe"; + case 3035 : return "ETRS89-extended LAEA Europe"; + case 3053 : return "Hjorsey 1955 Lambert"; + case 3057 : return "ISN93 Lambert 1993"; + case 3081 : return "NAD83 Texas State Mapping System"; + case 3082 : return "NAD83 Texas Centric Lambert Conformal"; + case 3083 : return "NAD83 Texas Centric Albers Equal Area"; + case 3084 : return "NAD83 HARN Texas Centric Lambert Conformal"; + case 3085 : return "NAD83 HARN Texas Centric Albers Equal Area"; + case 3300 : return "Estonian Coordinate System of 1992"; + case 3407 : return "Hong Kong 1963 Grid System"; + case 3408 : return "NSIDC EASE-Grid North"; + case 3409 : return "NSIDC EASE-Grid South"; + case 3410 : return "NSIDC EASE-Grid Global"; + case 3411 : return "NSIDC Sea Ice Polar Stereographic North"; + case 3412 : return "NSIDC Sea Ice Polar Stereographic South"; + case 3413 : return "WGS 84 NSIDC Sea Ice Polar Stereographic North"; + case 3414 : return "SVY21 Singapore TM"; + case 3786 : return "Popular Visualisation CRS / Mercator"; + case 3812 : return "ETRS89 Belgian Lambert 2008"; + case 3814 : return "NAD83 Mississippi TM"; + case 3815 : return "NAD83 HARN Mississippi TM"; + case 3816 : return "NAD83 NSRS2007 Mississippi TM"; + case 3857 : return "WGS 84 / Pseudo-Mercator"; + case 4087 : return "WGS 84 / World Equidistant Cylindrical"; + case 4088 : return "World Equidistant Cylindrical Sphere"; + case 5329 : return "Segara Jakarta NEIEZ"; + case 5330 : return "Batavia Jakarta NEIEZ"; + case 5331 : return "Makassar Jakarta NEIEZ"; + case 5456 : return "Ocotepeque 1935 Costa Rica Norte"; + case 5457 : return "Ocotepeque 1935 Costa Rica Sur"; + case 5479 : return "RSRGD2000 / MSLC2000"; + case 5480 : return "RSRGD2000 / BCLC2000"; + case 5481 : return "RSRGD2000 / PCLC2000"; + case 5482 : return "RSRGD2000 / RSPS2000"; + case 5588 : return "NAD27 New Brunswick Stereographic NAD27"; + case 5589 : return "Sibun Gorge 1922 Colony Grid"; + case 5641 : return "SIRGAS 2000 Brazil Mercator"; + case 6784 : return "NAD83 CORS96 Oregon Baker zone (m)"; + case 6785 : return "NAD83 CORS96 Oregon Baker zone (ft)"; + case 6786 : return "NAD83 2011 Oregon Baker zone (m)"; + case 6787 : return "NAD83 2011 Oregon Baker zone (ft)"; + case 6788 : return "NAD83 CORS96 Oregon Bend-Klamath Falls zone (m)"; + case 6789 : return "NAD83 CORS96 Oregon Bend-Klamath Falls zone (ft)"; + case 6790 : return "NAD83 2011 Oregon Bend-Klamath Falls zone (m)"; + case 6791 : return "NAD83 2011 Oregon Bend-Klamath Falls zone (ft)"; + case 6792 : return "NAD83 CORS96 Oregon Bend-Redmond-Prineville zone (m)"; + case 6793 : return "NAD83 CORS96 Oregon Bend-Redmond-Prineville zone (ft)"; + case 6794 : return "NAD83 2011 Oregon Bend-Redmond-Prineville zone (m)"; + case 6795 : return "NAD83 2011 Oregon Bend-Redmond-Prineville zone (ft)"; + case 6796 : return "NAD83 CORS96 Oregon Bend-Burns zone (m)"; + case 6797 : return "NAD83 CORS96 Oregon Bend-Burns zone (ft)"; + case 6798 : return "NAD83 2011 Oregon Bend-Burns zone (m)"; + case 6799 : return "NAD83 2011 Oregon Bend-Burns zone (ft)"; + case 6800 : return "NAD83 CORS96 Oregon Canyonville-Grants Pass zone (m)"; + case 6801 : return "NAD83 CORS96 Oregon Canyonville-Grants Pass zone (ft)"; + case 6802 : return "NAD83 2011 Oregon Canyonville-Grants Pass zone (m)"; + case 6803 : return "NAD83 2011 Oregon Canyonville-Grants Pass zone (ft)"; + case 6804 : return "NAD83 CORS96 Oregon Columbia River East zone (m)"; + case 6805 : return "NAD83 CORS96 Oregon Columbia River East zone (ft)"; + case 6806 : return "NAD83 2011 Oregon Columbia River East zone (m)"; + case 6807 : return "NAD83 2011 Oregon Columbia River East zone (ft)"; + case 6808 : return "NAD83 CORS96 Oregon Columbia River West zone (m)"; + case 6809 : return "NAD83 CORS96 Oregon Columbia River West zone (ft)"; + case 6810 : return "NAD83 2011 Oregon Columbia River West zone (m)"; + case 6811 : return "NAD83 2011 Oregon Columbia River West zone (ft)"; + case 6812 : return "NAD83 CORS96 Oregon Cottage Grove-Canyonville zone (m)"; + case 6813 : return "NAD83 CORS96 Oregon Cottage Grove-Canyonville zone (ft)"; + case 6814 : return "NAD83 2011 Oregon Cottage Grove-Canyonville zone (m)"; + case 6815 : return "NAD83 2011 Oregon Cottage Grove-Canyonville zone (ft)"; + case 6816 : return "NAD83 CORS96 Oregon Dufur-Madras zone (m)"; + case 6817 : return "NAD83 CORS96 Oregon Dufur-Madras zone (ft)"; + case 6818 : return "NAD83 2011 Oregon Dufur-Madras zone (m)"; + case 6819 : return "NAD83 2011 Oregon Dufur-Madras zone (ft)"; + case 6820 : return "NAD83 CORS96 Oregon Eugene zone (m)"; + case 6821 : return "NAD83 CORS96 Oregon Eugene zone (ft)"; + case 6822 : return "NAD83 2011 Oregon Eugene zone (m)"; + case 6823 : return "NAD83 2011 Oregon Eugene zone (ft)"; + case 6824 : return "NAD83 CORS96 Oregon Grants Pass-Ashland zone (m)"; + case 6825 : return "NAD83 CORS96 Oregon Grants Pass-Ashland zone (ft)"; + case 6826 : return "NAD83 2011 Oregon Grants Pass-Ashland zone (m)"; + case 6827 : return "NAD83 2011 Oregon Grants Pass-Ashland zone (ft)"; + case 6828 : return "NAD83 CORS96 Oregon Gresham-Warm Springs zone (m)"; + case 6829 : return "NAD83 CORS96 Oregon Gresham-Warm Springs zone (ft)"; + case 6830 : return "NAD83 2011 Oregon Gresham-Warm Springs zone (m)"; + case 6831 : return "NAD83 2011 Oregon Gresham-Warm Springs zone (ft)"; + case 6832 : return "NAD83 CORS96 Oregon La Grande zone (m)"; + case 6833 : return "NAD83 CORS96 Oregon La Grande zone (ft)"; + case 6834 : return "NAD83 2011 Oregon La Grande zone (m)"; + case 6835 : return "NAD83 2011 Oregon La Grande zone (ft)"; + case 6836 : return "NAD83 CORS96 Oregon Ontario zone (m)"; + case 6837 : return "NAD83 CORS96 Oregon Ontario zone (ft)"; + case 6838 : return "NAD83 2011 Oregon Ontario zone (m)"; + case 6839 : return "NAD83 2011 Oregon Ontario zone (ft)"; + case 6840 : return "NAD83 CORS96 Oregon Coast zone (m)"; + case 6841 : return "NAD83 CORS96 Oregon Coast zone (ft)"; + case 6842 : return "NAD83 2011 Oregon Coast zone (m)"; + case 6843 : return "NAD83 2011 Oregon Coast zone (ft)"; + case 6844 : return "NAD83 CORS96 Oregon Pendleton zone (m)"; + case 6845 : return "NAD83 CORS96 Oregon Pendleton zone (ft)"; + case 6846 : return "NAD83 2011 Oregon Pendleton zone (m)"; + case 6847 : return "NAD83 2011 Oregon Pendleton zone (ft)"; + case 6848 : return "NAD83 CORS96 Oregon Pendleton-La Grande zone (m)"; + case 6849 : return "NAD83 CORS96 Oregon Pendleton-La Grande zone (ft)"; + case 6850 : return "NAD83 2011 Oregon Pendleton-La Grande zone (m)"; + case 6851 : return "NAD83 2011 Oregon Pendleton-La Grande zone (ft)"; + case 6852 : return "NAD83 CORS96 Oregon Portland zone (m)"; + case 6853 : return "NAD83 CORS96 Oregon Portland zone (ft)"; + case 6854 : return "NAD83 2011 Oregon Portland zone (m)"; + case 6855 : return "NAD83 2011 Oregon Portland zone (ft)"; + case 6856 : return "NAD83 CORS96 Oregon Salem zone (m)"; + case 6857 : return "NAD83 CORS96 Oregon Salem zone (ft)"; + case 6858 : return "NAD83 2011 Oregon Salem zone (m)"; + case 6859 : return "NAD83 2011 Oregon Salem zone (ft)"; + case 6860 : return "NAD83 CORS96 Oregon Santiam Pass zone (m)"; + case 6861 : return "NAD83 CORS96 Oregon Santiam Pass zone (ft)"; + case 6862 : return "NAD83 2011 Oregon Santiam Pass zone (m)"; + case 6863 : return "NAD83 2011 Oregon Santiam Pass zone (ft)"; + case 8065 : return "NAD83 2011 PCCS zone 1 (ft)"; + case 8066 : return "NAD83 2011 PCCS zone 2 (ft)"; + case 8067 : return "NAD83 2011 PCCS zone 3 (ft)"; + case 8068 : return "NAD83 2011 PCCS zone 4 (ft)"; + case 8441 : return "Tananarive Laborde Grid"; + case 20137 : return "Adindan UTM zone 37N"; + case 20138 : return "Adindan UTM zone 38N"; + case 20248 : return "AGD66 AMG zone 48"; + case 20249 : return "AGD66 AMG zone 49"; + case 20250 : return "AGD66 AMG zone 50"; + case 20251 : return "AGD66 AMG zone 51"; + case 20252 : return "AGD66 AMG zone 52"; + case 20253 : return "AGD66 AMG zone 53"; + case 20254 : return "AGD66 AMG zone 54"; + case 20255 : return "AGD66 AMG zone 55"; + case 20256 : return "AGD66 AMG zone 56"; + case 20257 : return "AGD66 AMG zone 57"; + case 20258 : return "AGD66 AMG zone 58"; + case 20348 : return "AGD84 AMG zone 48"; + case 20349 : return "AGD84 AMG zone 49"; + case 20350 : return "AGD84 AMG zone 50"; + case 20351 : return "AGD84 AMG zone 51"; + case 20352 : return "AGD84 AMG zone 52"; + case 20353 : return "AGD84 AMG zone 53"; + case 20354 : return "AGD84 AMG zone 54"; + case 20355 : return "AGD84 AMG zone 55"; + case 20356 : return "AGD84 AMG zone 56"; + case 20357 : return "AGD84 AMG zone 57"; + case 20358 : return "AGD84 AMG zone 58"; + case 20437 : return "Ain el Abd UTM zone 37N"; + case 20438 : return "Ain el Abd UTM zone 38N"; + case 20439 : return "Ain el Abd UTM zone 39N"; + case 20499 : return "Ain el Abd Bahrain Grid"; + case 20538 : return "Afgooye UTM zone 38N"; + case 20539 : return "Afgooye UTM zone 39N"; + case 20700 : return "Lisbon Portuguese Grid"; + case 20822 : return "Aratu UTM zone 22S"; + case 20823 : return "Aratu UTM zone 23S"; + case 20824 : return "Aratu UTM zone 24S"; + case 20973 : return "Arc 1950 Lo13"; + case 20975 : return "Arc 1950 Lo15"; + case 20977 : return "Arc 1950 Lo17"; + case 20979 : return "Arc 1950 Lo19"; + case 20981 : return "Arc 1950 Lo21"; + case 20983 : return "Arc 1950 Lo23"; + case 20985 : return "Arc 1950 Lo25"; + case 20987 : return "Arc 1950 Lo27"; + case 20989 : return "Arc 1950 Lo29"; + case 20991 : return "Arc 1950 Lo31"; + case 20993 : return "Arc 1950 Lo33"; + case 20995 : return "Arc 1950 Lo35"; + case 21100 : return "Batavia NEIEZ"; + case 21148 : return "Batavia UTM zone 48S"; + case 21149 : return "Batavia UTM zone 49S"; + case 21150 : return "Batavia UTM zone 50S"; + case 21413 : return "Beijing Gauss zone 13"; + case 21414 : return "Beijing Gauss zone 14"; + case 21415 : return "Beijing Gauss zone 15"; + case 21416 : return "Beijing Gauss zone 16"; + case 21417 : return "Beijing Gauss zone 17"; + case 21418 : return "Beijing Gauss zone 18"; + case 21419 : return "Beijing Gauss zone 19"; + case 21420 : return "Beijing Gauss zone 20"; + case 21421 : return "Beijing Gauss zone 21"; + case 21422 : return "Beijing Gauss zone 22"; + case 21423 : return "Beijing Gauss zone 23"; + case 21473 : return "Beijing Gauss 13N"; + case 21474 : return "Beijing Gauss 14N"; + case 21475 : return "Beijing Gauss 15N"; + case 21476 : return "Beijing Gauss 16N"; + case 21477 : return "Beijing Gauss 17N"; + case 21478 : return "Beijing Gauss 18N"; + case 21479 : return "Beijing Gauss 19N"; + case 21480 : return "Beijing Gauss 20N"; + case 21481 : return "Beijing Gauss 21N"; + case 21482 : return "Beijing Gauss 22N"; + case 21483 : return "Beijing Gauss 23N"; + case 21500 : return "Belge Lambert 50"; + case 21790 : return "Bern 1898 Swiss Old"; + case 21817 : return "Bogota UTM zone 17N"; + case 21818 : return "Bogota UTM zone 18N"; + case 21891 : return "Bogota Colombia 3W"; + case 21892 : return "Bogota Colombia Bogota"; + case 21893 : return "Bogota Colombia 3E"; + case 21894 : return "Bogota Colombia 6E"; + case 22032 : return "Camacupa UTM 32S"; + case 22033 : return "Camacupa UTM 33S"; + case 22191 : return "C Inchauspe Argentina 1"; + case 22192 : return "C Inchauspe Argentina 2"; + case 22193 : return "C Inchauspe Argentina 3"; + case 22194 : return "C Inchauspe Argentina 4"; + case 22195 : return "C Inchauspe Argentina 5"; + case 22196 : return "C Inchauspe Argentina 6"; + case 22197 : return "C Inchauspe Argentina 7"; + case 22332 : return "Carthage UTM zone 32N"; + case 22391 : return "Carthage Nord Tunisie"; + case 22392 : return "Carthage Sud Tunisie"; + case 22523 : return "Corrego Alegre UTM 23S"; + case 22524 : return "Corrego Alegre UTM 24S"; + case 22832 : return "Douala UTM zone 32N"; + case 22992 : return "Egypt 1907 Red Belt"; + case 22993 : return "Egypt 1907 Purple Belt"; + case 22994 : return "Egypt 1907 Ext Purple"; + case 23028 : return "ED50 UTM zone 28N"; + case 23029 : return "ED50 UTM zone 29N"; + case 23030 : return "ED50 UTM zone 30N"; + case 23031 : return "ED50 UTM zone 31N"; + case 23032 : return "ED50 UTM zone 32N"; + case 23033 : return "ED50 UTM zone 33N"; + case 23034 : return "ED50 UTM zone 34N"; + case 23035 : return "ED50 UTM zone 35N"; + case 23036 : return "ED50 UTM zone 36N"; + case 23037 : return "ED50 UTM zone 37N"; + case 23038 : return "ED50 UTM zone 38N"; + case 23239 : return "Fahud UTM zone 39N"; + case 23240 : return "Fahud UTM zone 40N"; + case 23433 : return "Garoua UTM zone 33N"; + case 23700 : return "HD72 EOV"; + case 23846 : return "ID74 UTM zone 46N"; + case 23847 : return "ID74 UTM zone 47N"; + case 23848 : return "ID74 UTM zone 48N"; + case 23849 : return "ID74 UTM zone 49N"; + case 23850 : return "ID74 UTM zone 50N"; + case 23851 : return "ID74 UTM zone 51N"; + case 23852 : return "ID74 UTM zone 52N"; + case 23853 : return "ID74 UTM zone 53N"; + case 23886 : return "ID74 UTM zone 46S"; + case 23887 : return "ID74 UTM zone 47S"; + case 23888 : return "ID74 UTM zone 48S"; + case 23889 : return "ID74 UTM zone 49S"; + case 23890 : return "ID74 UTM zone 50S"; + case 23891 : return "ID74 UTM zone 51S"; + case 23892 : return "ID74 UTM zone 52S"; + case 23893 : return "ID74 UTM zone 53S"; + case 23894 : return "ID74 UTM zone 54S"; + case 23947 : return "Indian 1954 UTM 47N"; + case 23948 : return "Indian 1954 UTM 48N"; + case 24047 : return "Indian 1975 UTM 47N"; + case 24048 : return "Indian 1975 UTM 48N"; + case 24100 : return "Jamaica 1875 Old Grid"; + case 24200 : return "JAD69 Jamaica Grid"; + case 24370 : return "Kalianpur India 0"; + case 24371 : return "Kalianpur India I"; + case 24372 : return "Kalianpur India IIa"; + case 24373 : return "Kalianpur India IIIa"; + case 24374 : return "Kalianpur India IVa"; + case 24382 : return "Kalianpur India IIb"; + case 24383 : return "Kalianpur India IIIb"; + case 24384 : return "Kalianpur India IVb"; + case 24500 : return "Kertau Singapore Grid"; + case 24547 : return "Kertau UTM zone 47N"; + case 24548 : return "Kertau UTM zone 48N"; + case 24720 : return "La Canoa UTM zone 20N"; + case 24721 : return "La Canoa UTM zone 21N"; + case 24818 : return "PSAD56 UTM zone 18N"; + case 24819 : return "PSAD56 UTM zone 19N"; + case 24820 : return "PSAD56 UTM zone 20N"; + case 24821 : return "PSAD56 UTM zone 21N"; + case 24877 : return "PSAD56 UTM zone 17S"; + case 24878 : return "PSAD56 UTM zone 18S"; + case 24879 : return "PSAD56 UTM zone 19S"; + case 24880 : return "PSAD56 UTM zone 20S"; + case 24891 : return "PSAD56 Peru west zone"; + case 24892 : return "PSAD56 Peru central"; + case 24893 : return "PSAD56 Peru east zone"; + case 25000 : return "Leigon Ghana Grid"; + case 25231 : return "Lome UTM zone 31N"; + case 25391 : return "Luzon Philippines I"; + case 25392 : return "Luzon Philippines II"; + case 25393 : return "Luzon Philippines III"; + case 25394 : return "Luzon Philippines IV"; + case 25395 : return "Luzon Philippines V"; + case 25700 : return "Makassar NEIEZ"; + case 25932 : return "Malongo 1987 UTM 32S"; + case 26191 : return "Merchich Nord Maroc"; + case 26192 : return "Merchich Sud Maroc"; + case 26193 : return "Merchich Sahara"; + case 26237 : return "Massawa UTM zone 37N"; + case 26331 : return "Minna UTM zone 31N"; + case 26332 : return "Minna UTM zone 32N"; + case 26391 : return "Minna Nigeria West"; + case 26392 : return "Minna Nigeria Mid Belt"; + case 26393 : return "Minna Nigeria East"; + case 26432 : return "Mhast UTM zone 32S"; + case 26591 : return "Monte Mario Italy 1"; + case 26592 : return "Monte Mario Italy 2"; + case 26632 : return "M poraloko UTM 32N"; + case 26692 : return "M poraloko UTM 32S"; + case 26703 : return "NAD27 UTM zone 3N"; + case 26704 : return "NAD27 UTM zone 4N"; + case 26705 : return "NAD27 UTM zone 5N"; + case 26706 : return "NAD27 UTM zone 6N"; + case 26707 : return "NAD27 UTM zone 7N"; + case 26708 : return "NAD27 UTM zone 8N"; + case 26709 : return "NAD27 UTM zone 9N"; + case 26710 : return "NAD27 UTM zone 10N"; + case 26711 : return "NAD27 UTM zone 11N"; + case 26712 : return "NAD27 UTM zone 12N"; + case 26713 : return "NAD27 UTM zone 13N"; + case 26714 : return "NAD27 UTM zone 14N"; + case 26715 : return "NAD27 UTM zone 15N"; + case 26716 : return "NAD27 UTM zone 16N"; + case 26717 : return "NAD27 UTM zone 17N"; + case 26718 : return "NAD27 UTM zone 18N"; + case 26719 : return "NAD27 UTM zone 19N"; + case 26720 : return "NAD27 UTM zone 20N"; + case 26721 : return "NAD27 UTM zone 21N"; + case 26722 : return "NAD27 UTM zone 22N"; + case 26729 : return "NAD27 Alabama East"; + case 26730 : return "NAD27 Alabama West"; + case 26731 : return "NAD27 Alaska zone 1"; + case 26732 : return "NAD27 Alaska zone 2"; + case 26733 : return "NAD27 Alaska zone 3"; + case 26734 : return "NAD27 Alaska zone 4"; + case 26735 : return "NAD27 Alaska zone 5"; + case 26736 : return "NAD27 Alaska zone 6"; + case 26737 : return "NAD27 Alaska zone 7"; + case 26738 : return "NAD27 Alaska zone 8"; + case 26739 : return "NAD27 Alaska zone 9"; + case 26740 : return "NAD27 Alaska zone 10"; + case 26741 : return "NAD27 California I"; + case 26742 : return "NAD27 California II"; + case 26743 : return "NAD27 California III"; + case 26744 : return "NAD27 California IV"; + case 26745 : return "NAD27 California V"; + case 26746 : return "NAD27 California VI"; + case 26747 : return "NAD27 California VII"; + case 26748 : return "NAD27 Arizona East"; + case 26749 : return "NAD27 Arizona Central"; + case 26750 : return "NAD27 Arizona West"; + case 26751 : return "NAD27 Arkansas North"; + case 26752 : return "NAD27 Arkansas South"; + case 26753 : return "NAD27 Colorado North"; + case 26754 : return "NAD27 Colorado Central"; + case 26755 : return "NAD27 Colorado South"; + case 26756 : return "NAD27 Connecticut"; + case 26757 : return "NAD27 Delaware"; + case 26758 : return "NAD27 Florida East"; + case 26759 : return "NAD27 Florida West"; + case 26760 : return "NAD27 Florida North"; + case 26761 : return "NAD27 Hawaii zone 1"; + case 26762 : return "NAD27 Hawaii zone 2"; + case 26763 : return "NAD27 Hawaii zone 3"; + case 26764 : return "NAD27 Hawaii zone 4"; + case 26765 : return "NAD27 Hawaii zone 5"; + case 26766 : return "NAD27 Georgia East"; + case 26767 : return "NAD27 Georgia West"; + case 26768 : return "NAD27 Idaho East"; + case 26769 : return "NAD27 Idaho Central"; + case 26770 : return "NAD27 Idaho West"; + case 26771 : return "NAD27 Illinois East"; + case 26772 : return "NAD27 Illinois West"; + case 26773 : return "NAD27 Indiana East"; + case 26774 : return "NAD27 Indiana West"; + case 26775 : return "NAD27 Iowa North"; + case 26776 : return "NAD27 Iowa South"; + case 26777 : return "NAD27 Kansas North"; + case 26778 : return "NAD27 Kansas South"; + case 26779 : return "NAD27 Kentucky North"; + case 26780 : return "NAD27 Kentucky South"; + case 26781 : return "NAD27 Louisiana North"; + case 26782 : return "NAD27 Louisiana South"; + case 26783 : return "NAD27 Maine East"; + case 26784 : return "NAD27 Maine West"; + case 26785 : return "NAD27 Maryland"; + case 26786 : return "NAD27 Massachusetts"; + case 26787 : return "NAD27 Massachusetts Is"; + case 26788 : return "NAD27 Michigan North"; + case 26789 : return "NAD27 Michigan Central"; + case 26790 : return "NAD27 Michigan South"; + case 26791 : return "NAD27 Minnesota North"; + case 26792 : return "NAD27 Minnesota Cent"; + case 26793 : return "NAD27 Minnesota South"; + case 26794 : return "NAD27 Mississippi East"; + case 26795 : return "NAD27 Mississippi West"; + case 26796 : return "NAD27 Missouri East"; + case 26797 : return "NAD27 Missouri Central"; + case 26798 : return "NAD27 Missouri West"; + case 26801 : return "NAD Michigan Michigan East"; + case 26802 : return "NAD Michigan Michigan Old Central"; + case 26803 : return "NAD Michigan Michigan West"; + case 26903 : return "NAD83 UTM zone 3N"; + case 26904 : return "NAD83 UTM zone 4N"; + case 26905 : return "NAD83 UTM zone 5N"; + case 26906 : return "NAD83 UTM zone 6N"; + case 26907 : return "NAD83 UTM zone 7N"; + case 26908 : return "NAD83 UTM zone 8N"; + case 26909 : return "NAD83 UTM zone 9N"; + case 26910 : return "NAD83 UTM zone 10N"; + case 26911 : return "NAD83 UTM zone 11N"; + case 26912 : return "NAD83 UTM zone 12N"; + case 26913 : return "NAD83 UTM zone 13N"; + case 26914 : return "NAD83 UTM zone 14N"; + case 26915 : return "NAD83 UTM zone 15N"; + case 26916 : return "NAD83 UTM zone 16N"; + case 26917 : return "NAD83 UTM zone 17N"; + case 26918 : return "NAD83 UTM zone 18N"; + case 26919 : return "NAD83 UTM zone 19N"; + case 26920 : return "NAD83 UTM zone 20N"; + case 26921 : return "NAD83 UTM zone 21N"; + case 26922 : return "NAD83 UTM zone 22N"; + case 26923 : return "NAD83 UTM zone 23N"; + case 26929 : return "NAD83 Alabama East"; + case 26930 : return "NAD83 Alabama West"; + case 26931 : return "NAD83 Alaska zone 1"; + case 26932 : return "NAD83 Alaska zone 2"; + case 26933 : return "NAD83 Alaska zone 3"; + case 26934 : return "NAD83 Alaska zone 4"; + case 26935 : return "NAD83 Alaska zone 5"; + case 26936 : return "NAD83 Alaska zone 6"; + case 26937 : return "NAD83 Alaska zone 7"; + case 26938 : return "NAD83 Alaska zone 8"; + case 26939 : return "NAD83 Alaska zone 9"; + case 26940 : return "NAD83 Alaska zone 10"; + case 26941 : return "NAD83 California 1"; + case 26942 : return "NAD83 California 2"; + case 26943 : return "NAD83 California 3"; + case 26944 : return "NAD83 California 4"; + case 26945 : return "NAD83 California 5"; + case 26946 : return "NAD83 California 6"; + case 26948 : return "NAD83 Arizona East"; + case 26949 : return "NAD83 Arizona Central"; + case 26950 : return "NAD83 Arizona West"; + case 26951 : return "NAD83 Arkansas North"; + case 26952 : return "NAD83 Arkansas South"; + case 26953 : return "NAD83 Colorado North"; + case 26954 : return "NAD83 Colorado Central"; + case 26955 : return "NAD83 Colorado South"; + case 26956 : return "NAD83 Connecticut"; + case 26957 : return "NAD83 Delaware"; + case 26958 : return "NAD83 Florida East"; + case 26959 : return "NAD83 Florida West"; + case 26960 : return "NAD83 Florida North"; + case 26961 : return "NAD83 Hawaii zone 1"; + case 26962 : return "NAD83 Hawaii zone 2"; + case 26963 : return "NAD83 Hawaii zone 3"; + case 26964 : return "NAD83 Hawaii zone 4"; + case 26965 : return "NAD83 Hawaii zone 5"; + case 26966 : return "NAD83 Georgia East"; + case 26967 : return "NAD83 Georgia West"; + case 26968 : return "NAD83 Idaho East"; + case 26969 : return "NAD83 Idaho Central"; + case 26970 : return "NAD83 Idaho West"; + case 26971 : return "NAD83 Illinois East"; + case 26972 : return "NAD83 Illinois West"; + case 26973 : return "NAD83 Indiana East"; + case 26974 : return "NAD83 Indiana West"; + case 26975 : return "NAD83 Iowa North"; + case 26976 : return "NAD83 Iowa South"; + case 26977 : return "NAD83 Kansas North"; + case 26978 : return "NAD83 Kansas South"; + case 26979 : return "NAD83 Kentucky North"; + case 26980 : return "NAD83 Kentucky South"; + case 26981 : return "NAD83 Louisiana North"; + case 26982 : return "NAD83 Louisiana South"; + case 26983 : return "NAD83 Maine East"; + case 26984 : return "NAD83 Maine West"; + case 26985 : return "NAD83 Maryland"; + case 26986 : return "NAD83 Massachusetts"; + case 26987 : return "NAD83 Massachusetts Is"; + case 26988 : return "NAD83 Michigan North"; + case 26989 : return "NAD83 Michigan Central"; + case 26990 : return "NAD83 Michigan South"; + case 26991 : return "NAD83 Minnesota North"; + case 26992 : return "NAD83 Minnesota Cent"; + case 26993 : return "NAD83 Minnesota South"; + case 26994 : return "NAD83 Mississippi East"; + case 26995 : return "NAD83 Mississippi West"; + case 26996 : return "NAD83 Missouri East"; + case 26997 : return "NAD83 Missouri Central"; + case 26998 : return "NAD83 Missouri West"; + case 27038 : return "Nahrwan 1967 UTM 38N"; + case 27039 : return "Nahrwan 1967 UTM 39N"; + case 27040 : return "Nahrwan 1967 UTM 40N"; + case 27120 : return "Naparima UTM 20N"; + case 27200 : return "GD49 NZ Map Grid"; + case 27291 : return "GD49 North Island Grid"; + case 27292 : return "GD49 South Island Grid"; + case 27429 : return "Datum 73 UTM zone 29N"; + case 27500 : return "ATF Nord de Guerre"; + case 27581 : return "NTF France I"; + case 27582 : return "NTF France II"; + case 27583 : return "NTF France III"; + case 27591 : return "NTF Nord France"; + case 27592 : return "NTF Centre France"; + case 27593 : return "NTF Sud France"; + case 27700 : return "British National Grid"; + case 28191 : return "Palestine 1923 Palestine Grid"; + case 28192 : return "Palestine 1923 Palestine Belt"; + case 28193 : return "Palestine 1923 Israeli CS Grid"; + case 28232 : return "Point Noire UTM 32S"; + case 28348 : return "GDA94 MGA zone 48"; + case 28349 : return "GDA94 MGA zone 49"; + case 28350 : return "GDA94 MGA zone 50"; + case 28351 : return "GDA94 MGA zone 51"; + case 28352 : return "GDA94 MGA zone 52"; + case 28353 : return "GDA94 MGA zone 53"; + case 28354 : return "GDA94 MGA zone 54"; + case 28355 : return "GDA94 MGA zone 55"; + case 28356 : return "GDA94 MGA zone 56"; + case 28357 : return "GDA94 MGA zone 57"; + case 28358 : return "GDA94 MGA zone 58"; + case 28404 : return "Pulkovo Gauss zone 4"; + case 28405 : return "Pulkovo Gauss zone 5"; + case 28406 : return "Pulkovo Gauss zone 6"; + case 28407 : return "Pulkovo Gauss zone 7"; + case 28408 : return "Pulkovo Gauss zone 8"; + case 28409 : return "Pulkovo Gauss zone 9"; + case 28410 : return "Pulkovo Gauss zone 10"; + case 28411 : return "Pulkovo Gauss zone 11"; + case 28412 : return "Pulkovo Gauss zone 12"; + case 28413 : return "Pulkovo Gauss zone 13"; + case 28414 : return "Pulkovo Gauss zone 14"; + case 28415 : return "Pulkovo Gauss zone 15"; + case 28416 : return "Pulkovo Gauss zone 16"; + case 28417 : return "Pulkovo Gauss zone 17"; + case 28418 : return "Pulkovo Gauss zone 18"; + case 28419 : return "Pulkovo Gauss zone 19"; + case 28420 : return "Pulkovo Gauss zone 20"; + case 28421 : return "Pulkovo Gauss zone 21"; + case 28422 : return "Pulkovo Gauss zone 22"; + case 28423 : return "Pulkovo Gauss zone 23"; + case 28424 : return "Pulkovo Gauss zone 24"; + case 28425 : return "Pulkovo Gauss zone 25"; + case 28426 : return "Pulkovo Gauss zone 26"; + case 28427 : return "Pulkovo Gauss zone 27"; + case 28428 : return "Pulkovo Gauss zone 28"; + case 28429 : return "Pulkovo Gauss zone 29"; + case 28430 : return "Pulkovo Gauss zone 30"; + case 28431 : return "Pulkovo Gauss zone 31"; + case 28432 : return "Pulkovo Gauss zone 32"; + case 28464 : return "Pulkovo Gauss 4N"; + case 28465 : return "Pulkovo Gauss 5N"; + case 28466 : return "Pulkovo Gauss 6N"; + case 28467 : return "Pulkovo Gauss 7N"; + case 28468 : return "Pulkovo Gauss 8N"; + case 28469 : return "Pulkovo Gauss 9N"; + case 28470 : return "Pulkovo Gauss 10N"; + case 28471 : return "Pulkovo Gauss 11N"; + case 28472 : return "Pulkovo Gauss 12N"; + case 28473 : return "Pulkovo Gauss 13N"; + case 28474 : return "Pulkovo Gauss 14N"; + case 28475 : return "Pulkovo Gauss 15N"; + case 28476 : return "Pulkovo Gauss 16N"; + case 28477 : return "Pulkovo Gauss 17N"; + case 28478 : return "Pulkovo Gauss 18N"; + case 28479 : return "Pulkovo Gauss 19N"; + case 28480 : return "Pulkovo Gauss 20N"; + case 28481 : return "Pulkovo Gauss 21N"; + case 28482 : return "Pulkovo Gauss 22N"; + case 28483 : return "Pulkovo Gauss 23N"; + case 28484 : return "Pulkovo Gauss 24N"; + case 28485 : return "Pulkovo Gauss 25N"; + case 28486 : return "Pulkovo Gauss 26N"; + case 28487 : return "Pulkovo Gauss 27N"; + case 28488 : return "Pulkovo Gauss 28N"; + case 28489 : return "Pulkovo Gauss 29N"; + case 28490 : return "Pulkovo Gauss 30N"; + case 28491 : return "Pulkovo Gauss 31N"; + case 28492 : return "Pulkovo Gauss 32N"; + case 28600 : return "Qatar National Grid"; + case 28991 : return "RD Netherlands Old"; + case 28992 : return "RD Netherlands New"; + case 29101 : return "SAD69 Brazil Polyconic"; + case 29118 : return "SAD69 UTM zone 18N"; + case 29119 : return "SAD69 UTM zone 19N"; + case 29120 : return "SAD69 UTM zone 20N"; + case 29121 : return "SAD69 UTM zone 21N"; + case 29122 : return "SAD69 UTM zone 22N"; + case 29177 : return "SAD69 UTM zone 17S"; + case 29178 : return "SAD69 UTM zone 18S"; + case 29179 : return "SAD69 UTM zone 19S"; + case 29180 : return "SAD69 UTM zone 20S"; + case 29181 : return "SAD69 UTM zone 21S"; + case 29182 : return "SAD69 UTM zone 22S"; + case 29183 : return "SAD69 UTM zone 23S"; + case 29184 : return "SAD69 UTM zone 24S"; + case 29185 : return "SAD69 UTM zone 25S"; + case 29220 : return "Sapper Hill UTM 20S"; + case 29221 : return "Sapper Hill UTM 21S"; + case 29333 : return "Schwarzeck UTM 33S"; + case 29635 : return "Sudan UTM zone 35N"; + case 29636 : return "Sudan UTM zone 36N"; + case 29700 : return "Tananarive Laborde"; + case 29738 : return "Tananarive UTM 38S"; + case 29739 : return "Tananarive UTM 39S"; + case 29800 : return "Timbalai 1948 Borneo"; + case 29849 : return "Timbalai 1948 UTM 49N"; + case 29850 : return "Timbalai 1948 UTM 50N"; + case 29900 : return "TM65 Irish Nat Grid"; + case 30200 : return "Trinidad 1903 Trinidad"; + case 30339 : return "TC 1948 UTM zone 39N"; + case 30340 : return "TC 1948 UTM zone 40N"; + case 30491 : return "Voirol N Algerie ancien"; + case 30492 : return "Voirol S Algerie ancien"; + case 30591 : return "Voirol Unifie N Algerie"; + case 30592 : return "Voirol Unifie S Algerie"; + case 30600 : return "Bern 1938 Swiss New"; + case 30729 : return "Nord Sahara UTM 29N"; + case 30730 : return "Nord Sahara UTM 30N"; + case 30731 : return "Nord Sahara UTM 31N"; + case 30732 : return "Nord Sahara UTM 32N"; + case 31028 : return "Yoff UTM zone 28N"; + case 31121 : return "Zanderij UTM zone 21N"; + case 31291 : return "MGI Austria West"; + case 31292 : return "MGI Austria Central"; + case 31293 : return "MGI Austria East"; + case 31300 : return "Belge Lambert 72"; + case 31491 : return "DHDN Germany zone 1"; + case 31492 : return "DHDN Germany zone 2"; + case 31493 : return "DHDN Germany zone 3"; + case 31494 : return "DHDN Germany zone 4"; + case 31495 : return "DHDN Germany zone 5"; + case 31700 : return "Dealul Piscului 1970 Stereo 70"; + case 32001 : return "NAD27 Montana North"; + case 32002 : return "NAD27 Montana Central"; + case 32003 : return "NAD27 Montana South"; + case 32005 : return "NAD27 Nebraska North"; + case 32006 : return "NAD27 Nebraska South"; + case 32007 : return "NAD27 Nevada East"; + case 32008 : return "NAD27 Nevada Central"; + case 32009 : return "NAD27 Nevada West"; + case 32010 : return "NAD27 New Hampshire"; + case 32011 : return "NAD27 New Jersey"; + case 32012 : return "NAD27 New Mexico East"; + case 32013 : return "NAD27 New Mexico Cent"; + case 32014 : return "NAD27 New Mexico West"; + case 32015 : return "NAD27 New York East"; + case 32016 : return "NAD27 New York Central"; + case 32017 : return "NAD27 New York West"; + case 32018 : return "NAD27 New York Long Is"; + case 32019 : return "NAD27 North Carolina"; + case 32020 : return "NAD27 North Dakota N"; + case 32021 : return "NAD27 North Dakota S"; + case 32022 : return "NAD27 Ohio North"; + case 32023 : return "NAD27 Ohio South"; + case 32024 : return "NAD27 Oklahoma North"; + case 32025 : return "NAD27 Oklahoma South"; + case 32026 : return "NAD27 Oregon North"; + case 32027 : return "NAD27 Oregon South"; + case 32028 : return "NAD27 Pennsylvania N"; + case 32029 : return "NAD27 Pennsylvania S"; + case 32030 : return "NAD27 Rhode Island"; + case 32031 : return "NAD27 South Carolina N"; + case 32033 : return "NAD27 South Carolina S"; + case 32034 : return "NAD27 South Dakota N"; + case 32035 : return "NAD27 South Dakota S"; + case 32036 : return "NAD27 Tennessee"; + case 32037 : return "NAD27 Texas North"; + case 32038 : return "NAD27 Texas North Cen"; + case 32039 : return "NAD27 Texas Central"; + case 32040 : return "NAD27 Texas South Cen"; + case 32041 : return "NAD27 Texas South"; + case 32042 : return "NAD27 Utah North"; + case 32043 : return "NAD27 Utah Central"; + case 32044 : return "NAD27 Utah South"; + case 32045 : return "NAD27 Vermont"; + case 32046 : return "NAD27 Virginia North"; + case 32047 : return "NAD27 Virginia South"; + case 32048 : return "NAD27 Washington North"; + case 32049 : return "NAD27 Washington South"; + case 32050 : return "NAD27 West Virginia N"; + case 32051 : return "NAD27 West Virginia S"; + case 32052 : return "NAD27 Wisconsin North"; + case 32053 : return "NAD27 Wisconsin Cen"; + case 32054 : return "NAD27 Wisconsin South"; + case 32055 : return "NAD27 Wyoming East"; + case 32056 : return "NAD27 Wyoming E Cen"; + case 32057 : return "NAD27 Wyoming W Cen"; + case 32058 : return "NAD27 Wyoming West"; + case 32059 : return "NAD27 Puerto Rico"; + case 32060 : return "NAD27 St Croix"; + case 32064 : return "NAD27 BLM 14N FtUS"; + case 32065 : return "NAD27 BLM 15N FtUS"; + case 32066 : return "NAD27 BLM 16N FtUS"; + case 32067 : return "NAD27 BLM 17N FtUS"; + case 32074 : return "NAD27 BLM 14N Feet"; + case 32075 : return "NAD27 BLM 15N Feet"; + case 32076 : return "NAD27 BLM 16N Feet"; + case 32077 : return "NAD27 BLM 17N Feet"; + case 32100 : return "NAD83 Montana"; + case 32104 : return "NAD83 Nebraska"; + case 32107 : return "NAD83 Nevada East"; + case 32108 : return "NAD83 Nevada Central"; + case 32109 : return "NAD83 Nevada West"; + case 32110 : return "NAD83 New Hampshire"; + case 32111 : return "NAD83 New Jersey"; + case 32112 : return "NAD83 New Mexico East"; + case 32113 : return "NAD83 New Mexico Cent"; + case 32114 : return "NAD83 New Mexico West"; + case 32115 : return "NAD83 New York East"; + case 32116 : return "NAD83 New York Central"; + case 32117 : return "NAD83 New York West"; + case 32118 : return "NAD83 New York Long Is"; + case 32119 : return "NAD83 North Carolina"; + case 32120 : return "NAD83 North Dakota N"; + case 32121 : return "NAD83 North Dakota S"; + case 32122 : return "NAD83 Ohio North"; + case 32123 : return "NAD83 Ohio South"; + case 32124 : return "NAD83 Oklahoma North"; + case 32125 : return "NAD83 Oklahoma South"; + case 32126 : return "NAD83 Oregon North"; + case 32127 : return "NAD83 Oregon South"; + case 32128 : return "NAD83 Pennsylvania N"; + case 32129 : return "NAD83 Pennsylvania S"; + case 32130 : return "NAD83 Rhode Island"; + case 32133 : return "NAD83 South Carolina"; + case 32134 : return "NAD83 South Dakota N"; + case 32135 : return "NAD83 South Dakota S"; + case 32136 : return "NAD83 Tennessee"; + case 32137 : return "NAD83 Texas North"; + case 32138 : return "NAD83 Texas North Cen"; + case 32139 : return "NAD83 Texas Central"; + case 32140 : return "NAD83 Texas South Cen"; + case 32141 : return "NAD83 Texas South"; + case 32142 : return "NAD83 Utah North"; + case 32143 : return "NAD83 Utah Central"; + case 32144 : return "NAD83 Utah South"; + case 32145 : return "NAD83 Vermont"; + case 32146 : return "NAD83 Virginia North"; + case 32147 : return "NAD83 Virginia South"; + case 32148 : return "NAD83 Washington North"; + case 32149 : return "NAD83 Washington South"; + case 32150 : return "NAD83 West Virginia N"; + case 32151 : return "NAD83 West Virginia S"; + case 32152 : return "NAD83 Wisconsin North"; + case 32153 : return "NAD83 Wisconsin Cen"; + case 32154 : return "NAD83 Wisconsin South"; + case 32155 : return "NAD83 Wyoming East"; + case 32156 : return "NAD83 Wyoming E Cen"; + case 32157 : return "NAD83 Wyoming W Cen"; + case 32158 : return "NAD83 Wyoming West"; + case 32161 : return "NAD83 Puerto Rico Virgin Is"; + case 32201 : return "WGS72 UTM zone 1N"; + case 32202 : return "WGS72 UTM zone 2N"; + case 32203 : return "WGS72 UTM zone 3N"; + case 32204 : return "WGS72 UTM zone 4N"; + case 32205 : return "WGS72 UTM zone 5N"; + case 32206 : return "WGS72 UTM zone 6N"; + case 32207 : return "WGS72 UTM zone 7N"; + case 32208 : return "WGS72 UTM zone 8N"; + case 32209 : return "WGS72 UTM zone 9N"; + case 32210 : return "WGS72 UTM zone 10N"; + case 32211 : return "WGS72 UTM zone 11N"; + case 32212 : return "WGS72 UTM zone 12N"; + case 32213 : return "WGS72 UTM zone 13N"; + case 32214 : return "WGS72 UTM zone 14N"; + case 32215 : return "WGS72 UTM zone 15N"; + case 32216 : return "WGS72 UTM zone 16N"; + case 32217 : return "WGS72 UTM zone 17N"; + case 32218 : return "WGS72 UTM zone 18N"; + case 32219 : return "WGS72 UTM zone 19N"; + case 32220 : return "WGS72 UTM zone 20N"; + case 32221 : return "WGS72 UTM zone 21N"; + case 32222 : return "WGS72 UTM zone 22N"; + case 32223 : return "WGS72 UTM zone 23N"; + case 32224 : return "WGS72 UTM zone 24N"; + case 32225 : return "WGS72 UTM zone 25N"; + case 32226 : return "WGS72 UTM zone 26N"; + case 32227 : return "WGS72 UTM zone 27N"; + case 32228 : return "WGS72 UTM zone 28N"; + case 32229 : return "WGS72 UTM zone 29N"; + case 32230 : return "WGS72 UTM zone 30N"; + case 32231 : return "WGS72 UTM zone 31N"; + case 32232 : return "WGS72 UTM zone 32N"; + case 32233 : return "WGS72 UTM zone 33N"; + case 32234 : return "WGS72 UTM zone 34N"; + case 32235 : return "WGS72 UTM zone 35N"; + case 32236 : return "WGS72 UTM zone 36N"; + case 32237 : return "WGS72 UTM zone 37N"; + case 32238 : return "WGS72 UTM zone 38N"; + case 32239 : return "WGS72 UTM zone 39N"; + case 32240 : return "WGS72 UTM zone 40N"; + case 32241 : return "WGS72 UTM zone 41N"; + case 32242 : return "WGS72 UTM zone 42N"; + case 32243 : return "WGS72 UTM zone 43N"; + case 32244 : return "WGS72 UTM zone 44N"; + case 32245 : return "WGS72 UTM zone 45N"; + case 32246 : return "WGS72 UTM zone 46N"; + case 32247 : return "WGS72 UTM zone 47N"; + case 32248 : return "WGS72 UTM zone 48N"; + case 32249 : return "WGS72 UTM zone 49N"; + case 32250 : return "WGS72 UTM zone 50N"; + case 32251 : return "WGS72 UTM zone 51N"; + case 32252 : return "WGS72 UTM zone 52N"; + case 32253 : return "WGS72 UTM zone 53N"; + case 32254 : return "WGS72 UTM zone 54N"; + case 32255 : return "WGS72 UTM zone 55N"; + case 32256 : return "WGS72 UTM zone 56N"; + case 32257 : return "WGS72 UTM zone 57N"; + case 32258 : return "WGS72 UTM zone 58N"; + case 32259 : return "WGS72 UTM zone 59N"; + case 32260 : return "WGS72 UTM zone 60N"; + case 32301 : return "WGS72 UTM zone 1S"; + case 32302 : return "WGS72 UTM zone 2S"; + case 32303 : return "WGS72 UTM zone 3S"; + case 32304 : return "WGS72 UTM zone 4S"; + case 32305 : return "WGS72 UTM zone 5S"; + case 32306 : return "WGS72 UTM zone 6S"; + case 32307 : return "WGS72 UTM zone 7S"; + case 32308 : return "WGS72 UTM zone 8S"; + case 32309 : return "WGS72 UTM zone 9S"; + case 32310 : return "WGS72 UTM zone 10S"; + case 32311 : return "WGS72 UTM zone 11S"; + case 32312 : return "WGS72 UTM zone 12S"; + case 32313 : return "WGS72 UTM zone 13S"; + case 32314 : return "WGS72 UTM zone 14S"; + case 32315 : return "WGS72 UTM zone 15S"; + case 32316 : return "WGS72 UTM zone 16S"; + case 32317 : return "WGS72 UTM zone 17S"; + case 32318 : return "WGS72 UTM zone 18S"; + case 32319 : return "WGS72 UTM zone 19S"; + case 32320 : return "WGS72 UTM zone 20S"; + case 32321 : return "WGS72 UTM zone 21S"; + case 32322 : return "WGS72 UTM zone 22S"; + case 32323 : return "WGS72 UTM zone 23S"; + case 32324 : return "WGS72 UTM zone 24S"; + case 32325 : return "WGS72 UTM zone 25S"; + case 32326 : return "WGS72 UTM zone 26S"; + case 32327 : return "WGS72 UTM zone 27S"; + case 32328 : return "WGS72 UTM zone 28S"; + case 32329 : return "WGS72 UTM zone 29S"; + case 32330 : return "WGS72 UTM zone 30S"; + case 32331 : return "WGS72 UTM zone 31S"; + case 32332 : return "WGS72 UTM zone 32S"; + case 32333 : return "WGS72 UTM zone 33S"; + case 32334 : return "WGS72 UTM zone 34S"; + case 32335 : return "WGS72 UTM zone 35S"; + case 32336 : return "WGS72 UTM zone 36S"; + case 32337 : return "WGS72 UTM zone 37S"; + case 32338 : return "WGS72 UTM zone 38S"; + case 32339 : return "WGS72 UTM zone 39S"; + case 32340 : return "WGS72 UTM zone 40S"; + case 32341 : return "WGS72 UTM zone 41S"; + case 32342 : return "WGS72 UTM zone 42S"; + case 32343 : return "WGS72 UTM zone 43S"; + case 32344 : return "WGS72 UTM zone 44S"; + case 32345 : return "WGS72 UTM zone 45S"; + case 32346 : return "WGS72 UTM zone 46S"; + case 32347 : return "WGS72 UTM zone 47S"; + case 32348 : return "WGS72 UTM zone 48S"; + case 32349 : return "WGS72 UTM zone 49S"; + case 32350 : return "WGS72 UTM zone 50S"; + case 32351 : return "WGS72 UTM zone 51S"; + case 32352 : return "WGS72 UTM zone 52S"; + case 32353 : return "WGS72 UTM zone 53S"; + case 32354 : return "WGS72 UTM zone 54S"; + case 32355 : return "WGS72 UTM zone 55S"; + case 32356 : return "WGS72 UTM zone 56S"; + case 32357 : return "WGS72 UTM zone 57S"; + case 32358 : return "WGS72 UTM zone 58S"; + case 32359 : return "WGS72 UTM zone 59S"; + case 32360 : return "WGS72 UTM zone 60S"; + case 32401 : return "WGS72BE UTM zone 1N"; + case 32402 : return "WGS72BE UTM zone 2N"; + case 32403 : return "WGS72BE UTM zone 3N"; + case 32404 : return "WGS72BE UTM zone 4N"; + case 32405 : return "WGS72BE UTM zone 5N"; + case 32406 : return "WGS72BE UTM zone 6N"; + case 32407 : return "WGS72BE UTM zone 7N"; + case 32408 : return "WGS72BE UTM zone 8N"; + case 32409 : return "WGS72BE UTM zone 9N"; + case 32410 : return "WGS72BE UTM zone 10N"; + case 32411 : return "WGS72BE UTM zone 11N"; + case 32412 : return "WGS72BE UTM zone 12N"; + case 32413 : return "WGS72BE UTM zone 13N"; + case 32414 : return "WGS72BE UTM zone 14N"; + case 32415 : return "WGS72BE UTM zone 15N"; + case 32416 : return "WGS72BE UTM zone 16N"; + case 32417 : return "WGS72BE UTM zone 17N"; + case 32418 : return "WGS72BE UTM zone 18N"; + case 32419 : return "WGS72BE UTM zone 19N"; + case 32420 : return "WGS72BE UTM zone 20N"; + case 32421 : return "WGS72BE UTM zone 21N"; + case 32422 : return "WGS72BE UTM zone 22N"; + case 32423 : return "WGS72BE UTM zone 23N"; + case 32424 : return "WGS72BE UTM zone 24N"; + case 32425 : return "WGS72BE UTM zone 25N"; + case 32426 : return "WGS72BE UTM zone 26N"; + case 32427 : return "WGS72BE UTM zone 27N"; + case 32428 : return "WGS72BE UTM zone 28N"; + case 32429 : return "WGS72BE UTM zone 29N"; + case 32430 : return "WGS72BE UTM zone 30N"; + case 32431 : return "WGS72BE UTM zone 31N"; + case 32432 : return "WGS72BE UTM zone 32N"; + case 32433 : return "WGS72BE UTM zone 33N"; + case 32434 : return "WGS72BE UTM zone 34N"; + case 32435 : return "WGS72BE UTM zone 35N"; + case 32436 : return "WGS72BE UTM zone 36N"; + case 32437 : return "WGS72BE UTM zone 37N"; + case 32438 : return "WGS72BE UTM zone 38N"; + case 32439 : return "WGS72BE UTM zone 39N"; + case 32440 : return "WGS72BE UTM zone 40N"; + case 32441 : return "WGS72BE UTM zone 41N"; + case 32442 : return "WGS72BE UTM zone 42N"; + case 32443 : return "WGS72BE UTM zone 43N"; + case 32444 : return "WGS72BE UTM zone 44N"; + case 32445 : return "WGS72BE UTM zone 45N"; + case 32446 : return "WGS72BE UTM zone 46N"; + case 32447 : return "WGS72BE UTM zone 47N"; + case 32448 : return "WGS72BE UTM zone 48N"; + case 32449 : return "WGS72BE UTM zone 49N"; + case 32450 : return "WGS72BE UTM zone 50N"; + case 32451 : return "WGS72BE UTM zone 51N"; + case 32452 : return "WGS72BE UTM zone 52N"; + case 32453 : return "WGS72BE UTM zone 53N"; + case 32454 : return "WGS72BE UTM zone 54N"; + case 32455 : return "WGS72BE UTM zone 55N"; + case 32456 : return "WGS72BE UTM zone 56N"; + case 32457 : return "WGS72BE UTM zone 57N"; + case 32458 : return "WGS72BE UTM zone 58N"; + case 32459 : return "WGS72BE UTM zone 59N"; + case 32460 : return "WGS72BE UTM zone 60N"; + case 32501 : return "WGS72BE UTM zone 1S"; + case 32502 : return "WGS72BE UTM zone 2S"; + case 32503 : return "WGS72BE UTM zone 3S"; + case 32504 : return "WGS72BE UTM zone 4S"; + case 32505 : return "WGS72BE UTM zone 5S"; + case 32506 : return "WGS72BE UTM zone 6S"; + case 32507 : return "WGS72BE UTM zone 7S"; + case 32508 : return "WGS72BE UTM zone 8S"; + case 32509 : return "WGS72BE UTM zone 9S"; + case 32510 : return "WGS72BE UTM zone 10S"; + case 32511 : return "WGS72BE UTM zone 11S"; + case 32512 : return "WGS72BE UTM zone 12S"; + case 32513 : return "WGS72BE UTM zone 13S"; + case 32514 : return "WGS72BE UTM zone 14S"; + case 32515 : return "WGS72BE UTM zone 15S"; + case 32516 : return "WGS72BE UTM zone 16S"; + case 32517 : return "WGS72BE UTM zone 17S"; + case 32518 : return "WGS72BE UTM zone 18S"; + case 32519 : return "WGS72BE UTM zone 19S"; + case 32520 : return "WGS72BE UTM zone 20S"; + case 32521 : return "WGS72BE UTM zone 21S"; + case 32522 : return "WGS72BE UTM zone 22S"; + case 32523 : return "WGS72BE UTM zone 23S"; + case 32524 : return "WGS72BE UTM zone 24S"; + case 32525 : return "WGS72BE UTM zone 25S"; + case 32526 : return "WGS72BE UTM zone 26S"; + case 32527 : return "WGS72BE UTM zone 27S"; + case 32528 : return "WGS72BE UTM zone 28S"; + case 32529 : return "WGS72BE UTM zone 29S"; + case 32530 : return "WGS72BE UTM zone 30S"; + case 32531 : return "WGS72BE UTM zone 31S"; + case 32532 : return "WGS72BE UTM zone 32S"; + case 32533 : return "WGS72BE UTM zone 33S"; + case 32534 : return "WGS72BE UTM zone 34S"; + case 32535 : return "WGS72BE UTM zone 35S"; + case 32536 : return "WGS72BE UTM zone 36S"; + case 32537 : return "WGS72BE UTM zone 37S"; + case 32538 : return "WGS72BE UTM zone 38S"; + case 32539 : return "WGS72BE UTM zone 39S"; + case 32540 : return "WGS72BE UTM zone 40S"; + case 32541 : return "WGS72BE UTM zone 41S"; + case 32542 : return "WGS72BE UTM zone 42S"; + case 32543 : return "WGS72BE UTM zone 43S"; + case 32544 : return "WGS72BE UTM zone 44S"; + case 32545 : return "WGS72BE UTM zone 45S"; + case 32546 : return "WGS72BE UTM zone 46S"; + case 32547 : return "WGS72BE UTM zone 47S"; + case 32548 : return "WGS72BE UTM zone 48S"; + case 32549 : return "WGS72BE UTM zone 49S"; + case 32550 : return "WGS72BE UTM zone 50S"; + case 32551 : return "WGS72BE UTM zone 51S"; + case 32552 : return "WGS72BE UTM zone 52S"; + case 32553 : return "WGS72BE UTM zone 53S"; + case 32554 : return "WGS72BE UTM zone 54S"; + case 32555 : return "WGS72BE UTM zone 55S"; + case 32556 : return "WGS72BE UTM zone 56S"; + case 32557 : return "WGS72BE UTM zone 57S"; + case 32558 : return "WGS72BE UTM zone 58S"; + case 32559 : return "WGS72BE UTM zone 59S"; + case 32560 : return "WGS72BE UTM zone 60S"; + case 32601 : return "WGS84 UTM zone 1N"; + case 32602 : return "WGS84 UTM zone 2N"; + case 32603 : return "WGS84 UTM zone 3N"; + case 32604 : return "WGS84 UTM zone 4N"; + case 32605 : return "WGS84 UTM zone 5N"; + case 32606 : return "WGS84 UTM zone 6N"; + case 32607 : return "WGS84 UTM zone 7N"; + case 32608 : return "WGS84 UTM zone 8N"; + case 32609 : return "WGS84 UTM zone 9N"; + case 32610 : return "WGS84 UTM zone 10N"; + case 32611 : return "WGS84 UTM zone 11N"; + case 32612 : return "WGS84 UTM zone 12N"; + case 32613 : return "WGS84 UTM zone 13N"; + case 32614 : return "WGS84 UTM zone 14N"; + case 32615 : return "WGS84 UTM zone 15N"; + case 32616 : return "WGS84 UTM zone 16N"; + case 32617 : return "WGS84 UTM zone 17N"; + case 32618 : return "WGS84 UTM zone 18N"; + case 32619 : return "WGS84 UTM zone 19N"; + case 32620 : return "WGS84 UTM zone 20N"; + case 32621 : return "WGS84 UTM zone 21N"; + case 32622 : return "WGS84 UTM zone 22N"; + case 32623 : return "WGS84 UTM zone 23N"; + case 32624 : return "WGS84 UTM zone 24N"; + case 32625 : return "WGS84 UTM zone 25N"; + case 32626 : return "WGS84 UTM zone 26N"; + case 32627 : return "WGS84 UTM zone 27N"; + case 32628 : return "WGS84 UTM zone 28N"; + case 32629 : return "WGS84 UTM zone 29N"; + case 32630 : return "WGS84 UTM zone 30N"; + case 32631 : return "WGS84 UTM zone 31N"; + case 32632 : return "WGS84 UTM zone 32N"; + case 32633 : return "WGS84 UTM zone 33N"; + case 32634 : return "WGS84 UTM zone 34N"; + case 32635 : return "WGS84 UTM zone 35N"; + case 32636 : return "WGS84 UTM zone 36N"; + case 32637 : return "WGS84 UTM zone 37N"; + case 32638 : return "WGS84 UTM zone 38N"; + case 32639 : return "WGS84 UTM zone 39N"; + case 32640 : return "WGS84 UTM zone 40N"; + case 32641 : return "WGS84 UTM zone 41N"; + case 32642 : return "WGS84 UTM zone 42N"; + case 32643 : return "WGS84 UTM zone 43N"; + case 32644 : return "WGS84 UTM zone 44N"; + case 32645 : return "WGS84 UTM zone 45N"; + case 32646 : return "WGS84 UTM zone 46N"; + case 32647 : return "WGS84 UTM zone 47N"; + case 32648 : return "WGS84 UTM zone 48N"; + case 32649 : return "WGS84 UTM zone 49N"; + case 32650 : return "WGS84 UTM zone 50N"; + case 32651 : return "WGS84 UTM zone 51N"; + case 32652 : return "WGS84 UTM zone 52N"; + case 32653 : return "WGS84 UTM zone 53N"; + case 32654 : return "WGS84 UTM zone 54N"; + case 32655 : return "WGS84 UTM zone 55N"; + case 32656 : return "WGS84 UTM zone 56N"; + case 32657 : return "WGS84 UTM zone 57N"; + case 32658 : return "WGS84 UTM zone 58N"; + case 32659 : return "WGS84 UTM zone 59N"; + case 32660 : return "WGS84 UTM zone 60N"; + case 32701 : return "WGS84 UTM zone 1S"; + case 32702 : return "WGS84 UTM zone 2S"; + case 32703 : return "WGS84 UTM zone 3S"; + case 32704 : return "WGS84 UTM zone 4S"; + case 32705 : return "WGS84 UTM zone 5S"; + case 32706 : return "WGS84 UTM zone 6S"; + case 32707 : return "WGS84 UTM zone 7S"; + case 32708 : return "WGS84 UTM zone 8S"; + case 32709 : return "WGS84 UTM zone 9S"; + case 32710 : return "WGS84 UTM zone 10S"; + case 32711 : return "WGS84 UTM zone 11S"; + case 32712 : return "WGS84 UTM zone 12S"; + case 32713 : return "WGS84 UTM zone 13S"; + case 32714 : return "WGS84 UTM zone 14S"; + case 32715 : return "WGS84 UTM zone 15S"; + case 32716 : return "WGS84 UTM zone 16S"; + case 32717 : return "WGS84 UTM zone 17S"; + case 32718 : return "WGS84 UTM zone 18S"; + case 32719 : return "WGS84 UTM zone 19S"; + case 32720 : return "WGS84 UTM zone 20S"; + case 32721 : return "WGS84 UTM zone 21S"; + case 32722 : return "WGS84 UTM zone 22S"; + case 32723 : return "WGS84 UTM zone 23S"; + case 32724 : return "WGS84 UTM zone 24S"; + case 32725 : return "WGS84 UTM zone 25S"; + case 32726 : return "WGS84 UTM zone 26S"; + case 32727 : return "WGS84 UTM zone 27S"; + case 32728 : return "WGS84 UTM zone 28S"; + case 32729 : return "WGS84 UTM zone 29S"; + case 32730 : return "WGS84 UTM zone 30S"; + case 32731 : return "WGS84 UTM zone 31S"; + case 32732 : return "WGS84 UTM zone 32S"; + case 32733 : return "WGS84 UTM zone 33S"; + case 32734 : return "WGS84 UTM zone 34S"; + case 32735 : return "WGS84 UTM zone 35S"; + case 32736 : return "WGS84 UTM zone 36S"; + case 32737 : return "WGS84 UTM zone 37S"; + case 32738 : return "WGS84 UTM zone 38S"; + case 32739 : return "WGS84 UTM zone 39S"; + case 32740 : return "WGS84 UTM zone 40S"; + case 32741 : return "WGS84 UTM zone 41S"; + case 32742 : return "WGS84 UTM zone 42S"; + case 32743 : return "WGS84 UTM zone 43S"; + case 32744 : return "WGS84 UTM zone 44S"; + case 32745 : return "WGS84 UTM zone 45S"; + case 32746 : return "WGS84 UTM zone 46S"; + case 32747 : return "WGS84 UTM zone 47S"; + case 32748 : return "WGS84 UTM zone 48S"; + case 32749 : return "WGS84 UTM zone 49S"; + case 32750 : return "WGS84 UTM zone 50S"; + case 32751 : return "WGS84 UTM zone 51S"; + case 32752 : return "WGS84 UTM zone 52S"; + case 32753 : return "WGS84 UTM zone 53S"; + case 32754 : return "WGS84 UTM zone 54S"; + case 32755 : return "WGS84 UTM zone 55S"; + case 32756 : return "WGS84 UTM zone 56S"; + case 32757 : return "WGS84 UTM zone 57S"; + case 32758 : return "WGS84 UTM zone 58S"; + case 32759 : return "WGS84 UTM zone 59S"; + case 32760 : return "WGS84 UTM zone 60S"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getProjectionDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_PROJECTION); + if (value == null) + return null; + + // Requirement 26.4 + // ProjectionGeoKey values in the range 1024-32766 SHALL be valid EPSG map projection (coordinate operation) codes + // NOTE: In GeoTIFF v1.0 the range was 10000-19999. Several values in this range have been deprecated or deleted from the EPSG Dataset. + + switch(value) + { + case 10101 : return "Alabama CS27 East"; + case 10102 : return "Alabama CS27 West"; + case 10131 : return "Alabama CS83 East"; + case 10132 : return "Alabama CS83 West"; + case 10201 : return "Arizona Coordinate System east"; + case 10202 : return "Arizona Coordinate System Central"; + case 10203 : return "Arizona Coordinate System west"; + case 10231 : return "Arizona CS83 east"; + case 10232 : return "Arizona CS83 Central"; + case 10233 : return "Arizona CS83 west"; + case 10301 : return "Arkansas CS27 North"; + case 10302 : return "Arkansas CS27 South"; + case 10331 : return "Arkansas CS83 North"; + case 10332 : return "Arkansas CS83 South"; + case 10401 : return "California CS27 I"; + case 10402 : return "California CS27 II"; + case 10403 : return "California CS27 III"; + case 10404 : return "California CS27 IV"; + case 10405 : return "California CS27 V"; + case 10406 : return "California CS27 VI"; + case 10407 : return "California CS27 VII"; + case 10431 : return "California CS83 1"; + case 10432 : return "California CS83 2"; + case 10433 : return "California CS83 3"; + case 10434 : return "California CS83 4"; + case 10435 : return "California CS83 5"; + case 10436 : return "California CS83 6"; + case 10501 : return "Colorado CS27 North"; + case 10502 : return "Colorado CS27 Central"; + case 10503 : return "Colorado CS27 South"; + case 10531 : return "Colorado CS83 North"; + case 10532 : return "Colorado CS83 Central"; + case 10533 : return "Colorado CS83 South"; + case 10600 : return "Connecticut CS27"; + case 10630 : return "Connecticut CS83"; + case 10700 : return "Delaware CS27"; + case 10730 : return "Delaware CS83"; + case 10901 : return "Florida CS27 East"; + case 10902 : return "Florida CS27 West"; + case 10903 : return "Florida CS27 North"; + case 10931 : return "Florida CS83 East"; + case 10932 : return "Florida CS83 West"; + case 10933 : return "Florida CS83 North"; + case 11001 : return "Georgia CS27 East"; + case 11002 : return "Georgia CS27 West"; + case 11031 : return "Georgia CS83 East"; + case 11032 : return "Georgia CS83 West"; + case 11101 : return "Idaho CS27 East"; + case 11102 : return "Idaho CS27 Central"; + case 11103 : return "Idaho CS27 West"; + case 11131 : return "Idaho CS83 East"; + case 11132 : return "Idaho CS83 Central"; + case 11133 : return "Idaho CS83 West"; + case 11201 : return "Illinois CS27 East"; + case 11202 : return "Illinois CS27 West"; + case 11231 : return "Illinois CS83 East"; + case 11232 : return "Illinois CS83 West"; + case 11301 : return "Indiana CS27 East"; + case 11302 : return "Indiana CS27 West"; + case 11331 : return "Indiana CS83 East"; + case 11332 : return "Indiana CS83 West"; + case 11401 : return "Iowa CS27 North"; + case 11402 : return "Iowa CS27 South"; + case 11431 : return "Iowa CS83 North"; + case 11432 : return "Iowa CS83 South"; + case 11501 : return "Kansas CS27 North"; + case 11502 : return "Kansas CS27 South"; + case 11531 : return "Kansas CS83 North"; + case 11532 : return "Kansas CS83 South"; + case 11601 : return "Kentucky CS27 North"; + case 11602 : return "Kentucky CS27 South"; + case 11631 : return "Kentucky CS83 North"; + case 11632 : return "Kentucky CS83 South"; + case 11701 : return "Louisiana CS27 North"; + case 11702 : return "Louisiana CS27 South"; + case 11731 : return "Louisiana CS83 North"; + case 11732 : return "Louisiana CS83 South"; + case 11801 : return "Maine CS27 East"; + case 11802 : return "Maine CS27 West"; + case 11831 : return "Maine CS83 East"; + case 11832 : return "Maine CS83 West"; + case 11900 : return "Maryland CS27"; + case 11930 : return "Maryland CS83"; + case 12001 : return "Massachusetts CS27 Mainland"; + case 12002 : return "Massachusetts CS27 Island"; + case 12031 : return "Massachusetts CS83 Mainland"; + case 12032 : return "Massachusetts CS83 Island"; + case 12101 : return "Michigan State Plane East"; + case 12102 : return "Michigan State Plane Old Central"; + case 12103 : return "Michigan State Plane West"; + case 12111 : return "Michigan CS27 North"; + case 12112 : return "Michigan CS27 Central"; + case 12113 : return "Michigan CS27 South"; + case 12141 : return "Michigan CS83 North"; + case 12142 : return "Michigan CS83 Central"; + case 12143 : return "Michigan CS83 South"; + case 12201 : return "Minnesota CS27 North"; + case 12202 : return "Minnesota CS27 Central"; + case 12203 : return "Minnesota CS27 South"; + case 12231 : return "Minnesota CS83 North"; + case 12232 : return "Minnesota CS83 Central"; + case 12233 : return "Minnesota CS83 South"; + case 12301 : return "Mississippi CS27 East"; + case 12302 : return "Mississippi CS27 West"; + case 12331 : return "Mississippi CS83 East"; + case 12332 : return "Mississippi CS83 West"; + case 12401 : return "Missouri CS27 East"; + case 12402 : return "Missouri CS27 Central"; + case 12403 : return "Missouri CS27 West"; + case 12431 : return "Missouri CS83 East"; + case 12432 : return "Missouri CS83 Central"; + case 12433 : return "Missouri CS83 West"; + case 12501 : return "Montana CS27 North"; + case 12502 : return "Montana CS27 Central"; + case 12503 : return "Montana CS27 South"; + case 12530 : return "Montana CS83"; + case 12601 : return "Nebraska CS27 North"; + case 12602 : return "Nebraska CS27 South"; + case 12630 : return "Nebraska CS83"; + case 12701 : return "Nevada CS27 East"; + case 12702 : return "Nevada CS27 Central"; + case 12703 : return "Nevada CS27 West"; + case 12731 : return "Nevada CS83 East"; + case 12732 : return "Nevada CS83 Central"; + case 12733 : return "Nevada CS83 West"; + case 12800 : return "New Hampshire CS27"; + case 12830 : return "New Hampshire CS83"; + case 12900 : return "New Jersey CS27"; + case 12930 : return "New Jersey CS83"; + case 13001 : return "New Mexico CS27 East"; + case 13002 : return "New Mexico CS27 Central"; + case 13003 : return "New Mexico CS27 West"; + case 13031 : return "New Mexico CS83 East"; + case 13032 : return "New Mexico CS83 Central"; + case 13033 : return "New Mexico CS83 West"; + case 13101 : return "New York CS27 East"; + case 13102 : return "New York CS27 Central"; + case 13103 : return "New York CS27 West"; + case 13104 : return "New York CS27 Long Island"; + case 13131 : return "New York CS83 East"; + case 13132 : return "New York CS83 Central"; + case 13133 : return "New York CS83 West"; + case 13134 : return "New York CS83 Long Island"; + case 13200 : return "North Carolina CS27"; + case 13230 : return "North Carolina CS83"; + case 13301 : return "North Dakota CS27 North"; + case 13302 : return "North Dakota CS27 South"; + case 13331 : return "North Dakota CS83 North"; + case 13332 : return "North Dakota CS83 South"; + case 13401 : return "Ohio CS27 North"; + case 13402 : return "Ohio CS27 South"; + case 13431 : return "Ohio CS83 North"; + case 13432 : return "Ohio CS83 South"; + case 13501 : return "Oklahoma CS27 North"; + case 13502 : return "Oklahoma CS27 South"; + case 13531 : return "Oklahoma CS83 North"; + case 13532 : return "Oklahoma CS83 South"; + case 13601 : return "Oregon CS27 North"; + case 13602 : return "Oregon CS27 South"; + case 13631 : return "Oregon CS83 North"; + case 13632 : return "Oregon CS83 South"; + case 13701 : return "Pennsylvania CS27 North"; + case 13702 : return "Pennsylvania CS27 South"; + case 13731 : return "Pennsylvania CS83 North"; + case 13732 : return "Pennsylvania CS83 South"; + case 13800 : return "Rhode Island CS27"; + case 13830 : return "Rhode Island CS83"; + case 13901 : return "South Carolina CS27 North"; + case 13902 : return "South Carolina CS27 South"; + case 13930 : return "South Carolina CS83"; + case 14001 : return "South Dakota CS27 North"; + case 14002 : return "South Dakota CS27 South"; + case 14031 : return "South Dakota CS83 North"; + case 14032 : return "South Dakota CS83 South"; + case 14100 : return "Tennessee CS27"; + case 14130 : return "Tennessee CS83"; + case 14201 : return "Texas CS27 North"; + case 14202 : return "Texas CS27 North Central"; + case 14203 : return "Texas CS27 Central"; + case 14204 : return "Texas CS27 South Central"; + case 14205 : return "Texas CS27 South"; + case 14231 : return "Texas CS83 North"; + case 14232 : return "Texas CS83 North Central"; + case 14233 : return "Texas CS83 Central"; + case 14234 : return "Texas CS83 South Central"; + case 14235 : return "Texas CS83 South"; + case 14301 : return "Utah CS27 North"; + case 14302 : return "Utah CS27 Central"; + case 14303 : return "Utah CS27 South"; + case 14331 : return "Utah CS83 North"; + case 14332 : return "Utah CS83 Central"; + case 14333 : return "Utah CS83 South"; + case 14400 : return "Vermont CS27"; + case 14430 : return "Vermont CS83"; + case 14501 : return "Virginia CS27 North"; + case 14502 : return "Virginia CS27 South"; + case 14531 : return "Virginia CS83 North"; + case 14532 : return "Virginia CS83 South"; + case 14601 : return "Washington CS27 North"; + case 14602 : return "Washington CS27 South"; + case 14631 : return "Washington CS83 North"; + case 14632 : return "Washington CS83 South"; + case 14701 : return "West Virginia CS27 North"; + case 14702 : return "West Virginia CS27 South"; + case 14731 : return "West Virginia CS83 North"; + case 14732 : return "West Virginia CS83 South"; + case 14801 : return "Wisconsin CS27 North"; + case 14802 : return "Wisconsin CS27 Central"; + case 14803 : return "Wisconsin CS27 South"; + case 14831 : return "Wisconsin CS83 North"; + case 14832 : return "Wisconsin CS83 Central"; + case 14833 : return "Wisconsin CS83 South"; + case 14901 : return "Wyoming CS27 East"; + case 14902 : return "Wyoming CS27 East Central"; + case 14903 : return "Wyoming CS27 West Central"; + case 14904 : return "Wyoming CS27 West"; + case 14931 : return "Wyoming CS83 East"; + case 14932 : return "Wyoming CS83 East Central"; + case 14933 : return "Wyoming CS83 West Central"; + case 14934 : return "Wyoming CS83 West"; + case 15001 : return "Alaska CS27 1"; + case 15002 : return "Alaska CS27 2"; + case 15003 : return "Alaska CS27 3"; + case 15004 : return "Alaska CS27 4"; + case 15005 : return "Alaska CS27 5"; + case 15006 : return "Alaska CS27 6"; + case 15007 : return "Alaska CS27 7"; + case 15008 : return "Alaska CS27 8"; + case 15009 : return "Alaska CS27 9"; + case 15010 : return "Alaska CS27 10"; + case 15031 : return "Alaska CS83 1"; + case 15032 : return "Alaska CS83 2"; + case 15033 : return "Alaska CS83 3"; + case 15034 : return "Alaska CS83 4"; + case 15035 : return "Alaska CS83 5"; + case 15036 : return "Alaska CS83 6"; + case 15037 : return "Alaska CS83 7"; + case 15038 : return "Alaska CS83 8"; + case 15039 : return "Alaska CS83 9"; + case 15040 : return "Alaska CS83 10"; + case 15101 : return "Hawaii CS27 1"; + case 15102 : return "Hawaii CS27 2"; + case 15103 : return "Hawaii CS27 3"; + case 15104 : return "Hawaii CS27 4"; + case 15105 : return "Hawaii CS27 5"; + case 15131 : return "Hawaii CS83 1"; + case 15132 : return "Hawaii CS83 2"; + case 15133 : return "Hawaii CS83 3"; + case 15134 : return "Hawaii CS83 4"; + case 15135 : return "Hawaii CS83 5"; + case 15201 : return "Puerto Rico CS27"; + case 15202 : return "St Croix"; + case 15230 : return "Puerto Rico Virgin Is"; + case 15302 : return "Kentucky CS27"; + case 15303 : return "Kentucky CS83 North"; + case 15914 : return "BLM 14N feet"; + case 15915 : return "BLM 15N feet"; + case 15916 : return "BLM 16N feet"; + case 15917 : return "BLM 17N feet"; + case 16001 : return "UTM zone 1N"; + case 16002 : return "UTM zone 2N"; + case 16003 : return "UTM zone 3N"; + case 16004 : return "UTM zone 4N"; + case 16005 : return "UTM zone 5N"; + case 16006 : return "UTM zone 6N"; + case 16007 : return "UTM zone 7N"; + case 16008 : return "UTM zone 8N"; + case 16009 : return "UTM zone 9N"; + case 16010 : return "UTM zone 10N"; + case 16011 : return "UTM zone 11N"; + case 16012 : return "UTM zone 12N"; + case 16013 : return "UTM zone 13N"; + case 16014 : return "UTM zone 14N"; + case 16015 : return "UTM zone 15N"; + case 16016 : return "UTM zone 16N"; + case 16017 : return "UTM zone 17N"; + case 16018 : return "UTM zone 18N"; + case 16019 : return "UTM zone 19N"; + case 16020 : return "UTM zone 20N"; + case 16021 : return "UTM zone 21N"; + case 16022 : return "UTM zone 22N"; + case 16023 : return "UTM zone 23N"; + case 16024 : return "UTM zone 24N"; + case 16025 : return "UTM zone 25N"; + case 16026 : return "UTM zone 26N"; + case 16027 : return "UTM zone 27N"; + case 16028 : return "UTM zone 28N"; + case 16029 : return "UTM zone 29N"; + case 16030 : return "UTM zone 30N"; + case 16031 : return "UTM zone 31N"; + case 16032 : return "UTM zone 32N"; + case 16033 : return "UTM zone 33N"; + case 16034 : return "UTM zone 34N"; + case 16035 : return "UTM zone 35N"; + case 16036 : return "UTM zone 36N"; + case 16037 : return "UTM zone 37N"; + case 16038 : return "UTM zone 38N"; + case 16039 : return "UTM zone 39N"; + case 16040 : return "UTM zone 40N"; + case 16041 : return "UTM zone 41N"; + case 16042 : return "UTM zone 42N"; + case 16043 : return "UTM zone 43N"; + case 16044 : return "UTM zone 44N"; + case 16045 : return "UTM zone 45N"; + case 16046 : return "UTM zone 46N"; + case 16047 : return "UTM zone 47N"; + case 16048 : return "UTM zone 48N"; + case 16049 : return "UTM zone 49N"; + case 16050 : return "UTM zone 50N"; + case 16051 : return "UTM zone 51N"; + case 16052 : return "UTM zone 52N"; + case 16053 : return "UTM zone 53N"; + case 16054 : return "UTM zone 54N"; + case 16055 : return "UTM zone 55N"; + case 16056 : return "UTM zone 56N"; + case 16057 : return "UTM zone 57N"; + case 16058 : return "UTM zone 58N"; + case 16059 : return "UTM zone 59N"; + case 16060 : return "UTM zone 60N"; + case 16101 : return "UTM zone 1S"; + case 16102 : return "UTM zone 2S"; + case 16103 : return "UTM zone 3S"; + case 16104 : return "UTM zone 4S"; + case 16105 : return "UTM zone 5S"; + case 16106 : return "UTM zone 6S"; + case 16107 : return "UTM zone 7S"; + case 16108 : return "UTM zone 8S"; + case 16109 : return "UTM zone 9S"; + case 16110 : return "UTM zone 10S"; + case 16111 : return "UTM zone 11S"; + case 16112 : return "UTM zone 12S"; + case 16113 : return "UTM zone 13S"; + case 16114 : return "UTM zone 14S"; + case 16115 : return "UTM zone 15S"; + case 16116 : return "UTM zone 16S"; + case 16117 : return "UTM zone 17S"; + case 16118 : return "UTM zone 18S"; + case 16119 : return "UTM zone 19S"; + case 16120 : return "UTM zone 20S"; + case 16121 : return "UTM zone 21S"; + case 16122 : return "UTM zone 22S"; + case 16123 : return "UTM zone 23S"; + case 16124 : return "UTM zone 24S"; + case 16125 : return "UTM zone 25S"; + case 16126 : return "UTM zone 26S"; + case 16127 : return "UTM zone 27S"; + case 16128 : return "UTM zone 28S"; + case 16129 : return "UTM zone 29S"; + case 16130 : return "UTM zone 30S"; + case 16131 : return "UTM zone 31S"; + case 16132 : return "UTM zone 32S"; + case 16133 : return "UTM zone 33S"; + case 16134 : return "UTM zone 34S"; + case 16135 : return "UTM zone 35S"; + case 16136 : return "UTM zone 36S"; + case 16137 : return "UTM zone 37S"; + case 16138 : return "UTM zone 38S"; + case 16139 : return "UTM zone 39S"; + case 16140 : return "UTM zone 40S"; + case 16141 : return "UTM zone 41S"; + case 16142 : return "UTM zone 42S"; + case 16143 : return "UTM zone 43S"; + case 16144 : return "UTM zone 44S"; + case 16145 : return "UTM zone 45S"; + case 16146 : return "UTM zone 46S"; + case 16147 : return "UTM zone 47S"; + case 16148 : return "UTM zone 48S"; + case 16149 : return "UTM zone 49S"; + case 16150 : return "UTM zone 50S"; + case 16151 : return "UTM zone 51S"; + case 16152 : return "UTM zone 52S"; + case 16153 : return "UTM zone 53S"; + case 16154 : return "UTM zone 54S"; + case 16155 : return "UTM zone 55S"; + case 16156 : return "UTM zone 56S"; + case 16157 : return "UTM zone 57S"; + case 16158 : return "UTM zone 58S"; + case 16159 : return "UTM zone 59S"; + case 16160 : return "UTM zone 60S"; + case 16200 : return "Gauss Kruger zone 0"; + case 16201 : return "Gauss Kruger zone 1"; + case 16202 : return "Gauss Kruger zone 2"; + case 16203 : return "Gauss Kruger zone 3"; + case 16204 : return "Gauss Kruger zone 4"; + case 16205 : return "Gauss Kruger zone 5"; + case 17348 : return "Map Grid of Australia 48"; + case 17349 : return "Map Grid of Australia 49"; + case 17350 : return "Map Grid of Australia 50"; + case 17351 : return "Map Grid of Australia 51"; + case 17352 : return "Map Grid of Australia 52"; + case 17353 : return "Map Grid of Australia 53"; + case 17354 : return "Map Grid of Australia 54"; + case 17355 : return "Map Grid of Australia 55"; + case 17356 : return "Map Grid of Australia 56"; + case 17357 : return "Map Grid of Australia 57"; + case 17358 : return "Map Grid of Australia 58"; + case 17448 : return "Australian Map Grid 48"; + case 17449 : return "Australian Map Grid 49"; + case 17450 : return "Australian Map Grid 50"; + case 17451 : return "Australian Map Grid 51"; + case 17452 : return "Australian Map Grid 52"; + case 17453 : return "Australian Map Grid 53"; + case 17454 : return "Australian Map Grid 54"; + case 17455 : return "Australian Map Grid 55"; + case 17456 : return "Australian Map Grid 56"; + case 17457 : return "Australian Map Grid 57"; + case 17458 : return "Australian Map Grid 58"; + case 18031 : return "Argentina 1"; + case 18032 : return "Argentina 2"; + case 18033 : return "Argentina 3"; + case 18034 : return "Argentina 4"; + case 18035 : return "Argentina 5"; + case 18036 : return "Argentina 6"; + case 18037 : return "Argentina 7"; + case 18051 : return "Colombia 3W"; + case 18052 : return "Colombia Bogota"; + case 18053 : return "Colombia 3E"; + case 18054 : return "Colombia 6E"; + case 18072 : return "Egypt Red Belt"; + case 18073 : return "Egypt Purple Belt"; + case 18074 : return "Extended Purple Belt"; + case 18141 : return "New Zealand North Island Nat Grid"; + case 18142 : return "New Zealand South Island Nat Grid"; + case 19900 : return "Bahrain Grid"; + case 19905 : return "Netherlands E Indies Equatorial"; + case 19912 : return "RSO Borneo"; + case 19926 : return "Stereo 70"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getProjectedCoordinateTransformDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_PROJECTED_COORDINATE_TRANSFORM); + if (value == null) + return null; + + // Requirements Class 27.0: ProjMethodGeoKey + // 27.3: ProjMethodGeoKey values in the range 1-27 SHALL be GeoTIFF map projection method codes (NOTE: See Annex C) + // 27.4: ProjMethodGeoKey values in the range 28-32766 SHALL be reserved + + switch(value) + { + case 1 : return "Transverse Mercator"; + case 2 : return "Transverse Mercator Modified Alaska"; + case 3 : return "Oblique Mercator"; + case 4 : return "Oblique Mercator Laborde"; + case 5 : return "Oblique Mercator Rosenmund"; + case 6 : return "Oblique Mercator Spherical"; + case 7 : return "Mercator"; + case 8 : return "Lambert Conf Conic 2SP"; + case 9 : return "Lambert Conf Conic 1SP"; + case 10 : return "Lambert Azim Equal Area"; + case 11 : return "Albers Equal Area"; + case 12 : return "Azimuthal Equidistant"; + case 13 : return "Equidistant Conic"; + case 14 : return "Stereographic"; + case 15 : return "Polar Stereographic"; + case 16 : return "Oblique Stereographic"; + case 17 : return "Equirectangular"; + case 18 : return "Cassini Soldner"; + case 19 : return "Gnomonic"; + case 20 : return "Miller Cylindrical"; + case 21 : return "Orthographic"; + case 22 : return "Polyconic"; + case 23 : return "Robinson"; + case 24 : return "Sinusoidal"; + case 25 : return "VanDerGrinten"; + case 26 : return "New Zealand Map Grid"; + case 27 : return "Transverse Mercator South Orientated"; + case 28 : return "Cylindrical Equal Area"; + case 9801 : return "Lambert Conic Conformal 1SP"; + case 9802 : return "Lambert Conic Conformal 2SP"; + case 9803 : return "Lambert Conic Conformal 2SP Belgium"; + case 9804 : return "Mercator variant A"; + case 9805 : return "Mercator variant B"; + case 9806 : return "Cassini-Soldner"; + case 9807 : return "Transverse Mercator"; + case 9808 : return "Transverse Mercator South Orientated"; + case 9809 : return "Oblique Stereographic"; + case 9810 : return "Polar Stereographic variant A"; + case 9811 : return "New Zealand Map Grid"; + case 9812 : return "Hotine Oblique Mercator variant A"; + case 9813 : return "Laborde Oblique Mercator"; + case 9814 : return "Swiss Oblique Cylindrical"; + case 9815 : return "Hotine Oblique Mercator variant B"; + case 9816 : return "Tunisia Mining Grid"; + case 9817 : return "Lambert Conic Near-Conformal"; + case 9818 : return "American Polyconic"; + case 9819 : return "Krovak"; + case 9820 : return "Lambert Azimuthal Equal Area"; + case 9821 : return "Lambert Azimuthal Equal Area Spherical"; + case 9822 : return "Albers Equal Area"; + case 9823 : return "Equidistant Cylindrical Spherical"; + case 9824 : return "Transverse Mercator Zoned Grid System"; + case 9825 : return "Pseudo Plate Carree"; + case 9826 : return "Lambert Conic Conformal West Orientated"; + case 9827 : return "Bonne"; + case 9828 : return "Bonne South Orientated"; + case 9829 : return "Polar Stereographic variant B"; + case 9830 : return "Polar Stereographic variant C"; + case 9831 : return "Guam"; + case 9832 : return "Modified Azimuthal Equidistant"; + case 9833 : return "Hyperbolic Cassini-Soldner"; + case 9834 : return "Lambert Cylindrical Equal Area Spherical"; + case 9835 : return "Lambert Cylindrical Equal Area"; + case 9836 : return "Geocentric/Topocentric Conversions"; + case 9837 : return "Geographic/Topocentric Conversions"; + case 9838 : return "Vertical Perspective"; + case 9839 : return "Vertical Perspective Orthographic Case"; + case 9840 : return "Orthographic"; + case 9841 : return "Mercator 1SP Spherical"; + case 9842 : return "Equidistant Cylindrical"; + case 9843 : return "Axis Order Reversal 2D"; + case 9844 : return "Axis Order Reversal Geographic3D Horizontal"; + case 32767 : return "User Defined"; + default : return "Unknown (" + value + ")"; + } + } + + public String getChartFormatDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_CHART_FORMAT); + if (value == null) + return null; + + switch(value) + { + case 47500 : return "General"; + case 47501 : return "Coastal"; + case 47502 : return "Harbor"; + case 47503 : return "SailingInternational"; + case 47504 : return "SmallCraft Route"; + case 47505 : return "SmallCraftArea"; + case 47506 : return "SmallCraftFolio"; + case 47507 : return "Topographic"; + case 47508 : return "Recreation"; + case 47509 : return "Index"; + case 47510 : return "Inset"; + default : return "Unknown (" + value + ")"; + } + } + + public String getChartSoundingDatumDescription() + { + Integer value = _directory.getInteger(GeoTiffDirectory.TAG_CHART_SOUNDING_DATUM); + if (value == null) + return null; + + switch(value) + { + case 47600 : return "Equatorial Spring Low Water"; + case 47601 : return "Indian Spring Low Water"; + case 47602 : return "Lowest Astronomical Tide"; + case 47603 : return "Lowest Low Water"; + case 47604 : return "Lowest Normal Low Water"; + case 47605 : return "Mean Higher High Water"; + case 47606 : return "Mean High Water"; + case 47607 : return "Mean High Water Springs"; + case 47608 : return "Mean Lower Low Water"; + case 47609 : return "Mean Lower Low Water Springs"; + case 47610 : return "Mean Low Water"; + case 47611 : return "Mean Sea Level"; + case 47612 : return "Tropic Higher High Water"; + case 47613 : return "Tropic Lower Low Water"; + default : return "Unknown (" + value + ")"; + } + } +} diff --git a/Source/com/drew/metadata/geotiff/GeoTiffDirectory.java b/Source/com/drew/metadata/geotiff/GeoTiffDirectory.java new file mode 100644 index 000000000..29c29b2c9 --- /dev/null +++ b/Source/com/drew/metadata/geotiff/GeoTiffDirectory.java @@ -0,0 +1,186 @@ +/* + * 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.metadata.geotiff; + +import java.util.HashMap; + +import com.drew.metadata.Directory; + +public class GeoTiffDirectory extends Directory { + + public static final int TAG_MODEL_TYPE = 0x0400; + public static final int TAG_RASTER_TYPE = 0x0401; + public static final int TAG_CITATION = 0x0402; + + public static final int TAG_GEOGRAPHIC_TYPE = 0x0800; + public static final int TAG_GEOGRAPHIC_CITATION = 0x0801; + public static final int TAG_GEODETIC_DATUM = 0x0802; + public static final int TAG_GEOGRAPHIC_PRIME_MERIDIAN = 0x0803; + public static final int TAG_GEOGRAPHIC_LINEAR_UNITS = 0x0804; + public static final int TAG_GEOGRAPHIC_LINEAR_UNIT_SIZE = 0x0805; + public static final int TAG_GEOGRAPHIC_ANGULAR_UNITS = 0x0806; + public static final int TAG_GEOGRAPHIC_ANGULAR_UNIT_SIZE = 0x0807; + public static final int TAG_GEOGRAPHIC_ELLIPSOID = 0x0808; + public static final int TAG_GEOGRAPHIC_SEMI_MAJOR_AXIS = 0x0809; + public static final int TAG_GEOGRAPHIC_SEMI_MINOR_AXIS = 0x080a; + public static final int TAG_GEOGRAPHIC_INV_FLATTENING = 0x080b; + public static final int TAG_GEOGRAPHIC_AZIMUTH_UNITS = 0x080c; + public static final int TAG_GEOGRAPHIC_PRIME_MERIDIAN_LONG = 0x080d; + public static final int TAG_GEOGRAPHIC_TO_WGS84 = 0x080e; + + // https://trac.osgeo.org/gdal/ticket/3901#comment:7 + // https://github.com/opengeospatial/geotiff/pull/106 + public static final int TAG_GDAL_PROJ_LINEAR_UNITS_INTERP_CORRECT = 0x0bf3; + + public static final int TAG_PROJECTED_CS_TYPE = 0x0c00; + public static final int TAG_PROJECTED_CSCITATION = 0x0c01; + public static final int TAG_PROJECTION = 0x0c02; + public static final int TAG_PROJECTED_COORDINATE_TRANSFORM = 0x0c03; + public static final int TAG_PROJ_LINEAR_UNITS = 0x0c04; + public static final int TAG_PROJ_LINEAR_UNIT_SIZE = 0x0c05; + public static final int TAG_PROJ_STD_PARALLEL_1 = 0x0c06; + public static final int TAG_PROJ_STD_PARALLEL_2 = 0x0c07; + public static final int TAG_PROJ_NAT_ORIGIN_LONG = 0x0c08; + public static final int TAG_PROJ_NAT_ORIGIN_LAT = 0x0c09; + public static final int TAG_PROJ_FALSE_EASTING = 0x0c0a; + public static final int TAG_PROJ_FALSE_NORTHING = 0x0c0b; + public static final int TAG_PROJ_FALSE_ORIGIN_LONG = 0x0c0c; + public static final int TAG_PROJ_FALSE_ORIGIN_LAT = 0x0c0d; + public static final int TAG_PROJ_FALSE_ORIGIN_EASTING = 0x0c0e; + public static final int TAG_PROJ_FALSE_ORIGIN_NORTHING = 0x0c0f; + public static final int TAG_PROJ_CENTER_LONG = 0x0c10; + public static final int TAG_PROJ_CENTER_LAT = 0x0c11; + public static final int TAG_PROJ_CENTER_EASTING = 0x0c12; + public static final int TAG_PROJ_CENTER_NORTHING = 0x0c13; + public static final int TAG_PROJ_SCALE_AT_NAT_ORIGIN = 0x0c14; + public static final int TAG_PROJ_SCALE_AT_CENTER = 0x0c15; + public static final int TAG_PROJ_AZIMUTH_ANGLE = 0x0c16; + public static final int TAG_PROJ_STRAIGHT_VERT_POLE_LONG = 0x0c17; + public static final int TAG_PROJ_RECTIFIED_GRID_ANGLE = 0x0c18; + + public static final int TAG_VERTICAL_CS_TYPE = 0x1000; + public static final int TAG_VERTICAL_CITATION = 0x1001; + public static final int TAG_VERTICAL_DATUM = 0x1002; + public static final int TAG_VERTICAL_UNITS = 0x1003; + + public static final int TAG_CHART_FORMAT = 0xb799; + public static final int TAG_CHART_SOURCE = 0xb79a; + public static final int TAG_CHART_SOURCE_EDITION = 0xb79b; + public static final int TAG_CHART_SOURCE_DATE = 0xb79c; + public static final int TAG_CHART_CORR_DATE = 0xb79d; + public static final int TAG_CHART_COUNTRY_ORIGIN = 0xb79e; + public static final int TAG_CHART_RASTER_EDITION = 0xb79f; + public static final int TAG_CHART_SOUNDING_DATUM = 0xb7a0; + public static final int TAG_CHART_DEPTH_UNITS = 0xb7a1; + public static final int TAG_CHART_MAG_VAR = 0xb7a2; + public static final int TAG_CHART_MAG_VAR_YEAR = 0xb7a3; + public static final int TAG_CHART_MAG_VAR_ANN_CHANGE = 0xb7a4; + public static final int TAG_CHART_WGS_NS_SHIFT = 0xb7a5; + public static final int TAG_INSET_NW_PIXEL_X = 0xb7a7; + public static final int TAG_INSET_NW_PIXEL_Y = 0xb7a8; + public static final int TAG_CHART_CONTOUR_INTERVAL = 0xb7a9; + + private static final HashMap _tagNameMap = new HashMap(); + static + { + _tagNameMap.put(TAG_MODEL_TYPE, "Model Type"); + _tagNameMap.put(TAG_RASTER_TYPE, "Raster Type"); + _tagNameMap.put(TAG_CITATION, "Citation"); + _tagNameMap.put(TAG_GEOGRAPHIC_TYPE, "Geographic Type"); + _tagNameMap.put(TAG_GEOGRAPHIC_CITATION, "Geographic Citation"); + _tagNameMap.put(TAG_GEODETIC_DATUM, "Geodetic Datum"); + _tagNameMap.put(TAG_GEOGRAPHIC_PRIME_MERIDIAN, "Prime Meridian"); + _tagNameMap.put(TAG_GEOGRAPHIC_LINEAR_UNITS, "Geographic Linear Units"); + _tagNameMap.put(TAG_GEOGRAPHIC_LINEAR_UNIT_SIZE, "Geographic Linear Unit Size"); + _tagNameMap.put(TAG_GEOGRAPHIC_ANGULAR_UNITS, "Geographic Angular Units"); + _tagNameMap.put(TAG_GEOGRAPHIC_ANGULAR_UNIT_SIZE, "Geographic Angular Unit Size"); + _tagNameMap.put(TAG_GEOGRAPHIC_ELLIPSOID, "Geographic Ellipsoid"); + _tagNameMap.put(TAG_GEOGRAPHIC_SEMI_MAJOR_AXIS, "Semi-major axis"); + _tagNameMap.put(TAG_GEOGRAPHIC_SEMI_MINOR_AXIS, "Semi-minor axis"); + _tagNameMap.put(TAG_GEOGRAPHIC_INV_FLATTENING, "Inv. Flattening"); + _tagNameMap.put(TAG_GEOGRAPHIC_AZIMUTH_UNITS, "Azimuth Units"); + _tagNameMap.put(TAG_GEOGRAPHIC_PRIME_MERIDIAN_LONG, "To WGS84"); + _tagNameMap.put(TAG_GEOGRAPHIC_TO_WGS84, "To WGS84"); + _tagNameMap.put(TAG_GDAL_PROJ_LINEAR_UNITS_INTERP_CORRECT, "GDAL ProjLinearUnitsInterpCorrectGeoKey"); + _tagNameMap.put(TAG_PROJECTED_CS_TYPE, "Projected Coordinate System Type"); + _tagNameMap.put(TAG_PROJECTED_CSCITATION, "Projected Coordinate System Citation"); + _tagNameMap.put(TAG_PROJECTION, "Projection"); + _tagNameMap.put(TAG_PROJECTED_COORDINATE_TRANSFORM, "Projected Coordinate Transform"); + _tagNameMap.put(TAG_PROJ_LINEAR_UNITS, "Projection Linear Units"); + _tagNameMap.put(TAG_PROJ_LINEAR_UNIT_SIZE, "Projection Linear Unit Size"); + _tagNameMap.put(TAG_PROJ_STD_PARALLEL_1, "Projection Standard Parallel 1"); + _tagNameMap.put(TAG_PROJ_STD_PARALLEL_2, "Projection Standard Parallel 2"); + _tagNameMap.put(TAG_PROJ_NAT_ORIGIN_LONG, "Projection Natural Origin Longitude"); + _tagNameMap.put(TAG_PROJ_NAT_ORIGIN_LAT, "Projection Natural Origin Latitude"); + _tagNameMap.put(TAG_PROJ_FALSE_EASTING, "Projection False Easting"); + _tagNameMap.put(TAG_PROJ_FALSE_NORTHING, "Projection False Northing"); + _tagNameMap.put(TAG_PROJ_FALSE_ORIGIN_LONG, "Projection False Origin Longitude"); + _tagNameMap.put(TAG_PROJ_FALSE_ORIGIN_LAT, "Projection False Origin Latitude"); + _tagNameMap.put(TAG_PROJ_FALSE_ORIGIN_EASTING, "Projection False Origin Easting"); + _tagNameMap.put(TAG_PROJ_FALSE_ORIGIN_NORTHING, "Projection False Origin Northing"); + _tagNameMap.put(TAG_PROJ_CENTER_LONG, "Projection Center Longitude"); + _tagNameMap.put(TAG_PROJ_CENTER_LAT, "Projection Center Latitude"); + _tagNameMap.put(TAG_PROJ_CENTER_EASTING, "Projection Center Easting"); + _tagNameMap.put(TAG_PROJ_CENTER_NORTHING, "Projection Center Northing"); + _tagNameMap.put(TAG_PROJ_SCALE_AT_NAT_ORIGIN, "Projection Scale at Natural Origin"); + _tagNameMap.put(TAG_PROJ_SCALE_AT_CENTER, "Projection Scale at Center"); + _tagNameMap.put(TAG_PROJ_AZIMUTH_ANGLE, "Projection Azimuth Angle"); + _tagNameMap.put(TAG_PROJ_STRAIGHT_VERT_POLE_LONG, "Projection Straight Vertical Pole Longitude"); + _tagNameMap.put(TAG_PROJ_RECTIFIED_GRID_ANGLE, "Projection Straight Vertical Pole Latitude"); + _tagNameMap.put(TAG_VERTICAL_CS_TYPE, "Vertical Coordinate System Type"); + _tagNameMap.put(TAG_VERTICAL_CITATION, "Vertical Citation"); + _tagNameMap.put(TAG_VERTICAL_DATUM, "Vertical Datum"); + _tagNameMap.put(TAG_VERTICAL_UNITS, "Vertical Units"); + _tagNameMap.put(TAG_CHART_FORMAT, "Chart Format"); + _tagNameMap.put(TAG_CHART_SOURCE, "Chart Source"); + _tagNameMap.put(TAG_CHART_SOURCE_EDITION, "Chart Source Edition"); + _tagNameMap.put(TAG_CHART_SOURCE_DATE, "Chart Source Date"); + _tagNameMap.put(TAG_CHART_CORR_DATE, "Chart Corr Date"); + _tagNameMap.put(TAG_CHART_COUNTRY_ORIGIN, "Chart Country Origin"); + _tagNameMap.put(TAG_CHART_RASTER_EDITION, "Chart Raster Edition"); + _tagNameMap.put(TAG_CHART_SOUNDING_DATUM, "Chart Sounding Datum"); + _tagNameMap.put(TAG_CHART_DEPTH_UNITS, "Chart Depth Units"); + _tagNameMap.put(TAG_CHART_MAG_VAR, "Chart Mag Var"); + _tagNameMap.put(TAG_CHART_MAG_VAR_YEAR, "Chart Mag Var Year"); + _tagNameMap.put(TAG_CHART_MAG_VAR_ANN_CHANGE, "Chart Mag Var Annual Change"); + _tagNameMap.put(TAG_CHART_WGS_NS_SHIFT, "Chart WGSNS Shift"); + _tagNameMap.put(TAG_INSET_NW_PIXEL_X, "Inset NW Pixel X"); + _tagNameMap.put(TAG_INSET_NW_PIXEL_Y, "Inset NW Pixel Y"); + _tagNameMap.put(TAG_CHART_CONTOUR_INTERVAL, "Chart Contour Interval"); + } + + public GeoTiffDirectory() + { + setDescriptor(new GeoTiffDescriptor(this)); + } + + @Override + protected HashMap getTagNameMap() + { + return _tagNameMap; + } + + @Override + public String getName() + { + return "GeoTIFF"; + } +} From 159acf91b2444e7d5bb7c8d46fbeafad63237c8c Mon Sep 17 00:00:00 2001 From: Vincent Privat Date: Mon, 20 Jun 2022 00:28:45 +0200 Subject: [PATCH 13/13] Add GeoTIFF test using sample data from OSGEO/libgeotiff --- Tests/Data/GeogToWGS84GeoKey5.lgo | 31 ++++++ Tests/Data/GeogToWGS84GeoKey5.tif | Bin 0 -> 4058 bytes Tests/Data/GeogToWGS84GeoKey5.txt | 10 ++ .../GeogAngularUnitsGeoKey_9114.tif | Bin 0 -> 312 bytes .../libgeotiff/GeogGeodeticDatumGeoKey.tif | Bin 0 -> 312 bytes .../libgeotiff/GeogPrimeMeridianGeoKey.tif | Bin 0 -> 441 bytes .../libgeotiff/ProjLinearUnitsGeoKey_9036.tif | Bin 0 -> 362 bytes ...046_transverse_mercator_south_oriented.tif | Bin 0 -> 288 bytes ...eGeoKey_27200_new_zealand_mapping_grid.tif | Bin 0 -> 288 bytes ...ctedCSTypeGeoKey_28191_cassini_soldner.tif | Bin 0 -> 288 bytes .../ProjectedCSTypeGeoKey_29101_polyconic.tif | Bin 0 -> 288 bytes ...Key_3032_polar_stereographic_variant_b.tif | Bin 0 -> 288 bytes ...oKey_3035_lambert_azimuthal_equal_area.tif | Bin 0 -> 288 bytes ...tedCSTypeGeoKey_3083_albers_equal_area.tif | Bin 0 -> 288 bytes ...ey_3410_lambert_cylindrical_equal_area.tif | Bin 0 -> 288 bytes .../ProjectedCSTypeGeoKey_3812_lcc2sp.tif | Bin 0 -> 288 bytes ...dCSTypeGeoKey_3814_transverse_mercator.tif | Bin 0 -> 288 bytes ...ypeGeoKey_4087_equidistant_cylindrical.tif | Bin 0 -> 288 bytes ...ProjectedCSTypeGeoKey_5329_mercator1sp.tif | Bin 0 -> 288 bytes .../ProjectedCSTypeGeoKey_5456_lcc1sp.tif | Bin 0 -> 288 bytes ...Key_5482_polar_stereographic_variant_a.tif | Bin 0 -> 288 bytes ...STypeGeoKey_5588_oblique_stereographic.tif | Bin 0 -> 288 bytes ...ProjectedCSTypeGeoKey_5641_mercator2sp.tif | Bin 0 -> 288 bytes ...6808_hotine_oblique_mercator_variant_a.tif | Bin 0 -> 288 bytes ...8065_hotine_oblique_mercator_variant_b.tif | Bin 0 -> 288 bytes ...peGeoKey_8441_oblique_mercator_laborde.tif | Bin 0 -> 288 bytes Tests/Data/libgeotiff/albers_equal_area.tif | Bin 0 -> 542 bytes Tests/Data/libgeotiff/byte.tif | Bin 0 -> 736 bytes Tests/Data/libgeotiff/byte_v11.tif | Bin 0 -> 736 bytes Tests/Data/libgeotiff/cassini_soldner.tif | Bin 0 -> 595 bytes .../libgeotiff/equidistant_cylindrical.tif | Bin 0 -> 526 bytes .../hotine_oblique_mercator_variant_a.tif | Bin 0 -> 578 bytes .../hotine_oblique_mercator_variant_b.tif | Bin 0 -> 563 bytes .../lambert_azimuthal_equal_area.tif | Bin 0 -> 492 bytes .../lambert_cylindrical_equal_area.tif | Bin 0 -> 539 bytes Tests/Data/libgeotiff/lcc1sp.tif | Bin 0 -> 531 bytes Tests/Data/libgeotiff/lcc2sp.tif | Bin 0 -> 533 bytes Tests/Data/libgeotiff/mercator1sp.tif | Bin 0 -> 522 bytes Tests/Data/libgeotiff/mercator2sp.tif | Bin 0 -> 522 bytes .../libgeotiff/new_zealand_mapping_grid.tif | Bin 0 -> 535 bytes .../libgeotiff/oblique_mercator_laborde.tif | Bin 0 -> 533 bytes .../Data/libgeotiff/oblique_stereographic.tif | Bin 0 -> 538 bytes Tests/Data/libgeotiff/pcs_32064.tif | Bin 0 -> 361 bytes .../Data/libgeotiff/pixel_is_point_wgs84.tif | Bin 0 -> 298 bytes .../polar_stereographic_variant_a.tif | Bin 0 -> 511 bytes .../polar_stereographic_variant_b.tif | Bin 0 -> 537 bytes Tests/Data/libgeotiff/polyconic.tif | Bin 0 -> 511 bytes Tests/Data/libgeotiff/transverse_mercator.tif | Bin 0 -> 509 bytes .../transverse_mercator_south_oriented.tif | Bin 0 -> 521 bytes .../drew/metadata/geotiff/GeoTiffTest.java | 102 ++++++++++++++++++ 50 files changed, 143 insertions(+) create mode 100644 Tests/Data/GeogToWGS84GeoKey5.lgo create mode 100644 Tests/Data/GeogToWGS84GeoKey5.tif create mode 100644 Tests/Data/GeogToWGS84GeoKey5.txt create mode 100644 Tests/Data/libgeotiff/GeogAngularUnitsGeoKey_9114.tif create mode 100644 Tests/Data/libgeotiff/GeogGeodeticDatumGeoKey.tif create mode 100644 Tests/Data/libgeotiff/GeogPrimeMeridianGeoKey.tif create mode 100644 Tests/Data/libgeotiff/ProjLinearUnitsGeoKey_9036.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_2046_transverse_mercator_south_oriented.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_27200_new_zealand_mapping_grid.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_28191_cassini_soldner.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_29101_polyconic.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_3032_polar_stereographic_variant_b.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_3035_lambert_azimuthal_equal_area.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_3083_albers_equal_area.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_3410_lambert_cylindrical_equal_area.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_3812_lcc2sp.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_3814_transverse_mercator.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_4087_equidistant_cylindrical.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_5329_mercator1sp.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_5456_lcc1sp.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_5482_polar_stereographic_variant_a.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_5588_oblique_stereographic.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_5641_mercator2sp.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_6808_hotine_oblique_mercator_variant_a.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_8065_hotine_oblique_mercator_variant_b.tif create mode 100644 Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_8441_oblique_mercator_laborde.tif create mode 100644 Tests/Data/libgeotiff/albers_equal_area.tif create mode 100644 Tests/Data/libgeotiff/byte.tif create mode 100644 Tests/Data/libgeotiff/byte_v11.tif create mode 100644 Tests/Data/libgeotiff/cassini_soldner.tif create mode 100644 Tests/Data/libgeotiff/equidistant_cylindrical.tif create mode 100644 Tests/Data/libgeotiff/hotine_oblique_mercator_variant_a.tif create mode 100644 Tests/Data/libgeotiff/hotine_oblique_mercator_variant_b.tif create mode 100644 Tests/Data/libgeotiff/lambert_azimuthal_equal_area.tif create mode 100644 Tests/Data/libgeotiff/lambert_cylindrical_equal_area.tif create mode 100644 Tests/Data/libgeotiff/lcc1sp.tif create mode 100644 Tests/Data/libgeotiff/lcc2sp.tif create mode 100644 Tests/Data/libgeotiff/mercator1sp.tif create mode 100644 Tests/Data/libgeotiff/mercator2sp.tif create mode 100644 Tests/Data/libgeotiff/new_zealand_mapping_grid.tif create mode 100644 Tests/Data/libgeotiff/oblique_mercator_laborde.tif create mode 100644 Tests/Data/libgeotiff/oblique_stereographic.tif create mode 100644 Tests/Data/libgeotiff/pcs_32064.tif create mode 100644 Tests/Data/libgeotiff/pixel_is_point_wgs84.tif create mode 100644 Tests/Data/libgeotiff/polar_stereographic_variant_a.tif create mode 100644 Tests/Data/libgeotiff/polar_stereographic_variant_b.tif create mode 100644 Tests/Data/libgeotiff/polyconic.tif create mode 100644 Tests/Data/libgeotiff/transverse_mercator.tif create mode 100644 Tests/Data/libgeotiff/transverse_mercator_south_oriented.tif create mode 100644 Tests/com/drew/metadata/geotiff/GeoTiffTest.java diff --git a/Tests/Data/GeogToWGS84GeoKey5.lgo b/Tests/Data/GeogToWGS84GeoKey5.lgo new file mode 100644 index 000000000..2c7e3815c --- /dev/null +++ b/Tests/Data/GeogToWGS84GeoKey5.lgo @@ -0,0 +1,31 @@ +Geotiff_Information: + Version: 1 + Key_Revision: 1.0 + Tagged_Information: + ModelTiepointTag (2,3): + 50.5 50.5 0 + 9.00105738 52.001376 0 + ModelPixelScaleTag (1,3): + 2.77777778e-005 2.77777778e-005 1 + End_Of_Tags. + Keyed_Information: + GTModelTypeGeoKey (Short,1): ModelTypeGeographic + GTRasterTypeGeoKey (Short,1): RasterPixelIsArea + GeographicTypeGeoKey (Short,1): User-Defined + GeogGeodeticDatumGeoKey (Short,1): User-Defined + GeogAngularUnitsGeoKey (Short,1): Angular_Degree + GeogEllipsoidGeoKey (Short,1): Ellipse_Bessel_1841 + Unknown-2062 (Double,7): 598.1 73.7 418.2 +0.202 0.045 -2.455 +6.7 + End_Of_Keys. + End_Of_Geotiff. + +Ellipsoid: 7004/(unknown) (0.00,0.00) + +Corner Coordinates: +Upper Left ( 8d59'58.76"E, 52d 0'10.00"N) +Lower Left ( 8d59'58.76"E, 51d59'59.90"N) +Upper Right ( 9d 0' 8.86"E, 52d 0'10.00"N) +Lower Right ( 9d 0' 8.86"E, 51d59'59.90"N) +Center ( 9d 0' 3.81"E, 52d 0' 4.95"N) diff --git a/Tests/Data/GeogToWGS84GeoKey5.tif b/Tests/Data/GeogToWGS84GeoKey5.tif new file mode 100644 index 0000000000000000000000000000000000000000..e878c608a9f8de65af1c86361f73f859fd90f505 GIT binary patch literal 4058 zcmeHJOG{K?7=F*0aTGD5n~@d{x~Zs`(Tz-$2UqG!Du|**1wlciL=?CwOfX#3GN@e! zZG!9%gqs$HF|KkEtt3*ZFw5)AoYV7smzm>bl4;XIkACw$=lh=b{^~a!9fzg8Tx863 z#l`3Uk~ue!k0yrISlmVNT^}neft!=xA)Y`lFw1cjv|Cor6`|9V<%pY+V!W6OT8J6Q z&mtDrG8tMiyYwHdN@)s?eCYfz9wx#0ZtzB}nS}1N;ZDJZ#aUH4?MswT;+!mmvWToq zkYU@>SR76xhjQ6-E%p0KP9Ry>vHFyJ4|7UzTgal&a9zQI$&D)Xujgqbo0haIP8yQ9 zR7)J`c~gw6>H)_&kY&?@nfzIx_RHB1!C@xUB&N$Uu;Mhhj1^tOvVF8~|`YYG$K)X$sq(kq!&MPR6O_budy z1<6=!#@RJ6DLxz1*B9lNDghI#pD zBqc|M&C8(LY`z^MeZ~$H{=jXny3w@Z=lOciw@u}|6w+|0X)Tk9+Ve+m=P)rG^1rbH z#n3Z`Nro;1E12XzX#G!`fin@hQ4^NIE1iwG+}644zO2rND%Pas=8Dv()S6qq>=R$2 z;s|rV<3tp`LKwoX!4=q#7+iKwBxY~~`;`XM)($5s+aTXSVP|N6PYBl-Oq+WRe-JOV zPMM0K7r4&gKH&W!-Gc@*=0m_Lc4#GWmF~jcY_&)e@NVGPc9G-2jEj#4zTkHjHmmPf z5lSaGef?Du1Fi*rH4u?F@MYlPfhxfl4S5F4=LihZmX#hbS2;G%KrWm26yyZf9js}r zek>{;gn(;l$9jPE2zwJ)pOM4?1alTE6%~1n^|VZ+9zoSJ0Fe>F^lv?zxB^@K-^9&F z>RbKawEge8bnV8i`014zxmE%J_Cn{gGKF8 \ No newline at end of file diff --git a/Tests/Data/libgeotiff/GeogAngularUnitsGeoKey_9114.tif b/Tests/Data/libgeotiff/GeogAngularUnitsGeoKey_9114.tif new file mode 100644 index 0000000000000000000000000000000000000000..e15144c1576bab5d348328651f16c899e7587d3e GIT binary patch literal 312 zcmebD)MDUbU|`5*;A3E5WM*IlvcVXM%>-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ>(@|4YD3&AP)lr3y96c0CYN7jEQAqJ20de7=RvSWcXjt z$g#0q0vL8oP(Cx5KCZ+9rZtt>z;vH72bj)~=H%GW4l(^b literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/ProjLinearUnitsGeoKey_9036.tif b/Tests/Data/libgeotiff/ProjLinearUnitsGeoKey_9036.tif new file mode 100644 index 0000000000000000000000000000000000000000..3169e5308743d5dab6098a2f1b1818e87325fcf3 GIT binary patch literal 362 zcmebD)MDUbU|`5*;Adc9WM*IlvcVXM%>-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZAO~dofD9r49G*!z zf|Q(@=L4n@%KaQ5Y#0slH^@MC1_l-g8|YlH7!%9Jc5$E#Bge*eHU@D9HlQaN8TynN pctEs6Een|TQTB6mF)~-sS8($2RWLO1Q_x5&2@Te)0SnhK005s$8N&bo literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_2046_transverse_mercator_south_oriented.tif b/Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_2046_transverse_mercator_south_oriented.tif new file mode 100644 index 0000000000000000000000000000000000000000..6fd6a90a6e22660c1a07fd0736677962b6d74a43 GIT binary patch literal 288 zcmebD)MDUbU|`5*;A3E5WM*IlvcVXM%>-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ@~ literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_4087_equidistant_cylindrical.tif b/Tests/Data/libgeotiff/ProjectedCSTypeGeoKey_4087_equidistant_cylindrical.tif new file mode 100644 index 0000000000000000000000000000000000000000..b01885846477600f397d7cfb727792c22aec8db1 GIT binary patch literal 288 zcmebD)MDUbU|`5*;A3E5WM*IlvcVXM%>-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ-q00OgpG#MzM8f{ZL+eIPZWNaA8hY;mX> zkohu5;=xF4zGfZ3`=#WXcRs$d1N5a?1}3JM~)0%Mx8YP2R1XBfvf}+B0PR>pu zE^6}+`~l*hD0C6ro%HTYEH`-Y@w|Dz_b%UEqp<`RKnY+ROercuTe;RLZupL&r-S zx<_ZYBc8rL?%xq!(s4NH1($H)vMP%CnxvPmnJ1v6Ein6#x_x;;c%h zs8JDc6d4y&@I+WRn<|=Buvsz-idI5QL^Nxs#%x8`wdO1tePV^8<$}*^b~mUC=rN=! z7{PLA8RSW+C-^ZLEUFqI7n9w|2_p?2P*mI@R(R!%f>)$;a~u|><;qwwt!9b}@wzgk z$hmrsdlS@+90ovxH})3tE$rehVT`QHMIj>?P823vm34LR#9ERTIVj+9N)HEctB+yW zi*+zufB(5KcFopx8BfXBeP41ZrMGzTSVDX~rDmG&W718WW`rkOd=>fW76)%OJrrVa} zmuq>n!T;iA<$vht*X`rT|NQo3h=H5o23Jy=Z7}Q(xykHowHt(ae7}4>8{*wN%i~86 X9xm=LUOYctynpxh)#C7Q`62xUqeR@! literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/byte_v11.tif b/Tests/Data/libgeotiff/byte_v11.tif new file mode 100644 index 0000000000000000000000000000000000000000..e6d8f023f21a9010720577590bb57ea7edbd288f GIT binary patch literal 736 zcmZuuJ&P1U5bfDrj|&z>@J-rtuU_{YAKy)1nv_^g%-qZvVXRWJAZ6*qS(QxD zqaxraDlVqriLh`sRSc^jSTYNmRzgffG;62EY(=-V<}4X=VufPljKA0HZcrD{V@Xvo zg5}UM$dgh}@MAJqR5d~^HoKD(RvJ8@sklR&@X8qtuPEvH=ddU(SH_8LHB)qmx0NA9 z&ee0=o1kvwFaR37vA2+KVHbA^V`N<}3K_w2qA=O2Y^!@G){?ZSK>?3bdN_bveFVc^ ztb^hD`_GxNYqqY-cuGd}eaWSi-r~jM2=SGant8&HN!Rc-6FeL7F6#3U2XAMBuQrQe zHaL5e`u>PR^ZLm7jNw}o&a(-={Aroy;69zD^wE}S2lx%}$!4Bz0DlEO-OSQH@DJdN zvpzh>|MErWf9UYn&7-LQ{PuXrftw*NrL-9Fm}iUA&0Y|eQGfY*G2}b9*GCWUKUm#c Ty?Fj)_5R)4SF3}A^@sEqqlVni literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/cassini_soldner.tif b/Tests/Data/libgeotiff/cassini_soldner.tif new file mode 100644 index 0000000000000000000000000000000000000000..54c3dbe11b0ba84dfb7c6656060dd77722856395 GIT binary patch literal 595 zcmebD)MDUZU|UgUWrI{PBZ;#iu>~1f!1{ui7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|M86(60 zdPa_o?ZH3}6O_*eru&pRpnOh_4eg9TL%6{-D+4c>2KtHzqMr#$GlOX%1{N@_sm#Fx z(#Oca38onsxWF_M12>okg%AWZRx=!byW8HuMRsY4>TE{`uIq~nVotC)HcoZ!w)^?d z;r7ouqw^sVj?*GM*vrC;9ANrN^YRjNQ&Vc(or4wpfGh=D1*nihYEDjOL2+u0OJYfB qE?7J}FTXsm#uY4{pP7PCTN6-}nVSkyUgUWrI{PBZ;#iu>~1f!1{QZ7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|L3Ncz+hx} zCcwzCv7Mbkoq-L^?^EXF*wD@hbTv1aW@TXD0jc|6&jh8J!L%R)3z*haX5#^=XJX(2 z)65LqV48(N08BG72!d&lg~Wl5+>&XsONtzTsvVYvL^y`K2P;^ZDCjGM=NILqD7Y4u fW~O8omn7zuC^%QUgUWrI{PBZ;#iu>~1f!1^4S7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|kh=f%Oi-E`OfxgEfN4!-E*_A2Rt9b` z&Bh=ArWqLo!89-|!88kl7?@^akO0%n3{jy#@YT%$4A}q-4@_JD literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/hotine_oblique_mercator_variant_b.tif b/Tests/Data/libgeotiff/hotine_oblique_mercator_variant_b.tif new file mode 100644 index 0000000000000000000000000000000000000000..d840338854cf2f1cac9fddef1324011e25e2f3e8 GIT binary patch literal 563 zcmebD)MDUZU|UgUWrI{PBZ;#iu>~1f!1|1s7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|>CjV1{Me#=yb3c6U)YSd5{8NU@$VQ zlVIf72y~4+0~?s%r_9N*p`8&Z$qlC289sAE&Ef&k|Ld8cG&7hES7QOwTFP8JAbqS1 z++doGK>$oMG6;fcV3>kw76vge&BP!9ra>VE1PTrgvt|Op&FBMQeuJ9>jDB43EWDWE zgFTqv!3^UgUWrI{PBZ;#iu>~1f!1`VTHH#vNiy^Vap=v

    WncsI`;<93HncM`urhFiX`m(^kh=f%Oi-E`Omi`?fN4!-E*_A2CI)UW&CDPG zrWqLo!8FJs5b$yUQw%C#dRKJ-*qt4<9*!%1zMdkxqzEMLuq-6P(KRF}*uqjlU%|)G T)ltEAsl0&(*z)Q_RhU0=AtgNNVmo|oh9Jns+|fL(y;U}Q2B*Oo52bR};pzQ+tXSJi-h zC0|y&vg{G(*Oa`k_|B6Jo$6l{kIyzt;+*2W(BTlvG(0VAY>>Z_&k7B<UgUWrI{PBZ;#iu>~1f!1{!k7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|6z+hzX z7G>nv*e<}J%)kcb_bGF7Y-nc$x|$nIvobL7fYkl3XM)nqV49PG1x#xybMS!FGcs_3 zX$A%^FwM-s4W?NbM8Gr?1JnC=K%n9f90~-Yp$B{lh4(J~|K1+tl!g{LM<(rdF{9}M zj_skP`&Z-_In0_lbLRGt2uJ_q{F2my)WXtK1w%_?Qw4nm=ltT5M1`QtUgUWrI{PBZ;#iu>~1f!1_d(7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|rd1;Pe89W2JgvawwbqyQKgj0}qe z7&$h!vopvsuz~q~%A6b<+8G(x8MwhT(DghZb^q&`pfod>=3rm})0)a`JRtQ<4D4W< znSlpPGcxdkX$A&9FwMfi52jfe_!fGdl}qz<_)*j7b}e7fVc9Amc;(>`6$%7XJsm(! zdvHM+?2i~<6~`4nUr&);QUnrrSQZlD=o%6fY+UgUWrI{PBZ;#iu>~1f!1_3u7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|h=6Heph7{HieK`nPELo$sm|Rw&)?gFgbfb*g5B*tGBO3Y3yDoNB-&{yzt^>mG@L6@ju$OZrl`a$Rb literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/mercator2sp.tif b/Tests/Data/libgeotiff/mercator2sp.tif new file mode 100644 index 0000000000000000000000000000000000000000..8b49fea308077a32d347c6675060b464616b170f GIT binary patch literal 522 zcmebD)MDUZU|UgUWrI{PBZ;#iu>~1f!1_3u7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+| z5@O`o2y~4c0~?s%r_9N*p`8)vYHl#i%D}(_Qun`}2}(1AX?6w{Fs-S~#sgB%#J~Zj z85uaiGy?+{m}X|+2Gc;xkwJs&0Vo$tUl0lgyZeGrvg3-Mucyc^DRKbHJ1h%{a18bg la(4_?FfuSOP|#O!DoU)%%u(=7ElN%-$uFuwlC5FL1^{p~ISv2- literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/new_zealand_mapping_grid.tif b/Tests/Data/libgeotiff/new_zealand_mapping_grid.tif new file mode 100644 index 0000000000000000000000000000000000000000..dd9a2dec50526ee5e5a195bff4d3aa1d89e99468 GIT binary patch literal 535 zcmebD)MDUZU|UgUWrI{PBZ;#iu>~1f!1~0P7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|WN&O|VlV)y z0T~Jhehv^OjD|~KWjH*Oas)}7ndbwhL2O2_Z-f{aSRibm)4^g)EF0TF;Q$IUMuz|O zj2s)=)qxx)Fuy^94NUhbb8>8GXJlYy;0Dty47^|(=o=o0J|-y545p!nYpRCKmqrn)YQE4%;b!k8is5DKxsZ& literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/oblique_mercator_laborde.tif b/Tests/Data/libgeotiff/oblique_mercator_laborde.tif new file mode 100644 index 0000000000000000000000000000000000000000..01eb113aabc389d1e5829294ffc987beea906772 GIT binary patch literal 533 zcmebD)MDUZU|UgUWrI{PBZ;#iu>~1f!1_d(7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|rd1;Pe89W2JgvawwXqyQKgj0`6Q z7&$h!b2CUWuz~q~%A6b<+8G(x8MwhT(DghZb^q&`pfod>W?^6f)0)a$JRtQf4BTLv zl|cYZGcpK*X<%4_X=Vm7FwMk}1_IBF4&>YrRrqf1?f~UGOmptedH(yoJ;-ScSrQO` vWNAR?2}KSd;in-HjvUgUWrI{PBZ;#iu>~1f!1^Sa7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|WN&O|VlW1& z0T~Jhehv^OjD|~KWjH*Oas)}7ndbwhL2O2_Z-f{aSRibm)4^g)EF0T(Knj3?!N{;$ zfRSTkI~#)z0~?s%r_9N*p`8)vYHl#i%J7*RY8DTO{$I}orJ2FB00Rq{)>7u+0qJ98 z-~`hQ3|wHEnSmQjvoMH&X<*=jfP=dOm|hTYKwl!J*!}-|dl0{&MGx$L5ZxYHx_?D} zk;AN+GiPoOiE#9DbTKkl&{yzFEmv?VD$Oe{&rHr%2rfx2O3hC%N-W68OjghUE7Gh1 IQ#B0P05#}CasU7T literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/pcs_32064.tif b/Tests/Data/libgeotiff/pcs_32064.tif new file mode 100644 index 0000000000000000000000000000000000000000..19ff4f3884b481287c9798c53876459f98f298c0 GIT binary patch literal 361 zcmebD)MDUZU|`^9U|?isU<9(j7>UgUWrI{PBZ;#iu>~1f!1^+PnnjVs#gN$IP&FX) zWst;!k=T6AJPgc0`9nb5)WXBS2BdER@%naV1_dC^1=PH;orysX$o2vm1P6W&5GIU< zOJHR+r>fh92?u&7{nRafC0hC(5KA6 p1EL*jS-`ZmvY(@ik-37tf|HN0f}x3@f<{_NXs~7tSh$8E8vstp8Q}l` literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/pixel_is_point_wgs84.tif b/Tests/Data/libgeotiff/pixel_is_point_wgs84.tif new file mode 100644 index 0000000000000000000000000000000000000000..f8a7ab272a51f540028547b287fb69f602c84109 GIT binary patch literal 298 zcmebD)MDUafB-%Q21aHEMj(d?h#8UCOi(rlP@Eac2B~91Vhb{|fc1jZ2qB4!BC*Aw zY>@flQ1vW8whWRQzGfZUgUWrI{PBZ;#iu>~1f!213HHH#vNiy^Vap=v

    pa5jw z0~rbjehv^OjD|~KWjH*Oas)}7ndbwhL2O2_Zv+__SRibm)4^g)EF0TJK?;C@!N@R2 zh>>GsI~Ri}0~?s%r_9N*p`8)vYHl#i%D}(_Qun`}2}(1AX?_M4Fs-S~$pcc)z`zBj znHjjjGz)_Wm}X)S2h&i`HG~}iS1En-3={zjw^n?o+7)X l$N?zquq-6PF(^35-Nnejz(7G?At*Q?7{siB3)V1X0|5E}Ip6>Q literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/polar_stereographic_variant_b.tif b/Tests/Data/libgeotiff/polar_stereographic_variant_b.tif new file mode 100644 index 0000000000000000000000000000000000000000..906b0fdbd465c468d5e58ef35765d0be80cbd0e3 GIT binary patch literal 537 zcmZvYze>YU6o-E|iEV0wwxF$u!i7${2*p8MOeQUeSSsphC`F64l^C~zqC%V;9i61B z%^UOq#7A%uGW!O4?rAJ{FmU+3oIf|`rdC^p4WIxB6M~FGb0c-tWor41=0#!g+=2nj zYx$Dq<+Mkgzog|Gn$O%jFsc4QarfXLLrf^%A`?5r5)BU{2U+p~c^nzoB>z;0PM$q? ztbd#o{uMV_^i<*fWtXX3@NJ5KHFjE^bA}a1RnV#ephUgUWrI{PBZ;#iu>~1f!213HHH#vNiy^Vap=v

    pa5jw z0~rbjehv^OjD|~KWjH*Oas)}7ndbwhL2O2_Zv+__SRibm)4^g)EF0S;K?;C@!N|ZR z#K^I+osB_~fep;>Q|9E@(9Q^SH8+@MWnka|srz5g1f`k5v={>mnATM0-~p*;WZ(qT z3=CXgnwfzcOtUbEfN3U>jYz=z0EF|w9z1KV{Y#1*K;mmcA{>JqUCb;M c^c9?n600(E6aw;dDwFf`GLvh-5;Y9j0M{Nf^#A|> literal 0 HcmV?d00001 diff --git a/Tests/Data/libgeotiff/transverse_mercator.tif b/Tests/Data/libgeotiff/transverse_mercator.tif new file mode 100644 index 0000000000000000000000000000000000000000..8273448194ee27a86e033506705de957a3ee8d1e GIT binary patch literal 509 zcmebD)MDUZU|UgUWrI{PBZ;#iu>~1f!213GHH#vNiy^Vap=v

    AO~dM z0~rbjehv^OjD|~KWjH*Oas)}7ndbwhL2O2_Zv+__SRibm)4^g)EF0U!K?;C@!N{;y zfRSTkI~#*I0~?s%r_9N*p`8)vYHl#i%D}(_Qun`}2}(1AX+{PXFs-S~!2?py$iNAv z85p?0G&2J?m}X%R0nUgUWrI{PBZ;#iu>~1f!1_3t7#Kv6#Kn-<;!rgp z^JS34gOS*L%{&auK>2e(+|BudaCcC5vB=4{+B*M`nv8W_9DK)jY YBqKjH+tNfqU%@Be(6k0!yoMng05vr>k^lez literal 0 HcmV?d00001 diff --git a/Tests/com/drew/metadata/geotiff/GeoTiffTest.java b/Tests/com/drew/metadata/geotiff/GeoTiffTest.java new file mode 100644 index 000000000..b9d2eb1ed --- /dev/null +++ b/Tests/com/drew/metadata/geotiff/GeoTiffTest.java @@ -0,0 +1,102 @@ +/* + * 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.metadata.geotiff; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.Objects; + +import org.junit.Test; + +import com.drew.imaging.tiff.TiffMetadataReader; +import com.drew.metadata.Metadata; +import com.drew.metadata.exif.ExifDirectoryBase; +import com.drew.metadata.exif.ExifIFD0Directory; + +public class GeoTiffTest { + + @Test + public void testGeogToWGS84GeoKey5() throws Exception { + Metadata metadata = TiffMetadataReader.readMetadata(new File("Tests/Data/GeogToWGS84GeoKey5.tif")); + assertNotNull(metadata); + + ExifIFD0Directory exif = checkExif(metadata, 24); + + assertEquals("[32 values]", exif.getDescription(ExifDirectoryBase.TAG_GEOTIFF_GEO_KEYS)); + assertEquals("[768 values]", exif.getDescription(ExifDirectoryBase.TAG_COLOR_MAP)); + assertEquals("0 0 1", exif.getDescription(ExifDirectoryBase.TAG_PIXEL_SCALE)); + assertEquals("50.5 50.5 0 9.001 52.001 0", exif.getDescription(ExifDirectoryBase.TAG_MODEL_TIE_POINT).replace(',', '.')); + assertNull(exif.getDescription(ExifDirectoryBase.TAG_GEOTIFF_GEO_ASCII_PARAMS)); + assertNull(exif.getDescription(ExifDirectoryBase.TAG_GEOTIFF_GEO_DOUBLE_PARAMS)); + assertNull(exif.getDescription(ExifDirectoryBase.TAG_GDAL_METADATA)); + assertNull(exif.getDescription(ExifDirectoryBase.TAG_GDAL_NO_DATA)); + + GeoTiffDirectory geotiff = checkGeoTiff(metadata); + + assertEquals("Geographic", geotiff.getDescription(GeoTiffDirectory.TAG_MODEL_TYPE)); + assertEquals("PixelIsArea", geotiff.getDescription(GeoTiffDirectory.TAG_RASTER_TYPE)); + assertEquals("User Defined", geotiff.getDescription(GeoTiffDirectory.TAG_GEOGRAPHIC_TYPE)); + assertEquals("User Defined", geotiff.getDescription(GeoTiffDirectory.TAG_GEODETIC_DATUM)); + assertEquals("Angular Degree", geotiff.getDescription(GeoTiffDirectory.TAG_GEOGRAPHIC_ANGULAR_UNITS)); + assertEquals("Bessel 1841", geotiff.getDescription(GeoTiffDirectory.TAG_GEOGRAPHIC_ELLIPSOID)); + assertEquals("598.1 73.7 418.2 0.202 0.045 -2.455 6.7", geotiff.getDescription(GeoTiffDirectory.TAG_GEOGRAPHIC_TO_WGS84).replace(',', '.')); + assertEquals(7, geotiff.getTagCount()); + } + + @Test + public void testLibgeotiff() throws Exception { + for (File tiffFile : new File("Tests/Data/libgeotiff").listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".tif"); + } + })) + { + Metadata metadata = TiffMetadataReader.readMetadata(tiffFile); + assertNotNull(tiffFile.getName(), metadata); + checkExif(metadata, 14); + String description = tiffFile.getName() + "\n " + checkGeoTiff(metadata).getTags(); + assertFalse(description, description.contains("Unknown")); + } + } + + private static ExifIFD0Directory checkExif(Metadata metadata, int numberOfTags) { + ExifIFD0Directory exif = metadata.getDirectoriesOfType(ExifIFD0Directory.class).iterator().next(); + assertNotNull(exif); + assertFalse(Objects.toString(exif.getErrors()), exif.hasErrors()); + assertEquals(numberOfTags, exif.getTagCount()); + return exif; + } + + private static GeoTiffDirectory checkGeoTiff(Metadata metadata) { + GeoTiffDirectory geotiff = metadata.getDirectoriesOfType(GeoTiffDirectory.class).iterator().next(); + assertNotNull(geotiff); + assertFalse(Objects.toString(geotiff.getErrors()), geotiff.hasErrors()); + assertTrue(geotiff.getTagCount() > 0); + return geotiff; + } +}