/* * @(#)TIFFOutputStream.java 1.0 2011-02-27 * * Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package org.monte.media.tiff; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteOrder; import java.util.Stack; import javax.imageio.stream.ImageOutputStream; /** * {@code TIFFOutputStream}. * <p> * References: * <p> * TIFF TM Revision 6.0. Final — June 3, 1992. * Adobe Systems Inc. * http://www.exif.org/specifications.html * * @author Werner Randelshofer * @version 1.0 2011-02-27 Created. */ public class TIFFOutputStream extends OutputStream { private ImageOutputStream out; private long offset; private Stack<IFD> ifdStack=new Stack<IFD>(); private enum State { INITIALIZED, STARTED, FINISHED }; private State state = State.INITIALIZED; private long firstIFDOffset = 8; public TIFFOutputStream(ImageOutputStream out) throws IOException { this.out = out; this.offset = out.getStreamPosition(); } public void setByteOrder(ByteOrder bo) { if (state == State.INITIALIZED && bo != out.getByteOrder()) { throw new IllegalStateException("Can't change byte order within TIFF file"); } out.setByteOrder(bo); } public ByteOrder getByteOrder() { return out.getByteOrder(); } public long getStreamPosition() throws IOException { return out.getStreamPosition() - offset; } public void seek(long position) throws IOException { out.seek(position + offset); } @Override public void write(int b) throws IOException { ensureStarted(); out.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { ensureStarted(); out.write(b, off, len); } public void writeIFD(IFD ifd, long nextIFD) throws IOException { ensureStarted(); writeSHORT(ifd.getCount()); long ifdOffset=getStreamPosition(); long valueOffset=getStreamPosition()+12*ifd.getCount()+4; for (int i=0,n=ifd.getCount();i<n;i++) { IFDEntry entry=ifd.get(i); writeSHORT(entry.getTagNumber()); writeSHORT(entry.getTypeNumber()); if (entry.isDataInValueOffset()) { writeLONG(entry.getValueOffset()); } else { writeLONG(valueOffset); valueOffset+=entry.getLength(); } } writeLONG(nextIFD); for (int i=0,n=ifd.getCount();i<n;i++) { IFDEntry entry=ifd.get(i); if (!entry.isDataInValueOffset()) { write((byte[])entry.getData()); } } } public long getFirstIFDOffset() { return firstIFDOffset; } public void setFirstIFDOffset(long newValue) { firstIFDOffset = newValue; } private void ensureStarted() throws IOException { if (state == State.INITIALIZED) { if (getByteOrder() == ByteOrder.LITTLE_ENDIAN) { writeSHORT(0x4949); // "II" little endian marker } else { writeSHORT(0x4D4D); // "MM" big endian marker } writeSHORT(42); // magic number state = State.STARTED; } } public void finish() throws IOException { ensureStarted(); if (state == State.STARTED) { state = State.FINISHED; long pos = getStreamPosition(); seek(4); writeLONG(firstIFDOffset); seek(pos); } } /** Writes a 32-bit unsigned integer. */ public void writeLONG(long v) throws IOException { out.writeInt((int) v); } /** Writes a 12-bit unsigned integer. */ public void writeSHORT(int v) throws IOException { out.writeShort((short) v); } @Override public void close() throws IOException { finish(); out.close(); } }