in src/main/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoder.java [173:946]
private int encode(RenderedImage im, TIFFEncodeParam encodeParam,
int ifdOffset, boolean isLast) throws IOException {
// Currently all images are stored uncompressed.
CompressionValue compression = encodeParam.getCompression();
if (compression == CompressionValue.JPEG_TTN2) {
throw new IllegalArgumentException(PropertyUtil.getString("TIFFImageEncoder12"));
}
// 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();
ColorModel colorModel = im.getColorModel();
int[] sampleSize = sampleModel.getSampleSize();
int dataTypeSize = sampleSize[0];
int numBands = sampleModel.getNumBands();
int dataType = sampleModel.getDataType();
validateImage(dataTypeSize, sampleSize, numBands, dataType, colorModel);
boolean dataTypeIsShort = dataType == DataBuffer.TYPE_SHORT
|| dataType == DataBuffer.TYPE_USHORT;
// Set image type.
ImageInfo imageInfo = ImageInfo.newInstance(im, dataTypeSize, numBands, colorModel,
encodeParam);
if (imageInfo.getType() == ImageType.UNSUPPORTED) {
throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder8"));
}
final int numTiles = imageInfo.getNumTiles();
final long bytesPerTile = imageInfo.getBytesPerTile();
final long bytesPerRow = imageInfo.getBytesPerRow();
final int tileHeight = imageInfo.getTileHeight();
final int tileWidth = imageInfo.getTileWidth();
long[] tileByteCounts = new long[numTiles];
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];
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<TIFFField> fields = new TreeSet<TIFFField>();
// Image Width
fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_WIDTH,
TIFFField.TIFF_LONG, 1,
new long[] {width}));
// Image Length
fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_LENGTH,
TIFFField.TIFF_LONG, 1,
new long[] {height}));
char [] shortSampleSize = new char[numBands];
for (int i = 0; i < numBands; i++) {
shortSampleSize[i] = (char) dataTypeSize;
}
fields.add(new TIFFField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE,
TIFFField.TIFF_SHORT, numBands,
shortSampleSize));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_COMPRESSION,
TIFFField.TIFF_SHORT, 1,
new char[] {(char)compression.getValue()}));
fields.add(
new TIFFField(TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION,
TIFFField.TIFF_SHORT, 1,
new char[] {(char) imageInfo.getType().getPhotometricInterpretation()}));
if (!isTiled) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_OFFSETS,
TIFFField.TIFF_LONG, numTiles,
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[] {tileHeight}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS,
TIFFField.TIFF_LONG, numTiles,
tileByteCounts));
}
if (imageInfo.getColormap() != null) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_COLORMAP,
TIFFField.TIFF_SHORT, imageInfo.getColormapSize(),
imageInfo.getColormap()));
}
if (isTiled) {
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_WIDTH,
TIFFField.TIFF_LONG, 1,
new long[] {tileWidth}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_LENGTH,
TIFFField.TIFF_LONG, 1,
new long[] {tileHeight}));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_OFFSETS,
TIFFField.TIFF_LONG, numTiles,
tileOffsets));
fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS,
TIFFField.TIFF_LONG, numTiles,
tileByteCounts));
}
if (imageInfo.getNumberOfExtraSamples() > 0) {
char[] extraSamples = new char[imageInfo.getNumberOfExtraSamples()];
for (int i = 0; i < imageInfo.getNumberOfExtraSamples(); i++) {
extraSamples[i] = (char) imageInfo.getExtraSamplesType().getValue();
}
fields.add(new TIFFField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES,
TIFFField.TIFF_SHORT, imageInfo.getNumberOfExtraSamples(),
extraSamples));
}
// Data Sample Format Extension fields.
if (dataType != DataBuffer.TYPE_BYTE) {
// SampleFormat
char[] sampleFormat = new char[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,
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.
}
if (imageInfo.getType() == ImageType.YCBCR) {
// YCbCrSubSampling: 2 is the default so we must write 1 as
// we do not (yet) do any subsampling.
char subsampleH = 1;
char subsampleV = 1;
fields.add(new TIFFField(TIFF_YCBCR_SUBSAMPLING,
TIFFField.TIFF_SHORT, 2,
new char[] {subsampleH, subsampleV}));
// YCbCr positioning.
fields.add(new TIFFField(TIFF_YCBCR_POSITIONING,
TIFFField.TIFF_SHORT, 1,
new char[]
{(char) ((compression == CompressionValue.JPEG_TTN2) ? 1 : 2)}));
// Reference black/white.
long[][] refbw;
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();
List extantTags = new ArrayList(fields.size());
for (TIFFField fld : fields) {
extantTags.add(fld.getTag());
}
for (TIFFField fld : extraFields) {
Integer tagValue = 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 compressed.
// 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;
boolean jpegRGBToYCbCr = false;
if (compression == CompressionValue.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 (dataTypeSize == 16 && tileOffsets[0] % 2 != 0) {
numBytesPadding = 1;
tileOffsets[0]++;
} else if (dataTypeSize == 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 & 0x01) != 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 (IOException e) {
// Allocate memory for the entire image data (!).
output = new ByteArrayOutputStream((int)totalBytesOfData);
}
}
int bufSize = 0;
switch(compression) {
case PACKBITS:
bufSize = (int) (bytesPerTile + ((bytesPerRow + 127) / 128) * tileHeight);
break;
case 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 =
((dataTypeSize == 1
&& sampleModel instanceof MultiPixelPackedSampleModel
&& dataType == DataBuffer.TYPE_BYTE)
|| (dataTypeSize == 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 != CompressionValue.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 != CompressionValue.JPEG_TTN2) { // JPEG access Raster
if (checkContiguous) {
if (dataTypeSize == 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 (dataTypeSize) {
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 == 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 == CompressionValue.NONE) {
output.write(bpixels, 0, rows * ((tileWidth + 7) / 8));
} else if (compression == CompressionValue.PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if (compression == CompressionValue.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 & 1) == 1) {
pixel = pixels[index++] << 4;
bpixels[k++] = (byte)pixel;
}
}
if (compression == CompressionValue.NONE) {
output.write(bpixels, 0, rows * ((tileWidth + 1) / 2));
} else if (compression == CompressionValue.PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if (compression == CompressionValue.DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
case 8:
if (compression != CompressionValue.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 == 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 == CompressionValue.NONE) {
output.write(bpixels, 0, size);
} else if (compression == CompressionValue.PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if (compression == CompressionValue.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++) {
int value = pixels[i];
bpixels[ls++] = (byte)((value & 0xff00) >> 8);
bpixels[ls++] = (byte) (value & 0x00ff);
}
if (compression == CompressionValue.NONE) {
output.write(bpixels, 0, size * 2);
} else if (compression == CompressionValue.PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if (compression == CompressionValue.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 == CompressionValue.NONE) {
output.write(bpixels, 0, size * 4);
} else if (compression == CompressionValue.PACKBITS) {
int numCompressedBytes =
compressPackBits(bpixels, rows,
bytesPerRow,
compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
} else if (compression == CompressionValue.DEFLATE) {
int numCompressedBytes =
deflate(deflater, bpixels, compressBuf);
tileByteCounts[tileNum++] = numCompressedBytes;
output.write(compressBuf, 0, numCompressedBytes);
}
break;
default:
break;
}
}
}
if (compression == CompressionValue.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;
if ((nextIFDOffset & 0x01) != 0) { // make it even
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.
// Open a FileInputStream from which to copy the data.
FileInputStream fileStream = new FileInputStream(tempFile);
try {
// 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;
}
} finally {
// Delete the temporary file.
fileStream.close();
}
boolean isDeleted = tempFile.delete();
assert isDeleted;
// 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(PropertyUtil.getString("TIFFImageEncoder13"));
}
}
return nextIFDOffset;
}