0001: /*
0002: * $RCSfile: TIFFImageEncoder.java,v $
0003: *
0004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Use is subject to license terms.
0007: *
0008: * $Revision: 1.3 $
0009: * $Date: 2006/02/22 23:03:30 $
0010: * $State: Exp $
0011: */
0012: package com.sun.media.jai.codecimpl;
0013:
0014: import java.io.ByteArrayOutputStream;
0015: import java.io.File;
0016: import java.io.FileInputStream;
0017: import java.io.IOException;
0018: import java.io.OutputStream;
0019: import java.io.RandomAccessFile;
0020: import java.awt.Point;
0021: import java.awt.Rectangle;
0022: import java.awt.color.ColorSpace;
0023: import java.awt.image.BufferedImage;
0024: import java.awt.image.ColorModel;
0025: import java.awt.image.ComponentSampleModel;
0026: import java.awt.image.DataBuffer;
0027: import java.awt.image.DataBufferByte;
0028: import java.awt.image.IndexColorModel;
0029: import java.awt.image.MultiPixelPackedSampleModel;
0030: import java.awt.image.Raster;
0031: import java.awt.image.RenderedImage;
0032: import java.awt.image.SampleModel;
0033: import java.awt.image.WritableRaster;
0034: import java.util.ArrayList;
0035: import java.util.Iterator;
0036: import java.util.SortedSet;
0037: import java.util.TreeSet;
0038: import java.util.zip.Deflater;
0039: import com.sun.media.jai.codec.ImageEncoderImpl;
0040: import com.sun.media.jai.codec.ImageEncodeParam;
0041: import com.sun.media.jai.codec.JPEGEncodeParam;
0042: import com.sun.media.jai.codec.SeekableOutputStream;
0043: import com.sun.media.jai.codec.TIFFEncodeParam;
0044: import com.sun.media.jai.codec.TIFFField;
0045: import com.sun.media.jai.codecimpl.util.RasterFactory;
0046:
0047: /**
0048: * A baseline TIFF writer. The writer outputs TIFF images in either Bilevel,
0049: * Greyscale, Palette color or Full Color modes.
0050: *
0051: * @since EA4
0052: */
0053: public class TIFFImageEncoder extends ImageEncoderImpl {
0054:
0055: // Image Types
0056: private static final int TIFF_UNSUPPORTED = -1;
0057: private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0;
0058: private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1;
0059: private static final int TIFF_GRAY = 2;
0060: private static final int TIFF_PALETTE = 3;
0061: private static final int TIFF_RGB = 4;
0062: private static final int TIFF_CMYK = 5;
0063: private static final int TIFF_YCBCR = 6;
0064: private static final int TIFF_CIELAB = 7;
0065: private static final int TIFF_GENERIC = 8;
0066:
0067: // Compression types
0068: private static final int COMP_NONE = TIFFEncodeParam.COMPRESSION_NONE;
0069: private static final int COMP_GROUP3_1D = TIFFEncodeParam.COMPRESSION_GROUP3_1D;
0070: private static final int COMP_GROUP3_2D = TIFFEncodeParam.COMPRESSION_GROUP3_2D;
0071: private static final int COMP_GROUP4 = TIFFEncodeParam.COMPRESSION_GROUP4;
0072: private static final int COMP_JPEG_TTN2 = TIFFEncodeParam.COMPRESSION_JPEG_TTN2;
0073: private static final int COMP_PACKBITS = TIFFEncodeParam.COMPRESSION_PACKBITS;
0074: private static final int COMP_DEFLATE = TIFFEncodeParam.COMPRESSION_DEFLATE;
0075:
0076: // Incidental tags
0077: private static final int TIFF_JPEG_TABLES = 347;
0078: private static final int TIFF_YCBCR_SUBSAMPLING = 530;
0079: private static final int TIFF_YCBCR_POSITIONING = 531;
0080: private static final int TIFF_REF_BLACK_WHITE = 532;
0081:
0082: // ExtraSamples types
0083: private static final int EXTRA_SAMPLE_UNSPECIFIED = 0;
0084: private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA = 1;
0085: private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
0086:
0087: // Default values
0088: private static final int DEFAULT_ROWS_PER_STRIP = 8;
0089:
0090: // Little endian flag
0091: private boolean isLittleEndian = false;
0092:
0093: private static final char[] intsToChars(int[] intArray) {
0094: int arrayLength = intArray.length;
0095: char[] charArray = new char[arrayLength];
0096: for (int i = 0; i < arrayLength; i++) {
0097: charArray[i] = (char) (intArray[i] & 0x0000ffff);
0098: }
0099: return charArray;
0100: }
0101:
0102: public TIFFImageEncoder(OutputStream output, ImageEncodeParam param) {
0103: super (output, param);
0104: if (this .param == null) {
0105: this .param = new TIFFEncodeParam();
0106: }
0107: }
0108:
0109: /**
0110: * Encodes a RenderedImage and writes the output to the
0111: * OutputStream associated with this ImageEncoder.
0112: */
0113: public void encode(RenderedImage im) throws IOException {
0114: // Get the encoding parameters.
0115: TIFFEncodeParam encodeParam = (TIFFEncodeParam) param;
0116:
0117: // Set the byte order flag before any data are written.
0118: isLittleEndian = encodeParam.getLittleEndian();
0119:
0120: // Write the file header (8 bytes).
0121: writeFileHeader();
0122:
0123: Iterator iter = encodeParam.getExtraImages();
0124: if (iter != null) {
0125: int ifdOffset = 8;
0126: RenderedImage nextImage = im;
0127: TIFFEncodeParam nextParam = encodeParam;
0128: boolean hasNext;
0129: do {
0130: hasNext = iter.hasNext();
0131: ifdOffset = encode(nextImage, nextParam, ifdOffset,
0132: !hasNext);
0133: if (hasNext) {
0134: Object obj = iter.next();
0135: if (obj instanceof RenderedImage) {
0136: nextImage = (RenderedImage) obj;
0137: nextParam = encodeParam;
0138: } else if (obj instanceof Object[]) {
0139: Object[] o = (Object[]) obj;
0140: nextImage = (RenderedImage) o[0];
0141: nextParam = (TIFFEncodeParam) o[1];
0142: }
0143: }
0144: } while (hasNext);
0145: } else {
0146: encode(im, encodeParam, 8, true);
0147: }
0148: }
0149:
0150: private int encode(RenderedImage im, TIFFEncodeParam encodeParam,
0151: int ifdOffset, boolean isLast) throws IOException {
0152: // Cannot store a packed byte image directly so reformat it.
0153: if (CodecUtils.isPackedByteImage(im)) {
0154: // Get the source ColorModel.
0155: ColorModel sourceCM = im.getColorModel();
0156:
0157: // Create an equivalent ComponentColorModel.
0158: ColorModel destCM = RasterFactory
0159: .createComponentColorModel(DataBuffer.TYPE_BYTE,
0160: sourceCM.getColorSpace(), sourceCM
0161: .hasAlpha(), sourceCM
0162: .isAlphaPremultiplied(), sourceCM
0163: .getTransparency());
0164:
0165: // Create a raster which can contain the entire source.
0166: Point origin = new Point(im.getMinX(), im.getMinY());
0167: WritableRaster raster = Raster.createWritableRaster(destCM
0168: .createCompatibleSampleModel(im.getWidth(), im
0169: .getHeight()), origin);
0170:
0171: // Copy the source data.
0172: raster.setRect(im.getData());
0173:
0174: // Replace the source reference with the new image.
0175: im = new SingleTileRenderedImage(raster, destCM);
0176: }
0177:
0178: // Currently all images are stored uncompressed.
0179: int compression = encodeParam.getCompression();
0180:
0181: // Get tiled output preference.
0182: boolean isTiled = encodeParam.getWriteTiled();
0183:
0184: // Set bounds.
0185: int minX = im.getMinX();
0186: int minY = im.getMinY();
0187: int width = im.getWidth();
0188: int height = im.getHeight();
0189:
0190: // Get SampleModel.
0191: SampleModel sampleModel = im.getSampleModel();
0192:
0193: // Retrieve and verify sample size.
0194: int sampleSize[] = sampleModel.getSampleSize();
0195: for (int i = 1; i < sampleSize.length; i++) {
0196: if (sampleSize[i] != sampleSize[0]) {
0197: throw new RuntimeException(JaiI18N
0198: .getString("TIFFImageEncoder0"));
0199: }
0200: }
0201:
0202: // Check low bit limits.
0203: int numBands = sampleModel.getNumBands();
0204: if ((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) {
0205: throw new RuntimeException(JaiI18N
0206: .getString("TIFFImageEncoder1"));
0207: }
0208:
0209: // Retrieve and verify data type.
0210: int dataType = sampleModel.getDataType();
0211: switch (dataType) {
0212: case DataBuffer.TYPE_BYTE:
0213: if (sampleSize[0] != 1 && sampleSize[0] != 4
0214: && sampleSize[0] != 8) {
0215: throw new RuntimeException(JaiI18N
0216: .getString("TIFFImageEncoder2"));
0217: }
0218: break;
0219: case DataBuffer.TYPE_SHORT:
0220: case DataBuffer.TYPE_USHORT:
0221: if (sampleSize[0] != 16) {
0222: throw new RuntimeException(JaiI18N
0223: .getString("TIFFImageEncoder3"));
0224: }
0225: break;
0226: case DataBuffer.TYPE_INT:
0227: case DataBuffer.TYPE_FLOAT:
0228: if (sampleSize[0] != 32) {
0229: throw new RuntimeException(JaiI18N
0230: .getString("TIFFImageEncoder4"));
0231: }
0232: break;
0233: default:
0234: throw new RuntimeException(JaiI18N
0235: .getString("TIFFImageEncoder5"));
0236: }
0237:
0238: boolean dataTypeIsShort = dataType == DataBuffer.TYPE_SHORT
0239: || dataType == DataBuffer.TYPE_USHORT;
0240:
0241: ColorModel colorModel = im.getColorModel();
0242: if (colorModel != null && colorModel instanceof IndexColorModel
0243: && dataType != DataBuffer.TYPE_BYTE) {
0244: // Don't support (unsigned) short palette-color images.
0245: throw new RuntimeException(JaiI18N
0246: .getString("TIFFImageEncoder6"));
0247: }
0248: IndexColorModel icm = null;
0249: int sizeOfColormap = 0;
0250: int colormap[] = null;
0251:
0252: // Set image type.
0253: int imageType = TIFF_UNSUPPORTED;
0254: int numExtraSamples = 0;
0255: int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED;
0256: if (colorModel instanceof IndexColorModel) { // Bilevel or palette
0257: icm = (IndexColorModel) colorModel;
0258: int mapSize = icm.getMapSize();
0259:
0260: if (sampleSize[0] == 1 && numBands == 1) { // Bilevel image
0261:
0262: if (mapSize != 2) {
0263: throw new IllegalArgumentException(JaiI18N
0264: .getString("TIFFImageEncoder7"));
0265: }
0266:
0267: byte r[] = new byte[mapSize];
0268: icm.getReds(r);
0269: byte g[] = new byte[mapSize];
0270: icm.getGreens(g);
0271: byte b[] = new byte[mapSize];
0272: icm.getBlues(b);
0273:
0274: if ((r[0] & 0xff) == 0 && (r[1] & 0xff) == 255
0275: && (g[0] & 0xff) == 0 && (g[1] & 0xff) == 255
0276: && (b[0] & 0xff) == 0 && (b[1] & 0xff) == 255) {
0277:
0278: imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
0279:
0280: } else if ((r[0] & 0xff) == 255 && (r[1] & 0xff) == 0
0281: && (g[0] & 0xff) == 255 && (g[1] & 0xff) == 0
0282: && (b[0] & 0xff) == 255 && (b[1] & 0xff) == 0) {
0283:
0284: imageType = TIFF_BILEVEL_WHITE_IS_ZERO;
0285:
0286: } else {
0287: imageType = TIFF_PALETTE;
0288: }
0289:
0290: } else if (numBands == 1) { // Non-bilevel image.
0291: // Palette color image.
0292: imageType = TIFF_PALETTE;
0293: }
0294: } else if (colorModel == null) {
0295:
0296: if (sampleSize[0] == 1 && numBands == 1) { // bilevel
0297: imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
0298: } else { // generic image
0299: imageType = TIFF_GENERIC;
0300: if (numBands > 1) {
0301: numExtraSamples = numBands - 1;
0302: }
0303: }
0304:
0305: } else { // colorModel is non-null but not an IndexColorModel
0306: ColorSpace colorSpace = colorModel.getColorSpace();
0307:
0308: switch (colorSpace.getType()) {
0309: case ColorSpace.TYPE_CMYK:
0310: imageType = TIFF_CMYK;
0311: break;
0312: case ColorSpace.TYPE_GRAY:
0313: imageType = TIFF_GRAY;
0314: break;
0315: case ColorSpace.TYPE_Lab:
0316: imageType = TIFF_CIELAB;
0317: break;
0318: case ColorSpace.TYPE_RGB:
0319: if (compression == COMP_JPEG_TTN2
0320: && encodeParam.getJPEGCompressRGBToYCbCr()) {
0321: imageType = TIFF_YCBCR;
0322: } else {
0323: imageType = TIFF_RGB;
0324: }
0325: break;
0326: case ColorSpace.TYPE_YCbCr:
0327: imageType = TIFF_YCBCR;
0328: break;
0329: default:
0330: imageType = TIFF_GENERIC; // generic
0331: break;
0332: }
0333:
0334: if (imageType == TIFF_GENERIC) {
0335: numExtraSamples = numBands - 1;
0336: } else if (numBands > 1) {
0337: numExtraSamples = numBands
0338: - colorSpace.getNumComponents();
0339: }
0340:
0341: if (numExtraSamples == 1 && colorModel.hasAlpha()) {
0342: extraSampleType = colorModel.isAlphaPremultiplied() ? EXTRA_SAMPLE_ASSOCIATED_ALPHA
0343: : EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
0344: }
0345: }
0346:
0347: if (imageType == TIFF_UNSUPPORTED) {
0348: throw new RuntimeException(JaiI18N
0349: .getString("TIFFImageEncoder8"));
0350: }
0351:
0352: // Check JPEG compatibility.
0353: if (compression == COMP_JPEG_TTN2) {
0354: if (imageType == TIFF_PALETTE) {
0355: throw new RuntimeException(JaiI18N
0356: .getString("TIFFImageEncoder11"));
0357: } else if (!(sampleSize[0] == 8 && (imageType == TIFF_GRAY
0358: || imageType == TIFF_RGB || imageType == TIFF_YCBCR))) {
0359: throw new RuntimeException(JaiI18N
0360: .getString("TIFFImageEncoder9"));
0361: }
0362: }
0363:
0364: // Check bilevel encoding compatibility.
0365: if ((imageType != TIFF_BILEVEL_WHITE_IS_ZERO && imageType != TIFF_BILEVEL_BLACK_IS_ZERO)
0366: && (compression == COMP_GROUP3_1D
0367: || compression == COMP_GROUP3_2D || compression == COMP_GROUP4)) {
0368: throw new RuntimeException(JaiI18N
0369: .getString("TIFFImageEncoder12"));
0370: }
0371:
0372: int photometricInterpretation = -1;
0373: switch (imageType) {
0374:
0375: case TIFF_BILEVEL_WHITE_IS_ZERO:
0376: photometricInterpretation = 0;
0377: break;
0378:
0379: case TIFF_BILEVEL_BLACK_IS_ZERO:
0380: photometricInterpretation = 1;
0381: break;
0382:
0383: case TIFF_GRAY:
0384: case TIFF_GENERIC:
0385: // Since the CS_GRAY colorspace is always of type black_is_zero
0386: photometricInterpretation = 1;
0387: break;
0388:
0389: case TIFF_PALETTE:
0390: photometricInterpretation = 3;
0391:
0392: icm = (IndexColorModel) colorModel;
0393: sizeOfColormap = icm.getMapSize();
0394:
0395: byte r[] = new byte[sizeOfColormap];
0396: icm.getReds(r);
0397: byte g[] = new byte[sizeOfColormap];
0398: icm.getGreens(g);
0399: byte b[] = new byte[sizeOfColormap];
0400: icm.getBlues(b);
0401:
0402: int redIndex = 0,
0403: greenIndex = sizeOfColormap;
0404: int blueIndex = 2 * sizeOfColormap;
0405: colormap = new int[sizeOfColormap * 3];
0406: for (int i = 0; i < sizeOfColormap; i++) {
0407: colormap[redIndex++] = (r[i] << 8) & 0xffff;
0408: colormap[greenIndex++] = (g[i] << 8) & 0xffff;
0409: colormap[blueIndex++] = (b[i] << 8) & 0xffff;
0410: }
0411:
0412: sizeOfColormap *= 3;
0413:
0414: break;
0415:
0416: case TIFF_RGB:
0417: photometricInterpretation = 2;
0418: break;
0419:
0420: case TIFF_CMYK:
0421: photometricInterpretation = 5;
0422: break;
0423:
0424: case TIFF_YCBCR:
0425: photometricInterpretation = 6;
0426: break;
0427:
0428: case TIFF_CIELAB:
0429: photometricInterpretation = 8;
0430: break;
0431:
0432: default:
0433: throw new RuntimeException(JaiI18N
0434: .getString("TIFFImageEncoder8"));
0435: }
0436:
0437: // Initialize tile dimensions.
0438: int tileWidth;
0439: int tileHeight;
0440: if (isTiled) {
0441: tileWidth = encodeParam.getTileWidth() > 0 ? encodeParam
0442: .getTileWidth() : im.getTileWidth();
0443: tileHeight = encodeParam.getTileHeight() > 0 ? encodeParam
0444: .getTileHeight() : im.getTileHeight();
0445: } else {
0446: tileWidth = width;
0447: // XXX Set rows per strip based on memory value if not specified?
0448: tileHeight = encodeParam.getTileHeight() > 0 ? encodeParam
0449: .getTileHeight() : DEFAULT_ROWS_PER_STRIP;
0450: }
0451:
0452: // Re-tile for JPEG conformance if needed.
0453: JPEGEncodeParam jep = null;
0454: if (compression == COMP_JPEG_TTN2) {
0455: // Get JPEGEncodeParam from encodeParam.
0456: jep = encodeParam.getJPEGEncodeParam();
0457:
0458: // Determine maximum subsampling.
0459: int maxSubH = jep.getHorizontalSubsampling(0);
0460: int maxSubV = jep.getVerticalSubsampling(0);
0461: for (int i = 1; i < numBands; i++) {
0462: int subH = jep.getHorizontalSubsampling(i);
0463: if (subH > maxSubH) {
0464: maxSubH = subH;
0465: }
0466: int subV = jep.getVerticalSubsampling(i);
0467: if (subV > maxSubV) {
0468: maxSubV = subV;
0469: }
0470: }
0471:
0472: int factorV = 8 * maxSubV;
0473: tileHeight = (int) ((float) tileHeight / (float) factorV + 0.5F)
0474: * factorV;
0475: if (tileHeight < factorV) {
0476: tileHeight = factorV;
0477: }
0478:
0479: if (isTiled) {
0480: int factorH = 8 * maxSubH;
0481: tileWidth = (int) ((float) tileWidth / (float) factorH + 0.5F)
0482: * factorH;
0483: if (tileWidth < factorH) {
0484: tileWidth = factorH;
0485: }
0486: }
0487: }
0488:
0489: int numTiles;
0490: if (isTiled) {
0491: // NB: Parentheses are used in this statement for correct rounding.
0492: numTiles = ((width + tileWidth - 1) / tileWidth)
0493: * ((height + tileHeight - 1) / tileHeight);
0494: } else {
0495: numTiles = (int) Math.ceil((double) height
0496: / (double) tileHeight);
0497: }
0498:
0499: long tileByteCounts[] = new long[numTiles];
0500:
0501: long bytesPerRow = (long) Math.ceil((sampleSize[0] / 8.0)
0502: * tileWidth * numBands);
0503:
0504: long bytesPerTile = bytesPerRow * tileHeight;
0505:
0506: for (int i = 0; i < numTiles; i++) {
0507: tileByteCounts[i] = bytesPerTile;
0508: }
0509:
0510: if (!isTiled) {
0511: // Last strip may have lesser rows
0512: long lastStripRows = height - (tileHeight * (numTiles - 1));
0513: tileByteCounts[numTiles - 1] = lastStripRows * bytesPerRow;
0514: }
0515:
0516: long totalBytesOfData = bytesPerTile * (numTiles - 1)
0517: + tileByteCounts[numTiles - 1];
0518:
0519: // The data will be written after the IFD: create the array here
0520: // but fill it in later.
0521: long tileOffsets[] = new long[numTiles];
0522:
0523: // Basic fields - have to be in increasing numerical order.
0524: // ImageWidth 256
0525: // ImageLength 257
0526: // BitsPerSample 258
0527: // Compression 259
0528: // PhotoMetricInterpretation 262
0529: // StripOffsets 273
0530: // RowsPerStrip 278
0531: // StripByteCounts 279
0532: // XResolution 282
0533: // YResolution 283
0534: // ResolutionUnit 296
0535:
0536: // Create Directory
0537: SortedSet fields = new TreeSet();
0538:
0539: // Image Width
0540: fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_WIDTH,
0541: TIFFField.TIFF_LONG, 1,
0542: (Object) (new long[] { (long) width })));
0543:
0544: // Image Length
0545: fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_LENGTH,
0546: TIFFField.TIFF_LONG, 1, new long[] { (long) height }));
0547:
0548: fields
0549: .add(new TIFFField(
0550: TIFFImageDecoder.TIFF_BITS_PER_SAMPLE,
0551: TIFFField.TIFF_SHORT, numBands,
0552: intsToChars(sampleSize)));
0553:
0554: fields.add(new TIFFField(TIFFImageDecoder.TIFF_COMPRESSION,
0555: TIFFField.TIFF_SHORT, 1,
0556: new char[] { (char) compression }));
0557:
0558: fields.add(new TIFFField(
0559: TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION,
0560: TIFFField.TIFF_SHORT, 1,
0561: new char[] { (char) photometricInterpretation }));
0562:
0563: if (!isTiled) {
0564: fields
0565: .add(new TIFFField(
0566: TIFFImageDecoder.TIFF_STRIP_OFFSETS,
0567: TIFFField.TIFF_LONG, numTiles,
0568: (long[]) tileOffsets));
0569: }
0570:
0571: fields
0572: .add(new TIFFField(
0573: TIFFImageDecoder.TIFF_SAMPLES_PER_PIXEL,
0574: TIFFField.TIFF_SHORT, 1,
0575: new char[] { (char) numBands }));
0576:
0577: if (!isTiled) {
0578: fields.add(new TIFFField(
0579: TIFFImageDecoder.TIFF_ROWS_PER_STRIP,
0580: TIFFField.TIFF_LONG, 1,
0581: new long[] { (long) tileHeight }));
0582:
0583: fields.add(new TIFFField(
0584: TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS,
0585: TIFFField.TIFF_LONG, numTiles,
0586: (long[]) tileByteCounts));
0587: }
0588:
0589: if (colormap != null) {
0590: fields.add(new TIFFField(TIFFImageDecoder.TIFF_COLORMAP,
0591: TIFFField.TIFF_SHORT, sizeOfColormap,
0592: intsToChars(colormap)));
0593: }
0594:
0595: if (isTiled) {
0596: fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_WIDTH,
0597: TIFFField.TIFF_LONG, 1,
0598: new long[] { (long) tileWidth }));
0599:
0600: fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_LENGTH,
0601: TIFFField.TIFF_LONG, 1,
0602: new long[] { (long) tileHeight }));
0603:
0604: fields
0605: .add(new TIFFField(
0606: TIFFImageDecoder.TIFF_TILE_OFFSETS,
0607: TIFFField.TIFF_LONG, numTiles,
0608: (long[]) tileOffsets));
0609:
0610: fields.add(new TIFFField(
0611: TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS,
0612: TIFFField.TIFF_LONG, numTiles,
0613: (long[]) tileByteCounts));
0614: }
0615:
0616: if (numExtraSamples > 0) {
0617: int[] extraSamples = new int[numExtraSamples];
0618: for (int i = 0; i < numExtraSamples; i++) {
0619: extraSamples[i] = extraSampleType;
0620: }
0621: fields.add(new TIFFField(
0622: TIFFImageDecoder.TIFF_EXTRA_SAMPLES,
0623: TIFFField.TIFF_SHORT, numExtraSamples,
0624: intsToChars(extraSamples)));
0625: }
0626:
0627: // Data Sample Format Extension fields.
0628: if (dataType != DataBuffer.TYPE_BYTE) {
0629: // SampleFormat
0630: int[] sampleFormat = new int[numBands];
0631: if (dataType == DataBuffer.TYPE_FLOAT) {
0632: sampleFormat[0] = 3;
0633: } else if (dataType == DataBuffer.TYPE_USHORT) {
0634: sampleFormat[0] = 1;
0635: } else {
0636: sampleFormat[0] = 2;
0637: }
0638: for (int b = 1; b < numBands; b++) {
0639: sampleFormat[b] = sampleFormat[0];
0640: }
0641: fields.add(new TIFFField(
0642: TIFFImageDecoder.TIFF_SAMPLE_FORMAT,
0643: TIFFField.TIFF_SHORT, numBands,
0644: intsToChars(sampleFormat)));
0645:
0646: // NOTE: We don't bother setting the SMinSampleValue and
0647: // SMaxSampleValue fields as these both default to the
0648: // extrema of the respective data types. Probably we should
0649: // check for the presence of the "extrema" property and
0650: // use it if available.
0651: }
0652:
0653: // Bilevel compression variables.
0654: boolean inverseFill = encodeParam.getReverseFillOrder();
0655: boolean T4encode2D = encodeParam.getT4Encode2D();
0656: boolean T4PadEOLs = encodeParam.getT4PadEOLs();
0657: TIFFFaxEncoder faxEncoder = null;
0658:
0659: // Add bilevel compression fields.
0660: if ((imageType == TIFF_BILEVEL_BLACK_IS_ZERO || imageType == TIFF_BILEVEL_WHITE_IS_ZERO)
0661: && (compression == COMP_GROUP3_1D
0662: || compression == COMP_GROUP3_2D || compression == COMP_GROUP4)) {
0663:
0664: // Create the encoder.
0665: faxEncoder = new TIFFFaxEncoder(inverseFill);
0666:
0667: // FillOrder field.
0668: fields.add(new TIFFField(TIFFImageDecoder.TIFF_FILL_ORDER,
0669: TIFFField.TIFF_SHORT, 1,
0670: new char[] { inverseFill ? (char) 2 : (char) 1 }));
0671:
0672: if (compression == COMP_GROUP3_2D) {
0673: // T4Options field.
0674: long T4Options = 0x00000000;
0675: if (T4encode2D) {
0676: T4Options |= 0x00000001;
0677: }
0678: if (T4PadEOLs) {
0679: T4Options |= 0x00000004;
0680: }
0681: fields.add(new TIFFField(
0682: TIFFImageDecoder.TIFF_T4_OPTIONS,
0683: TIFFField.TIFF_LONG, 1,
0684: new long[] { T4Options }));
0685: } else if (compression == COMP_GROUP4) {
0686: // T6Options field.
0687: fields.add(new TIFFField(
0688: TIFFImageDecoder.TIFF_T6_OPTIONS,
0689: TIFFField.TIFF_LONG, 1,
0690: new long[] { (long) 0x00000000 }));
0691: }
0692: }
0693:
0694: // Initialize some JPEG variables.
0695: com.sun.image.codec.jpeg.JPEGEncodeParam jpegEncodeParam = null;
0696: com.sun.image.codec.jpeg.JPEGImageEncoder jpegEncoder = null;
0697: int jpegColorID = 0;
0698:
0699: if (compression == COMP_JPEG_TTN2) {
0700:
0701: // Initialize JPEG color ID.
0702: jpegColorID = com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_UNKNOWN;
0703: switch (imageType) {
0704: case TIFF_GRAY:
0705: case TIFF_PALETTE:
0706: jpegColorID = com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_GRAY;
0707: break;
0708: case TIFF_RGB:
0709: jpegColorID = com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_RGB;
0710: break;
0711: case TIFF_YCBCR:
0712: jpegColorID = com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_YCbCr;
0713: break;
0714: }
0715:
0716: // Get the JDK encoding parameters.
0717: Raster tile00 = im.getTile(im.getMinTileX(), im
0718: .getMinTileY());
0719: jpegEncodeParam = com.sun.image.codec.jpeg.JPEGCodec
0720: .getDefaultJPEGEncodeParam(tile00, jpegColorID);
0721:
0722: // Modify per values passed in.
0723: JPEGImageEncoder.modifyEncodeParam(jep, jpegEncodeParam,
0724: numBands);
0725:
0726: // JPEGTables field.
0727: if (jep.getWriteImageOnly()) {
0728: // Write an abbreviated tables-only stream to JPEGTables field.
0729: jpegEncodeParam.setImageInfoValid(false);
0730: jpegEncodeParam.setTableInfoValid(true);
0731: ByteArrayOutputStream tableStream = new ByteArrayOutputStream();
0732: jpegEncoder = com.sun.image.codec.jpeg.JPEGCodec
0733: .createJPEGEncoder(tableStream, jpegEncodeParam);
0734: jpegEncoder.encode(tile00);
0735: byte[] tableData = tableStream.toByteArray();
0736: fields.add(new TIFFField(TIFF_JPEG_TABLES,
0737: TIFFField.TIFF_UNDEFINED, tableData.length,
0738: tableData));
0739:
0740: // Reset encoder so it's recreated below.
0741: jpegEncoder = null;
0742: }
0743: }
0744:
0745: if (imageType == TIFF_YCBCR) {
0746: // YCbCrSubSampling: 2 is the default so we must write 1 as
0747: // we do not (yet) do any subsampling.
0748: int subsampleH = 1;
0749: int subsampleV = 1;
0750:
0751: // If JPEG, update values.
0752: if (compression == COMP_JPEG_TTN2) {
0753: // Determine maximum subsampling.
0754: subsampleH = jep.getHorizontalSubsampling(0);
0755: subsampleV = jep.getVerticalSubsampling(0);
0756: for (int i = 1; i < numBands; i++) {
0757: int subH = jep.getHorizontalSubsampling(i);
0758: if (subH > subsampleH) {
0759: subsampleH = subH;
0760: }
0761: int subV = jep.getVerticalSubsampling(i);
0762: if (subV > subsampleV) {
0763: subsampleV = subV;
0764: }
0765: }
0766: }
0767:
0768: fields.add(new TIFFField(TIFF_YCBCR_SUBSAMPLING,
0769: TIFFField.TIFF_SHORT, 2, new char[] {
0770: (char) subsampleH, (char) subsampleV }));
0771:
0772: // YCbCr positioning.
0773: fields
0774: .add(new TIFFField(
0775: TIFF_YCBCR_POSITIONING,
0776: TIFFField.TIFF_SHORT,
0777: 1,
0778: new char[] { compression == COMP_JPEG_TTN2 ? (char) 1
0779: : (char) 2 }));
0780:
0781: // Reference black/white.
0782: long[][] refbw;
0783: if (compression == COMP_JPEG_TTN2) {
0784: refbw = new long[][] { // no headroon/footroom
0785: { 0, 1 }, { 255, 1 }, { 128, 1 }, { 255, 1 },
0786: { 128, 1 }, { 255, 1 } };
0787: } else {
0788: refbw = new long[][] { // CCIR 601.1 headroom/footroom (presumptive)
0789: { 15, 1 }, { 235, 1 }, { 128, 1 }, { 240, 1 },
0790: { 128, 1 }, { 240, 1 } };
0791: }
0792: fields.add(new TIFFField(TIFF_REF_BLACK_WHITE,
0793: TIFFField.TIFF_RATIONAL, 6, refbw));
0794: }
0795:
0796: // ---- No more automatically generated fields should be added
0797: // after this point. ----
0798:
0799: // Add extra fields specified via the encoding parameters.
0800: TIFFField[] extraFields = encodeParam.getExtraFields();
0801: if (extraFields != null) {
0802: ArrayList extantTags = new ArrayList(fields.size());
0803: Iterator fieldIter = fields.iterator();
0804: while (fieldIter.hasNext()) {
0805: TIFFField fld = (TIFFField) fieldIter.next();
0806: extantTags.add(new Integer(fld.getTag()));
0807: }
0808:
0809: int numExtraFields = extraFields.length;
0810: for (int i = 0; i < numExtraFields; i++) {
0811: TIFFField fld = extraFields[i];
0812: Integer tagValue = new Integer(fld.getTag());
0813: if (!extantTags.contains(tagValue)) {
0814: fields.add(fld);
0815: extantTags.add(tagValue);
0816: }
0817: }
0818: }
0819:
0820: // ---- No more fields of any type should be added after this. ----
0821:
0822: // Determine the size of the IFD which is written after the header
0823: // of the stream or after the data of the previous image in a
0824: // multi-page stream.
0825: int dirSize = getDirectorySize(fields);
0826:
0827: // The first data segment is written after the field overflow
0828: // following the IFD so initialize the first offset accordingly.
0829: tileOffsets[0] = ifdOffset + dirSize;
0830:
0831: // Branch here depending on whether data are being comrpressed.
0832: // If not, then the IFD is written immediately.
0833: // If so then there are three possibilities:
0834: // A) the OutputStream is a SeekableOutputStream (outCache null);
0835: // B) the OutputStream is not a SeekableOutputStream and a file cache
0836: // is used (outCache non-null, tempFile non-null);
0837: // C) the OutputStream is not a SeekableOutputStream and a memory cache
0838: // is used (outCache non-null, tempFile null).
0839:
0840: OutputStream outCache = null;
0841: byte[] compressBuf = null;
0842: File tempFile = null;
0843:
0844: int nextIFDOffset = 0;
0845: boolean skipByte = false;
0846:
0847: Deflater deflater = null;
0848: int deflateLevel = Deflater.DEFAULT_COMPRESSION;
0849:
0850: boolean jpegRGBToYCbCr = false;
0851:
0852: if (compression == COMP_NONE) {
0853: // Determine the number of bytes of padding necessary between
0854: // the end of the IFD and the first data segment such that the
0855: // alignment of the data conforms to the specification (required
0856: // for uncompressed data only).
0857: int numBytesPadding = 0;
0858: if (sampleSize[0] == 16 && tileOffsets[0] % 2 != 0) {
0859: numBytesPadding = 1;
0860: tileOffsets[0]++;
0861: } else if (sampleSize[0] == 32 && tileOffsets[0] % 4 != 0) {
0862: numBytesPadding = (int) (4 - tileOffsets[0] % 4);
0863: tileOffsets[0] += numBytesPadding;
0864: }
0865:
0866: // Update the data offsets (which TIFFField stores by reference).
0867: for (int i = 1; i < numTiles; i++) {
0868: tileOffsets[i] = tileOffsets[i - 1]
0869: + tileByteCounts[i - 1];
0870: }
0871:
0872: if (!isLast) {
0873: // Determine the offset of the next IFD.
0874: nextIFDOffset = (int) (tileOffsets[0] + totalBytesOfData);
0875:
0876: // IFD offsets must be on a word boundary.
0877: if (nextIFDOffset % 2 != 0) {
0878: nextIFDOffset++;
0879: skipByte = true;
0880: }
0881: }
0882:
0883: // Write the IFD and field overflow before the image data.
0884: writeDirectory(ifdOffset, fields, nextIFDOffset);
0885:
0886: // Write any padding bytes needed between the end of the IFD
0887: // and the start of the actual image data.
0888: if (numBytesPadding != 0) {
0889: for (int padding = 0; padding < numBytesPadding; padding++) {
0890: output.write((byte) 0);
0891: }
0892: }
0893: } else {
0894: // If compressing, the cannot be written yet as the size of the
0895: // data segments is unknown.
0896:
0897: if ((output instanceof SeekableOutputStream)) {
0898: // Simply seek to the first data segment position.
0899: ((SeekableOutputStream) output).seek(tileOffsets[0]);
0900: } else {
0901: // Cache the original OutputStream.
0902: outCache = output;
0903:
0904: try {
0905: // Attempt to create a temporary file.
0906: tempFile = File.createTempFile("jai-SOS-", ".tmp");
0907: tempFile.deleteOnExit();
0908: RandomAccessFile raFile = new RandomAccessFile(
0909: tempFile, "rw");
0910: output = new SeekableOutputStream(raFile);
0911: // XXX Be sure that this file is deleted no matter how
0912: // this method is exited!
0913: } catch (Exception e) {
0914: tempFile = null;
0915: // Allocate memory for the entire image data (!).
0916: output = new ByteArrayOutputStream(
0917: (int) totalBytesOfData);
0918: }
0919: }
0920:
0921: int bufSize = 0;
0922: switch (compression) {
0923: case COMP_GROUP3_1D:
0924: // This initial buffer size is based on an alternating 1-0
0925: // pattern generating the most bits when converted to code
0926: // words: 9 bits out for each pair of bits in. So the number
0927: // of bit pairs is determined, multiplied by 9, converted to
0928: // bytes, and a ceil() is taken to account for fill bits at the
0929: // end of each line. The "2" addend accounts for the case
0930: // of the pattern beginning with black. The buffer is intended
0931: // to hold only a single row.
0932: bufSize = (int) Math
0933: .ceil((((tileWidth + 1) / 2) * 9 + 2) / 8.0);
0934: break;
0935: case COMP_GROUP3_2D:
0936: case COMP_GROUP4:
0937: // Calculate the maximum row as the G3-1D size plus the EOL,
0938: // multiply this by the number of rows in the tile, and add
0939: // 6 EOLs for the RTC (return to control).
0940: bufSize = (int) Math
0941: .ceil((((tileWidth + 1) / 2) * 9 + 2) / 8.0);
0942: bufSize = tileHeight * (bufSize + 2) + 12;
0943: break;
0944: case COMP_PACKBITS:
0945: bufSize = (int) (bytesPerTile + ((bytesPerRow + 127) / 128)
0946: * tileHeight);
0947: break;
0948: case COMP_JPEG_TTN2:
0949: bufSize = 0;
0950:
0951: // Set color conversion flag.
0952: if (imageType == TIFF_YCBCR
0953: && colorModel != null
0954: && colorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
0955: jpegRGBToYCbCr = true;
0956: }
0957: break;
0958: case COMP_DEFLATE:
0959: bufSize = (int) bytesPerTile;
0960: deflater = new Deflater(encodeParam.getDeflateLevel());
0961: break;
0962: default:
0963: bufSize = 0;
0964: }
0965: if (bufSize != 0) {
0966: compressBuf = new byte[bufSize];
0967: }
0968: }
0969:
0970: // ---- Writing of actual image data ----
0971:
0972: // Buffer for up to tileHeight rows of pixels
0973: int[] pixels = null;
0974: float[] fpixels = null;
0975:
0976: // Whether to test for contiguous data.
0977: boolean checkContiguous = ((sampleSize[0] == 1
0978: && sampleModel instanceof MultiPixelPackedSampleModel && dataType == DataBuffer.TYPE_BYTE) || (sampleSize[0] == 8 && sampleModel instanceof ComponentSampleModel));
0979:
0980: // Also create a buffer to hold tileHeight lines of the
0981: // data to be written to the file, so we can use array writes.
0982: byte[] bpixels = null;
0983: if (compression != COMP_JPEG_TTN2) {
0984: if (dataType == DataBuffer.TYPE_BYTE) {
0985: bpixels = new byte[tileHeight * tileWidth * numBands];
0986: } else if (dataTypeIsShort) {
0987: bpixels = new byte[2 * tileHeight * tileWidth
0988: * numBands];
0989: } else if (dataType == DataBuffer.TYPE_INT
0990: || dataType == DataBuffer.TYPE_FLOAT) {
0991: bpixels = new byte[4 * tileHeight * tileWidth
0992: * numBands];
0993: }
0994: }
0995:
0996: // Process tileHeight rows at a time
0997: int lastRow = minY + height;
0998: int lastCol = minX + width;
0999: int tileNum = 0;
1000: for (int row = minY; row < lastRow; row += tileHeight) {
1001: int rows = isTiled ? tileHeight : Math.min(tileHeight,
1002: lastRow - row);
1003: int size = rows * tileWidth * numBands;
1004:
1005: for (int col = minX; col < lastCol; col += tileWidth) {
1006: // Grab the pixels
1007: Raster src = im.getData(new Rectangle(col, row,
1008: tileWidth, rows));
1009:
1010: boolean useDataBuffer = false;
1011: if (compression != COMP_JPEG_TTN2) { // JPEG access Raster
1012: if (checkContiguous) {
1013: if (sampleSize[0] == 8) { // 8-bit
1014: ComponentSampleModel csm = (ComponentSampleModel) src
1015: .getSampleModel();
1016: int[] bankIndices = csm.getBankIndices();
1017: int[] bandOffsets = csm.getBandOffsets();
1018: int pixelStride = csm.getPixelStride();
1019: int lineStride = csm.getScanlineStride();
1020:
1021: if (pixelStride != numBands
1022: || lineStride != bytesPerRow) {
1023: useDataBuffer = false;
1024: } else {
1025: useDataBuffer = true;
1026: for (int i = 0; useDataBuffer
1027: && i < numBands; i++) {
1028: if (bankIndices[i] != 0
1029: || bandOffsets[i] != i) {
1030: useDataBuffer = false;
1031: }
1032: }
1033: }
1034: } else { // 1-bit
1035: MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) src
1036: .getSampleModel();
1037: if (mpp.getNumBands() == 1
1038: && mpp.getDataBitOffset() == 0
1039: && mpp.getPixelBitStride() == 1) {
1040: useDataBuffer = true;
1041: }
1042: }
1043: }
1044:
1045: if (!useDataBuffer) {
1046: if (dataType == DataBuffer.TYPE_FLOAT) {
1047: fpixels = src.getPixels(col, row,
1048: tileWidth, rows, fpixels);
1049: } else {
1050: pixels = src.getPixels(col, row, tileWidth,
1051: rows, pixels);
1052: }
1053: }
1054: }
1055:
1056: int index;
1057:
1058: int pixel = 0;
1059: ;
1060: int k = 0;
1061: switch (sampleSize[0]) {
1062:
1063: case 1:
1064:
1065: if (useDataBuffer) {
1066: byte[] btmp = ((DataBufferByte) src
1067: .getDataBuffer()).getData();
1068: MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel) src
1069: .getSampleModel();
1070: int lineStride = mpp.getScanlineStride();
1071: int inOffset = mpp.getOffset(col
1072: - src.getSampleModelTranslateX(), row
1073: - src.getSampleModelTranslateY());
1074: if (lineStride == (int) bytesPerRow) {
1075: System.arraycopy(btmp, inOffset, bpixels,
1076: 0, (int) bytesPerRow * rows);
1077: } else {
1078: int outOffset = 0;
1079: for (int j = 0; j < rows; j++) {
1080: System.arraycopy(btmp, inOffset,
1081: bpixels, outOffset,
1082: (int) bytesPerRow);
1083: inOffset += lineStride;
1084: outOffset += (int) bytesPerRow;
1085: }
1086: }
1087: } else {
1088: index = 0;
1089:
1090: // For each of the rows in a strip
1091: for (int i = 0; i < rows; i++) {
1092:
1093: // Write number of pixels exactly divisible by 8
1094: for (int j = 0; j < tileWidth / 8; j++) {
1095:
1096: pixel = (pixels[index++] << 7)
1097: | (pixels[index++] << 6)
1098: | (pixels[index++] << 5)
1099: | (pixels[index++] << 4)
1100: | (pixels[index++] << 3)
1101: | (pixels[index++] << 2)
1102: | (pixels[index++] << 1)
1103: | pixels[index++];
1104: bpixels[k++] = (byte) pixel;
1105: }
1106:
1107: // Write the pixels remaining after division by 8
1108: if (tileWidth % 8 > 0) {
1109: pixel = 0;
1110: for (int j = 0; j < tileWidth % 8; j++) {
1111: pixel |= (pixels[index++] << (7 - j));
1112: }
1113: bpixels[k++] = (byte) pixel;
1114: }
1115: }
1116: }
1117:
1118: if (compression == COMP_NONE) {
1119: output.write(bpixels, 0, rows
1120: * ((tileWidth + 7) / 8));
1121: } else if (compression == COMP_GROUP3_1D) {
1122: int rowStride = (tileWidth + 7) / 8;
1123: int rowOffset = 0;
1124: int numCompressedBytes = 0;
1125: for (int tileRow = 0; tileRow < rows; tileRow++) {
1126: int numCompressedBytesInRow = faxEncoder
1127: .encodeRLE(bpixels, rowOffset, 0,
1128: tileWidth, compressBuf);
1129: output.write(compressBuf, 0,
1130: numCompressedBytesInRow);
1131: rowOffset += rowStride;
1132: numCompressedBytes += numCompressedBytesInRow;
1133: }
1134: tileByteCounts[tileNum++] = numCompressedBytes;
1135: } else if (compression == COMP_GROUP3_2D) {
1136: int numCompressedBytes = faxEncoder.encodeT4(
1137: !T4encode2D,// 1D == !2D
1138: T4PadEOLs, bpixels,
1139: (tileWidth + 7) / 8, 0, tileWidth,
1140: rows, compressBuf);
1141: tileByteCounts[tileNum++] = numCompressedBytes;
1142: output
1143: .write(compressBuf, 0,
1144: numCompressedBytes);
1145: } else if (compression == COMP_GROUP4) {
1146: int numCompressedBytes = faxEncoder.encodeT6(
1147: bpixels, (tileWidth + 7) / 8, 0,
1148: tileWidth, rows, compressBuf);
1149: tileByteCounts[tileNum++] = numCompressedBytes;
1150: output
1151: .write(compressBuf, 0,
1152: numCompressedBytes);
1153: } else if (compression == COMP_PACKBITS) {
1154: int numCompressedBytes = compressPackBits(
1155: bpixels, rows, (int) bytesPerRow,
1156: compressBuf);
1157: tileByteCounts[tileNum++] = numCompressedBytes;
1158: output
1159: .write(compressBuf, 0,
1160: numCompressedBytes);
1161: } else if (compression == COMP_DEFLATE) {
1162: int numCompressedBytes = deflate(deflater,
1163: bpixels, compressBuf);
1164: tileByteCounts[tileNum++] = numCompressedBytes;
1165: output
1166: .write(compressBuf, 0,
1167: numCompressedBytes);
1168: }
1169:
1170: break;
1171:
1172: case 4:
1173:
1174: index = 0;
1175:
1176: // For each of the rows in a strip
1177: for (int i = 0; i < rows; i++) {
1178:
1179: // Write the number of pixels that will fit into an
1180: // even number of nibbles.
1181: for (int j = 0; j < tileWidth / 2; j++) {
1182: pixel = (pixels[index++] << 4)
1183: | pixels[index++];
1184: bpixels[k++] = (byte) pixel;
1185: }
1186:
1187: // Last pixel for odd-length lines
1188: if ((tileWidth % 2) == 1) {
1189: pixel = pixels[index++] << 4;
1190: bpixels[k++] = (byte) pixel;
1191: }
1192: }
1193:
1194: if (compression == COMP_NONE) {
1195: output.write(bpixels, 0, rows
1196: * ((tileWidth + 1) / 2));
1197: } else if (compression == COMP_PACKBITS) {
1198: int numCompressedBytes = compressPackBits(
1199: bpixels, rows, (int) bytesPerRow,
1200: compressBuf);
1201: tileByteCounts[tileNum++] = numCompressedBytes;
1202: output
1203: .write(compressBuf, 0,
1204: numCompressedBytes);
1205: } else if (compression == COMP_DEFLATE) {
1206: int numCompressedBytes = deflate(deflater,
1207: bpixels, compressBuf);
1208: tileByteCounts[tileNum++] = numCompressedBytes;
1209: output
1210: .write(compressBuf, 0,
1211: numCompressedBytes);
1212: }
1213: break;
1214:
1215: case 8:
1216:
1217: if (compression != COMP_JPEG_TTN2) {
1218: if (useDataBuffer) {
1219: byte[] btmp = ((DataBufferByte) src
1220: .getDataBuffer()).getData();
1221: ComponentSampleModel csm = (ComponentSampleModel) src
1222: .getSampleModel();
1223: int inOffset = csm
1224: .getOffset(
1225: col
1226: - src
1227: .getSampleModelTranslateX(),
1228: row
1229: - src
1230: .getSampleModelTranslateY());
1231: int lineStride = csm.getScanlineStride();
1232: if (lineStride == (int) bytesPerRow) {
1233: System.arraycopy(btmp, inOffset,
1234: bpixels, 0, (int) bytesPerRow
1235: * rows);
1236: } else {
1237: int outOffset = 0;
1238: for (int j = 0; j < rows; j++) {
1239: System.arraycopy(btmp, inOffset,
1240: bpixels, outOffset,
1241: (int) bytesPerRow);
1242: inOffset += lineStride;
1243: outOffset += (int) bytesPerRow;
1244: }
1245: }
1246: } else {
1247: for (int i = 0; i < size; i++) {
1248: bpixels[i] = (byte) pixels[i];
1249: }
1250: }
1251: }
1252:
1253: if (compression == COMP_NONE) {
1254: output.write(bpixels, 0, size);
1255: } else if (compression == COMP_PACKBITS) {
1256: int numCompressedBytes = compressPackBits(
1257: bpixels, rows, (int) bytesPerRow,
1258: compressBuf);
1259: tileByteCounts[tileNum++] = numCompressedBytes;
1260: output
1261: .write(compressBuf, 0,
1262: numCompressedBytes);
1263: } else if (compression == COMP_JPEG_TTN2) {
1264: long startPos = getOffset(output);
1265:
1266: // Recreate encoder and parameters if the encoder
1267: // is null (first data segment) or if its size
1268: // doesn't match the current data segment.
1269: if (jpegEncoder == null
1270: || jpegEncodeParam.getWidth() != src
1271: .getWidth()
1272: || jpegEncodeParam.getHeight() != src
1273: .getHeight()) {
1274:
1275: jpegEncodeParam = com.sun.image.codec.jpeg.JPEGCodec
1276: .getDefaultJPEGEncodeParam(src,
1277: jpegColorID);
1278:
1279: JPEGImageEncoder.modifyEncodeParam(jep,
1280: jpegEncodeParam, numBands);
1281:
1282: jpegEncoder = com.sun.image.codec.jpeg.JPEGCodec
1283: .createJPEGEncoder(output,
1284: jpegEncodeParam);
1285: }
1286:
1287: if (jpegRGBToYCbCr) {
1288: WritableRaster wRas = null;
1289: if (src instanceof WritableRaster) {
1290: wRas = (WritableRaster) src;
1291: } else {
1292: wRas = src
1293: .createCompatibleWritableRaster();
1294: wRas.setRect(src);
1295: }
1296:
1297: if (wRas.getMinX() != 0
1298: || wRas.getMinY() != 0) {
1299: wRas = wRas
1300: .createWritableTranslatedChild(
1301: 0, 0);
1302: }
1303: BufferedImage bi = new BufferedImage(
1304: colorModel, wRas, false, null);
1305: jpegEncoder.encode(bi);
1306: } else {
1307: jpegEncoder.encode(src
1308: .createTranslatedChild(0, 0));
1309: }
1310:
1311: long endPos = getOffset(output);
1312: tileByteCounts[tileNum++] = (int) (endPos - startPos);
1313: } else if (compression == COMP_DEFLATE) {
1314: int numCompressedBytes = deflate(deflater,
1315: bpixels, compressBuf);
1316: tileByteCounts[tileNum++] = numCompressedBytes;
1317: output
1318: .write(compressBuf, 0,
1319: numCompressedBytes);
1320: }
1321: break;
1322:
1323: case 16:
1324:
1325: int ls = 0;
1326: for (int i = 0; i < size; i++) {
1327: short value = (short) pixels[i];
1328: bpixels[ls++] = (byte) ((value & 0xff00) >> 8);
1329: bpixels[ls++] = (byte) (value & 0x00ff);
1330: }
1331:
1332: if (compression == COMP_NONE) {
1333: output.write(bpixels, 0, size * 2);
1334: } else if (compression == COMP_PACKBITS) {
1335: int numCompressedBytes = compressPackBits(
1336: bpixels, rows, (int) bytesPerRow,
1337: compressBuf);
1338: tileByteCounts[tileNum++] = numCompressedBytes;
1339: output
1340: .write(compressBuf, 0,
1341: numCompressedBytes);
1342: } else if (compression == COMP_DEFLATE) {
1343: int numCompressedBytes = deflate(deflater,
1344: bpixels, compressBuf);
1345: tileByteCounts[tileNum++] = numCompressedBytes;
1346: output
1347: .write(compressBuf, 0,
1348: numCompressedBytes);
1349: }
1350: break;
1351:
1352: case 32:
1353: if (dataType == DataBuffer.TYPE_INT) {
1354: int li = 0;
1355: for (int i = 0; i < size; i++) {
1356: int value = pixels[i];
1357: bpixels[li++] = (byte) ((value & 0xff000000) >> 24);
1358: bpixels[li++] = (byte) ((value & 0x00ff0000) >> 16);
1359: bpixels[li++] = (byte) ((value & 0x0000ff00) >> 8);
1360: bpixels[li++] = (byte) (value & 0x000000ff);
1361: }
1362: } else { // DataBuffer.TYPE_FLOAT
1363: int lf = 0;
1364: for (int i = 0; i < size; i++) {
1365: int value = Float
1366: .floatToIntBits(fpixels[i]);
1367: bpixels[lf++] = (byte) ((value & 0xff000000) >> 24);
1368: bpixels[lf++] = (byte) ((value & 0x00ff0000) >> 16);
1369: bpixels[lf++] = (byte) ((value & 0x0000ff00) >> 8);
1370: bpixels[lf++] = (byte) (value & 0x000000ff);
1371: }
1372: }
1373: if (compression == COMP_NONE) {
1374: output.write(bpixels, 0, size * 4);
1375: } else if (compression == COMP_PACKBITS) {
1376: int numCompressedBytes = compressPackBits(
1377: bpixels, rows, (int) bytesPerRow,
1378: compressBuf);
1379: tileByteCounts[tileNum++] = numCompressedBytes;
1380: output
1381: .write(compressBuf, 0,
1382: numCompressedBytes);
1383: } else if (compression == COMP_DEFLATE) {
1384: int numCompressedBytes = deflate(deflater,
1385: bpixels, compressBuf);
1386: tileByteCounts[tileNum++] = numCompressedBytes;
1387: output
1388: .write(compressBuf, 0,
1389: numCompressedBytes);
1390: }
1391: break;
1392:
1393: }
1394: }
1395: }
1396:
1397: if (compression == COMP_NONE) {
1398: // Write an extra byte for IFD word alignment if needed.
1399: if (skipByte) {
1400: output.write((byte) 0);
1401: }
1402: } else {
1403: // Recompute tile offsets from the size of the compressed tiles.
1404: int totalBytes = 0;
1405: for (int i = 1; i < numTiles; i++) {
1406: int numBytes = (int) tileByteCounts[i - 1];
1407: totalBytes += numBytes;
1408: tileOffsets[i] = tileOffsets[i - 1] + numBytes;
1409: }
1410: totalBytes += (int) tileByteCounts[numTiles - 1];
1411:
1412: nextIFDOffset = isLast ? 0 : ifdOffset + dirSize
1413: + totalBytes;
1414:
1415: // IFD offsets must be on a word boundary.
1416: if (nextIFDOffset % 2 != 0) {
1417: nextIFDOffset++;
1418: skipByte = true;
1419: }
1420:
1421: if (outCache == null) {
1422: // Original OutputStream must be a SeekableOutputStream.
1423:
1424: // Write an extra byte for IFD word alignment if needed.
1425: if (skipByte) {
1426: output.write((byte) 0);
1427: }
1428:
1429: SeekableOutputStream sos = (SeekableOutputStream) output;
1430:
1431: // Save current position.
1432: long savePos = sos.getFilePointer();
1433:
1434: // Seek backward to the IFD offset and write IFD.
1435: sos.seek(ifdOffset);
1436: writeDirectory(ifdOffset, fields, nextIFDOffset);
1437:
1438: // Seek forward to position after data.
1439: sos.seek(savePos);
1440: } else if (tempFile != null) {
1441:
1442: // Using a file cache for the image data.
1443:
1444: // Close the original SeekableOutputStream.
1445: output.close();
1446:
1447: // Open a FileInputStream from which to copy the data.
1448: FileInputStream fileStream = new FileInputStream(
1449: tempFile);
1450:
1451: // Reset variable to the original OutputStream.
1452: output = outCache;
1453:
1454: // Write the IFD.
1455: writeDirectory(ifdOffset, fields, nextIFDOffset);
1456:
1457: // Write the image data.
1458: byte[] copyBuffer = new byte[8192];
1459: int bytesCopied = 0;
1460: while (bytesCopied < totalBytes) {
1461: int bytesRead = fileStream.read(copyBuffer);
1462: if (bytesRead == -1) {
1463: break;
1464: }
1465: output.write(copyBuffer, 0, bytesRead);
1466: bytesCopied += bytesRead;
1467: }
1468:
1469: // Delete the temporary file.
1470: fileStream.close();
1471: tempFile.delete();
1472:
1473: // Write an extra byte for IFD word alignment if needed.
1474: if (skipByte) {
1475: output.write((byte) 0);
1476: }
1477: } else if (output instanceof ByteArrayOutputStream) {
1478:
1479: // Using a memory cache for the image data.
1480:
1481: ByteArrayOutputStream memoryStream = (ByteArrayOutputStream) output;
1482:
1483: // Reset variable to the original OutputStream.
1484: output = outCache;
1485:
1486: // Write the IFD.
1487: writeDirectory(ifdOffset, fields, nextIFDOffset);
1488:
1489: // Write the image data.
1490: memoryStream.writeTo(output);
1491:
1492: // Write an extra byte for IFD word alignment if needed.
1493: if (skipByte) {
1494: output.write((byte) 0);
1495: }
1496: } else {
1497: // This should never happen.
1498: throw new IllegalStateException();
1499: }
1500: }
1501:
1502: return nextIFDOffset;
1503: }
1504:
1505: /**
1506: * Calculates the size of the IFD.
1507: */
1508: private int getDirectorySize(SortedSet fields) {
1509: // Get the number of entries.
1510: int numEntries = fields.size();
1511:
1512: // Initialize the size excluding that of any values > 4 bytes.
1513: int dirSize = 2 + numEntries * 12 + 4;
1514:
1515: // Loop over fields adding the size of all values > 4 bytes.
1516: Iterator iter = fields.iterator();
1517: while (iter.hasNext()) {
1518: // Get the field.
1519: TIFFField field = (TIFFField) iter.next();
1520:
1521: // Determine the size of the field value.
1522: int valueSize = getValueSize(field);
1523:
1524: // Add any excess size.
1525: if (valueSize > 4) {
1526: dirSize += valueSize;
1527: }
1528: }
1529:
1530: return dirSize;
1531: }
1532:
1533: private void writeFileHeader() throws IOException {
1534: // 8 byte image file header
1535:
1536: // Byte order used within the file
1537: if (isLittleEndian) {
1538: // Little Endian
1539: output.write('I');
1540: output.write('I');
1541: } else {
1542: // Big Endian
1543: output.write('M');
1544: output.write('M');
1545: }
1546:
1547: // Magic value
1548: writeUnsignedShort(42);
1549:
1550: // Offset in bytes of the first IFD.
1551: writeLong(8);
1552: }
1553:
1554: private void writeDirectory(int this IFDOffset, SortedSet fields,
1555: int nextIFDOffset) throws IOException {
1556:
1557: // 2 byte count of number of directory entries (fields)
1558: int numEntries = fields.size();
1559:
1560: long offsetBeyondIFD = this IFDOffset + 12 * numEntries + 4 + 2;
1561: ArrayList tooBig = new ArrayList();
1562:
1563: // Write number of fields in the IFD
1564: writeUnsignedShort(numEntries);
1565:
1566: Iterator iter = fields.iterator();
1567: while (iter.hasNext()) {
1568:
1569: // 12 byte field entry TIFFField
1570: TIFFField field = (TIFFField) iter.next();
1571:
1572: // byte 0-1 Tag that identifies a field
1573: int tag = field.getTag();
1574: writeUnsignedShort(tag);
1575:
1576: // byte 2-3 The field type
1577: int type = field.getType();
1578: writeUnsignedShort(type);
1579:
1580: // bytes 4-7 the number of values of the indicated type except
1581: // ASCII-valued fields which require the total number of bytes.
1582: int count = field.getCount();
1583: int valueSize = getValueSize(field);
1584: writeLong(type == TIFFField.TIFF_ASCII ? valueSize : count);
1585:
1586: // bytes 8 - 11 the value or value offset
1587: if (valueSize > 4) {
1588:
1589: // We need an offset as data won't fit into 4 bytes
1590: writeLong(offsetBeyondIFD);
1591: offsetBeyondIFD += valueSize;
1592: tooBig.add(field);
1593:
1594: } else {
1595:
1596: writeValuesAsFourBytes(field);
1597: }
1598:
1599: }
1600:
1601: // Address of next IFD
1602: writeLong(nextIFDOffset);
1603:
1604: // Write the tag values that did not fit into 4 bytes
1605: for (int i = 0; i < tooBig.size(); i++) {
1606: writeValues((TIFFField) tooBig.get(i));
1607: }
1608: }
1609:
1610: /**
1611: * Determine the number of bytes in the value portion of the field.
1612: */
1613: private static final int getValueSize(TIFFField field) {
1614: int type = field.getType();
1615: int count = field.getCount();
1616: int valueSize = 0;
1617: if (type == TIFFField.TIFF_ASCII) {
1618: for (int i = 0; i < count; i++) {
1619: byte[] stringBytes = field.getAsString(i).getBytes();
1620: valueSize += stringBytes.length;
1621: if (stringBytes[stringBytes.length - 1] != (byte) 0) {
1622: valueSize++;
1623: }
1624: }
1625: } else {
1626: valueSize = count * sizeOfType[type];
1627: }
1628: return valueSize;
1629: }
1630:
1631: private static final int[] sizeOfType = { 0, // 0 = n/a
1632: 1, // 1 = byte
1633: 1, // 2 = ascii
1634: 2, // 3 = short
1635: 4, // 4 = long
1636: 8, // 5 = rational
1637: 1, // 6 = sbyte
1638: 1, // 7 = undefined
1639: 2, // 8 = sshort
1640: 4, // 9 = slong
1641: 8, // 10 = srational
1642: 4, // 11 = float
1643: 8 // 12 = double
1644: };
1645:
1646: private void writeValuesAsFourBytes(TIFFField field)
1647: throws IOException {
1648:
1649: int dataType = field.getType();
1650: int count = field.getCount();
1651:
1652: switch (dataType) {
1653:
1654: // 8 bits
1655: case TIFFField.TIFF_BYTE:
1656: case TIFFField.TIFF_SBYTE:
1657: case TIFFField.TIFF_UNDEFINED:
1658: byte bytes[] = field.getAsBytes();
1659:
1660: for (int i = 0; i < count; i++) {
1661: output.write(bytes[i]);
1662: }
1663:
1664: for (int i = 0; i < (4 - count); i++) {
1665: output.write(0);
1666: }
1667:
1668: break;
1669:
1670: // unsigned 16 bits
1671: case TIFFField.TIFF_SHORT:
1672: char shorts[] = field.getAsChars();
1673:
1674: for (int i = 0; i < count; i++) {
1675: writeUnsignedShort(shorts[i]);
1676: }
1677:
1678: for (int i = 0; i < (2 - count); i++) {
1679: writeUnsignedShort(0);
1680: }
1681:
1682: break;
1683:
1684: // signed 16 bits
1685: case TIFFField.TIFF_SSHORT:
1686: short sshorts[] = field.getAsShorts();
1687:
1688: for (int i = 0; i < count; i++) {
1689: writeUnsignedShort(sshorts[i]);
1690: }
1691:
1692: for (int i = 0; i < (2 - count); i++) {
1693: writeUnsignedShort(0);
1694: }
1695:
1696: break;
1697:
1698: // unsigned 32 bits
1699: case TIFFField.TIFF_LONG:
1700: writeLong(field.getAsLong(0));
1701: break;
1702:
1703: // signed 32 bits
1704: case TIFFField.TIFF_SLONG:
1705: writeLong(field.getAsInt(0));
1706: break;
1707:
1708: case TIFFField.TIFF_FLOAT:
1709: writeLong(Float.floatToIntBits(field.getAsFloat(0)));
1710: break;
1711:
1712: case TIFFField.TIFF_ASCII:
1713: int asciiByteCount = 0;
1714: for (int i = 0; i < count; i++) {
1715: byte[] stringBytes = field.getAsString(i).getBytes();
1716: output.write(stringBytes);
1717: asciiByteCount += stringBytes.length;
1718: if (stringBytes[stringBytes.length - 1] != (byte) 0) {
1719: output.write(0);
1720: asciiByteCount++;
1721: }
1722: }
1723: for (int i = 0; i < (4 - asciiByteCount); i++) {
1724: output.write(0);
1725: }
1726: break;
1727:
1728: default:
1729: throw new RuntimeException(JaiI18N
1730: .getString("TIFFImageEncoder10"));
1731: }
1732:
1733: }
1734:
1735: private void writeValues(TIFFField field) throws IOException {
1736:
1737: int dataType = field.getType();
1738: int count = field.getCount();
1739:
1740: switch (dataType) {
1741:
1742: // unsigned 8 bits
1743: case TIFFField.TIFF_BYTE:
1744: case TIFFField.TIFF_SBYTE:
1745: case TIFFField.TIFF_UNDEFINED:
1746: byte bytes[] = field.getAsBytes();
1747: for (int i = 0; i < count; i++) {
1748: output.write(bytes[i]);
1749: }
1750: break;
1751:
1752: // unsigned 16 bits
1753: case TIFFField.TIFF_SHORT:
1754: char shorts[] = field.getAsChars();
1755: for (int i = 0; i < count; i++) {
1756: writeUnsignedShort(shorts[i]);
1757: }
1758: break;
1759:
1760: // signed 16 bits
1761: case TIFFField.TIFF_SSHORT:
1762: short sshorts[] = field.getAsShorts();
1763: for (int i = 0; i < count; i++) {
1764: writeUnsignedShort(sshorts[i]);
1765: }
1766: break;
1767:
1768: // unsigned 32 bits
1769: case TIFFField.TIFF_LONG:
1770: long longs[] = field.getAsLongs();
1771: for (int i = 0; i < count; i++) {
1772: writeLong(longs[i]);
1773: }
1774: break;
1775:
1776: // signed 32 bits
1777: case TIFFField.TIFF_SLONG:
1778: int slongs[] = field.getAsInts();
1779: for (int i = 0; i < count; i++) {
1780: writeLong(slongs[i]);
1781: }
1782: break;
1783:
1784: case TIFFField.TIFF_FLOAT:
1785: float[] floats = field.getAsFloats();
1786: for (int i = 0; i < count; i++) {
1787: int intBits = Float.floatToIntBits(floats[i]);
1788: writeLong(intBits);
1789: }
1790: break;
1791:
1792: case TIFFField.TIFF_DOUBLE:
1793: double[] doubles = field.getAsDoubles();
1794: for (int i = 0; i < count; i++) {
1795: long longBits = Double.doubleToLongBits(doubles[i]);
1796: writeLong((int) (longBits >> 32));
1797: writeLong((int) (longBits & 0xffffffff));
1798: }
1799: break;
1800:
1801: // unsigned rationals
1802: case TIFFField.TIFF_RATIONAL:
1803: long rationals[][] = field.getAsRationals();
1804: for (int i = 0; i < count; i++) {
1805: writeLong(rationals[i][0]);
1806: writeLong(rationals[i][1]);
1807: }
1808: break;
1809:
1810: // signed rationals
1811: case TIFFField.TIFF_SRATIONAL:
1812: int srationals[][] = field.getAsSRationals();
1813: for (int i = 0; i < count; i++) {
1814: writeLong(srationals[i][0]);
1815: writeLong(srationals[i][1]);
1816: }
1817: break;
1818:
1819: case TIFFField.TIFF_ASCII:
1820: for (int i = 0; i < count; i++) {
1821: byte[] stringBytes = field.getAsString(i).getBytes();
1822: output.write(stringBytes);
1823: if (stringBytes[stringBytes.length - 1] != (byte) 0) {
1824: output.write(0);
1825: }
1826: }
1827: break;
1828:
1829: default:
1830: throw new RuntimeException(JaiI18N
1831: .getString("TIFFImageEncoder10"));
1832:
1833: }
1834:
1835: }
1836:
1837: // Here s is never expected to have value greater than what can be
1838: // stored in 2 bytes.
1839: private void writeUnsignedShort(int s) throws IOException {
1840: if (isLittleEndian) {
1841: output.write(s & 0x00ff);
1842: output.write((s & 0xff00) >>> 8);
1843: } else {
1844: output.write((s & 0xff00) >>> 8);
1845: output.write(s & 0x00ff);
1846: }
1847: }
1848:
1849: private void writeLong(long l) throws IOException {
1850: if (isLittleEndian) {
1851: output.write(((int) l & 0x000000ff));
1852: output.write((int) ((l & 0x0000ff00) >>> 8));
1853: output.write((int) ((l & 0x00ff0000) >>> 16));
1854: output.write((int) ((l & 0xff000000) >>> 24));
1855: } else {
1856: output.write((int) ((l & 0xff000000) >>> 24));
1857: output.write((int) ((l & 0x00ff0000) >>> 16));
1858: output.write((int) ((l & 0x0000ff00) >>> 8));
1859: output.write(((int) l & 0x000000ff));
1860: }
1861: }
1862:
1863: /**
1864: * Returns the current offset in the supplied OutputStream.
1865: * This method should only be used if compressing data.
1866: */
1867: private long getOffset(OutputStream out) throws IOException {
1868: if (out instanceof ByteArrayOutputStream) {
1869: return ((ByteArrayOutputStream) out).size();
1870: } else if (out instanceof SeekableOutputStream) {
1871: return ((SeekableOutputStream) out).getFilePointer();
1872: } else {
1873: // Shouldn't happen.
1874: throw new IllegalStateException();
1875: }
1876: }
1877:
1878: /**
1879: * Performs PackBits compression on a tile of data.
1880: */
1881: private static int compressPackBits(byte[] data, int numRows,
1882: int bytesPerRow, byte[] compData) {
1883: int inOffset = 0;
1884: int outOffset = 0;
1885:
1886: for (int i = 0; i < numRows; i++) {
1887: outOffset = packBits(data, inOffset, bytesPerRow, compData,
1888: outOffset);
1889: inOffset += bytesPerRow;
1890: }
1891:
1892: return outOffset;
1893: }
1894:
1895: /**
1896: * Performs PackBits compression for a single buffer of data.
1897: * This should be called for each row of each tile. The returned
1898: * value is the offset into the output buffer after compression.
1899: */
1900: private static int packBits(byte[] input, int inOffset,
1901: int inCount, byte[] output, int outOffset) {
1902: int inMax = inOffset + inCount - 1;
1903: int inMaxMinus1 = inMax - 1;
1904:
1905: while (inOffset <= inMax) {
1906: int run = 1;
1907: byte replicate = input[inOffset];
1908: while (run < 127 && inOffset < inMax
1909: && input[inOffset] == input[inOffset + 1]) {
1910: run++;
1911: inOffset++;
1912: }
1913: if (run > 1) {
1914: inOffset++;
1915: output[outOffset++] = (byte) (-(run - 1));
1916: output[outOffset++] = replicate;
1917: }
1918:
1919: run = 0;
1920: int saveOffset = outOffset;
1921: while (run < 128
1922: && ((inOffset < inMax && input[inOffset] != input[inOffset + 1]) || (inOffset < inMaxMinus1 && input[inOffset] != input[inOffset + 2]))) {
1923: run++;
1924: output[++outOffset] = input[inOffset++];
1925: }
1926: if (run > 0) {
1927: output[saveOffset] = (byte) (run - 1);
1928: outOffset++;
1929: }
1930:
1931: if (inOffset == inMax) {
1932: if (run > 0 && run < 128) {
1933: output[saveOffset]++;
1934: output[outOffset++] = input[inOffset++];
1935: } else {
1936: output[outOffset++] = (byte) 0;
1937: output[outOffset++] = input[inOffset++];
1938: }
1939: }
1940: }
1941:
1942: return outOffset;
1943: }
1944:
1945: private static int deflate(Deflater deflater, byte[] inflated,
1946: byte[] deflated) {
1947: deflater.setInput(inflated);
1948: deflater.finish();
1949: int numCompressedBytes = deflater.deflate(deflated);
1950: deflater.reset();
1951: return numCompressedBytes;
1952: }
1953: }
|