blob: f9f5ec18b0785b00712b512f5c6687916f75b9ba [file] [log] [blame]
/*
* Copyright (c) 2014-2021 by Wen Yu
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* or any later version.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
*
* Change History - most recent changes go on top of previous changes
*
* IFD.java
*
* Who Date Description
* ==== ========= =======================================================================
* WY 15Dec2014 Added removeChild() method
* WY 24Nov2014 Added getChild() method
* WY 02Apr2014 Added setNextIFDOffset() to work with the case of non-contiguous IFDs
* WY 30Mar2014 Added children map, changed write() method to write child nodes as well.
*/
package pixy.image.tiff;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import pixy.io.RandomAccessOutputStream;
import pixy.string.StringUtils;
/**
* Image File Directory
*
* @author Wen Yu, yuwen_66@yahoo.com
* @version 1.0 01/04/2013
*/
public final class IFD {
/**
* Create a children map for sub IFDs. A sub IFD is associated with a tag of the current IFD
* which serves as pointer to the sub IFD.
*/
private Map<Tag, IFD> children = new HashMap<Tag, IFD>();
/** Create a fields map to hold all of the fields for this IFD */
private Map<Short, TiffField<?>> tiffFields = new HashMap<Short, TiffField<?>>();
private int endOffset;
private int startOffset;
public IFD() {}
// Copy constructor
public IFD(IFD other) {
// Defensive copy
this.children = Collections.unmodifiableMap(other.children);
this.tiffFields = Collections.unmodifiableMap(other.tiffFields);
this.startOffset = other.startOffset;
this.endOffset = other.endOffset;
}
public void addChild(Tag tag, IFD child) {
children.put(tag, child);
}
public void addField(TiffField<?> tiffField) {
tiffFields.put(tiffField.getTag(), tiffField);
}
public void addFields(Collection<TiffField<?>> tiffFields) {
for(TiffField<?> field : tiffFields) {
addField(field);
}
}
public IFD getChild(Tag tag) {
return children.get(tag);
}
public Map<Tag, IFD> getChildren() {
return Collections.unmodifiableMap(children);
}
public int getEndOffset() {
return endOffset;
}
public TiffField<?> getField(Tag tag) {
return tiffFields.get(tag.getValue());
}
/**
* Return a String representation of the field
* @param tag Tag for the field
* @return a String representation of the field
*/
public String getFieldAsString(Tag tag) {
TiffField<?> field = tiffFields.get(tag.getValue());
if(field != null) {
FieldType ftype = field.getType();
String suffix = null;
if(ftype == FieldType.SHORT || ftype == FieldType.SSHORT)
suffix = tag.getFieldAsString(field.getDataAsLong());
else
suffix = tag.getFieldAsString(field.getData());
return field.getDataAsString() + (StringUtils.isNullOrEmpty(suffix)?"":" => " + suffix);
}
return "";
}
/** Get all the fields for this IFD from the internal map. */
public Collection<TiffField<?>> getFields() {
return Collections.unmodifiableCollection(tiffFields.values());
}
public int getSize() {
return tiffFields.size();
}
public int getStartOffset() {
return startOffset;
}
/** Remove all the entries from the IDF fields map */
public void removeAllFields() {
tiffFields.clear();
}
public IFD removeChild(Tag tag) {
return children.remove(tag);
}
/** Remove a specific field associated with the given tag */
public TiffField<?> removeField(Tag tag) {
return tiffFields.remove(tag.getValue());
}
/**
* Set the next IFD offset pointer
* <p>
* Note: This should <em>ONLY</em> be called
* after the current IFD has been written to the RandomAccessOutputStream
*
* @param os RandomAccessOutputStream
* @param nextOffset next IFD offset value
* @throws IOException
*/
public void setNextIFDOffset(RandomAccessOutputStream os, int nextOffset) throws IOException {
os.seek(endOffset - 4);
os.writeInt(nextOffset);
}
/** Write this IFD and all the children, if any, to the output stream
*
* @param os RandomAccessOutputStream
* @param offset stream offset to write this IFD
*
* @throws IOException
*/
public int write(RandomAccessOutputStream os, int offset) throws IOException {
startOffset = offset;
// Write this IFD and its children, if any, to the RandomAccessOutputStream
List<TiffField<?>> list = new ArrayList<TiffField<?>>(tiffFields.values());
// Make sure tiffFields are in incremental order.
Collections.sort(list);
os.seek(offset);
os.writeShort(list.size());
offset += 2;
endOffset = offset + list.size() * 12 + 4;
// The first available offset to write tiffFields.
int toOffset = endOffset;
os.seek(offset); // Set first field offset.
for (TiffField<?> tiffField : list)
{
toOffset = tiffField.write(os, toOffset);
offset += 12; // Move to next field. Each field is of fixed length 12.
os.seek(offset); // Reset position to next directory field.
}
/* Set the stream position at the end of the IFD to update
* next IFD offset
*/
os.seek(offset);
os.writeInt(0); // Set next IFD offset to default 0
// Write sub IFDs if any (we assume bare-bone sub IFDs pointed by long field type with no image data associated)
if(children.size() > 0) {
for (Map.Entry<Tag, IFD> entry : children.entrySet()) {
Tag key = entry.getKey();
IFD value = entry.getValue();
// Update parent field if present, otherwise skip
TiffField<?> tiffField = this.getField(key);
if(tiffField != null) {
int dataPos = tiffField.getDataOffset();
os.seek(dataPos);
os.writeInt(toOffset);
os.seek(toOffset);
toOffset = value.write(os, toOffset);
}
}
}
return toOffset;
}
}