ij.io.TiffEncoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ij Show documentation
Show all versions of ij Show documentation
ImageJ is an open source Java image processing program inspired by NIH Image for the Macintosh.
package ij.io;
import java.io.*;
/**Saves an image described by a FileInfo object as an uncompressed 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 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; // XResolution, YResolution and ResolutionUnit
if (fi.fileType==fi.GRAY32_FLOAT)
nEntries++; // SampleFormat tag
makeDescriptionString();
if (description!=null)
nEntries++; // ImageDescription tag
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; // 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 + nMetaDataEntries*4 + metaDataSize;
fi.offset = (int)imageOffset;
//ij.IJ.log(imageOffset+", "+ifdSize+", "+bpsSize+", "+descriptionSize+", "+scaleSize+", "+colorMapSize+", "+nMetaDataEntries*4+", "+metaDataSize);
}
/** Saves the image as a TIFF file. The OutputStream 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(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; i0) {
isNonNullLabel = true;
break;
}
}
if (isNonNullLabel) {
for (int i=0; i0) nTypes++;
nMetaDataEntries += nSliceLabels;
}
}
if (fi.displayRanges!=null) {
nMetaDataEntries++;
size += fi.displayRanges.length*8;
nTypes++;
}
if (fi.channelLuts!=null) {
for (int i=0; i0) nMetaDataEntries++; // add entry for header
int hdrSize = 4 + nTypes*8;
if (size>0) size += hdrSize;
nMetaDataTypes = nTypes;
return size;
}
/** Writes the 8-byte image file header. */
void writeHeader(OutputStream out) throws IOException {
byte[] hdr = new byte[8];
if (littleEndian) {
hdr[0] = 73; // "II" (Intel byte order)
hdr[1] = 73;
hdr[2] = 42; // 42 (magic number)
hdr[3] = 0;
hdr[4] = 8; // 8 (offset to first IFD)
hdr[5] = 0;
hdr[6] = 0;
hdr[7] = 0;
} else {
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(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); // may be an offset
}
/** Writes one IFD (Image File Directory). */
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);
}
/** Writes the 6 bytes of data required by RGB BitsPerSample tag. */
void writeBitsPerPixel(OutputStream out) throws IOException {
int bitsPerPixel = fi.fileType==FileInfo.RGB48?16:8;
writeShort(out, bitsPerPixel);
writeShort(out, bitsPerPixel);
writeShort(out, bitsPerPixel);
}
/** Writes the 16 bytes of data required by the XResolution and YResolution tags. */
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);
}
/** Writes the variable length ImageDescription string. */
void writeDescription(OutputStream out) throws IOException {
out.write(description,0,description.length);
}
/** Writes color palette following the image. */
void writeColorMap(OutputStream out) throws IOException {
byte[] colorTable16 = new byte[MAP_SIZE*2];
int j=littleEndian?1:0;
for (int i=0; i0)
writeInt(out, fi.info.length()*2);
for (int i=0; i0) {
writeInt(out, TiffDecoder.LABELS); // type="labl"
writeInt(out, nSliceLabels); // count
}
if (fi.displayRanges!=null) {
writeInt(out, TiffDecoder.RANGES); // type="rang"
writeInt(out, 1); // count
}
if (fi.channelLuts!=null) {
writeInt(out, TiffDecoder.LUTS); // type="luts"
writeInt(out, fi.channelLuts.length); // count
}
if (fi.plot!=null) {
writeInt(out, TiffDecoder.PLOT); // type="plot"
writeInt(out, 1); // count
}
if (fi.roi!=null) {
writeInt(out, TiffDecoder.ROI); // type="roi "
writeInt(out, 1); // count
}
if (fi.overlay!=null) {
writeInt(out, TiffDecoder.OVERLAY); // type="over"
writeInt(out, fi.overlay.length); // count
}
if (fi.properties!=null) {
writeInt(out, TiffDecoder.PROPERTIES); // type="prop"
writeInt(out, fi.properties.length); // count
}
for (int i=0; i>>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);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy