// Attempt to create a temporary file.
tempFile = File.createTempFile("jai-SOS-", ".tmp");
tempFile.deleteOnExit();
RandomAccessFile raFile =
new RandomAccessFile(tempFile, "rw");
output = new SeekableOutputStream(raFile);
// XXX Be sure that this file is deleted no matter how
// this method is exited!
} catch(Exception e) {
tempFile = null;
// 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 tile offsets from 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;
// IFD offsets must be on a word boundary.
if(nextIFDOffset % 2 != 0) {
nextIFDOffset++;
skipByte = true;
}
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.
// Close the original SeekableOutputStream.