//
// TiffWriter.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.out;
import java.io.IOException;
import loci.common.RandomAccessInputStream;
import loci.common.RandomAccessOutputStream;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.FormatWriter;
import loci.formats.ImageTools;
import loci.formats.codec.CompressionType;
import loci.formats.gui.AWTImageTools;
import loci.formats.meta.MetadataRetrieve;
import loci.formats.tiff.IFD;
import loci.formats.tiff.TiffCompression;
import loci.formats.tiff.TiffParser;
import loci.formats.tiff.TiffRational;
import loci.formats.tiff.TiffSaver;
import ome.xml.model.primitives.PositiveFloat;
/**
* TiffWriter is the file format writer for TIFF files.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/out/TiffWriter.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/out/TiffWriter.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class TiffWriter extends FormatWriter {
// -- Constants --
public static final String COMPRESSION_UNCOMPRESSED =
CompressionType.UNCOMPRESSED.getCompression();
public static final String COMPRESSION_LZW =
CompressionType.LZW.getCompression();
public static final String COMPRESSION_J2K =
CompressionType.J2K.getCompression();
public static final String COMPRESSION_J2K_LOSSY =
CompressionType.J2K_LOSSY.getCompression();
public static final String COMPRESSION_JPEG =
CompressionType.JPEG.getCompression();
// -- Fields --
/** Whether or not the output file is a BigTIFF file. */
protected boolean isBigTiff;
/** The TiffSaver that will do most of the writing. */
protected TiffSaver tiffSaver;
/** Input stream to use when overwriting data. */
protected RandomAccessInputStream in;
/** Whether or not to check the parameters passed to saveBytes. */
private boolean checkParams = true;
/**
* Sets the compression code for the specified IFD.
*
* @param ifd The IFD table to handle.
*/
private void formatCompression(IFD ifd)
throws FormatException
{
if (compression == null) compression = "";
TiffCompression compressType = TiffCompression.UNCOMPRESSED;
if (compression.equals(COMPRESSION_LZW)) {
compressType = TiffCompression.LZW;
}
else if (compression.equals(COMPRESSION_J2K)) {
compressType = TiffCompression.JPEG_2000;
}
else if (compression.equals(COMPRESSION_J2K_LOSSY)) {
compressType = TiffCompression.JPEG_2000_LOSSY;
}
else if (compression.equals(COMPRESSION_JPEG)) {
compressType = TiffCompression.JPEG;
}
Object v = ifd.get(new Integer(IFD.COMPRESSION));
if (v == null)
ifd.put(new Integer(IFD.COMPRESSION), compressType.getCode());
}
// -- Constructors --
public TiffWriter() {
this("Tagged Image File Format", new String[] {"tif", "tiff"});
}
public TiffWriter(String format, String[] exts) {
super(format, exts);
compressionTypes = new String[] {
COMPRESSION_UNCOMPRESSED,
COMPRESSION_LZW,
COMPRESSION_J2K,
COMPRESSION_J2K_LOSSY,
COMPRESSION_JPEG
};
isBigTiff = false;
}
// -- IFormatHandler API methods --
/* @see loci.formats.IFormatHandler#setId(String) */
public void setId(String id) throws FormatException, IOException {
super.setId(id);
synchronized (this) {
setupTiffSaver();
}
}
// -- TiffWriter API methods --
/**
* Saves the given image to the specified (possibly already open) file.
* The IFD hashtable allows specification of TIFF parameters such as bit
* depth, compression and units.
*/
public void saveBytes(int no, byte[] buf, IFD ifd)
throws IOException, FormatException
{
MetadataRetrieve r = getMetadataRetrieve();
int w = r.getPixelsSizeX(series).getValue().intValue();
int h = r.getPixelsSizeY(series).getValue().intValue();
saveBytes(no, buf, ifd, 0, 0, w, h);
}
/**
* Saves the given image to the specified series in the current file.
* The IFD hashtable allows specification of TIFF parameters such as bit
* depth, compression and units.
*/
public void saveBytes(int no, byte[] buf, IFD ifd, int x, int y, int w, int h)
throws IOException, FormatException
{
if (checkParams) checkParams(no, buf, x, y, w, h);
if (ifd == null) ifd = new IFD();
MetadataRetrieve retrieve = getMetadataRetrieve();
int type = FormatTools.pixelTypeFromString(
retrieve.getPixelsType(series).toString());
int index = no;
// This operation is synchronized
synchronized (this) {
// This operation is synchronized against the TIFF saver.
synchronized (tiffSaver) {
index = prepareToWriteImage(no, buf, ifd, x, y, w, h);
if (index == -1) {
return;
}
}
}
tiffSaver.writeImage(buf, ifd, index, type, x, y, w, h,
no == getPlaneCount() - 1 && getSeries() == retrieve.getImageCount() - 1);
}
/**
* Performs the preparation for work prior to the usage of the TIFF saver.
* This method is factored out from <code>saveBytes()</code> in an attempt to
* ensure thread safety.
*/
private int prepareToWriteImage(
int no, byte[] buf, IFD ifd, int x, int y, int w, int h)
throws IOException, FormatException {
MetadataRetrieve retrieve = getMetadataRetrieve();
Boolean bigEndian = retrieve.getPixelsBinDataBigEndian(series, 0);
boolean littleEndian = bigEndian == null ?
false : !bigEndian.booleanValue();
// Ensure that no more than one thread manipulated the initialized array
// at one time.
synchronized (this) {
if (no < initialized[series].length && !initialized[series][no]) {
initialized[series][no] = true;
RandomAccessInputStream tmp = new RandomAccessInputStream(currentId);
if (tmp.length() == 0) {
synchronized (this) {
// write TIFF header
tiffSaver.writeHeader();
}
}
tmp.close();
}
}
int c = getSamplesPerPixel();
int type = FormatTools.pixelTypeFromString(
retrieve.getPixelsType(series).toString());
int bytesPerPixel = FormatTools.getBytesPerPixel(type);
int blockSize = w * h * c * bytesPerPixel;
if (blockSize > buf.length) {
c = buf.length / (w * h * bytesPerPixel);
}
if (bytesPerPixel > 1 && c != 1 && c != 3) {
// split channels
checkParams = false;
if (no == 0) {
initialized[series] = new boolean[initialized[series].length * c];
}
for (int i=0; i<c; i++) {
byte[] b = ImageTools.splitChannels(buf, i, c, bytesPerPixel,
false, interleaved);
saveBytes(no * c + i, b, (IFD) ifd.clone(), x, y, w, h);
}
checkParams = true;
return -1;
}
formatCompression(ifd);
byte[][] lut = AWTImageTools.get8BitLookupTable(cm);
if (lut != null) {
int[] colorMap = new int[lut.length * lut[0].length];
for (int i=0; i<lut.length; i++) {
for (int j=0; j<lut[0].length; j++) {
colorMap[i * lut[0].length + j] = (int) ((lut[i][j] & 0xff) << 8);
}
}
ifd.putIFDValue(IFD.COLOR_MAP, colorMap);
}
int width = retrieve.getPixelsSizeX(series).getValue().intValue();
int height = retrieve.getPixelsSizeY(series).getValue().intValue();
ifd.put(new Integer(IFD.IMAGE_WIDTH), new Integer(width));
ifd.put(new Integer(IFD.IMAGE_LENGTH), new Integer(height));
PositiveFloat px = retrieve.getPixelsPhysicalSizeX(series);
Double physicalSizeX = px == null ? null : px.getValue();
if (physicalSizeX == null || physicalSizeX.doubleValue() == 0) {
physicalSizeX = 0d;
}
else physicalSizeX = 1d / physicalSizeX;
PositiveFloat py = retrieve.getPixelsPhysicalSizeY(series);
Double physicalSizeY = py == null ? null : py.getValue();
if (physicalSizeY == null || physicalSizeY.doubleValue() == 0) {
physicalSizeY = 0d;
}
else physicalSizeY = 1d / physicalSizeY;
ifd.put(IFD.RESOLUTION_UNIT, 3);
ifd.put(IFD.X_RESOLUTION,
new TiffRational((long) (physicalSizeX * 1000 * 10000), 1000));
ifd.put(IFD.Y_RESOLUTION,
new TiffRational((long) (physicalSizeY * 1000 * 10000), 1000));
if (!isBigTiff) {
isBigTiff = (out.length() + 2
* (width * height * c * bytesPerPixel)) >= 4294967296L;
if (isBigTiff) {
throw new FormatException("File is too large; call setBigTiff(true)");
}
}
// write the image
ifd.put(new Integer(IFD.LITTLE_ENDIAN), new Boolean(littleEndian));
if (!ifd.containsKey(IFD.REUSE)) {
ifd.put(IFD.REUSE, out.length());
out.seek(out.length());
}
else {
out.seek((Long) ifd.get(IFD.REUSE));
}
ifd.putIFDValue(IFD.PLANAR_CONFIGURATION,
interleaved || getSamplesPerPixel() == 1 ? 1 : 2);
int sampleFormat = 1;
if (FormatTools.isSigned(type)) sampleFormat = 2;
if (FormatTools.isFloatingPoint(type)) sampleFormat = 3;
ifd.putIFDValue(IFD.SAMPLE_FORMAT, sampleFormat);
int index = no;
int realSeries = getSeries();
for (int i=0; i<realSeries; i++) {
setSeries(i);
index += getPlaneCount();
}
setSeries(realSeries);
return index;
}
// -- FormatWriter API methods --
/* (non-Javadoc)
* @see loci.formats.FormatWriter#close()
*/
@Override
public void close() throws IOException {
super.close();
if (in != null) {
in.close();
}
}
/* @see loci.formats.FormatWriter#getPlaneCount() */
public int getPlaneCount() {
MetadataRetrieve retrieve = getMetadataRetrieve();
int c = getSamplesPerPixel();
int type = FormatTools.pixelTypeFromString(
retrieve.getPixelsType(series).toString());
int bytesPerPixel = FormatTools.getBytesPerPixel(type);
if (bytesPerPixel > 1 && c != 1 && c != 3) {
return super.getPlaneCount() * c;
}
return super.getPlaneCount();
}
// -- IFormatWriter API methods --
/**
* @see loci.formats.IFormatWriter#saveBytes(int, byte[], int, int, int, int)
*/
public void saveBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
IFD ifd = new IFD();
if (!sequential) {
TiffParser parser = new TiffParser(currentId);
try {
long[] ifdOffsets = parser.getIFDOffsets();
if (no < ifdOffsets.length) {
ifd = parser.getIFD(ifdOffsets[no]);
}
}
finally {
RandomAccessInputStream tiffParserStream = parser.getStream();
if (tiffParserStream != null) {
tiffParserStream.close();
}
}
}
saveBytes(no, buf, ifd, x, y, w, h);
}
/* @see loci.formats.IFormatWriter#canDoStacks(String) */
public boolean canDoStacks() { return true; }
/* @see loci.formats.IFormatWriter#getPixelTypes(String) */
public int[] getPixelTypes(String codec) {
if (codec != null && (codec.startsWith(COMPRESSION_J2K) ||
codec.equals(COMPRESSION_JPEG)))
{
return new int[] {FormatTools.INT8, FormatTools.UINT8,
FormatTools.INT16, FormatTools.UINT16};
}
return new int[] {FormatTools.INT8, FormatTools.UINT8, FormatTools.INT16,
FormatTools.UINT16, FormatTools.INT32, FormatTools.UINT32,
FormatTools.FLOAT, FormatTools.DOUBLE};
}
// -- TiffWriter API methods --
/**
* Sets whether or not BigTIFF files should be written.
* This flag is not reset when close() is called.
*/
public void setBigTiff(boolean bigTiff) {
FormatTools.assertId(currentId, false, 1);
isBigTiff = bigTiff;
}
// -- Helper methods --
private void setupTiffSaver() throws IOException {
out.close();
out = new RandomAccessOutputStream(currentId);
tiffSaver = new TiffSaver(out, currentId);
MetadataRetrieve retrieve = getMetadataRetrieve();
Boolean bigEndian = retrieve.getPixelsBinDataBigEndian(series, 0);
boolean littleEndian = bigEndian == null ?
false : !bigEndian.booleanValue();
tiffSaver.setWritingSequentially(sequential);
tiffSaver.setLittleEndian(littleEndian);
tiffSaver.setBigTiff(isBigTiff);
tiffSaver.setCodecOptions(options);
}
}