All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.drew.metadata.exif.ExifReader Maven / Gradle / Ivy

Go to download

Java library for extracting EXIF, IPTC, XMP, ICC and other metadata from image and video files.

There is a newer version: 2.19.0
Show newest version
/*
 * Copyright 2002-2012 Drew Noakes
 *
 *    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:
 *
 *    http://drewnoakes.com/code/exif/
 *    http://code.google.com/p/metadata-extractor/
 */
package com.drew.metadata.exif;

import com.drew.lang.BufferBoundsException;
import com.drew.lang.BufferReader;
import com.drew.lang.Rational;
import com.drew.lang.annotations.NotNull;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataReader;

import java.util.HashSet;
import java.util.Set;

/**
 * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory},
 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories.
 *
 * @author Drew Noakes http://drewnoakes.com
 */
public class ExifReader implements MetadataReader
{
    // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following
    
    /** The number of bytes used per format descriptor. */
    @NotNull
    private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };

    /** The number of formats known. */
    private static final int MAX_FORMAT_CODE = 12;

    // Format types
    // TODO use an enum for these?
    /** An 8-bit unsigned integer. */
    private static final int FMT_BYTE = 1;
    /** A fixed-length character string. */
    private static final int FMT_STRING = 2;
    /** An unsigned 16-bit integer. */
    private static final int FMT_USHORT = 3;
    /** An unsigned 32-bit integer. */
    private static final int FMT_ULONG = 4;
    private static final int FMT_URATIONAL = 5;
    /** An 8-bit signed integer. */
    private static final int FMT_SBYTE = 6;
    private static final int FMT_UNDEFINED = 7;
    /** A signed 16-bit integer. */
    private static final int FMT_SSHORT = 8;
    /** A signed 32-bit integer. */
    private static final int FMT_SLONG = 9;
    private static final int FMT_SRATIONAL = 10;
    /** A 32-bit floating point number. */
    private static final int FMT_SINGLE = 11;
    /** A 64-bit floating point number. */
    private static final int FMT_DOUBLE = 12;

    /** This tag is a pointer to the Exif SubIFD. */
    public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769;
    /** This tag is a pointer to the Exif Interop IFD. */
    public static final int TAG_INTEROP_OFFSET = 0xA005;
    /** This tag is a pointer to the Exif GPS IFD. */
    public static final int TAG_GPS_INFO_OFFSET = 0x8825;
    /** This tag is a pointer to the Exif Makernote IFD. */
    public static final int TAG_MAKER_NOTE_OFFSET = 0x927C;

    public static final int TIFF_HEADER_START_OFFSET = 6;

    /**
     * Performs the Exif data extraction, adding found values to the specified
     * instance of Metadata.
     *
     * @param reader   The buffer reader from which Exif data should be read.
     * @param metadata The Metadata object into which extracted values should be merged.
     */
    public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
    {
        final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class);

        // check for the header length
        if (reader.getLength() <= 14) {
            directory.addError("Exif data segment must contain at least 14 bytes");
            return;
        }

        // check for the header preamble
        try {
            if (!reader.getString(0, 6).equals("Exif\0\0")) {
                directory.addError("Exif data segment doesn't begin with 'Exif'");
                return;
            }

            extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader);
        } catch (BufferBoundsException e) {
            directory.addError("Exif data segment ended prematurely");
        }
    }

    /**
     * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified
     * instance of Metadata.
     *
     * @param reader   The BufferReader from which TIFF data should be read.
     * @param metadata The Metadata object into which extracted values should be merged.
     */
    public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)
    {
        final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);

        try {
            extractIFD(metadata, directory, 0, reader);
        } catch (BufferBoundsException e) {
            directory.addError("Exif data segment ended prematurely");
        }
    }

    private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException
    {
        // this should be either "MM" or "II"
        String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);

        if ("MM".equals(byteOrderIdentifier)) {
            reader.setMotorolaByteOrder(true);
        } else if ("II".equals(byteOrderIdentifier)) {
            reader.setMotorolaByteOrder(false);
        } else {
            directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
            return;
        }

        // Check the next two values for correctness.
        if (reader.getUInt16(2 + tiffHeaderOffset) != 0x2a) {
            directory.addError("Invalid Exif start - should have 0x2A at offset 8 in Exif header");
            return;
        }

        int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;

        // David Ekholm sent a digital camera image that has this problem
        if (firstDirectoryOffset >= reader.getLength() - 1) {
            directory.addError("First exif directory offset is beyond end of Exif data segment");
            // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case
            firstDirectoryOffset = 14;
        }

        Set processedDirectoryOffsets = new HashSet();

        processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);

        // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
        ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);
        if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {
            Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
            Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
            if (offset != null && length != null) {
                try {
                    byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
                    thumbnailDirectory.setThumbnailData(thumbnailData);
                } catch (BufferBoundsException ex) {
                    directory.addError("Invalid thumbnail data specification: " + ex.getMessage());
                }
            }
        }
    }

    /**
     * Process one of the nested Tiff IFD directories.
     * 

* Header * 2 bytes: number of tags *

* Then for each tag * 2 bytes: tag type * 2 bytes: format code * 4 bytes: component count */ private void processDirectory(@NotNull Directory directory, @NotNull Set processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException { // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset))) return; // remember that we've visited this directory so that we don't visit it again later processedDirectoryOffsets.add(dirStartOffset); if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) { directory.addError("Ignored directory marked to start outside data segment"); return; } // First two bytes in the IFD are the number of tags in this directory int dirTagCount = reader.getUInt16(dirStartOffset); int dirLength = (2 + (12 * dirTagCount) + 4); if (dirLength + dirStartOffset > reader.getLength()) { directory.addError("Illegally sized directory"); return; } // Handle each tag in this directory for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) { final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber); // 2 bytes for the tag type final int tagType = reader.getUInt16(tagOffset); // 2 bytes for the format code final int formatCode = reader.getUInt16(tagOffset + 2); if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) { // This error suggests that we are processing at an incorrect index and will generate // rubbish until we go out of bounds (which may be a while). Exit now. directory.addError("Invalid TIFF tag format code: " + formatCode); return; } // 4 bytes dictate the number of components in this tag's data final int componentCount = reader.getInt32(tagOffset + 4); if (componentCount < 0) { directory.addError("Negative TIFF tag component count"); continue; } // each component may have more than one byte... calculate the total number of bytes final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode]; final int tagValueOffset; if (byteCount > 4) { // If it's bigger than 4 bytes, the dir entry contains an offset. // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an // offset relative to the start of the makernote itself, not the TIFF segment. final int offsetVal = reader.getInt32(tagOffset + 8); if (offsetVal + byteCount > reader.getLength()) { // Bogus pointer offset and / or byteCount value directory.addError("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; } if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) { directory.addError("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()) { directory.addError("Illegal number of bytes: " + byteCount); continue; } switch (tagType) { case TAG_EXIF_SUB_IFD_OFFSET: { final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset); processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); continue; } case TAG_INTEROP_OFFSET: { final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset); processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); continue; } case TAG_GPS_INFO_OFFSET: { final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset); processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); continue; } case TAG_MAKER_NOTE_OFFSET: { processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader); continue; } default: { processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader); break; } } } // at the end of each IFD is an optional link to the next IFD final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount); int nextDirectoryOffset = reader.getInt32(finalTagOffset); if (nextDirectoryOffset != 0) { nextDirectoryOffset += tiffHeaderOffset; if (nextDirectoryOffset >= 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 (nextDirectoryOffset < dirStartOffset) { // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory return; } // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class); processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader); } } private void processMakerNote(int subdirOffset, @NotNull Set processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException { // Determine the camera model and makernote format Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class); if (ifd0Directory==null) return; String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE); //final String firstTwoChars = reader.getString(subdirOffset, 2); final String firstThreeChars = reader.getString(subdirOffset, 3); final String firstFourChars = reader.getString(subdirOffset, 4); final String firstFiveChars = reader.getString(subdirOffset, 5); final String firstSixChars = reader.getString(subdirOffset, 6); final String firstSevenChars = reader.getString(subdirOffset, 7); final String firstEightChars = reader.getString(subdirOffset, 8); final String firstTwelveChars = reader.getString(subdirOffset, 12); if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) { // Olympus Makernote // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader); } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) { if ("Nikon".equals(firstFiveChars)) { /* There are two scenarios here: * Type 1: ** * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon........... * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................ * Type 3: ** * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*... * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200 */ switch (reader.getUInt8(subdirOffset + 6)) { case 1: processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader); break; case 2: processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader); break; default: ifd0Directory.addError("Unsupported Nikon makernote data ignored."); break; } } else { // The IFD begins with the first MakerNote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models. processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); } } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) { processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader); } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) { processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader); } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) { // force MM for this directory boolean isMotorola = reader.isMotorolaByteOrder(); reader.setMotorolaByteOrder(true); // skip 12 byte header + 2 for "MM" + 6 processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader); reader.setMotorolaByteOrder(isMotorola); } else if ("KDK".equals(firstThreeChars)) { processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader); } else if ("Canon".equalsIgnoreCase(cameraModel)) { processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) { if ("QVC\u0000\u0000\u0000".equals(firstSixChars)) processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader); else processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) { boolean byteOrderBefore = reader.isMotorolaByteOrder(); // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering 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) int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8); processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader); reader.setMotorolaByteOrder(byteOrderBefore); } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) { // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote // area that commences immediately. processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader); } else if ("KYOCERA".equals(firstSevenChars)) { // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader); } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) { // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD // 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 processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader); } 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 // Offsets are relative to the start of the current IFD tag, not the TIFF header // Observed for: // - Pentax ist D processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader); } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) { // NON-Standard TIFF IFD Data using Pentax Tags // IFD has no Next-IFD pointer at end of IFD, and // Offsets are relative to the start of the current IFD tag, not the TIFF header // Observed for: // - PENTAX Optio 330 // - PENTAX Optio 430 processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader); // } 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 // // TODO add support for minolta/konica cameras // exifDirectory.addError("Unsupported Konica/Minolta data ignored."); } else { // TODO how to store makernote data when it's not from a supported camera model? // this is difficult as the starting offset is not known. we could look for it... } } private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException { // Directory simply stores raw values // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions switch (formatCode) { case FMT_UNDEFINED: // this includes exif user comments directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount)); break; case FMT_STRING: String string = reader.getNullTerminatedString(tagValueOffset, componentCount); directory.setString(tagType, string); /* // special handling for certain known tags, proposed by Yuri Binev but left out for now, // as it gives the false impression that the image was captured in the same timezone // in which the string is parsed if (tagType==ExifSubIFDDirectory.TAG_DATETIME || tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL || tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) { String[] datePatterns = { "yyyy:MM:dd HH:mm:ss", "yyyy:MM:dd HH:mm", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"}; for (String datePattern : datePatterns) { try { DateFormat parser = new SimpleDateFormat(datePattern); Date date = parser.parse(string); directory.setDate(tagType, date); break; } catch (ParseException ex) { // simply try the next pattern } } } */ break; case FMT_SRATIONAL: if (componentCount == 1) { directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4))); } else if (componentCount > 1) { Rational[] rationals = new Rational[componentCount]; for (int i = 0; i < componentCount; i++) rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i))); directory.setRationalArray(tagType, rationals); } break; case FMT_URATIONAL: if (componentCount == 1) { directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4))); } else if (componentCount > 1) { Rational[] rationals = new Rational[componentCount]; for (int i = 0; i < componentCount; i++) rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i))); directory.setRationalArray(tagType, rationals); } break; case FMT_SINGLE: if (componentCount == 1) { directory.setFloat(tagType, reader.getFloat32(tagValueOffset)); } else { float[] floats = new float[componentCount]; for (int i = 0; i < componentCount; i++) floats[i] = reader.getFloat32(tagValueOffset + (i * 4)); directory.setFloatArray(tagType, floats); } break; case FMT_DOUBLE: if (componentCount == 1) { directory.setDouble(tagType, reader.getDouble64(tagValueOffset)); } else { double[] doubles = new double[componentCount]; for (int i = 0; i < componentCount; i++) doubles[i] = reader.getDouble64(tagValueOffset + (i * 4)); directory.setDoubleArray(tagType, doubles); } break; // // Note that all integral types are stored as int32 internally (the largest supported by TIFF) // case FMT_SBYTE: if (componentCount == 1) { directory.setInt(tagType, reader.getInt8(tagValueOffset)); } else { int[] bytes = new int[componentCount]; for (int i = 0; i < componentCount; i++) bytes[i] = reader.getInt8(tagValueOffset + i); directory.setIntArray(tagType, bytes); } break; case FMT_BYTE: if (componentCount == 1) { directory.setInt(tagType, reader.getUInt8(tagValueOffset)); } else { int[] bytes = new int[componentCount]; for (int i = 0; i < componentCount; i++) bytes[i] = reader.getUInt8(tagValueOffset + i); directory.setIntArray(tagType, bytes); } break; case FMT_USHORT: if (componentCount == 1) { int i = reader.getUInt16(tagValueOffset); directory.setInt(tagType, i); } else { int[] ints = new int[componentCount]; for (int i = 0; i < componentCount; i++) ints[i] = reader.getUInt16(tagValueOffset + (i * 2)); directory.setIntArray(tagType, ints); } break; case FMT_SSHORT: if (componentCount == 1) { int i = reader.getInt16(tagValueOffset); directory.setInt(tagType, i); } else { int[] ints = new int[componentCount]; for (int i = 0; i < componentCount; i++) ints[i] = reader.getInt16(tagValueOffset + (i * 2)); directory.setIntArray(tagType, ints); } break; case FMT_SLONG: case FMT_ULONG: // NOTE 'long' in this case means 32 bit, not 64 if (componentCount == 1) { int i = reader.getInt32(tagValueOffset); directory.setInt(tagType, i); } else { int[] ints = new int[componentCount]; for (int i = 0; i < componentCount; i++) ints[i] = reader.getInt32(tagValueOffset + (i * 4)); directory.setIntArray(tagType, ints); } break; default: directory.addError("Unknown format code " + formatCode + " for tag " + tagType); } } /** * Determine the offset at which a given InteropArray entry begins within the specified IFD. * * @param dirStartOffset the offset at which the IFD starts * @param entryNumber the zero-based entry number */ private int calculateTagOffset(int dirStartOffset, int entryNumber) { // add 2 bytes for the tag count // each entry is 12 bytes, so we skip 12 * the number seen so far return dirStartOffset + 2 + (12 * entryNumber); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy