/*
* org.openmicroscopy.shoola.util.image.io.TIFFEncoder
*
*------------------------------------------------------------------------------
* Copyright (C) 2006 University of Dundee. All rights reserved.
*
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.image.io;
//Java imports
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.io.DataOutputStream;
import java.io.IOException;
//Third-party libraries
//Application-internal dependencies
/**
* Saves a buffered image as an uncompressed, big-Endian TIFF.
* We use the band interleaving model to retrieve the pixel value.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author <br>Andrea Falconi
* <a href="mailto:a.falconi@dundee.ac.uk">
* a.falconi@dundee.ac.uk</a>
* @version 2.2
* <small>
* (<b>Internal version:</b> $Revision$ $Date$)
* </small>
* @since OME2.2
*/
public class TIFFEncoder
extends Encoder
{
/** The number of bits per sample. */
private int bitsPerSample;
/** The number of samples per pixel. */
private int samplesPerPixel;
/** The number of entries. */
private int nEntries;
/**
* The space for the photometric interpretation, default value is
* <code>1</code>.
*/
private int photoInterp;
/** The space of the <code>image File Directory</code>. */
private int ifdSize;
/** The value identifying the image's size. */
private int imageSize;
/**
* Writes the 6 bytes of data required by the RGB BitsPerSample tag.
*
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeBitsPerPixel()
throws IOException
{
output.writeShort(8);
output.writeShort(8);
output.writeShort(8);
}
/**
* Writes one Image File Directory.
*
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeIFD()
throws IOException
{
int tagDataOffset = TIFFEncoderCst.HDR_SIZE + ifdSize;
output.writeShort(nEntries);
writeEntry(TIFFEncoderCst.NEW_SUBFILE_TYPE, 4, 1, 0);
writeEntry(TIFFEncoderCst.IMAGE_WIDTH, 3, 1, imageWidth);
writeEntry(TIFFEncoderCst.IMAGE_LENGTH, 3, 1, imageHeight);
if (colorType == ColorSpace.TYPE_RGB) {
writeEntry(TIFFEncoderCst.BITS_PER_SAMPLE, 3, 3, tagDataOffset);
tagDataOffset += TIFFEncoderCst.BPS_DATA_SIZE;
} else
writeEntry(TIFFEncoderCst.BITS_PER_SAMPLE, 3, 1, bitsPerSample);
writeEntry(TIFFEncoderCst.PHOTO_INTERP, 3, 1, photoInterp);
writeEntry(TIFFEncoderCst.STRIP_OFFSETS, 4, 1,
TIFFEncoderCst.IMAGE_START);
writeEntry(TIFFEncoderCst.SAMPLES_PER_PIXEL, 3, 1, samplesPerPixel);
writeEntry(TIFFEncoderCst.ROWS_PER_STRIP, 3, 1, imageHeight);
writeEntry(TIFFEncoderCst.STRIP_BYTE_COUNT, 4, 1, imageSize);
//X resolution info, Y resolution info..
writeEntry(TIFFEncoderCst.X_RESOLUTION, 5, 1, tagDataOffset);
writeEntry(TIFFEncoderCst.Y_RESOLUTION, 5, 1, tagDataOffset+8);
int unit = 2; //TODO: support all unit
writeEntry(TIFFEncoderCst.RESOLUTION_UNIT, 3, 1, unit);
output.writeInt(0); // only one image
}
/**
* Writes one 12-byte IFD entry.
*
* @param tag The tag value.
* @param fieldType The field type.
* @param count
* @param value
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeEntry(int tag, int fieldType, int count, int value)
throws IOException
{
output.writeShort(tag);
output.writeShort(fieldType);
output.writeInt(count);
if (count == 1 && fieldType == TIFFEncoderCst.SHORT)
value <<= 16; //left justify 16-bit values
output.writeInt(value); // may be an offset
}
/**
* Writes the pixel value, band model.
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeRGBPixels()
throws IOException
{
DataBuffer db = image.getRaster().getDataBuffer();
if (db instanceof DataBufferByte)
writeRGBDataBufferByte((DataBufferByte) db);
else if (db instanceof DataBufferInt)
writeRGBDataBufferInt((DataBufferInt) db);
}
/**
* Writes the data for <code>int</code> values.
*
* @param dbi The buffer containing the data.
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeRGBDataBufferInt(DataBufferInt dbi)
throws IOException
{
int xres = image.getWidth();
int yres = image.getHeight();
int[] pixels = dbi.getData();
byte[] bank = new byte[pixels.length];
for (int iy = 0; iy < yres; iy++) {
for (int ix = 0; ix < xres; ix++) {
int off = iy*xres+ix;
int pixel = pixels[off];
int r = (pixel>>16) & 0xff;
int g = (pixel>>8) & 0xff;
int b = pixel & 0xff;
pixel = (0xff<<24)|(r<<16)|(g<<8)|b;
bank[off] = (byte) pixel;
}
}
output.write(bank, 0, bank.length);
}
/**
* Writes the data for <code>byte</code> values.
*
* @param bufferByte The buffer containing the data.
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeRGBDataBufferByte(DataBufferByte bufferByte)
throws IOException
{
int bytesWritten = 0;
int size = imageWidth*imageHeight*3;
int count = imageWidth*24; //3*8
byte[] buffer = new byte[count];
int i, j;
byte[] red = bufferByte.getData(Encoder.RED_BAND);
byte[] green = bufferByte.getData(Encoder.GREEN_BAND);
byte[] blue = bufferByte.getData(Encoder.BLUE_BAND);
while (bytesWritten < size) {
if ((bytesWritten+count) > size)
count = size-bytesWritten;
j = bytesWritten/3;
//TIFF save as BRG and not RGB.
for (i = 0; i < count; i += 3) {
buffer[i] = red[j];
buffer[i+1] = green[j];
buffer[i+2] = blue[j];
j++;
}
output.write(buffer, 0, count);
bytesWritten += count;
}
writeColorMap(red, green, blue);
}
/**
* Writes the color palette following the image.
*
* @param red The byte array for the red band.
* @param green The byte array for the green band.
* @param blue The byte array for the blue band.
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeColorMap(byte[] red, byte[] green, byte[] blue)
throws IOException
{
byte[] colorTable16 = new byte[TIFFEncoderCst.MAP_SIZE*2];
int j = 0;
int max = 251;
//if (red.length < max) max = red.length;
for (int i = 0 ; i < max; i++) {
colorTable16[j] = red[i];
colorTable16[512+j] = green[i];
colorTable16[1024+j] = blue[i];
j += 2;
}
output.write(colorTable16);
}
/**
* Writes the scale.
*
* @throws IOException Exception thrown if an error occurred during the
* encoding process.
*/
private void writeScale()
throws IOException
{
double xscale = 1.0/imageWidth;
double yscale = 1.0/imageHeight;
double scale = 1000000.0;
if (xscale > 1000.0) scale = 1000.0;
output.writeInt((int) (xscale*scale));
output.writeInt((int) scale);
output.writeInt((int) (yscale*scale));
output.writeInt((int) scale);
}
/**
* Creates a new instance.
*
* @param image The image to encode. Mustn't be <code>null</code>.
* @param output The output stream. Mustn't be <code>null</code>.
*/
public TIFFEncoder(BufferedImage image, DataOutputStream output)
{
super(image, output);
}
/**
* Writes an uncompressed big-endian TIFF.
* @see Encoder#write()
*/
public void write()
throws EncoderException
{
try {
output.write(TIFFEncoderCst.header);
writeIFD();
int bpsSize = 0, scaleSize;
if (colorType == ColorSpace.TYPE_RGB) {
writeBitsPerPixel();
bpsSize = TIFFEncoderCst.BPS_DATA_SIZE;
}
scaleSize = TIFFEncoderCst.SCALE_DATA_SIZE;
writeScale();
int size = TIFFEncoderCst.IMAGE_START-
(TIFFEncoderCst.HDR_SIZE+ifdSize+bpsSize+scaleSize);
output.write(new byte[size]); // force image to start at offset 768
writeRGBPixels();
} catch (IOException e) {
throw new EncoderException("Cannot encode the image.", e);
}
}
/**
* Initializes the values needed by this encoder.
* @see Encoder#init()
*/
protected void init()
{
bitsPerSample = 8;
samplesPerPixel = 1;
nEntries = 12;
photoInterp = 1;
ifdSize = 6+nEntries*12;
int bytesPerPixel = 1;
if (colorType == ColorSpace.TYPE_RGB) {
photoInterp = 2;
samplesPerPixel = 3;
bytesPerPixel = 3;
}
imageSize = imageWidth*imageHeight*bytesPerPixel;
}
}