帆软使用的第三方框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1892 lines
70 KiB

/*
* Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduct the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that Software is not designed,licensed or intended for use in
* the design, construction, operation or maintenance of any nuclear facility.
*/
package com.fr.third.JAI;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.zip.Deflater;
/**
* A baseline TIFF writer. The writer outputs TIFF images in either Bilevel,
* Greyscale, Palette color or Full Color modes.
*
*/
public class TIFFImageEncoder extends ImageEncoderImpl {
// Image Types
private static final int TIFF_UNSUPPORTED = -1;
private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0;
private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1;
private static final int TIFF_GRAY = 2;
private static final int TIFF_PALETTE = 3;
private static final int TIFF_RGB = 4;
private static final int TIFF_CMYK = 5;
private static final int TIFF_YCBCR = 6;
private static final int TIFF_CIELAB = 7;
private static final int TIFF_GENERIC = 8;
// Compression types
private static final int COMP_NONE =
TIFFEncodeParam.COMPRESSION_NONE;
private static final int COMP_GROUP3_1D =
TIFFEncodeParam.COMPRESSION_GROUP3_1D;
private static final int COMP_GROUP3_2D =
TIFFEncodeParam.COMPRESSION_GROUP3_2D;
private static final int COMP_GROUP4 =
TIFFEncodeParam.COMPRESSION_GROUP4;
private static final int COMP_JPEG_TTN2 =
TIFFEncodeParam.COMPRESSION_JPEG_TTN2;
private static final int COMP_PACKBITS =
TIFFEncodeParam.COMPRESSION_PACKBITS;
private static final int COMP_DEFLATE =
TIFFEncodeParam.COMPRESSION_DEFLATE;
// Incidental tags
private static final int TIFF_JPEG_TABLES = 347;
private static final int TIFF_YCBCR_SUBSAMPLING = 530;
private static final int TIFF_YCBCR_POSITIONING = 531;
private static final int TIFF_REF_BLACK_WHITE = 532;
// ExtraSamples types
private static final int EXTRA_SAMPLE_UNSPECIFIED = 0;
private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA = 1;
private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
// Default values
private static final int DEFAULT_ROWS_PER_STRIP = 8;
private static final char[] intsToChars(int[] intArray) {
int arrayLength = intArray.length;
char[] charArray = new char[arrayLength];
for(int i = 0; i < arrayLength; i++) {
charArray[i] = (char)(intArray[i]&0x0000ffff);
}
return charArray;
}
public TIFFImageEncoder(OutputStream output, ImageEncodeParam param) {
super(output, param);
if (this.param == null) {
this.param = new TIFFEncodeParam();
}
}
/**
* Encodes a RenderedImage and writes the output to the
* OutputStream associated with this ImageEncoder.
*/
public void encode(RenderedImage im) throws IOException {
// Write the file header (8 bytes).
writeFileHeader();
// Get the encoding parameters.
TIFFEncodeParam encodeParam = (TIFFEncodeParam)param;
Iterator iter = encodeParam.getExtraImages();
if(iter != null) {
int ifdOffset = 8;
RenderedImage nextImage = im;
TIFFEncodeParam nextParam = encodeParam;
boolean hasNext;
do {
hasNext = iter.hasNext();
ifdOffset = encode(nextImage, nextParam, ifdOffset, !hasNext);
if(hasNext) {
Object obj = iter.next();
if(obj instanceof RenderedImage) {
nextImage = (RenderedImage)obj;
nextParam = encodeParam;
} else if(obj instanceof Object[]) {
Object[] o = (Object[])obj;
nextImage = (RenderedImage)o[0];
nextParam = (TIFFEncodeParam)o[1];
}
}
} while(hasNext);
} else {
encode(im, encodeParam, 8, true);
}
}
private int encode(RenderedImage im, TIFFEncodeParam encodeParam,
int ifdOffset, boolean isLast) throws IOException {
// Currently all images are stored uncompressed.
int compression = encodeParam.getCompression();
// Get tiled output preference.
boolean isTiled = encodeParam.getWriteTiled();
// Set bounds.
int minX = im.getMinX();
int minY = im.getMinY();
int width = im.getWidth();
int height = im.getHeight();
// Get SampleModel.
SampleModel sampleModel = im.getSampleModel();
// Retrieve and verify sample size.
int sampleSize[] = sampleModel.getSampleSize();
for(int i = 1; i < sampleSize.length; i++) {
if(sampleSize[i] != sampleSize[0]) {
throw new Error(JaiI18N.getString("TIFFImageEncoder0"));
}
}
// Check low bit limits.
int numBands = sampleModel.getNumBands();
if((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) {
throw new Error(JaiI18N.getString("TIFFImageEncoder1"));
}
// Retrieve and verify data type.
int dataType = sampleModel.getDataType();
switch(dataType) {
case DataBuffer.TYPE_BYTE:
if(sampleSize[0] != 1 && sampleSize[0] == 4 &&
sampleSize[0] != 8) {
throw new Error(JaiI18N.getString("TIFFImageEncoder2"));
}
break;
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT:
if(sampleSize[0] != 16) {
throw new Error(JaiI18N.getString("TIFFImageEncoder3"));
}
break;
case DataBuffer.TYPE_INT:
case DataBuffer.TYPE_FLOAT:
if(sampleSize[0] != 32) {
throw new Error(JaiI18N.getString("TIFFImageEncoder4"));
}
break;
default:
throw new Error(JaiI18N.getString("TIFFImageEncoder5"));
}
boolean dataTypeIsShort =
dataType == DataBuffer.TYPE_SHORT ||
dataType == DataBuffer.TYPE_USHORT;
ColorModel colorModel = im.getColorModel();
if (colorModel != null &&
colorModel instanceof IndexColorModel &&
dataType != DataBuffer.TYPE_BYTE) {
// Don't support (unsigned) short palette-color images.
throw new Error(JaiI18N.getString("TIFFImageEncoder6"));
}
IndexColorModel icm = null;
int sizeOfColormap = 0;
int colormap[] = null;
// Set image type.
int imageType = TIFF_UNSUPPORTED;
int numExtraSamples = 0;
int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED;
if(colorModel instanceof IndexColorModel) { // Bilevel or palette
icm = (IndexColorModel)colorModel;
int mapSize = icm.getMapSize();
if(sampleSize[0] == 1 && numBands == 1) { // Bilevel image
if (mapSize != 2) {
throw new IllegalArgumentException(
JaiI18N.getString("TIFFImageEncoder7"));
}
byte r[] = new byte[mapSize];
icm.getReds(r);
byte g[] = new byte[mapSize];
icm.getGreens(g);
byte b[] = new byte[mapSize];
icm.getBlues(b);
if ((r[0] & 0xff) == 0 &&
(r[1] & 0xff) == 255 &&
(g[0] & 0xff) == 0 &&
(g[1] & 0xff) == 255 &&
(b[0] & 0xff) == 0 &&
(b[1] & 0xff) == 255) {
imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
} else if ((r[0] & 0xff) == 255 &&
(r[1] & 0xff) == 0 &&
(g[0] & 0xff) == 255 &&
(g[1] & 0xff) == 0 &&
(b[0] & 0xff) == 255 &&
(b[1] & 0xff) == 0) {
imageType = TIFF_BILEVEL_WHITE_IS_ZERO;
} else {
imageType = TIFF_PALETTE;
}
} else if(numBands == 1) { // Non-bilevel image.
// Palette color image.
imageType = TIFF_PALETTE;
}
} else if(colorModel == null) {
if(sampleSize[0] == 1 && numBands == 1) { // bilevel
imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
} else { // generic image
imageType = TIFF_GENERIC;
if(numBands > 1) {
numExtraSamples = numBands - 1;
}
}
} else { // colorModel is non-null but not an IndexColorModel
ColorSpace colorSpace = colorModel.getColorSpace();
switch(colorSpace.getType()) {
case ColorSpace.TYPE_CMYK:
imageType = TIFF_CMYK;
break;
case ColorSpace.TYPE_GRAY:
imageType = TIFF_GRAY;
break;
case ColorSpace.TYPE_Lab:
imageType = TIFF_CIELAB;
break;
case ColorSpace.TYPE_RGB:
if(compression == COMP_JPEG_TTN2 &&
encodeParam.getJPEGCompressRGBToYCbCr()) {
imageType = TIFF_YCBCR;
} else {
imageType = TIFF_RGB;
}
break;
case ColorSpace.TYPE_YCbCr:
imageType = TIFF_YCBCR;
break;
default:
imageType = TIFF_GENERIC; // generic
break;
}
if(imageType == TIFF_GENERIC) {
numExtraSamples = numBands - 1;
} else if(numBands > 1) {
numExtraSamples = numBands - colorSpace.getNumComponents();
}
if(numExtraSamples == 1 && colorModel.hasAlpha()) {
extraSampleType = colorModel.isAlphaPremultiplied() ?
EXTRA_SAMPLE_ASSOCIATED_ALPHA :
EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
}
}
if(imageType == TIFF_UNSUPPORTED) {
throw new Error(JaiI18N.getString("TIFFImageEncoder8"));
}
// Check JPEG compatibility.
if(compression == COMP_JPEG_TTN2) {
if(imageType == TIFF_PALETTE) {
throw new Error(JaiI18N.getString("TIFFImageEncoder11"));
} else if(!(sampleSize[0] == 8 &&
(imageType == TIFF_GRAY ||
imageType == TIFF_RGB ||
imageType == TIFF_YCBCR))) {
throw new Error(JaiI18N.getString("TIFFImageEncoder9"));
}
}
int photometricInterpretation = -1;
switch (imageType) {
case TIFF_BILEVEL_WHITE_IS_ZERO:
photometricInterpretation = 0;
break;
case TIFF_BILEVEL_BLACK_IS_ZERO:
photometricInterpretation = 1;
break;
case TIFF_GRAY:
case TIFF_GENERIC:
// Since the CS_GRAY colorspace is always of type black_is_zero
photometricInterpretation = 1;
break;
case TIFF_PALETTE:
photometricInterpretation = 3;
icm = (IndexColorModel)colorModel;
sizeOfColormap = icm.getMapSize();
byte r[] = new byte[sizeOfColormap];
icm.getReds(r);
byte g[] = new byte[sizeOfColormap];
icm.getGreens(g);
byte b[] = new byte[sizeOfColormap];
icm.getBlues(b);
int redIndex = 0, greenIndex = sizeOfColormap;
int blueIndex = 2 * sizeOfColormap;
colormap = new int[sizeOfColormap * 3];
for (int i=0; i<sizeOfColormap; i++) {
colormap[redIndex++] = (r[i] << 8) & 0xffff;
colormap[greenIndex++] = (g[i] << 8) & 0xffff;
colormap[blueIndex++] = (b[i] << 8) & 0xffff;
}
sizeOfColormap *= 3;
break;
case TIFF_RGB:
photometricInterpretation = 2;
break;
case TIFF_CMYK:
photometricInterpretation = 5;
break;
case TIFF_YCBCR:
photometricInterpretation = 6;
break;
case TIFF_CIELAB:
photometricInterpretation = 8;
break;
default:
throw new Error(JaiI18N.getString("TIFFImageEncoder8"));
}
// Initialize tile dimensions.
int tileWidth;
int tileHeight;
if(isTiled) {
tileWidth = encodeParam.getTileWidth() > 0 ?
encodeParam.getTileWidth() : im.getTileWidth();
tileHeight = encodeParam.getTileHeight() > 0 ?
encodeParam.getTileHeight() : im.getTileHeight();
} else {
tileWidth = width;
tileHeight = encodeParam.getTileHeight() > 0 ?
encodeParam.getTileHeight() : DEFAULT_ROWS_PER_STRIP;
}
// Re-tile for JPEG conformance if needed.
JPEGEncodeParam jep = null;
if(compression == COMP_JPEG_TTN2) {
// Get JPEGEncodeParam from encodeParam.
jep = encodeParam.getJPEGEncodeParam();
// Determine maximum subsampling.
int maxSubH = jep.getHorizontalSubsampling(0);
int maxSubV = jep.getVerticalSubsampling(0);
for(int i = 1; i < numBands; i++) {
int subH = jep.getHorizontalSubsampling(i);
if(subH > maxSubH) {
maxSubH = subH;
}
int subV = jep.getVerticalSubsampling(i);
if(subV > maxSubV) {
maxSubV = subV;
}
}
int factorV = 8*maxSubV;
tileHeight =
(int)((float)tileHeight/(float)factorV + 0.5F)*factorV;
if(tileHeight < factorV) {
tileHeight = factorV;
}
if(isTiled) {
int factorH = 8*maxSubH;
tileWidth =
(int)((float)tileWidth/(float)factorH + 0.5F)*factorH;
if(tileWidth < factorH) {
tileWidth = factorH;
}
}
}
int numTiles;
if(isTiled) {
// NB: Parentheses are used in this statement for correct rounding.
numTiles =
((width + tileWidth - 1)/tileWidth) *
((height + tileHeight - 1)/tileHeight);
} else {
numTiles = (int)Math.ceil((double)height/(double)tileHeight);
}
long tileByteCounts[] = new long[numTiles];
long bytesPerRow =
(long)Math.ceil((sampleSize[0] / 8.0) * tileWidth * numBands);
long bytesPerTile = bytesPerRow * tileHeight;
for (int i=0; i<numTiles; i++) {
tileByteCounts[i] = bytesPerTile;
}
if(!isTiled) {
// Last strip may have lesser rows
long lastStripRows = height - (tileHeight * (numTiles-1));
tileByteCounts[numTiles-1] = lastStripRows * bytesPerRow;
}
long totalBytesOfData = bytesPerTile * (numTiles - 1) +
tileByteCounts[numTiles-1];
// The data will be written after the IFD: create the array here
// but fill it in later.
long tileOffsets[] = new long[numTiles];
// Basic fields - have to be in increasing numerical order.
// ImageWidth 256
// ImageLength 257
// BitsPerSample 258
// Compression 259
// PhotoMetricInterpretation 262
// StripOffsets 273
// RowsPerStrip 278
// StripByteCounts 279
// XResolution 282
// YResolution 283
// ResolutionUnit 296
// Create Directory
SortedSet fields = new TreeSet();
// Image Width
fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_WIDTH,
TIFFField.TIFF_LONG, 1,
(Object)(new long[] {(long)width})));
// Image Length
fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_LENGTH,
TIFFField.TIFF_LONG, 1,
new long[] {(long)height}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE,
TIFFField.TIFF_SHORT, numBands,
intsToChars(sampleSize)));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_COMPRESSION,
TIFFField.TIFF_SHORT, 1,
new char[] {(char)compression}));
fields.add(
new TIFFField(TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION,
TIFFField.TIFF_SHORT, 1,
new char[] {(char)photometricInterpretation}));
if(!isTiled) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_OFFSETS,
TIFFField.TIFF_LONG, numTiles,
(long[])tileOffsets));
}
fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLES_PER_PIXEL,
TIFFField.TIFF_SHORT, 1,
new char[] {(char)numBands}));
if(!isTiled) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_ROWS_PER_STRIP,
TIFFField.TIFF_LONG, 1,
new long[] {(long)tileHeight}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS,
TIFFField.TIFF_LONG, numTiles,
(long[])tileByteCounts));
}
if (colormap != null) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_COLORMAP,
TIFFField.TIFF_SHORT, sizeOfColormap,
intsToChars(colormap)));
}
if(isTiled) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_WIDTH,
TIFFField.TIFF_LONG, 1,
new long[] {(long)tileWidth}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_LENGTH,
TIFFField.TIFF_LONG, 1,
new long[] {(long)tileHeight}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_OFFSETS,
TIFFField.TIFF_LONG, numTiles,
(long[])tileOffsets));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS,
TIFFField.TIFF_LONG, numTiles,
(long[])tileByteCounts));
}
if(numExtraSamples > 0) {
int[] extraSamples = new int[numExtraSamples];
for(int i = 0; i < numExtraSamples; i++) {
extraSamples[i] = extraSampleType;
}
fields.add(new TIFFField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES,
TIFFField.TIFF_SHORT, numExtraSamples,
intsToChars(extraSamples)));
}
// Data Sample Format Extension fields.
if(dataType != DataBuffer.TYPE_BYTE) {
// SampleFormat
int[] sampleFormat = new int[numBands];
if(dataType == DataBuffer.TYPE_FLOAT) {
sampleFormat[0] = 3;
} else if(dataType == DataBuffer.TYPE_USHORT) {
sampleFormat[0] = 1;
} else {
sampleFormat[0] = 2;
}
for(int b = 1; b < numBands; b++) {
sampleFormat[b] = sampleFormat[0];
}
fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLE_FORMAT,
TIFFField.TIFF_SHORT, numBands,
intsToChars(sampleFormat)));
// NOTE: We don't bother setting the SMinSampleValue and
// SMaxSampleValue fields as these both default to the
// extrema of the respective data types. Probably we should
// check for the presence of the "extrema" property and
// use it if available.
}
// Bilevel compression variables.
boolean inverseFill = encodeParam.getReverseFillOrder();
boolean T4encode2D = encodeParam.getT4Encode2D();
boolean T4PadEOLs = encodeParam.getT4PadEOLs();
TIFFFaxEncoder faxEncoder = null;
// Add bilevel compression fields.
if((imageType == TIFF_BILEVEL_BLACK_IS_ZERO ||
imageType == TIFF_BILEVEL_WHITE_IS_ZERO) &&
(compression == COMP_GROUP3_1D ||
compression == COMP_GROUP3_2D ||
compression == COMP_GROUP4)) {
// Create the encoder.
faxEncoder = new TIFFFaxEncoder(inverseFill);
// FillOrder field.
fields.add(new TIFFField(TIFFImageDecoder.TIFF_FILL_ORDER,
TIFFField.TIFF_SHORT, 1,
new char[] {inverseFill ?
(char)2 : (char)1}));
if(compression == COMP_GROUP3_2D) {
// T4Options field.
long T4Options = 0x00000000;
if(T4encode2D) {
T4Options |= 0x00000001;
}
if(T4PadEOLs) {
T4Options |= 0x00000004;
}
fields.add(new TIFFField(TIFFImageDecoder.TIFF_T4_OPTIONS,
TIFFField.TIFF_LONG, 1,
new long[] {T4Options}));
} else if(compression == COMP_GROUP4) {
// T6Options field.
fields.add(new TIFFField(TIFFImageDecoder.TIFF_T6_OPTIONS,
TIFFField.TIFF_LONG, 1,
new long[] {(long)0x00000000}));
}
}
// Initialize some JPEG variables.
com.sun.image.codec.jpeg.JPEGEncodeParam jpegEncodeParam = null;
com.sun.image.codec.jpeg.JPEGImageEncoder jpegEncoder = null;
int jpegColorID = 0;
if(compression == COMP_JPEG_TTN2) {
// Initialize JPEG color ID.
jpegColorID =
com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_UNKNOWN;
switch(imageType) {
case TIFF_GRAY:
case TIFF_PALETTE:
jpegColorID =
com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_GRAY;
break;
case TIFF_RGB:
jpegColorID =
com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_RGB;
break;
case TIFF_YCBCR:
jpegColorID =
com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_YCbCr;
break;
}
// Get the JDK encoding parameters.
Raster tile00 = im.getTile(0, 0);
jpegEncodeParam =
com.sun.image.codec.jpeg.JPEGCodec.getDefaultJPEGEncodeParam(
tile00, jpegColorID);
// Modify per values passed in.
JPEGImageEncoder.modifyEncodeParam(jep, jpegEncodeParam, numBands);
// JPEGTables field.
if(jep.getWriteImageOnly()) {
// Write an abbreviated tables-only stream to JPEGTables field.
jpegEncodeParam.setImageInfoValid(false);
jpegEncodeParam.setTableInfoValid(true);
ByteArrayOutputStream tableStream =
new ByteArrayOutputStream();
jpegEncoder =
com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder(
tableStream,
jpegEncodeParam);
jpegEncoder.encode(tile00);
byte[] tableData = tableStream.toByteArray();
fields.add(new TIFFField(TIFF_JPEG_TABLES,
TIFFField.TIFF_UNDEFINED,
tableData.length,
tableData));
// Reset encoder so it's recreated below.
jpegEncoder = null;
}
}
if(imageType == TIFF_YCBCR) {
// YCbCrSubSampling: 2 is the default so we must write 1 as
// we do not (yet) do any subsampling.
int subsampleH = 1;
int subsampleV = 1;
// If JPEG, update values.
if(compression == COMP_JPEG_TTN2) {
// Determine maximum subsampling.
subsampleH = jep.getHorizontalSubsampling(0);
subsampleV = jep.getVerticalSubsampling(0);
for(int i = 1; i < numBands; i++) {
int subH = jep.getHorizontalSubsampling(i);
if(subH > subsampleH) {
subsampleH = subH;
}
int subV = jep.getVerticalSubsampling(i);
if(subV > subsampleV) {
subsampleV = subV;
}
}
}
fields.add(new TIFFField(TIFF_YCBCR_SUBSAMPLING,
TIFFField.TIFF_SHORT, 2,
new char[] {(char)subsampleH,
(char)subsampleV}));
// YCbCr positioning.
fields.add(new TIFFField(TIFF_YCBCR_POSITIONING,
TIFFField.TIFF_SHORT, 1,
new char[] {compression == COMP_JPEG_TTN2 ?
(char)1 : (char)2}));
// Reference black/white.
long[][] refbw;
if(compression == COMP_JPEG_TTN2) {
refbw =
new long[][] { // no headroon/footroom
{0, 1}, {255, 1}, {128, 1}, {255, 1}, {128, 1}, {255, 1}
};
} else {
refbw =
new long[][] { // CCIR 601.1 headroom/footroom (presumptive)
{15, 1}, {235, 1}, {128, 1}, {240, 1}, {128, 1}, {240, 1}
};
}
fields.add(new TIFFField(TIFF_REF_BLACK_WHITE,
TIFFField.TIFF_RATIONAL, 6,
refbw));
}
// ---- No more automatically generated fields should be added
// after this point. ----
// Add extra fields specified via the encoding parameters.
TIFFField[] extraFields = encodeParam.getExtraFields();
if(extraFields != null) {
ArrayList extantTags = new ArrayList(fields.size());
Iterator fieldIter = fields.iterator();
while(fieldIter.hasNext()) {
TIFFField fld = (TIFFField)fieldIter.next();
extantTags.add(new Integer(fld.getTag()));
}
int numExtraFields = extraFields.length;
for(int i = 0; i < numExtraFields; i++) {
TIFFField fld = extraFields[i];
Integer tagValue = new Integer(fld.getTag());
if(!extantTags.contains(tagValue)) {
fields.add(fld);
extantTags.add(tagValue);
}
}
}
// ---- No more fields of any type should be added after this. ----
// Determine the size of the IFD which is written after the header
// of the stream or after the data of the previous image in a
// multi-page stream.
int dirSize = getDirectorySize(fields);
// The first data segment is written after the field overflow
// following the IFD so initialize the first offset accordingly.
tileOffsets[0] = ifdOffset + dirSize;
// Branch here depending on whether data are being comrpressed.
// If not, then the IFD is written immediately.
// If so then there are three possibilities:
// A) the OutputStream is a SeekableOutputStream (outCache null);
// B) the OutputStream is not a SeekableOutputStream and a file cache
// is used (outCache non-null, tempFile non-null);
// C) the OutputStream is not a SeekableOutputStream and a memory cache
// is used (outCache non-null, tempFile null).
OutputStream outCache = null;
byte[] compressBuf = null;
File tempFile = null;
int nextIFDOffset = 0;
boolean skipByte = false;
Deflater deflater = null;
int deflateLevel = Deflater.DEFAULT_COMPRESSION;
boolean jpegRGBToYCbCr = false;
if(compression == COMP_NONE) {
// Determine the number of bytes of padding necessary between
// the end of the IFD and the first data segment such that the
// alignment of the data conforms to the specification (required
// for uncompressed data only).
int numBytesPadding = 0;
if(sampleSize[0] == 16 && tileOffsets[0] % 2 != 0) {
numBytesPadding = 1;
tileOffsets[0]++;
} else if(sampleSize[0] == 32 && tileOffsets[0] % 4 != 0) {
numBytesPadding = (int)(4 - tileOffsets[0] % 4);
tileOffsets[0] += numBytesPadding;
}
// Update the data offsets (which TIFFField stores by reference).
for (int i = 1; i < numTiles; i++) {
tileOffsets[i] = tileOffsets[i-1] + tileByteCounts[i-1];
}
if(!isLast) {
// Determine the offset of the next IFD.
nextIFDOffset = (int)(tileOffsets[0] + totalBytesOfData);
// IFD offsets must be on a word boundary.
if(nextIFDOffset % 2 != 0) {
nextIFDOffset++;
skipByte = true;
}
}
// Write the IFD and field overflow before the image data.
writeDirectory(ifdOffset, fields, nextIFDOffset);
// Write any padding bytes needed between the end of the IFD
// and the start of the actual image data.
if(numBytesPadding != 0) {
for(int padding = 0; padding < numBytesPadding; padding++) {
output.write((byte)0);
}
}
} else {
// If compressing, the cannot be written yet as the size of the
// data segments is unknown.
if((output instanceof SeekableOutputStream)) {
// Simply seek to the first data segment position.
((SeekableOutputStream)output).seek(tileOffsets[0]);
} else {
// Cache the original OutputStream.
outCache = output;
try {
// Attempt to create a temporary file.
tempFile = File.createTempFile("jai-SOS-", ".tmp");
tempFile.deleteOnExit();
RandomAccessFile raFile =
new RandomAccessFile(tempFile, "rw");
output = new SeekableOutputStream(raFile);
// this method is exited!
} catch(Exception e) {
// Allocate memory for the entire image data (!).
output = new ByteArrayOutputStream((int)totalBytesOfData);
}
}
int bufSize = 0;
switch(compression) {
case COMP_GROUP3_1D:
// This initial buffer size is based on an alternating 1-0
// pattern generating the most bits when converted to code
// words: 9 bits out for each pair of bits in. So the number
// of bit pairs is determined, multiplied by 9, converted to
// bytes, and a ceil() is taken to account for fill bits at the
// end of each line. The "2" addend accounts for the case
// of the pattern beginning with black. The buffer is intended
// to hold only a single row.
bufSize = (int)Math.ceil((((tileWidth + 1)/2)*9 + 2)/8.0);
break;
case COMP_GROUP3_2D:
case COMP_GROUP4:
// Calculate the maximum row as the G3-1D size plus the EOL,
// multiply this by the number of rows in the tile, and add
// 6 EOLs for the RTC (return to control).
bufSize = (int)Math.ceil((((tileWidth + 1)/2)*9 + 2)/8.0);
bufSize = tileHeight*(bufSize + 2) + 12;
break;
case COMP_PACKBITS:
bufSize = (int)(bytesPerTile +
((bytesPerRow+127)/128)*tileHeight);
break;
case COMP_JPEG_TTN2:
bufSize = 0;
// Set color conversion flag.
if(imageType == TIFF_YCBCR &&
colorModel != null &&
colorModel.getColorSpace().getType() ==
ColorSpace.TYPE_RGB) {
jpegRGBToYCbCr = true;
}
break;
case COMP_DEFLATE:
bufSize = (int)bytesPerTile;
deflater = new Deflater(encodeParam.getDeflateLevel());
break;
default:
bufSize = 0;
}
if(bufSize != 0) {
compressBuf = new byte[bufSize];
}
}
// ---- Writing of actual image data ----
// Buffer for up to tileHeight rows of pixels
int[] pixels = null;
float[] fpixels = null;
// Whether to test for contiguous data.
boolean checkContiguous =
((sampleSize[0] == 1 &&
sampleModel instanceof MultiPixelPackedSampleModel &&
dataType == DataBuffer.TYPE_BYTE) ||
(sampleSize[0] == 8 &&
sampleModel instanceof ComponentSampleModel));
// Also create a buffer to hold tileHeight lines of the
// data to be written to the file, so we can use array writes.
byte[] bpixels = null;
if(compression != COMP_JPEG_TTN2) {
if(dataType == DataBuffer.TYPE_BYTE) {
bpixels = new byte[tileHeight * tileWidth * numBands];
} else if(dataTypeIsShort) {
bpixels = new byte[2 * tileHeight * tileWidth * numBands];
} else if(dataType == DataBuffer.TYPE_INT ||
dataType == DataBuffer.TYPE_FLOAT) {
bpixels = new byte[4 * tileHeight * tileWidth * numBands];
}
}
// Process tileHeight rows at a time
int lastRow = minY + height;
int lastCol = minX + width;
int tileNum = 0;
for (int row = minY; row < lastRow; row += tileHeight) {
int rows = isTiled ?
tileHeight : Math.min(tileHeight, lastRow - row);
int size = rows * tileWidth * numBands;
for(int col = minX; col < lastCol; col += tileWidth) {
// Grab the pixels
Raster src =
im.getData(new Rectangle(col, row, tileWidth, rows));
boolean useDataBuffer = false;
if(compression != COMP_JPEG_TTN2) { // JPEG access Raster
if(checkContiguous) {
if(sampleSize[0] == 8) { // 8-bit
ComponentSampleModel csm =
(ComponentSampleModel)src.getSampleModel();
int[] bankIndices = csm.getBankIndices();
int[] bandOffsets = csm.getBandOffsets();
int pixelStride = csm.getPixelStride();
int lineStride = csm.getScanlineStride();
if(pixelStride != numBands ||
lineStride != bytesPerRow) {
useDataBuffer = false;
} else {
useDataBuffer = true;
for(int i = 0;
useDataBuffer && i < numBands;
i++) {
if(bankIndices[i] != 0 ||
bandOffsets[i] != i) {
useDataBuffer = false;
}
}
}
} else { // 1-bit
MultiPixelPackedSampleModel mpp =
(MultiPixelPackedSampleModel)src.getSampleModel();
if(mpp.getNumBands() == 1 &&
mpp.getDataBitOffset() == 0 &&
mpp.getPixelBitStride() == 1) {
useDataBuffer = true;
}
}
}
if(!useDataBuffer) {
if(dataType == DataBuffer.TYPE_FLOAT) {
fpixels = src.getPixels(col, row, tileWidth, rows,
fpixels);
} else {
pixels = src.getPixels(col, row, tileWidth, rows,
pixels);
}
}
}
int index;
int pixel = 0;;
int k = 0;
switch(sampleSize[0]) {
case 1:
if(useDataBuffer) {
byte[] btmp =
((DataBufferByte)src.getDataBuffer()).getData();
MultiPixelPackedSampleModel mpp =
(MultiPixelPackedSampleModel)src.getSampleModel();
int lineStride = mpp.getScanlineStride();
int inOffset =
mpp.getOffset(col -
src.getSampleModelTranslateX(),
row -
src.getSampleModelTranslateY());
if(lineStride == (int)bytesPerRow) {
System.arraycopy(btmp, inOffset,
bpixels, 0,
(int)bytesPerRow*rows);
} else {
int outOffset = 0;
for(int j = 0; j < rows; j++) {
System.arraycopy(btmp, inOffset,
bpixels, outOffset,
(int)bytesPerRow);
inOffset += lineStride;
outOffset += (int)bytesPerRow;
}
}
} else {
index = 0;
// For each of the rows in a strip
for (int i=0; i<rows; i++) {
// Write number of pixels exactly divisible by 8
for (int j=0; j<tileWidth/8; j++) {
pixel =
(pixels[index++] << 7) |
(pixels[index++] << 6) |
(pixels[index++] << 5) |
(pixels[index++] << 4) |
(pixels[index++] << 3) |
(pixels[index++] << 2) |
(pixels[index++] << 1) |
pixels[index++];
bpixels[k++] = (byte)pixel;
}
// Write the pixels remaining after division by 8
if (tileWidth%8 > 0) {
pixel = 0;
for (int j=0; j<tileWidth%8; j++) {
pixel |= (pixels[index++] << (7 - j));
}
bpixels[k++] = (byte)pixel;
}
}
}
if(compression == COMP_NONE) {
output.write(bpixels, 0, rows * ((tileWidth+7)/8));
} else if(compression == COMP_GROUP3_1D) {
int rowStride = (tileWidth + 7)/8;
int rowOffset = 0;
int numCompressedBytes = 0;
for(int tileRow = 0; tileRow < rows; tileRow++) {
int numCompressedBytesInRow =
faxEncoder.encodeRLE(bpixels,
rowOffset, 0, tileWidth,
compressBuf);
output.write(compressBuf,
0, numCompressedBytesInRow);
rowOffset += rowStride;
numCompressedBytes += numCompressedBytesInRow;
}
tileByteCounts[tileNum++] = numCompressedBytes;
} else if(compression == COMP_GROUP3_2D) {
int numCompressedBytes =
faxEncoder.encodeT4(!T4encode2D,// 1D == !2D
T4PadEOLs,
bpixels,
(tileWidth+7)/8,
0,
tileWidth,
rows,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_GROUP4) {
int numCompressedBytes =
faxEncoder.encodeT6(bpixels,
(tileWidth+7)/8,
0,
tileWidth,
rows,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
(int)bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
case 4:
index = 0;
// For each of the rows in a strip
for (int i=0; i<rows; i++) {
// Write the number of pixels that will fit into an
// even number of nibbles.
for (int j=0; j<tileWidth/2; j++) {
pixel = (pixels[index++] << 4) | pixels[index++];
bpixels[k++] = (byte)pixel;
}
// Last pixel for odd-length lines
if ((tileWidth % 2) == 1) {
pixel = pixels[index++] << 4;
bpixels[k++] = (byte)pixel;
}
}
if(compression == COMP_NONE) {
output.write(bpixels, 0, rows * ((tileWidth+1)/2));
} else if(compression == COMP_PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
(int)bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
case 8:
if(compression != COMP_JPEG_TTN2) {
if(useDataBuffer) {
byte[] btmp =
((DataBufferByte)src.getDataBuffer()).getData();
ComponentSampleModel csm =
(ComponentSampleModel)src.getSampleModel();
int inOffset =
csm.getOffset(col -
src.getSampleModelTranslateX(),
row -
src.getSampleModelTranslateY());
int lineStride = csm.getScanlineStride();
if(lineStride == (int)bytesPerRow) {
System.arraycopy(btmp,
inOffset,
bpixels, 0,
(int)bytesPerRow*rows);
} else {
int outOffset = 0;
for(int j = 0; j < rows; j++) {
System.arraycopy(btmp, inOffset,
bpixels, outOffset,
(int)bytesPerRow);
inOffset += lineStride;
outOffset += (int)bytesPerRow;
}
}
} else {
for (int i = 0; i < size; i++) {
bpixels[i] = (byte)pixels[i];
}
}
}
if(compression == COMP_NONE) {
output.write(bpixels, 0, size);
} else if(compression == COMP_PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
(int)bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_JPEG_TTN2) {
long startPos = getOffset(output);
// Recreate encoder and parameters if the encoder
// is null (first data segment) or if its size
// doesn't match the current data segment.
if(jpegEncoder == null ||
jpegEncodeParam.getWidth() != src.getWidth() ||
jpegEncodeParam.getHeight() != src.getHeight()) {
jpegEncodeParam =
com.sun.image.codec.jpeg.JPEGCodec.
getDefaultJPEGEncodeParam(src, jpegColorID);
JPEGImageEncoder.modifyEncodeParam(jep,
jpegEncodeParam,
numBands);
jpegEncoder =
com.sun.image.codec.jpeg.JPEGCodec.
createJPEGEncoder(output,
jpegEncodeParam);
}
if(jpegRGBToYCbCr) {
WritableRaster wRas = null;
if(src instanceof WritableRaster) {
wRas = (WritableRaster)src;
} else {
wRas = src.createCompatibleWritableRaster();
wRas.setRect(src);
}
if (wRas.getMinX() != 0 || wRas.getMinY() != 0) {
wRas =
wRas.createWritableTranslatedChild(0, 0);
}
BufferedImage bi =
new BufferedImage(colorModel, wRas,
false, null);
jpegEncoder.encode(bi);
} else {
jpegEncoder.encode(src.createTranslatedChild(0,
0));
}
long endPos = getOffset(output);
tileByteCounts[tileNum++] = (int)(endPos - startPos);
} else if(compression == COMP_DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
case 16:
int ls = 0;
for (int i = 0; i < size; i++) {
short value = (short)pixels[i];
bpixels[ls++] = (byte)((value & 0xff00) >> 8);
bpixels[ls++] = (byte)(value & 0x00ff);
}
if(compression == COMP_NONE) {
output.write(bpixels, 0, size*2);
} else if(compression == COMP_PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
(int)bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
case 32:
if(dataType == DataBuffer.TYPE_INT) {
int li = 0;
for (int i = 0; i < size; i++) {
int value = pixels[i];
bpixels[li++] = (byte)((value & 0xff000000) >> 24);
bpixels[li++] = (byte)((value & 0x00ff0000) >> 16);
bpixels[li++] = (byte)((value & 0x0000ff00) >> 8);
bpixels[li++] = (byte)(value & 0x000000ff);
}
} else { // DataBuffer.TYPE_FLOAT
int lf = 0;
for (int i = 0; i < size; i++) {
int value = Float.floatToIntBits(fpixels[i]);
bpixels[lf++] = (byte)((value & 0xff000000) >> 24);
bpixels[lf++] = (byte)((value & 0x00ff0000) >> 16);
bpixels[lf++] = (byte)((value & 0x0000ff00) >> 8);
bpixels[lf++] = (byte)(value & 0x000000ff);
}
}
if(compression == COMP_NONE) {
output.write(bpixels, 0, size*4);
} else if(compression == COMP_PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
(int)bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if(compression == COMP_DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
}
}
}
if(compression == COMP_NONE) {
// Write an extra byte for IFD word alignment if needed.
if(skipByte) {
output.write((byte)0);
}
} else {
// Recompute the tile offsets the size of the compressed tiles.
int totalBytes = 0;
for (int i=1; i<numTiles; i++) {
int numBytes = (int)tileByteCounts[i-1];
totalBytes += numBytes;
tileOffsets[i] = tileOffsets[i-1] + numBytes;
}
totalBytes += (int)tileByteCounts[numTiles-1];
nextIFDOffset = isLast ?
0 : ifdOffset + dirSize + totalBytes;
skipByte = nextIFDOffset % 2 != 0;
if(outCache == null) {
// Original OutputStream must be a SeekableOutputStream.
// Write an extra byte for IFD word alignment if needed.
if(skipByte) {
output.write((byte)0);
}
SeekableOutputStream sos = (SeekableOutputStream)output;
// Save current position.
long savePos = sos.getFilePointer();
// Seek backward to the IFD offset and write IFD.
sos.seek(ifdOffset);
writeDirectory(ifdOffset, fields, nextIFDOffset);
// Seek forward to position after data.
sos.seek(savePos);
} else if(tempFile != null) {
// Using a file cache for the image data.
// Open a FileInputStream from which to copy the data.
FileInputStream fileStream = new FileInputStream(tempFile);
// Close the original SeekableOutputStream.
output.close();
// Reset variable to the original OutputStream.
output = outCache;
// Write the IFD.
writeDirectory(ifdOffset, fields, nextIFDOffset);
// Write the image data.
byte[] copyBuffer = new byte[8192];
int bytesCopied = 0;
while(bytesCopied < totalBytes) {
int bytesRead = fileStream.read(copyBuffer);
if(bytesRead == -1) {
break;
}
output.write(copyBuffer, 0, bytesRead);
bytesCopied += bytesRead;
}
// Delete the temporary file.
fileStream.close();
tempFile.delete();
// Write an extra byte for IFD word alignment if needed.
if(skipByte) {
output.write((byte)0);
}
} else if(output instanceof ByteArrayOutputStream) {
// Using a memory cache for the image data.
ByteArrayOutputStream memoryStream =
(ByteArrayOutputStream)output;
// Reset variable to the original OutputStream.
output = outCache;
// Write the IFD.
writeDirectory(ifdOffset, fields, nextIFDOffset);
// Write the image data.
memoryStream.writeTo(output);
// Write an extra byte for IFD word alignment if needed.
if(skipByte) {
output.write((byte)0);
}
} else {
// This should never happen.
throw new IllegalStateException();
}
}
return nextIFDOffset;
}
/**
* Calculates the size of the IFD.
*/
private int getDirectorySize(SortedSet fields) {
// Get the number of entries.
int numEntries = fields.size();
// Initialize the size excluding that of any values > 4 bytes.
int dirSize = 2 + numEntries*12 + 4;
// Loop over fields adding the size of all values > 4 bytes.
Iterator iter = fields.iterator();
while(iter.hasNext()) {
// Get the field.
TIFFField field = (TIFFField)iter.next();
// Determine the size of the field value.
int valueSize = getValueSize(field);
// Add any excess size.
if(valueSize > 4) {
dirSize += valueSize;
}
}
return dirSize;
}
private void writeFileHeader() throws IOException {
// 8 byte image file header
// Byte order used within the file - Big Endian
output.write('M');
output.write('M');
// Magic value
output.write(0);
output.write(42);
// Offset in bytes of the first IFD.
writeLong(8);
}
private void writeDirectory(int thisIFDOffset, SortedSet fields,
int nextIFDOffset)
throws IOException {
// 2 byte count of number of directory entries (fields)
int numEntries = fields.size();
long offsetBeyondIFD = thisIFDOffset + 12 * numEntries + 4 + 2;
ArrayList tooBig = new ArrayList();
// Write number of fields in the IFD
writeUnsignedShort(numEntries);
Iterator iter = fields.iterator();
while(iter.hasNext()) {
// 12 byte field entry TIFFField
TIFFField field = (TIFFField)iter.next();
// byte 0-1 Tag that identifies a field
int tag = field.getTag();
writeUnsignedShort(tag);
// byte 2-3 The field type
int type = field.getType();
writeUnsignedShort(type);
// bytes 4-7 the number of values of the indicated type except
// ASCII-valued fields which require the total number of bytes.
int count = field.getCount();
int valueSize = getValueSize(field);
writeLong(type == TIFFField.TIFF_ASCII ? valueSize : count);
// bytes 8 - 11 the value or value offset
if (valueSize > 4) {
// We need an offset as data won't fit into 4 bytes
writeLong(offsetBeyondIFD);
offsetBeyondIFD += valueSize;
tooBig.add(field);
} else {
writeValuesAsFourBytes(field);
}
}
// Address of next IFD
writeLong(nextIFDOffset);
// Write the tag values that did not fit into 4 bytes
for (int i = 0; i < tooBig.size(); i++) {
writeValues((TIFFField)tooBig.get(i));
}
}
/**
* Determine the number of bytes in the value portion of the field.
*/
private static final int getValueSize(TIFFField field) {
int type = field.getType();
int count = field.getCount();
int valueSize = 0;
if(type == TIFFField.TIFF_ASCII) {
for(int i = 0; i < count; i++) {
byte[] stringBytes = field.getAsString(i).getBytes();
valueSize += stringBytes.length;
if(stringBytes[stringBytes.length-1] != (byte)0) {
valueSize++;
}
}
} else {
valueSize = count * sizeOfType[type];
}
return valueSize;
}
private static final int[] sizeOfType = {
0, // 0 = n/a
1, // 1 = byte
1, // 2 = ascii
2, // 3 = short
4, // 4 = long
8, // 5 = rational
1, // 6 = sbyte
1, // 7 = undefined
2, // 8 = sshort
4, // 9 = slong
8, // 10 = srational
4, // 11 = float
8 // 12 = double
};
private void writeValuesAsFourBytes(TIFFField field) throws IOException {
int dataType = field.getType();
int count = field.getCount();
switch (dataType) {
// 8 bits
case TIFFField.TIFF_BYTE:
case TIFFField.TIFF_SBYTE:
case TIFFField.TIFF_UNDEFINED:
byte bytes[] = field.getAsBytes();
for (int i=0; i<count; i++) {
output.write(bytes[i]);
}
for (int i = 0; i < (4 - count); i++) {
output.write(0);
}
break;
// unsigned 16 bits
case TIFFField.TIFF_SHORT:
char shorts[] = field.getAsChars();
for (int i=0; i<count; i++) {
writeUnsignedShort(shorts[i]);
}
for (int i = 0; i < (2 - count); i++) {
writeUnsignedShort(0);
}
break;
// signed 16 bits
case TIFFField.TIFF_SSHORT:
short sshorts[] = field.getAsShorts();
for (int i=0; i<count; i++) {
writeUnsignedShort(sshorts[i]);
}
for (int i = 0; i < (2 - count); i++) {
writeUnsignedShort(0);
}
break;
// unsigned 32 bits
case TIFFField.TIFF_LONG:
writeLong(field.getAsLong(0));
break;
// signed 32 bits
case TIFFField.TIFF_SLONG:
writeLong(field.getAsInt(0));
break;
case TIFFField.TIFF_FLOAT:
writeLong(Float.floatToIntBits(field.getAsFloat(0)));
break;
case TIFFField.TIFF_ASCII:
int asciiByteCount = 0;
for (int i=0; i<count; i++) {
byte[] stringBytes = field.getAsString(i).getBytes();
output.write(stringBytes);
asciiByteCount += stringBytes.length;
if(stringBytes[stringBytes.length-1] != (byte)0) {
output.write(0);
asciiByteCount++;
}
}
for (int i = 0; i < (4 - asciiByteCount); i++) {
output.write(0);
}
break;
default:
throw new Error(JaiI18N.getString("TIFFImageEncoder10"));
}
}
private void writeValues(TIFFField field) throws IOException {
int dataType = field.getType();
int count = field.getCount();
switch (dataType) {
// unsigned 8 bits
case TIFFField.TIFF_BYTE:
case TIFFField.TIFF_SBYTE:
case TIFFField.TIFF_UNDEFINED:
byte bytes[] = field.getAsBytes();
for (int i=0; i<count; i++) {
output.write(bytes[i]);
}
break;
// unsigned 16 bits
case TIFFField.TIFF_SHORT:
char shorts[] = field.getAsChars();
for (int i=0; i<count; i++) {
writeUnsignedShort(shorts[i]);
}
break;
// signed 16 bits
case TIFFField.TIFF_SSHORT:
short sshorts[] = field.getAsShorts();
for (int i=0; i<count; i++) {
writeUnsignedShort(sshorts[i]);
}
break;
// unsigned 32 bits
case TIFFField.TIFF_LONG:
long longs[] = field.getAsLongs();
for (int i=0; i<count; i++) {
writeLong(longs[i]);
}
break;
// signed 32 bits
case TIFFField.TIFF_SLONG:
int slongs[] = field.getAsInts();
for (int i=0; i<count; i++) {
writeLong(slongs[i]);
}
break;
case TIFFField.TIFF_FLOAT:
float[] floats = field.getAsFloats();
for (int i=0; i<count; i++) {
int intBits = Float.floatToIntBits(floats[i]);
writeLong(intBits);
}
break;
case TIFFField.TIFF_DOUBLE:
double[] doubles = field.getAsDoubles();
for (int i=0; i<count; i++) {
long longBits = Double.doubleToLongBits(doubles[i]);
writeLong((int)(longBits >> 32));
writeLong((int)(longBits & 0xffffffff));
}
break;
// unsigned rationals
case TIFFField.TIFF_RATIONAL:
long rationals[][] = field.getAsRationals();
for (int i=0; i<count; i++) {
writeLong(rationals[i][0]);
writeLong(rationals[i][1]);
}
break;
// signed rationals
case TIFFField.TIFF_SRATIONAL:
int srationals[][] = field.getAsSRationals();
for (int i=0; i<count; i++) {
writeLong(srationals[i][0]);
writeLong(srationals[i][1]);
}
break;
case TIFFField.TIFF_ASCII:
for (int i=0; i<count; i++) {
byte[] stringBytes = field.getAsString(i).getBytes();
output.write(stringBytes);
if(stringBytes[stringBytes.length-1] != (byte)0) {
output.write(0);
}
}
break;
default:
throw new Error(JaiI18N.getString("TIFFImageEncoder10"));
}
}
// Here s is never expected to have value greater than what can be
// stored in 2 bytes.
private void writeUnsignedShort(int s) throws IOException {
output.write((s & 0xff00) >>> 8);
output.write(s & 0x00ff);
}
private void writeLong(long l) throws IOException {
output.write( (int)((l & 0xff000000) >>> 24));
output.write( (int)((l & 0x00ff0000) >>> 16));
output.write( (int)((l & 0x0000ff00) >>> 8));
output.write( ((int)l & 0x000000ff));
}
/**
* Returns the current offset in the supplied OutputStream.
* This method should only be used if compressing data.
*/
private long getOffset(OutputStream out) throws IOException {
if(out instanceof ByteArrayOutputStream) {
return ((ByteArrayOutputStream)out).size();
} else if(out instanceof SeekableOutputStream) {
return ((SeekableOutputStream)out).getFilePointer();
} else {
// Shouldn't happen.
throw new IllegalStateException();
}
}
/**
* Performs PackBits compression on a tile of data.
*/
private static int compressPackBits(byte[] data, int numRows,
int bytesPerRow, byte[] compData) {
int inOffset = 0;
int outOffset = 0;
for(int i = 0; i < numRows; i++) {
outOffset = packBits(data, inOffset, bytesPerRow,
compData, outOffset);
inOffset += bytesPerRow;
}
return outOffset;
}
/**
* Performs PackBits compression for a single buffer of data.
* This should be called for each row of each tile. The returned
* value is the offset into the output buffer after compression.
*/
private static int packBits(byte[] input, int inOffset, int inCount,
byte[] output, int outOffset) {
int inMax = inOffset + inCount - 1;
int inMaxMinus1 = inMax - 1;
while(inOffset <= inMax) {
int run = 1;
byte replicate = input[inOffset];
while(run < 127 && inOffset < inMax &&
input[inOffset] == input[inOffset+1]) {
run++;
inOffset++;
}
if(run > 1) {
inOffset++;
output[outOffset++] = (byte)(-(run - 1));
output[outOffset++] = replicate;
}
run = 0;
int saveOffset = outOffset;
while(run < 128 &&
((inOffset < inMax &&
input[inOffset] != input[inOffset+1]) ||
(inOffset < inMaxMinus1 &&
input[inOffset] != input[inOffset+2]))) {
run++;
output[++outOffset] = input[inOffset++];
}
if(run > 0) {
output[saveOffset] = (byte)(run - 1);
outOffset++;
}
if(inOffset == inMax) {
if(run > 0 && run < 128) {
output[saveOffset]++;
output[outOffset++] = input[inOffset++];
} else {
output[outOffset++] = (byte)0;
output[outOffset++] = input[inOffset++];
}
}
}
return outOffset;
}
private static int deflate(Deflater deflater,
byte[] inflated, byte[] deflated) {
deflater.setInput(inflated);
deflater.finish();
int numCompressedBytes = deflater.deflate(deflated);
deflater.reset();
return numCompressedBytes;
}
}