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

com.easyinnova.tiff.reader.TiffReader Maven / Gradle / Ivy

There is a newer version: 1.9.7
Show newest version
/**
 * 

TiffReader.java

*

* This program is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version; or, at your choice, under the terms of the * Mozilla Public License, v. 2.0. SPDX GPL-3.0+ or MPL-2.0+. *

*

* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License and the Mozilla Public License for more details. *

*

* You should have received a copy of the GNU General Public License and the Mozilla Public License * along with this program. If not, see http://www.gnu.org/licenses/ and at http://mozilla.org/MPL/2.0 . *

*

* NB: for the © statement, include Easy Innova SL or other company/Person contributing the code. *

*

* © 2015 Easy Innova, SL *

* * @author Víctor Muñoz Solà * @version 1.0 * @since 28/5/2015 * */ package com.easyinnova.tiff.reader; import com.easyinnova.tiff.io.TiffInputStream; import com.easyinnova.tiff.model.IccProfileCreators; import com.easyinnova.tiff.model.ReadIccConfigIOException; import com.easyinnova.tiff.model.ReadTagsIOException; import com.easyinnova.tiff.model.Tag; import com.easyinnova.tiff.model.TagValue; import com.easyinnova.tiff.model.TiffDocument; import com.easyinnova.tiff.model.TiffTags; import com.easyinnova.tiff.model.ValidationResult; import com.easyinnova.tiff.model.types.IFD; import com.easyinnova.tiff.model.types.abstractTiffType; import com.easyinnova.tiff.profiles.BaselineProfile; import com.easyinnova.tiff.profiles.TiffEPProfile; import com.easyinnova.tiff.profiles.TiffITProfile; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashSet; /** * Reads and parses a Tiff file, storing it in an internal model. */ public class TiffReader { /** The model containing the Tiff data. */ TiffDocument tiffModel; /** The stream to get data from the file. */ TiffInputStream data; /** The size in bytes of the tags' values field (=4 for Tiff, =8 for BigTiff). */ // TODO: Use it int tagValueSize = 4; /** The result of the validation. */ ValidationResult validation; /** * Tolerance to duplicate tags.
* 0: No tolerance. 1: Tolerate duplicates keeping first appearance only */ int duplicateTagTolerance = 10; /** * Tolerance to errors in the next IFD pointer.
* 0: No tolerance. 1: Tolerate errors assuming there is no next Ifd (offset = 0) */ int nextIFDTolerance = 0; /** * Tolerance to errors in the byte ordering.
* 0: No tolerance. 1: Lower case tolerance. 2: Full tolerance, assuming little endian. * */ int byteOrderErrorTolerance = 0; /** * Default constructor.
* Instantiates a new empty tiff reader. * * @throws ReadTagsIOException the read tags io exception * @throws ReadIccConfigIOException the read icc config io exception */ public TiffReader() throws ReadTagsIOException, ReadIccConfigIOException { tiffModel = null; TiffTags.getTiffTags(); IccProfileCreators.getIccProfileCreators(); } /** * Gets the internal model of the Tiff file. * * @return the model */ public TiffDocument getModel() { return tiffModel; } /** * Gets the stream. * * @return the stream */ public TiffInputStream getStream() { return data; } /** * Gets the result of the validation. * * @return the validation result */ public ValidationResult getBaselineValidation() { return validation; // BaselineProfile bp = new BaselineProfile(tiffModel); // bp.validate(); // return bp.getValidation(); } /** * Gets the result of the validation. * * @return the validation result */ public ValidationResult getTiffEPValidation() { TiffEPProfile bpep = new TiffEPProfile(tiffModel); bpep.validate(); return bpep.getValidation(); } /** * Gets the result of the validation. * * @param profile the TiffIT profile (0: default, 1: P1, 2: P2) * @return the validation result */ public ValidationResult getTiffITValidation(int profile) { TiffITProfile bpit = new TiffITProfile(tiffModel, profile); bpit.validate(); return bpit.getValidation(); } /** * Parses a Tiff File and create an internal model representation. * * @param filename the Tiff filename * @return Error code (0: successful, -1: file not found, -2: IO exception) */ public int readFile(String filename) { int result = 0; try { if (Files.exists(Paths.get(filename))) { data = new TiffInputStream(new File(filename)); tiffModel = new TiffDocument(); validation = new ValidationResult(); tiffModel.setSize(data.size()); boolean correctHeader = readHeader(); if (correctHeader) { if (tiffModel.getMagicNumber() < 42) { validation .addError("Incorrect tiff magic number", "Header", tiffModel.getMagicNumber()); } else if (tiffModel.getMagicNumber() == 43) { validation.addErrorLoc("Big tiff file not yet supported", "Header"); } else if (validation.isCorrect()) { readIFDs(); BaselineProfile bp = new BaselineProfile(tiffModel); bp.validate(); getBaselineValidation().add(bp.getValidation()); } } data.close(); } else { // File not found result = -1; } } catch (Exception ex) { // IO exception result = -2; } return result; } /** * Reads the Tiff header. * * @return true, if successful */ private boolean readHeader() { boolean correct = true; ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; int c1 = 0; int c2 = 0; try { c1 = data.readByte().toInt(); c2 = data.readByte().toInt(); } catch (Exception ex) { validation.addErrorLoc("Header IO Exception", "Header"); } // read the first two bytes, in order to know the byte ordering if (c1 == 'I' && c2 == 'I') { byteOrder = ByteOrder.LITTLE_ENDIAN; } else if (c1 == 'M' && c2 == 'M') { byteOrder = ByteOrder.BIG_ENDIAN; } else if (byteOrderErrorTolerance > 0 && c1 == 'i' && c2 == 'i') { validation.addWarning("Byte Order in lower case", "" + c1 + c2, "Header"); byteOrder = ByteOrder.LITTLE_ENDIAN; } else if (byteOrderErrorTolerance > 0 && c1 == 'm' && c2 == 'm') { validation.addWarning("Byte Order in lower case", "" + c1 + c2, "Header"); byteOrder = ByteOrder.BIG_ENDIAN; } else if (byteOrderErrorTolerance > 1) { validation.addWarning("Non-sense Byte Order. Trying Little Endian.", "" + c1 + c2, "Header"); byteOrder = ByteOrder.LITTLE_ENDIAN; } else { validation.addErrorLoc("Invalid Byte Order " + c1 + c2, "Header"); correct = false; } if (correct) { tiffModel.setByteOrder(byteOrder); data.setByteOrder(byteOrder); try { // read magic number int magic = data.readShort().toInt(); tiffModel.setMagicNumber(magic); } catch (Exception ex) { validation.addErrorLoc("Magic number parsing error", "Header"); correct = false; } } return correct; } /** * Read the IFDs contained in the Tiff file. */ private void readIFDs() { int offset0 = 0; try { // The pointer to the first IFD is located in bytes 4-7 offset0 = data.readLong(4).toInt(); tiffModel.setFirstIFDOffset(offset0); if (offset0 == 0) validation.addErrorLoc("There is no first IFD", "Header"); else if (offset0 > data.size()) validation.addErrorLoc("Incorrect offset", "Header"); } catch (Exception ex) { validation.addErrorLoc("IO exception", "Header"); } if (validation.isCorrect()) { int nifd = 1; try { IfdReader ifd0 = readIFD(offset0, true, 0); HashSet usedOffsets = new HashSet(); usedOffsets.add(offset0); if (ifd0.getIfd() == null) { validation.addErrorLoc("Parsing error in first IFD", "IFD" + 0); } else { IFD iifd = ifd0.getIfd(); iifd.setNextOffset(ifd0.getNextIfdOffset()); tiffModel.addIfd0(iifd); IfdReader current_ifd = ifd0; // Read next IFDs boolean stop = false; while (current_ifd.getNextIfdOffset() > 0 && !stop) { if (usedOffsets.contains(current_ifd.getNextIfdOffset())) { // Circular reference validation.addErrorLoc("IFD offset already used", "IFD" + nifd); stop = true; } else if (current_ifd.getNextIfdOffset() > data.size()) { validation.addErrorLoc("Incorrect offset", "IFD" + nifd); stop = true; } else { usedOffsets.add(current_ifd.getNextIfdOffset()); IfdReader next_ifd = readIFD(current_ifd.getNextIfdOffset(), true, nifd); if (next_ifd == null) { validation.addErrorLoc("Parsing error in IFD " + nifd, "IFD" + nifd); stop = true; } else { iifd = next_ifd.getIfd(); iifd.setNextOffset(next_ifd.getNextIfdOffset()); current_ifd.getIfd().setNextIFD(iifd); current_ifd = next_ifd; } nifd++; } } } } catch (Exception ex) { validation.addErrorLoc("IFD parsing error", "IFD" + nifd); } try { tiffModel.createMetadataDictionary(); } catch (Exception ex) { } } } /** * Parses the image file descriptor data. * * @param offset the file offset (in bytes) pointing to the IFD * @param isImage the is image * @param n the IFD number * @return the ifd reading result */ private IfdReader readIFD(int offset, boolean isImage, int n) { IFD ifd = new IFD(isImage); ifd.setOffset(offset); IfdReader ir = new IfdReader(); ir.setIfd(ifd); int nextIfdOffset = 0; try { if (offset % 2 != 0) { validation.addErrorLoc("Bad word alignment in the offset of the IFD", "IFD" + n); } int index = offset; int directoryEntries = data.readShort(offset).toInt(); if (directoryEntries < 1) { validation.addError("Incorrect number of IFD entries", "IFD" + n, directoryEntries); } else if (directoryEntries > 500) { if (n < 0) { validation.addError("Incorrect number of IFD entries", "SubIFD" + (-n), directoryEntries); } else { validation.addError("Incorrect number of IFD entries", "IFD" + n, directoryEntries); } } else { index += 2; // Reads the tags for (int i = 0; i < directoryEntries; i++) { int tagid = 0; int tagType = -1; int tagN = -1; try { tagid = data.readShort(index).toInt(); tagType = data.readShort(index + 2).toInt(); tagN = data.readLong(index + 4).toInt(); checkType(tagid, tagType, n); TagValue tv = getValue(tagType, tagN, tagid, index + 8, ifd, n); if (ifd.containsTagId(tagid)) { if (duplicateTagTolerance > 0) validation.addWarning("Duplicate tag", "" + tagid, "IFD" + n); else validation.addError("Duplicate tag", "IFD" + n, tagid); } ifd.addTag(tv); } catch (Exception ex) { validation.addErrorLoc("Parse error in tag #" + i + " (" + tagid + ")", "IFD" + n); TagValue tv = new TagValue(tagid, tagType); tv.setReadOffset(index + 8); tv.setReadLength(tagN); ifd.addTag(tv); } index += 12; } // Reads the position of the next IFD nextIfdOffset = 0; try { nextIfdOffset = data.readLong(index).toInt(); } catch (Exception ex) { nextIfdOffset = 0; if (nextIFDTolerance > 0) validation.addWarning("Unreadable next IFD offset", "", "IFD" + n); else validation.addErrorLoc("Unreadable next IFD offset", "IFD" + n); } if (nextIfdOffset > 0 && nextIfdOffset < 7) { validation.addError("Invalid next IFD offset", "IFD" + n, nextIfdOffset); nextIfdOffset = 0; } ir.setNextIfdOffset(nextIfdOffset); ir.readImage(); } } catch (Exception ex) { validation.addErrorLoc("IO Exception", "IFD" + n); ir.setIfd(null); } return ir; } /** * Check tag type. * * @param tagid the tagid * @param tagType the tag type * @param n the n */ private void checkType(int tagid, int tagType, int n) { if (TiffTags.hasTag(tagid) && !TiffTags.getTag(tagid).getName().equals("IPTC")) { boolean found = false; String stagType = TiffTags.getTagTypeName(tagType); if (stagType.equals("SUBIFD")) stagType = "IFD"; if (stagType.equals("UNDEFINED")) stagType = "BYTE"; for (String vType : TiffTags.getTag(tagid).getType()) { String vType2 = vType; if (vType2.equals("UNDEFINED")) vType2 = "BYTE"; if (vType2.equals(stagType)) { found = true; } } if (!found) { validation.addError("Incorrect type for tag " + TiffTags.getTag(tagid).getName(), "IFD" + n, stagType); } } } /** * Gets the value of the given tag field. * * @param tagtype the tag type * @param n the cardinality * @param id the tag id * @param beginOffset the offset position of the tag value * @param parentIFD the parent ifd * @param nifd the ifd number * @return the tag value object */ protected TagValue getValue(int tagtype, int n, int id, int beginOffset, IFD parentIFD, int nifd) { int type = tagtype; if (id == 330 && type != 13) type = 13; // Create TagValue object TagValue tv = new TagValue(id, type); tv.setTagOffset(beginOffset - 8); // Defined tags int offset = beginOffset; // Get type Size int typeSize = TiffTags.getTypeSize(type); boolean ok = true; // Check if the tag value fits in the directory entry value field, and get offset if not if (typeSize * n > tagValueSize) { try { offset = data.readLong(offset).toInt(); if (offset % 2 != 0) { validation.addErrorLoc("Bad word alignment in the offset of tag " + id, "IFD" + n); } } catch (Exception ex) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + n); ok = false; } } tv.setReadOffset(offset); tv.setReadLength(n); if (ok) { try { for (int i = 0; i < n; i++) { // Get N tag values switch (type) { case 1: tv.add(data.readByte(offset)); break; case 2: tv.add(data.readAscii(offset)); break; case 6: tv.add(data.readSByte(offset)); break; case 7: tv.add(data.readUndefined(offset)); break; case 3: tv.add(data.readShort(offset)); break; case 8: tv.add(data.readSShort(offset)); break; case 4: tv.add(data.readLong(offset)); break; case 9: tv.add(data.readSLong(offset)); break; case 5: tv.add(data.readRational(offset)); break; case 10: tv.add(data.readSRational(offset)); break; case 11: tv.add(data.readFloat(offset)); break; case 12: tv.add(data.readDouble(offset)); break; case 13: int ifdOffset = data.readLong(offset).toInt(); if (ifdOffset % 2 != 0) { validation .addErrorLoc("Bad word alignment in the offset of the sub IFD", "IFD" + n); } IfdReader ifd = readIFD(ifdOffset, true, -nifd); IFD subIfd = ifd.getIfd(); subIfd.setParent(parentIFD); parentIFD.setsubIFD(subIfd); tv.add(subIfd); break; } offset += typeSize; } } catch (Exception ex) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); ok = false; } } if (type == 2) { tv.readString(); } if (ok && TiffTags.hasTag(id)) { Tag t = TiffTags.getTag(id); if (t.hasTypedef()) { String tagclass = t.getTypedef(); try { abstractTiffType instanceOfMyClass = (abstractTiffType) Class.forName("com.easyinnova.tiff.model.types." + tagclass) .getConstructor().newInstance(); if (instanceOfMyClass.isIFD()) { long ifdOffset = tv.getFirstNumericValue(); try { if (ifdOffset % 2 != 0) { validation.addErrorLoc("Bad word alignment in the offset of Exif", "IFD" + n); } IfdReader ifd = readIFD((int) ifdOffset, false, -1); IFD exifIfd = ifd.getIfd(); exifIfd.setIsIFD(true); tv.clear(); tv.add(exifIfd); } catch (Exception ex) { validation.addErrorLoc("Parse error in Exif", "IFD" + nifd); } } else { instanceOfMyClass.read(tv); } } catch (ClassNotFoundException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (NoSuchMethodException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (SecurityException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (InstantiationException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (IllegalAccessException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (IllegalArgumentException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (InvocationTargetException e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } catch (Exception e) { validation.addErrorLoc("Parse error getting tag " + id + " value", "IFD" + nifd); } } } return tv; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy