package ij.io;
import java.io.*;
public class TiffEncoder {
static final int HDR_SIZE = 8;
static final int MAP_SIZE = 768; static final int BPS_DATA_SIZE = 6;
static final int SCALE_DATA_SIZE = 16;
private FileInfo fi;
private int bitsPerSample;
private int photoInterp;
private int samplesPerPixel;
private int nEntries;
private int ifdSize;
private long imageOffset;
private int imageSize;
private long stackSize;
private byte[] description;
private int metaDataSize;
private int nMetaDataTypes;
private int nMetaDataEntries;
private int nSliceLabels;
private int extraMetaDataEntries;
private int scaleSize;
private boolean littleEndian = ij.Prefs.intelByteOrder;
private byte buffer[] = new byte[8];
private int colorMapSize = 0;
public TiffEncoder (FileInfo fi) {
this.fi = fi;
fi.intelByteOrder = littleEndian;
bitsPerSample = 8;
samplesPerPixel = 1;
nEntries = 9;
int bytesPerPixel = 1;
int bpsSize = 0;
switch (fi.fileType) {
case FileInfo.GRAY8:
photoInterp = fi.whiteIsZero?0:1;
break;
case FileInfo.GRAY16_UNSIGNED:
case FileInfo.GRAY16_SIGNED:
bitsPerSample = 16;
photoInterp = fi.whiteIsZero?0:1;
if (fi.lutSize>0) {
nEntries = 10;
colorMapSize = MAP_SIZE*2;
}
bytesPerPixel = 2;
break;
case FileInfo.GRAY32_FLOAT:
bitsPerSample = 32;
photoInterp = fi.whiteIsZero?0:1;
if (fi.lutSize>0) {
nEntries = 10;
colorMapSize = MAP_SIZE*2;
}
bytesPerPixel = 4;
break;
case FileInfo.RGB:
photoInterp = 2;
samplesPerPixel = 3;
bytesPerPixel = 3;
bpsSize = BPS_DATA_SIZE;
break;
case FileInfo.RGB48:
bitsPerSample = 16;
photoInterp = 2;
samplesPerPixel = 3;
bytesPerPixel = 6;
fi.nImages /= 3;
bpsSize = BPS_DATA_SIZE;
break;
case FileInfo.COLOR8:
photoInterp = 3;
nEntries = 10;
colorMapSize = MAP_SIZE*2;
break;
default:
photoInterp = 0;
}
if (fi.unit!=null && fi.pixelWidth!=0 && fi.pixelHeight!=0)
nEntries += 3; if (fi.fileType==fi.GRAY32_FLOAT)
nEntries++; makeDescriptionString();
if (description!=null)
nEntries++; long size = (long)fi.width*fi.height*bytesPerPixel;
imageSize = size<=0xffffffffL?(int)size:0;
stackSize = (long)imageSize*fi.nImages;
metaDataSize = getMetaDataSize();
if (metaDataSize>0)
nEntries += 2; ifdSize = 2 + nEntries*12 + 4;
int descriptionSize = description!=null?description.length:0;
scaleSize = fi.unit!=null && fi.pixelWidth!=0 && fi.pixelHeight!=0?SCALE_DATA_SIZE:0;
imageOffset = HDR_SIZE+ifdSize+bpsSize+descriptionSize+scaleSize+colorMapSize + nMetaDataEntries*4 + metaDataSize;
fi.offset = (int)imageOffset;
}
public void write(OutputStream out) throws IOException {
writeHeader(out);
long nextIFD = 0L;
if (fi.nImages>1)
nextIFD = imageOffset+stackSize;
boolean bigTiff = nextIFD+fi.nImages*ifdSize>=0xffffffffL;
if (bigTiff)
nextIFD = 0L;
writeIFD(out, (int)imageOffset, (int)nextIFD);
if (fi.fileType==FileInfo.RGB||fi.fileType==FileInfo.RGB48)
writeBitsPerPixel(out);
if (description!=null)
writeDescription(out);
if (scaleSize>0)
writeScale(out);
if (colorMapSize>0)
writeColorMap(out);
if (metaDataSize>0)
writeMetaData(out);
new ImageWriter(fi).write(out);
if (nextIFD>0L) {
int ifdSize2 = ifdSize;
if (metaDataSize>0) {
metaDataSize = 0;
nEntries -= 2;
ifdSize2 -= 2*12;
}
for (int i=2; i<=fi.nImages; i++) {
if (i==fi.nImages)
nextIFD = 0;
else
nextIFD += ifdSize2;
imageOffset += imageSize;
writeIFD(out, (int)imageOffset, (int)nextIFD);
}
} else if (bigTiff)
ij.IJ.log("Stack is larger than 4GB. Most TIFF readers will only open the first image. Use this information to open as raw:\n"+fi);
}
public void write(DataOutputStream out) throws IOException {
write((OutputStream)out);
}
int getMetaDataSize() {
nSliceLabels = 0;
nMetaDataEntries = 0;
int size = 0;
int nTypes = 0;
if (fi.info!=null && fi.info.length()>0) {
nMetaDataEntries = 1;
size = fi.info.length()*2;
nTypes++;
}
if (fi.sliceLabels!=null) {
int max = Math.min(fi.sliceLabels.length, fi.nImages);
boolean isNonNullLabel = false;
for (int i=0; i<max; i++) {
if (fi.sliceLabels[i]!=null && fi.sliceLabels[i].length()>0) {
isNonNullLabel = true;
break;
}
}
if (isNonNullLabel) {
for (int i=0; i<max; i++) {
nSliceLabels++;
if (fi.sliceLabels[i]!=null)
size += fi.sliceLabels[i].length()*2;
}
if (nSliceLabels>0) nTypes++;
nMetaDataEntries += nSliceLabels;
}
}
if (fi.displayRanges!=null) {
nMetaDataEntries++;
size += fi.displayRanges.length*8;
nTypes++;
}
if (fi.channelLuts!=null) {
for (int i=0; i<fi.channelLuts.length; i++) {
if (fi.channelLuts[i]!=null)
size += fi.channelLuts[i].length;
}
nTypes++;
nMetaDataEntries += fi.channelLuts.length;
}
if (fi.plot!=null) {
nMetaDataEntries++;
size += fi.plot.length;
nTypes++;
}
if (fi.roi!=null) {
nMetaDataEntries++;
size += fi.roi.length;
nTypes++;
}
if (fi.overlay!=null) {
for (int i=0; i<fi.overlay.length; i++) {
if (fi.overlay[i]!=null)
size += fi.overlay[i].length;
}
nTypes++;
nMetaDataEntries += fi.overlay.length;
}
if (fi.metaDataTypes!=null && fi.metaData!=null && fi.metaData[0]!=null
&& fi.metaDataTypes.length==fi.metaData.length) {
extraMetaDataEntries = fi.metaData.length;
nTypes += extraMetaDataEntries;
nMetaDataEntries += extraMetaDataEntries;
for (int i=0; i<extraMetaDataEntries; i++) {
if (fi.metaData[i]!=null)
size += fi.metaData[i].length;
}
}
if (nMetaDataEntries>0) nMetaDataEntries++; int hdrSize = 4 + nTypes*8;
if (size>0) size += hdrSize;
nMetaDataTypes = nTypes;
return size;
}
void writeHeader(OutputStream out) throws IOException {
byte[] hdr = new byte[8];
if (littleEndian) {
hdr[0] = 73; hdr[1] = 73;
hdr[2] = 42; hdr[3] = 0;
hdr[4] = 8; hdr[5] = 0;
hdr[6] = 0;
hdr[7] = 0;
} else {
hdr[0] = 77; hdr[1] = 77;
hdr[2] = 0; hdr[3] = 42;
hdr[4] = 0; hdr[5] = 0;
hdr[6] = 0;
hdr[7] = 8;
}
out.write(hdr);
}
void writeEntry(OutputStream out, int tag, int fieldType, int count, int value) throws IOException {
writeShort(out, tag);
writeShort(out, fieldType);
writeInt(out, count);
if (count==1 && fieldType==TiffDecoder.SHORT) {
writeShort(out, value);
writeShort(out, 0);
} else
writeInt(out, value); }
void writeIFD(OutputStream out, int imageOffset, int nextIFD) throws IOException {
int tagDataOffset = HDR_SIZE + ifdSize;
writeShort(out, nEntries);
writeEntry(out, TiffDecoder.NEW_SUBFILE_TYPE, 4, 1, 0);
writeEntry(out, TiffDecoder.IMAGE_WIDTH, 4, 1, fi.width);
writeEntry(out, TiffDecoder.IMAGE_LENGTH, 4, 1, fi.height);
if (fi.fileType==FileInfo.RGB||fi.fileType==FileInfo.RGB48) {
writeEntry(out, TiffDecoder.BITS_PER_SAMPLE, 3, 3, tagDataOffset);
tagDataOffset += BPS_DATA_SIZE;
} else
writeEntry(out, TiffDecoder.BITS_PER_SAMPLE, 3, 1, bitsPerSample);
writeEntry(out, TiffDecoder.PHOTO_INTERP, 3, 1, photoInterp);
if (description!=null) {
writeEntry(out, TiffDecoder.IMAGE_DESCRIPTION, 2, description.length, tagDataOffset);
tagDataOffset += description.length;
}
writeEntry(out, TiffDecoder.STRIP_OFFSETS, 4, 1, imageOffset);
writeEntry(out, TiffDecoder.SAMPLES_PER_PIXEL,3, 1, samplesPerPixel);
writeEntry(out, TiffDecoder.ROWS_PER_STRIP, 3, 1, fi.height);
writeEntry(out, TiffDecoder.STRIP_BYTE_COUNT, 4, 1, imageSize);
if (fi.unit!=null && fi.pixelWidth!=0 && fi.pixelHeight!=0) {
writeEntry(out, TiffDecoder.X_RESOLUTION, 5, 1, tagDataOffset);
writeEntry(out, TiffDecoder.Y_RESOLUTION, 5, 1, tagDataOffset+8);
tagDataOffset += SCALE_DATA_SIZE;
int unit = 1;
if (fi.unit.equals("inch"))
unit = 2;
else if (fi.unit.equals("cm"))
unit = 3;
writeEntry(out, TiffDecoder.RESOLUTION_UNIT, 3, 1, unit);
}
if (fi.fileType==fi.GRAY32_FLOAT) {
int format = TiffDecoder.FLOATING_POINT;
writeEntry(out, TiffDecoder.SAMPLE_FORMAT, 3, 1, format);
}
if (colorMapSize>0) {
writeEntry(out, TiffDecoder.COLOR_MAP, 3, MAP_SIZE, tagDataOffset);
tagDataOffset += MAP_SIZE*2;
}
if (metaDataSize>0) {
writeEntry(out, TiffDecoder.META_DATA_BYTE_COUNTS, 4, nMetaDataEntries, tagDataOffset);
writeEntry(out, TiffDecoder.META_DATA, 1, metaDataSize, tagDataOffset+4*nMetaDataEntries);
tagDataOffset += nMetaDataEntries*4 + metaDataSize;
}
writeInt(out, nextIFD);
}
void writeBitsPerPixel(OutputStream out) throws IOException {
int bitsPerPixel = fi.fileType==FileInfo.RGB48?16:8;
writeShort(out, bitsPerPixel);
writeShort(out, bitsPerPixel);
writeShort(out, bitsPerPixel);
}
void writeScale(OutputStream out) throws IOException {
double xscale = 1.0/fi.pixelWidth;
double yscale = 1.0/fi.pixelHeight;
double scale = 1000000.0;
if (xscale*scale>Integer.MAX_VALUE||yscale*scale>Integer.MAX_VALUE)
scale = (int)(Integer.MAX_VALUE/Math.max(xscale,yscale));
writeInt(out, (int)(xscale*scale));
writeInt(out, (int)scale);
writeInt(out, (int)(yscale*scale));
writeInt(out, (int)scale);
}
void writeDescription(OutputStream out) throws IOException {
out.write(description,0,description.length);
}
void writeColorMap(OutputStream out) throws IOException {
byte[] colorTable16 = new byte[MAP_SIZE*2];
int j=littleEndian?1:0;
for (int i=0; i<fi.lutSize; i++) {
colorTable16[j] = fi.reds[i];
colorTable16[512+j] = fi.greens[i];
colorTable16[1024+j] = fi.blues[i];
j += 2;
}
out.write(colorTable16);
}
void writeMetaData(OutputStream out) throws IOException {
writeInt(out, 4+nMetaDataTypes*8); if (fi.info!=null && fi.info.length()>0)
writeInt(out, fi.info.length()*2);
for (int i=0; i<nSliceLabels; i++) {
if (fi.sliceLabels[i]==null)
writeInt(out, 0);
else
writeInt(out, fi.sliceLabels[i].length()*2);
}
if (fi.displayRanges!=null)
writeInt(out, fi.displayRanges.length*8);
if (fi.channelLuts!=null) {
for (int i=0; i<fi.channelLuts.length; i++)
writeInt(out, fi.channelLuts[i].length);
}
if (fi.plot!=null)
writeInt(out, fi.plot.length);
if (fi.roi!=null)
writeInt(out, fi.roi.length);
if (fi.overlay!=null) {
for (int i=0; i<fi.overlay.length; i++)
writeInt(out, fi.overlay[i].length);
}
for (int i=0; i<extraMetaDataEntries; i++)
writeInt(out, fi.metaData[i].length);
writeInt(out, TiffDecoder.MAGIC_NUMBER); if (fi.info!=null) {
writeInt(out, TiffDecoder.INFO); writeInt(out, 1); }
if (nSliceLabels>0) {
writeInt(out, TiffDecoder.LABELS); writeInt(out, nSliceLabels); }
if (fi.displayRanges!=null) {
writeInt(out, TiffDecoder.RANGES); writeInt(out, 1); }
if (fi.channelLuts!=null) {
writeInt(out, TiffDecoder.LUTS); writeInt(out, fi.channelLuts.length); }
if (fi.plot!=null) {
writeInt(out, TiffDecoder.PLOT); writeInt(out, 1); }
if (fi.roi!=null) {
writeInt(out, TiffDecoder.ROI); writeInt(out, 1); }
if (fi.overlay!=null) {
writeInt(out, TiffDecoder.OVERLAY); writeInt(out, fi.overlay.length); }
for (int i=0; i<extraMetaDataEntries; i++) {
writeInt(out, fi.metaDataTypes[i]);
writeInt(out, 1); }
if (fi.info!=null)
writeChars(out, fi.info);
for (int i=0; i<nSliceLabels; i++) {
if (fi.sliceLabels[i]!=null)
writeChars(out, fi.sliceLabels[i]);
}
if (fi.displayRanges!=null) {
for (int i=0; i<fi.displayRanges.length; i++)
writeDouble(out, fi.displayRanges[i]);
}
if (fi.channelLuts!=null) {
for (int i=0; i<fi.channelLuts.length; i++)
out.write(fi.channelLuts[i]);
}
if (fi.plot!=null)
out.write(fi.plot);
if (fi.roi!=null)
out.write(fi.roi);
if (fi.overlay!=null) {
for (int i=0; i<fi.overlay.length; i++)
out.write(fi.overlay[i]);
}
for (int i=0; i<extraMetaDataEntries; i++)
out.write(fi.metaData[i]);
}
void makeDescriptionString() {
if (fi.description!=null) {
if (fi.description.charAt(fi.description.length()-1)!=(char)0)
fi.description += " ";
description = fi.description.getBytes();
description[description.length-1] = (byte)0;
} else
description = null;
}
final void writeShort(OutputStream out, int v) throws IOException {
if (littleEndian) {
out.write(v&255);
out.write((v>>>8)&255);
} else {
out.write((v>>>8)&255);
out.write(v&255);
}
}
final void writeInt(OutputStream out, int v) throws IOException {
if (littleEndian) {
out.write(v&255);
out.write((v>>>8)&255);
out.write((v>>>16)&255);
out.write((v>>>24)&255);
} else {
out.write((v>>>24)&255);
out.write((v>>>16)&255);
out.write((v>>>8)&255);
out.write(v&255);
}
}
final void writeLong(OutputStream out, long v) throws IOException {
if (littleEndian) {
buffer[7] = (byte)(v>>>56);
buffer[6] = (byte)(v>>>48);
buffer[5] = (byte)(v>>>40);
buffer[4] = (byte)(v>>>32);
buffer[3] = (byte)(v>>>24);
buffer[2] = (byte)(v>>>16);
buffer[1] = (byte)(v>>> 8);
buffer[0] = (byte)v;
out.write(buffer, 0, 8);
} else {
buffer[0] = (byte)(v>>>56);
buffer[1] = (byte)(v>>>48);
buffer[2] = (byte)(v>>>40);
buffer[3] = (byte)(v>>>32);
buffer[4] = (byte)(v>>>24);
buffer[5] = (byte)(v>>>16);
buffer[6] = (byte)(v>>> 8);
buffer[7] = (byte)v;
out.write(buffer, 0, 8);
}
}
final void writeDouble(OutputStream out, double v) throws IOException {
writeLong(out, Double.doubleToLongBits(v));
}
final void writeChars(OutputStream out, String s) throws IOException {
int len = s.length();
if (littleEndian) {
for (int i = 0 ; i < len ; i++) {
int v = s.charAt(i);
out.write(v&255);
out.write((v>>>8)&255);
}
} else {
for (int i = 0 ; i < len ; i++) {
int v = s.charAt(i);
out.write((v>>>8)&255);
out.write(v&255);
}
}
}
}