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