|
TiffEncoder |
|
package ij.io; import java.io.*; /**Saves an image described by a FileInfo object as an uncompressed, big-endian TIFF file.*/ public class TiffEncoder { static final int HDR_SIZE = 8; static final int MAP_SIZE = 768; // in 16-bit words 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 metaDataEntries; private int nSliceLabels; private int extraMetaDataEntries; private int scaleSize; public TiffEncoder (FileInfo fi) { this.fi = fi; fi.intelByteOrder = false; bitsPerSample = 8; samplesPerPixel = 1; nEntries = 9; int bytesPerPixel = 1; int bpsSize = 0; int colorMapSize = 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; bytesPerPixel = 2; break; case FileInfo.GRAY32_FLOAT: bitsPerSample = 32; photoInterp = fi.whiteIsZero?0:1; bytesPerPixel = 4; break; case FileInfo.RGB: photoInterp = 2; samplesPerPixel = 3; bytesPerPixel = 3; bpsSize = BPS_DATA_SIZE; break; case FileInfo.RGB48: bitsPerSample = 16; photoInterp = fi.whiteIsZero?0:1; 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; // XResolution, YResolution and ResolutionUnit if (fi.fileType==fi.GRAY32_FLOAT) nEntries++; // SampleFormat tag makeDescriptionString(); if (description!=null) nEntries++; // ImageDescription tag imageSize = fi.width*fi.height*bytesPerPixel; stackSize = (long)imageSize*fi.nImages; metaDataSize = getMetaDataSize(); if (metaDataSize>0) nEntries += 2; // MetaData & MetaDataCounts 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 + metaDataEntries*4 + metaDataSize; fi.offset = (int)imageOffset; //ij.IJ.log(imageOffset+", "+ifdSize+", "+bpsSize+", "+descriptionSize+", "+scaleSize+", "+colorMapSize+", "+metaDataEntries*4+", "+metaDataSize); } /** Saves the image as a TIFF file. The DataOutputStream is not closed. The fi.pixels field must contain the image data. If fi.nImages>1 then fi.pixels must be a 2D array. The fi.offset field is ignored. */ public void write(DataOutputStream out) throws IOException { writeHeader(out); long nextIFD = 0L; if (fi.nImages>1) nextIFD = imageOffset+stackSize; if (nextIFD+fi.nImages*ifdSize>=0xffffffffL) 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 (fi.fileType==FileInfo.COLOR8) writeColorMap(out); if (metaDataSize>0) writeMetaData(out); new ImageWriter(fi).write(out); if (nextIFD>0L) { for (int i=2; i<=fi.nImages; i++) { if (i==fi.nImages) nextIFD = 0; else nextIFD += ifdSize; imageOffset += imageSize; writeIFD(out, (int)imageOffset, (int)nextIFD); } } } int getMetaDataSize() { //if (stackSize+IMAGE_START>0xffffffffL) return 0; nSliceLabels = 0; metaDataEntries = 0; int size = 0; int nTypes = 0; if (fi.info!=null) { metaDataEntries = 1; size = fi.info.length()*2; nTypes++; } if (fi.nImages>1 && fi.sliceLabels!=null) { int max = fi.sliceLabels.length; for (int i=0; i<fi.nImages&&i<max; i++) { if (fi.sliceLabels[i]!=null&&fi.sliceLabels[i].length()>0) { nSliceLabels++; size += fi.sliceLabels[i].length()*2; } else break; } if (nSliceLabels>0) nTypes++; metaDataEntries += nSliceLabels; } if (fi.metaDataTypes!=null && fi.metaData!=null && fi.metaData[0]!=null && fi.metaDataTypes.length==fi.metaData.length) { extraMetaDataEntries = fi.metaData.length; nTypes += extraMetaDataEntries; metaDataEntries += extraMetaDataEntries; for (int i=0; i<extraMetaDataEntries; i++) size += fi.metaData.length; } if (metaDataEntries>0) metaDataEntries++; // add entry for header int hdrSize = 4 + nTypes*8; if (size>0) size += hdrSize; return size; } /** Writes the 8-byte image file header. */ void writeHeader(DataOutputStream out) throws IOException { byte[] hdr = new byte[8]; hdr[0] = 77; // "MM" (Motorola byte order) hdr[1] = 77; hdr[2] = 0; // 42 (magic number) hdr[3] = 42; hdr[4] = 0; // 8 (offset to first IFD) hdr[5] = 0; hdr[6] = 0; hdr[7] = 8; out.write(hdr); } /** Writes one 12-byte IFD entry. */ void writeEntry(DataOutputStream out, int tag, int fieldType, int count, int value) throws IOException { out.writeShort(tag); out.writeShort(fieldType); out.writeInt(count); if (count==1 && fieldType==TiffDecoder.SHORT) value <<= 16; //left justify 16-bit values out.writeInt(value); // may be an offset } /** Writes one IFD (Image File Directory). */ void writeIFD(DataOutputStream out, int imageOffset, int nextIFD) throws IOException { int tagDataOffset = HDR_SIZE + ifdSize; out.writeShort(nEntries); writeEntry(out, TiffDecoder.NEW_SUBFILE_TYPE, 4, 1, 0); writeEntry(out, TiffDecoder.IMAGE_WIDTH, 3, 1, fi.width); writeEntry(out, TiffDecoder.IMAGE_LENGTH, 3, 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 (fi.fileType==FileInfo.COLOR8) { writeEntry(out, TiffDecoder.COLOR_MAP, 3, MAP_SIZE, tagDataOffset); tagDataOffset += MAP_SIZE*2; } if (metaDataSize>0) { writeEntry(out, TiffDecoder.META_DATA_BYTE_COUNTS, 4, metaDataEntries, tagDataOffset); writeEntry(out, TiffDecoder.META_DATA, 1, metaDataSize, tagDataOffset+4*metaDataEntries); tagDataOffset += metaDataEntries*4 + metaDataSize; } out.writeInt(nextIFD); } /** Writes the 6 bytes of data required by RGB BitsPerSample tag. */ void writeBitsPerPixel(DataOutputStream out) throws IOException { int bitsPerPixel = fi.fileType==FileInfo.RGB48?16:8; out.writeShort(bitsPerPixel); out.writeShort(bitsPerPixel); out.writeShort(bitsPerPixel); } /** Writes the 16 bytes of data required by the XResolution and YResolution tags. */ void writeScale(DataOutputStream out) throws IOException { double xscale = 1.0/fi.pixelWidth; double yscale = 1.0/fi.pixelHeight; double scale = 1000000.0; if (xscale>1000.0) scale = 1000.0; out.writeInt((int)(xscale*scale)); out.writeInt((int)scale); out.writeInt((int)(yscale*scale)); out.writeInt((int)scale); } /** Writes the variable length ImageDescription string. */ void writeDescription(DataOutputStream out) throws IOException { out.write(description,0,description.length); } /** Writes color palette following the image. */ void writeColorMap(DataOutputStream out) throws IOException { byte[] colorTable16 = new byte[MAP_SIZE*2]; int j=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); } /** Writes image metadata ("info" image propery, stack slice labels and extra metadata) following the image and any color palette. */ void writeMetaData(DataOutputStream out) throws IOException { // write byte counts int nTypes = 0; if (fi.info!=null) nTypes++; if (nSliceLabels>0) nTypes++; nTypes += extraMetaDataEntries; out.writeInt(4+nTypes*8); // header size if (fi.info!=null) out.writeInt(fi.info.length()*2); for (int i=0; i<nSliceLabels; i++) out.writeInt(fi.sliceLabels[i].length()*2); for (int i=0; i<extraMetaDataEntries; i++) out.writeInt(fi.metaData[i].length); // write header out.writeInt(0x494a494a); // magic number ("IJIJ") if (fi.info!=null) { out.writeInt(0x696e666f); // type="info" out.writeInt(1); // count } if (nSliceLabels>0) { out.writeInt(0x6c61626c); // type="labl" out.writeInt(nSliceLabels); // count } for (int i=0; i<extraMetaDataEntries; i++) { out.writeInt(fi.metaDataTypes[i]); out.writeInt(1); // count } // write data if (fi.info!=null) out.writeChars(fi.info); for (int i=0; i<nSliceLabels; i++) out.writeChars(fi.sliceLabels[i]); for (int i=0; i<extraMetaDataEntries; i++) out.write(fi.metaData[i]); } /** Creates an optional image description string for saving calibration data. For stacks, also saves the stack size so ImageJ can open the stack without decoding an IFD for each slice.*/ 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; } }
|
TiffEncoder |
|