0001: /*
0002: * $RCSfile: BMPImageWriter.java,v $
0003: *
0004: *
0005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * - Redistribution of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: *
0014: * - Redistribution in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * Neither the name of Sun Microsystems, Inc. or the names of
0020: * contributors may be used to endorse or promote products derived
0021: * from this software without specific prior written permission.
0022: *
0023: * This software is provided "AS IS," without a warranty of any
0024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0035: * POSSIBILITY OF SUCH DAMAGES.
0036: *
0037: * You acknowledge that this software is not designed or intended for
0038: * use in the design, construction, operation or maintenance of any
0039: * nuclear facility.
0040: *
0041: * $Revision: 1.2 $
0042: * $Date: 2006/04/14 21:29:14 $
0043: * $State: Exp $
0044: */
0045: package com.sun.media.imageioimpl.plugins.bmp;
0046:
0047: import java.awt.Point;
0048: import java.awt.Rectangle;
0049: import java.awt.image.ColorModel;
0050: import java.awt.image.ComponentSampleModel;
0051: import java.awt.image.DataBuffer;
0052: import java.awt.image.DataBufferByte;
0053: import java.awt.image.DataBufferInt;
0054: import java.awt.image.DataBufferShort;
0055: import java.awt.image.DataBufferUShort;
0056: import java.awt.image.DirectColorModel;
0057: import java.awt.image.IndexColorModel;
0058: import java.awt.image.MultiPixelPackedSampleModel;
0059: import java.awt.image.BandedSampleModel;
0060: import java.awt.image.Raster;
0061: import java.awt.image.RenderedImage;
0062: import java.awt.image.SampleModel;
0063: import java.awt.image.SinglePixelPackedSampleModel;
0064: import java.awt.image.WritableRaster;
0065: import java.awt.image.BufferedImage;
0066:
0067: import java.io.IOException;
0068: import java.io.ByteArrayOutputStream;
0069: import java.nio.ByteOrder;
0070: import java.util.Iterator;
0071:
0072: import javax.imageio.IIOImage;
0073: import javax.imageio.IIOException;
0074: import javax.imageio.ImageIO;
0075: import javax.imageio.ImageTypeSpecifier;
0076: import javax.imageio.ImageWriteParam;
0077: import javax.imageio.ImageWriter;
0078: import javax.imageio.metadata.IIOMetadata;
0079: import javax.imageio.metadata.IIOMetadataNode;
0080: import javax.imageio.metadata.IIOMetadataFormatImpl;
0081: import javax.imageio.metadata.IIOInvalidTreeException;
0082: import javax.imageio.spi.ImageWriterSpi;
0083: import javax.imageio.stream.ImageOutputStream;
0084: import javax.imageio.event.IIOWriteProgressListener;
0085: import javax.imageio.event.IIOWriteWarningListener;
0086:
0087: import org.w3c.dom.Node;
0088: import org.w3c.dom.NodeList;
0089:
0090: import com.sun.media.imageio.plugins.bmp.BMPImageWriteParam;
0091: import com.sun.media.imageioimpl.common.ImageUtil;
0092:
0093: /**
0094: * The Java Image IO plugin writer for encoding a binary RenderedImage into
0095: * a BMP format.
0096: *
0097: * The encoding process may clip, subsample using the parameters
0098: * specified in the <code>ImageWriteParam</code>.
0099: *
0100: * @see com.sun.media.imageio.plugins.bmp.BMPImageWriteParam
0101: */
0102: public class BMPImageWriter extends ImageWriter implements BMPConstants {
0103: /** The output stream to write into */
0104: private ImageOutputStream stream = null;
0105: private ByteArrayOutputStream embedded_stream = null;
0106: private int compressionType;
0107: private boolean isTopDown;
0108: private int w, h;
0109: private int compImageSize = 0;
0110: private int[] bitMasks;
0111: private int[] bitPos;
0112: private byte[] bpixels;
0113: private short[] spixels;
0114: private int[] ipixels;
0115:
0116: /** Constructs <code>BMPImageWriter</code> based on the provided
0117: * <code>ImageWriterSpi</code>.
0118: */
0119: public BMPImageWriter(ImageWriterSpi originator) {
0120: super (originator);
0121: }
0122:
0123: public void setOutput(Object output) {
0124: super .setOutput(output); // validates output
0125: if (output != null) {
0126: if (!(output instanceof ImageOutputStream))
0127: throw new IllegalArgumentException(I18N
0128: .getString("BMPImageWriter0"));
0129: this .stream = (ImageOutputStream) output;
0130: stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
0131: } else
0132: this .stream = null;
0133: }
0134:
0135: public ImageWriteParam getDefaultWriteParam() {
0136: return new BMPImageWriteParam();
0137: }
0138:
0139: public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
0140: return null;
0141: }
0142:
0143: public IIOMetadata getDefaultImageMetadata(
0144: ImageTypeSpecifier imageType, ImageWriteParam param) {
0145: BMPMetadata meta = new BMPMetadata();
0146: meta.initialize(imageType.getColorModel(), imageType
0147: .getSampleModel(), param);
0148: return meta;
0149: }
0150:
0151: public IIOMetadata convertStreamMetadata(IIOMetadata inData,
0152: ImageWriteParam param) {
0153: return null;
0154: }
0155:
0156: public IIOMetadata convertImageMetadata(IIOMetadata inData,
0157: ImageTypeSpecifier imageType, ImageWriteParam param) {
0158:
0159: // Check arguments.
0160: if (inData == null) {
0161: throw new IllegalArgumentException("inData == null!");
0162: }
0163: if (imageType == null) {
0164: throw new IllegalArgumentException("imageType == null!");
0165: }
0166:
0167: BMPMetadata outData = null;
0168:
0169: // Obtain a BMPMetadata object.
0170: if (inData instanceof BMPMetadata) {
0171: // Clone the input metadata.
0172: outData = (BMPMetadata) ((BMPMetadata) inData).clone();
0173: } else {
0174: try {
0175: outData = new BMPMetadata(inData);
0176: } catch (IIOInvalidTreeException e) {
0177: // XXX Warning
0178: outData = new BMPMetadata();
0179: }
0180: }
0181:
0182: // Update the metadata per the image type and param.
0183: outData.initialize(imageType.getColorModel(), imageType
0184: .getSampleModel(), param);
0185:
0186: return outData;
0187: }
0188:
0189: public boolean canWriteRasters() {
0190: return true;
0191: }
0192:
0193: public void write(IIOMetadata streamMetadata, IIOImage image,
0194: ImageWriteParam param) throws IOException {
0195:
0196: if (stream == null) {
0197: throw new IllegalStateException(I18N
0198: .getString("BMPImageWriter7"));
0199: }
0200:
0201: if (image == null) {
0202: throw new IllegalArgumentException(I18N
0203: .getString("BMPImageWriter8"));
0204: }
0205:
0206: clearAbortRequest();
0207: processImageStarted(0);
0208: if (param == null)
0209: param = getDefaultWriteParam();
0210:
0211: BMPImageWriteParam bmpParam = (BMPImageWriteParam) param;
0212:
0213: // Default is using 24 bits per pixel.
0214: int bitsPerPixel = 24;
0215: boolean isPalette = false;
0216: int paletteEntries = 0;
0217: IndexColorModel icm = null;
0218:
0219: RenderedImage input = null;
0220: Raster inputRaster = null;
0221: boolean writeRaster = image.hasRaster();
0222: Rectangle sourceRegion = param.getSourceRegion();
0223: SampleModel sampleModel = null;
0224: ColorModel colorModel = null;
0225:
0226: compImageSize = 0;
0227:
0228: if (writeRaster) {
0229: inputRaster = image.getRaster();
0230: sampleModel = inputRaster.getSampleModel();
0231: colorModel = ImageUtil.createColorModel(null, sampleModel);
0232: if (sourceRegion == null)
0233: sourceRegion = inputRaster.getBounds();
0234: else
0235: sourceRegion = sourceRegion.intersection(inputRaster
0236: .getBounds());
0237: } else {
0238: input = image.getRenderedImage();
0239: sampleModel = input.getSampleModel();
0240: colorModel = input.getColorModel();
0241: Rectangle rect = new Rectangle(input.getMinX(), input
0242: .getMinY(), input.getWidth(), input.getHeight());
0243: if (sourceRegion == null)
0244: sourceRegion = rect;
0245: else
0246: sourceRegion = sourceRegion.intersection(rect);
0247: }
0248:
0249: IIOMetadata imageMetadata = image.getMetadata();
0250: BMPMetadata bmpImageMetadata = null;
0251: ImageTypeSpecifier imageType = new ImageTypeSpecifier(
0252: colorModel, sampleModel);
0253: if (imageMetadata != null) {
0254: // Convert metadata.
0255: bmpImageMetadata = (BMPMetadata) convertImageMetadata(
0256: imageMetadata, imageType, param);
0257: } else {
0258: // Use default.
0259: bmpImageMetadata = (BMPMetadata) getDefaultImageMetadata(
0260: imageType, param);
0261: }
0262:
0263: if (sourceRegion.isEmpty())
0264: throw new RuntimeException(I18N.getString("BMPImageWrite0"));
0265:
0266: int scaleX = param.getSourceXSubsampling();
0267: int scaleY = param.getSourceYSubsampling();
0268: int xOffset = param.getSubsamplingXOffset();
0269: int yOffset = param.getSubsamplingYOffset();
0270:
0271: // cache the data type;
0272: int dataType = sampleModel.getDataType();
0273:
0274: sourceRegion.translate(xOffset, yOffset);
0275: sourceRegion.width -= xOffset;
0276: sourceRegion.height -= yOffset;
0277:
0278: int minX = sourceRegion.x / scaleX;
0279: int minY = sourceRegion.y / scaleY;
0280: w = (sourceRegion.width + scaleX - 1) / scaleX;
0281: h = (sourceRegion.height + scaleY - 1) / scaleY;
0282: xOffset = sourceRegion.x % scaleX;
0283: yOffset = sourceRegion.y % scaleY;
0284:
0285: Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
0286: boolean noTransform = destinationRegion.equals(sourceRegion);
0287:
0288: // Raw data can only handle bytes, everything greater must be ASCII.
0289: int[] sourceBands = param.getSourceBands();
0290: boolean noSubband = true;
0291: int numBands = sampleModel.getNumBands();
0292:
0293: if (sourceBands != null) {
0294: sampleModel = sampleModel
0295: .createSubsetSampleModel(sourceBands);
0296: colorModel = null;
0297: noSubband = false;
0298: numBands = sampleModel.getNumBands();
0299: } else {
0300: sourceBands = new int[numBands];
0301: for (int i = 0; i < numBands; i++)
0302: sourceBands[i] = i;
0303: }
0304:
0305: int[] bandOffsets = null;
0306: boolean bgrOrder = true;
0307:
0308: if (sampleModel instanceof ComponentSampleModel) {
0309: bandOffsets = ((ComponentSampleModel) sampleModel)
0310: .getBandOffsets();
0311: if (sampleModel instanceof BandedSampleModel) {
0312: // for images with BandedSampleModel we can not work
0313: // with raster directly and must use writePixels()
0314: bgrOrder = false;
0315: } else {
0316: // we can work with raster directly only in case of
0317: // BGR component order.
0318: // In any other case we must use writePixels()
0319: for (int i = 0; i < bandOffsets.length; i++) {
0320: bgrOrder &= (bandOffsets[i] == (bandOffsets.length
0321: - i - 1));
0322: }
0323: }
0324: } else {
0325: if (sampleModel instanceof SinglePixelPackedSampleModel) {
0326:
0327: // BugId 4892214: we can not work with raster directly
0328: // if image have different color order than RGB.
0329: // We should use writePixels() for such images.
0330: int[] bitOffsets = ((SinglePixelPackedSampleModel) sampleModel)
0331: .getBitOffsets();
0332: for (int i = 0; i < bitOffsets.length - 1; i++) {
0333: bgrOrder &= bitOffsets[i] > bitOffsets[i + 1];
0334: }
0335: }
0336: }
0337:
0338: if (bandOffsets == null) {
0339: // we will use getPixels() to extract pixel data for writePixels()
0340: // Please note that getPixels() provides rgb bands order.
0341: bandOffsets = new int[numBands];
0342: for (int i = 0; i < numBands; i++)
0343: bandOffsets[i] = i;
0344: }
0345:
0346: noTransform &= bgrOrder;
0347:
0348: int sampleSize[] = sampleModel.getSampleSize();
0349:
0350: //XXX: check more
0351:
0352: // Number of bytes that a scanline for the image written out will have.
0353: int destScanlineBytes = w * numBands;
0354:
0355: switch (bmpParam.getCompressionMode()) {
0356: case ImageWriteParam.MODE_EXPLICIT:
0357: compressionType = getCompressionType(bmpParam
0358: .getCompressionType());
0359: break;
0360: case ImageWriteParam.MODE_COPY_FROM_METADATA:
0361: compressionType = bmpImageMetadata.compression;
0362: break;
0363: case ImageWriteParam.MODE_DEFAULT:
0364: compressionType = getPreferredCompressionType(colorModel,
0365: sampleModel);
0366: break;
0367: default:
0368: // ImageWriteParam.MODE_DISABLED:
0369: compressionType = BI_RGB;
0370: }
0371:
0372: if (!canEncodeImage(compressionType, colorModel, sampleModel)) {
0373: if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
0374: throw new IIOException("Image can not be encoded with "
0375: + "compression type "
0376: + compressionTypeNames[compressionType]);
0377: } else {
0378: // Set to something appropriate
0379: compressionType = getPreferredCompressionType(
0380: colorModel, sampleModel);
0381: }
0382: }
0383:
0384: byte r[] = null, g[] = null, b[] = null, a[] = null;
0385:
0386: if (compressionType == BMPConstants.BI_BITFIELDS) {
0387: bitsPerPixel = DataBuffer.getDataTypeSize(sampleModel
0388: .getDataType());
0389:
0390: if (bitsPerPixel != 16 && bitsPerPixel != 32) {
0391: // we should use 32bpp images in case of BI_BITFIELD
0392: // compression to avoid color conversion artefacts
0393: bitsPerPixel = 32;
0394:
0395: // Setting this flag to false ensures that generic
0396: // writePixels() will be used to store image data
0397: noTransform = false;
0398: }
0399:
0400: destScanlineBytes = w * bitsPerPixel + 7 >> 3;
0401:
0402: isPalette = true;
0403: paletteEntries = 3;
0404: r = new byte[paletteEntries];
0405: g = new byte[paletteEntries];
0406: b = new byte[paletteEntries];
0407: a = new byte[paletteEntries];
0408:
0409: int rmask = 0x00ff0000;
0410: int gmask = 0x0000ff00;
0411: int bmask = 0x000000ff;
0412:
0413: if (bitsPerPixel == 16) {
0414: /* NB: canEncodeImage() ensures we have image of
0415: * either USHORT_565_RGB or USHORT_555_RGB type here.
0416: * Technically, it should work for other direct color
0417: * model types but it might be non compatible with win98
0418: * and friends.
0419: */
0420: if (colorModel instanceof DirectColorModel) {
0421: DirectColorModel dcm = (DirectColorModel) colorModel;
0422: rmask = dcm.getRedMask();
0423: gmask = dcm.getGreenMask();
0424: bmask = dcm.getBlueMask();
0425: } else {
0426: // it is unlikely, but if it happens, we should throw
0427: // an exception related to unsupported image format
0428: throw new IOException(
0429: "Image can not be encoded with "
0430: + "compression type "
0431: + compressionTypeNames[compressionType]);
0432: }
0433: }
0434: writeMaskToPalette(rmask, 0, r, g, b, a);
0435: writeMaskToPalette(gmask, 1, r, g, b, a);
0436: writeMaskToPalette(bmask, 2, r, g, b, a);
0437:
0438: if (!noTransform) {
0439: // prepare info for writePixels procedure
0440: bitMasks = new int[3];
0441: bitMasks[0] = rmask;
0442: bitMasks[1] = gmask;
0443: bitMasks[2] = bmask;
0444:
0445: bitPos = new int[3];
0446: bitPos[0] = firstLowBit(rmask);
0447: bitPos[1] = firstLowBit(gmask);
0448: bitPos[2] = firstLowBit(bmask);
0449: }
0450:
0451: if (colorModel instanceof IndexColorModel) {
0452: icm = (IndexColorModel) colorModel;
0453: }
0454: } else { // handle BI_RGB compression
0455: if (colorModel instanceof IndexColorModel) {
0456: isPalette = true;
0457: icm = (IndexColorModel) colorModel;
0458: paletteEntries = icm.getMapSize();
0459:
0460: if (paletteEntries <= 2) {
0461: bitsPerPixel = 1;
0462: destScanlineBytes = w + 7 >> 3;
0463: } else if (paletteEntries <= 16) {
0464: bitsPerPixel = 4;
0465: destScanlineBytes = w + 1 >> 1;
0466: } else if (paletteEntries <= 256) {
0467: bitsPerPixel = 8;
0468: } else {
0469: // Cannot be written as a Palette image. So write out as
0470: // 24 bit image.
0471: bitsPerPixel = 24;
0472: isPalette = false;
0473: paletteEntries = 0;
0474: destScanlineBytes = w * 3;
0475: }
0476:
0477: if (isPalette == true) {
0478: r = new byte[paletteEntries];
0479: g = new byte[paletteEntries];
0480: b = new byte[paletteEntries];
0481:
0482: icm.getReds(r);
0483: icm.getGreens(g);
0484: icm.getBlues(b);
0485: }
0486:
0487: } else {
0488: // Grey scale images
0489: if (numBands == 1) {
0490:
0491: isPalette = true;
0492: paletteEntries = 256;
0493: bitsPerPixel = sampleSize[0];
0494:
0495: destScanlineBytes = (w * bitsPerPixel + 7 >> 3);
0496:
0497: r = new byte[256];
0498: g = new byte[256];
0499: b = new byte[256];
0500:
0501: for (int i = 0; i < 256; i++) {
0502: r[i] = (byte) i;
0503: g[i] = (byte) i;
0504: b[i] = (byte) i;
0505: }
0506:
0507: } else {
0508: if (sampleModel instanceof SinglePixelPackedSampleModel
0509: && noSubband) {
0510: /* NB: the actual pixel size can be smaller than
0511: * size of used DataBuffer element.
0512: * For example: in case of TYPE_INT_RGB actual pixel
0513: * size is 24 bits, but size of DataBuffere element
0514: * is 32 bits
0515: */
0516: int[] sample_sizes = sampleModel
0517: .getSampleSize();
0518: bitsPerPixel = 0;
0519: for (int i = 0; i < sample_sizes.length; i++) {
0520: bitsPerPixel += sample_sizes[i];
0521: }
0522: bitsPerPixel = roundBpp(bitsPerPixel);
0523: if (bitsPerPixel != DataBuffer
0524: .getDataTypeSize(sampleModel
0525: .getDataType())) {
0526: noTransform = false;
0527: }
0528: destScanlineBytes = w * bitsPerPixel + 7 >> 3;
0529: }
0530: }
0531: }
0532: }
0533:
0534: // actual writing of image data
0535: int fileSize = 0;
0536: int offset = 0;
0537: int headerSize = 0;
0538: int imageSize = 0;
0539: int xPelsPerMeter = bmpImageMetadata.xPixelsPerMeter;
0540: int yPelsPerMeter = bmpImageMetadata.yPixelsPerMeter;
0541: int colorsUsed = bmpImageMetadata.colorsUsed > 0 ? bmpImageMetadata.colorsUsed
0542: : paletteEntries;
0543: int colorsImportant = paletteEntries;
0544:
0545: // Calculate padding for each scanline
0546: int padding = destScanlineBytes % 4;
0547: if (padding != 0) {
0548: padding = 4 - padding;
0549: }
0550:
0551: // FileHeader is 14 bytes, BitmapHeader is 40 bytes,
0552: // add palette size and that is where the data will begin
0553: offset = 54 + paletteEntries * 4;
0554:
0555: imageSize = (destScanlineBytes + padding) * h;
0556: fileSize = imageSize + offset;
0557: headerSize = 40;
0558:
0559: long headPos = stream.getStreamPosition();
0560:
0561: if (param instanceof BMPImageWriteParam) {
0562: isTopDown = ((BMPImageWriteParam) param).isTopDown();
0563: // topDown = true is only allowed for RGB and BITFIELDS compression
0564: // types by the BMP specification
0565: if (compressionType != BI_RGB
0566: && compressionType != BI_BITFIELDS)
0567: isTopDown = false;
0568: } else {
0569: isTopDown = false;
0570: }
0571:
0572: writeFileHeader(fileSize, offset);
0573:
0574: writeInfoHeader(headerSize, bitsPerPixel);
0575:
0576: // compression
0577: stream.writeInt(compressionType);
0578:
0579: // imageSize
0580: stream.writeInt(imageSize);
0581:
0582: // xPelsPerMeter
0583: stream.writeInt(xPelsPerMeter);
0584:
0585: // yPelsPerMeter
0586: stream.writeInt(yPelsPerMeter);
0587:
0588: // Colors Used
0589: stream.writeInt(colorsUsed);
0590:
0591: // Colors Important
0592: stream.writeInt(colorsImportant);
0593:
0594: // palette
0595: if (isPalette == true) {
0596:
0597: // write palette
0598: if (compressionType == BMPConstants.BI_BITFIELDS) {
0599: // write masks for red, green and blue components.
0600: for (int i = 0; i < 3; i++) {
0601: int mask = (a[i] & 0xFF) + ((r[i] & 0xFF) * 0x100)
0602: + ((g[i] & 0xFF) * 0x10000)
0603: + ((b[i] & 0xFF) * 0x1000000);
0604: stream.writeInt(mask);
0605: }
0606: } else {
0607: for (int i = 0; i < paletteEntries; i++) {
0608: stream.writeByte(b[i]);
0609: stream.writeByte(g[i]);
0610: stream.writeByte(r[i]);
0611: stream.writeByte((byte) 0);// rgbReserved RGBQUAD entry
0612: }
0613: }
0614: }
0615:
0616: // Writing of actual image data
0617: int scanlineBytes = w * numBands;
0618:
0619: // Buffer for up to 8 rows of pixels
0620: int[] pixels = new int[scanlineBytes * scaleX];
0621:
0622: // Also create a buffer to hold one line of the data
0623: // to be written to the file, so we can use array writes.
0624: bpixels = new byte[destScanlineBytes];
0625:
0626: int l;
0627:
0628: if (compressionType == BMPConstants.BI_JPEG
0629: || compressionType == BMPConstants.BI_PNG) {
0630: // prepare embedded buffer
0631: embedded_stream = new ByteArrayOutputStream();
0632: writeEmbedded(image, bmpParam);
0633: // update the file/image Size
0634: embedded_stream.flush();
0635: imageSize = embedded_stream.size();
0636:
0637: long endPos = stream.getStreamPosition();
0638: fileSize = (int) (offset + imageSize);
0639: stream.seek(headPos);
0640: writeSize(fileSize, 2);
0641: stream.seek(headPos);
0642: writeSize(imageSize, 34);
0643: stream.seek(endPos);
0644: stream.write(embedded_stream.toByteArray());
0645: embedded_stream = null;
0646:
0647: if (abortRequested()) {
0648: processWriteAborted();
0649: } else {
0650: processImageComplete();
0651: stream.flushBefore(stream.getStreamPosition());
0652: }
0653:
0654: return;
0655: }
0656:
0657: int maxBandOffset = bandOffsets[0];
0658: for (int i = 1; i < bandOffsets.length; i++)
0659: if (bandOffsets[i] > maxBandOffset)
0660: maxBandOffset = bandOffsets[i];
0661:
0662: int[] pixel = new int[maxBandOffset + 1];
0663:
0664: int destScanlineLength = destScanlineBytes;
0665:
0666: if (noTransform && noSubband) {
0667: destScanlineLength = destScanlineBytes
0668: / (DataBuffer.getDataTypeSize(dataType) >> 3);
0669: }
0670: for (int i = 0; i < h; i++) {
0671: if (abortRequested()) {
0672: break;
0673: }
0674:
0675: int row = minY + i;
0676:
0677: if (!isTopDown)
0678: row = minY + h - i - 1;
0679:
0680: // Get the pixels
0681: Raster src = inputRaster;
0682:
0683: Rectangle srcRect = new Rectangle(minX * scaleX + xOffset,
0684: row * scaleY + yOffset, (w - 1) * scaleX + 1, 1);
0685: if (!writeRaster)
0686: src = input.getData(srcRect);
0687:
0688: if (noTransform && noSubband) {
0689: SampleModel sm = src.getSampleModel();
0690: int pos = 0;
0691: int startX = srcRect.x - src.getSampleModelTranslateX();
0692: int startY = srcRect.y - src.getSampleModelTranslateY();
0693: if (sm instanceof ComponentSampleModel) {
0694: ComponentSampleModel csm = (ComponentSampleModel) sm;
0695: pos = csm.getOffset(startX, startY, 0);
0696: for (int nb = 1; nb < csm.getNumBands(); nb++) {
0697: if (pos > csm.getOffset(startX, startY, nb)) {
0698: pos = csm.getOffset(startX, startY, nb);
0699: }
0700: }
0701: } else if (sm instanceof MultiPixelPackedSampleModel) {
0702: MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel) sm;
0703: pos = mppsm.getOffset(startX, startY);
0704: } else if (sm instanceof SinglePixelPackedSampleModel) {
0705: SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm;
0706: pos = sppsm.getOffset(startX, startY);
0707: }
0708:
0709: if (compressionType == BMPConstants.BI_RGB
0710: || compressionType == BMPConstants.BI_BITFIELDS) {
0711: switch (dataType) {
0712: case DataBuffer.TYPE_BYTE:
0713: byte[] bdata = ((DataBufferByte) src
0714: .getDataBuffer()).getData();
0715: stream.write(bdata, pos, destScanlineLength);
0716: break;
0717:
0718: case DataBuffer.TYPE_SHORT:
0719: short[] sdata = ((DataBufferShort) src
0720: .getDataBuffer()).getData();
0721: stream.writeShorts(sdata, pos,
0722: destScanlineLength);
0723: break;
0724:
0725: case DataBuffer.TYPE_USHORT:
0726: short[] usdata = ((DataBufferUShort) src
0727: .getDataBuffer()).getData();
0728: stream.writeShorts(usdata, pos,
0729: destScanlineLength);
0730: break;
0731:
0732: case DataBuffer.TYPE_INT:
0733: int[] idata = ((DataBufferInt) src
0734: .getDataBuffer()).getData();
0735: stream
0736: .writeInts(idata, pos,
0737: destScanlineLength);
0738: break;
0739: }
0740:
0741: for (int k = 0; k < padding; k++) {
0742: stream.writeByte(0);
0743: }
0744: } else if (compressionType == BMPConstants.BI_RLE4) {
0745: if (bpixels == null
0746: || bpixels.length < scanlineBytes)
0747: bpixels = new byte[scanlineBytes];
0748: src.getPixels(srcRect.x, srcRect.y, srcRect.width,
0749: srcRect.height, pixels);
0750: for (int h = 0; h < scanlineBytes; h++) {
0751: bpixels[h] = (byte) pixels[h];
0752: }
0753: encodeRLE4(bpixels, scanlineBytes);
0754: } else if (compressionType == BMPConstants.BI_RLE8) {
0755: //byte[] bdata =
0756: //((DataBufferByte)src.getDataBuffer()).getData();
0757: //System.out.println("bdata.length="+bdata.length);
0758: //System.arraycopy(bdata, pos, bpixels, 0, scanlineBytes);
0759: if (bpixels == null
0760: || bpixels.length < scanlineBytes)
0761: bpixels = new byte[scanlineBytes];
0762: src.getPixels(srcRect.x, srcRect.y, srcRect.width,
0763: srcRect.height, pixels);
0764: for (int h = 0; h < scanlineBytes; h++) {
0765: bpixels[h] = (byte) pixels[h];
0766: }
0767:
0768: encodeRLE8(bpixels, scanlineBytes);
0769: }
0770: } else {
0771: src.getPixels(srcRect.x, srcRect.y, srcRect.width,
0772: srcRect.height, pixels);
0773:
0774: if (scaleX != 1 || maxBandOffset != numBands - 1) {
0775: for (int j = 0, k = 0, n = 0; j < w; j++, k += scaleX
0776: * numBands, n += numBands) {
0777: System.arraycopy(pixels, k, pixel, 0,
0778: pixel.length);
0779:
0780: for (int m = 0; m < numBands; m++) {
0781: // pixel data is provided here in RGB order
0782: pixels[n + m] = pixel[sourceBands[m]];
0783: }
0784: }
0785: }
0786: writePixels(0, scanlineBytes, bitsPerPixel, pixels,
0787: padding, numBands, icm);
0788: }
0789:
0790: processImageProgress(100.0f * (((float) i) / ((float) h)));
0791: }
0792:
0793: if (compressionType == BMPConstants.BI_RLE4
0794: || compressionType == BMPConstants.BI_RLE8) {
0795: // Write the RLE EOF marker and
0796: stream.writeByte(0);
0797: stream.writeByte(1);
0798: incCompImageSize(2);
0799: // update the file/image Size
0800: imageSize = compImageSize;
0801: fileSize = compImageSize + offset;
0802: long endPos = stream.getStreamPosition();
0803: stream.seek(headPos);
0804: writeSize(fileSize, 2);
0805: stream.seek(headPos);
0806: writeSize(imageSize, 34);
0807: stream.seek(endPos);
0808: }
0809:
0810: if (abortRequested()) {
0811: processWriteAborted();
0812: } else {
0813: processImageComplete();
0814: stream.flushBefore(stream.getStreamPosition());
0815: }
0816: }
0817:
0818: private void writePixels(int l, int scanlineBytes,
0819: int bitsPerPixel, int pixels[], int padding, int numBands,
0820: IndexColorModel icm) throws IOException {
0821: int pixel = 0;
0822: int k = 0;
0823: switch (bitsPerPixel) {
0824:
0825: case 1:
0826:
0827: for (int j = 0; j < scanlineBytes / 8; j++) {
0828: bpixels[k++] = (byte) ((pixels[l++] << 7)
0829: | (pixels[l++] << 6) | (pixels[l++] << 5)
0830: | (pixels[l++] << 4) | (pixels[l++] << 3)
0831: | (pixels[l++] << 2) | (pixels[l++] << 1) | pixels[l++]);
0832: }
0833:
0834: // Partially filled last byte, if any
0835: if (scanlineBytes % 8 > 0) {
0836: pixel = 0;
0837: for (int j = 0; j < scanlineBytes % 8; j++) {
0838: pixel |= (pixels[l++] << (7 - j));
0839: }
0840: bpixels[k++] = (byte) pixel;
0841: }
0842: stream.write(bpixels, 0, (scanlineBytes + 7) / 8);
0843:
0844: break;
0845:
0846: case 4:
0847: if (compressionType == BMPConstants.BI_RLE4) {
0848: byte[] bipixels = new byte[scanlineBytes];
0849: for (int h = 0; h < scanlineBytes; h++) {
0850: bipixels[h] = (byte) pixels[l++];
0851: }
0852: encodeRLE4(bipixels, scanlineBytes);
0853: } else {
0854: for (int j = 0; j < scanlineBytes / 2; j++) {
0855: pixel = (pixels[l++] << 4) | pixels[l++];
0856: bpixels[k++] = (byte) pixel;
0857: }
0858: // Put the last pixel of odd-length lines in the 4 MSBs
0859: if ((scanlineBytes % 2) == 1) {
0860: pixel = pixels[l] << 4;
0861: bpixels[k++] = (byte) pixel;
0862: }
0863: stream.write(bpixels, 0, (scanlineBytes + 1) / 2);
0864: }
0865: break;
0866:
0867: case 8:
0868: if (compressionType == BMPConstants.BI_RLE8) {
0869: for (int h = 0; h < scanlineBytes; h++) {
0870: bpixels[h] = (byte) pixels[l++];
0871: }
0872: encodeRLE8(bpixels, scanlineBytes);
0873: } else {
0874: for (int j = 0; j < scanlineBytes; j++) {
0875: bpixels[j] = (byte) pixels[l++];
0876: }
0877: stream.write(bpixels, 0, scanlineBytes);
0878: }
0879: break;
0880:
0881: case 16:
0882: if (spixels == null)
0883: spixels = new short[scanlineBytes / numBands];
0884: /*
0885: * We expect that pixel data comes in RGB order.
0886: * We will assemble short pixel taking into account
0887: * the compression type:
0888: *
0889: * BI_RGB - the RGB order should be maintained.
0890: * BI_BITFIELDS - use bitPos array that was built
0891: * according to bitfields masks.
0892: */
0893: for (int j = 0, m = 0; j < scanlineBytes; m++) {
0894: spixels[m] = 0;
0895: if (compressionType == BMPConstants.BI_RGB) {
0896: /*
0897: * please note that despite other cases,
0898: * the 16bpp BI_RGB requires the RGB data order
0899: */
0900: spixels[m] = (short) (((0x1f & pixels[j]) << 10)
0901: | ((0x1f & pixels[j + 1]) << 5) | ((0x1f & pixels[j + 2])));
0902: j += 3;
0903: } else {
0904: for (int i = 0; i < numBands; i++, j++) {
0905: spixels[m] |= (((pixels[j]) << bitPos[i]) & bitMasks[i]);
0906: }
0907: }
0908: }
0909: stream.writeShorts(spixels, 0, spixels.length);
0910: break;
0911:
0912: case 24:
0913: if (numBands == 3) {
0914: for (int j = 0; j < scanlineBytes; j += 3) {
0915: // Since BMP needs BGR format
0916: bpixels[k++] = (byte) (pixels[l + 2]);
0917: bpixels[k++] = (byte) (pixels[l + 1]);
0918: bpixels[k++] = (byte) (pixels[l]);
0919: l += 3;
0920: }
0921: stream.write(bpixels, 0, scanlineBytes);
0922: } else {
0923: // Case where IndexColorModel had > 256 colors.
0924: int entries = icm.getMapSize();
0925:
0926: byte r[] = new byte[entries];
0927: byte g[] = new byte[entries];
0928: byte b[] = new byte[entries];
0929:
0930: icm.getReds(r);
0931: icm.getGreens(g);
0932: icm.getBlues(b);
0933: int index;
0934:
0935: for (int j = 0; j < scanlineBytes; j++) {
0936: index = pixels[l];
0937: bpixels[k++] = b[index];
0938: bpixels[k++] = g[index];
0939: bpixels[k++] = b[index];
0940: l++;
0941: }
0942: stream.write(bpixels, 0, scanlineBytes * 3);
0943: }
0944: break;
0945:
0946: case 32:
0947: if (ipixels == null)
0948: ipixels = new int[scanlineBytes / numBands];
0949: if (numBands == 3) {
0950: /*
0951: * We expect that pixel data comes in RGB order.
0952: * We will assemble int pixel taking into account
0953: * the compression type.
0954: *
0955: * BI_RGB - the BGR order should be used.
0956: * BI_BITFIELDS - use bitPos array that was built
0957: * according to bitfields masks.
0958: */
0959: for (int j = 0, m = 0; j < scanlineBytes; m++) {
0960: ipixels[m] = 0;
0961: if (compressionType == BMPConstants.BI_RGB) {
0962: ipixels[m] = ((0xff & pixels[j + 2]) << 16)
0963: | ((0xff & pixels[j + 1]) << 8)
0964: | ((0xff & pixels[j]));
0965: j += 3;
0966: } else {
0967: for (int i = 0; i < numBands; i++, j++) {
0968: ipixels[m] |= (((pixels[j]) << bitPos[i]) & bitMasks[i]);
0969: }
0970: }
0971: }
0972: } else {
0973: // We have two possibilities here:
0974: // 1. we are writing the indexed image with bitfields
0975: // compression (this covers also the case of BYTE_BINARY)
0976: // => use icm to get actual RGB color values.
0977: // 2. we are writing the gray-scaled image with BI_BITFIELDS
0978: // compression
0979: // => just replicate the level of gray to color components.
0980: for (int j = 0; j < scanlineBytes; j++) {
0981: if (icm != null) {
0982: ipixels[j] = icm.getRGB(pixels[j]);
0983: } else {
0984: ipixels[j] = pixels[j] << 16 | pixels[j] << 8
0985: | pixels[j];
0986: }
0987: }
0988: }
0989: stream.writeInts(ipixels, 0, ipixels.length);
0990: break;
0991: }
0992:
0993: // Write out the padding
0994: if (compressionType == BMPConstants.BI_RGB
0995: || compressionType == BMPConstants.BI_BITFIELDS) {
0996: for (k = 0; k < padding; k++) {
0997: stream.writeByte(0);
0998: }
0999: }
1000: }
1001:
1002: private void encodeRLE8(byte[] bpixels, int scanlineBytes)
1003: throws IOException {
1004:
1005: int runCount = 1, absVal = -1, j = -1;
1006: byte runVal = 0, nextVal = 0;
1007:
1008: runVal = bpixels[++j];
1009: byte[] absBuf = new byte[256];
1010:
1011: while (j < scanlineBytes - 1) {
1012: nextVal = bpixels[++j];
1013: if (nextVal == runVal) {
1014: if (absVal >= 3) {
1015: /// Check if there was an existing Absolute Run
1016: stream.writeByte(0);
1017: stream.writeByte(absVal);
1018: incCompImageSize(2);
1019: for (int a = 0; a < absVal; a++) {
1020: stream.writeByte(absBuf[a]);
1021: incCompImageSize(1);
1022: }
1023: if (!isEven(absVal)) {
1024: //Padding
1025: stream.writeByte(0);
1026: incCompImageSize(1);
1027: }
1028: } else if (absVal > -1) {
1029: /// Absolute Encoding for less than 3
1030: /// treated as regular encoding
1031: /// Do not include the last element since it will
1032: /// be inclued in the next encoding/run
1033: for (int b = 0; b < absVal; b++) {
1034: stream.writeByte(1);
1035: stream.writeByte(absBuf[b]);
1036: incCompImageSize(2);
1037: }
1038: }
1039: absVal = -1;
1040: runCount++;
1041: if (runCount == 256) {
1042: /// Only 255 values permitted
1043: stream.writeByte(runCount - 1);
1044: stream.writeByte(runVal);
1045: incCompImageSize(2);
1046: runCount = 1;
1047: }
1048: } else {
1049: if (runCount > 1) {
1050: /// If there was an existing run
1051: stream.writeByte(runCount);
1052: stream.writeByte(runVal);
1053: incCompImageSize(2);
1054: } else if (absVal < 0) {
1055: // First time..
1056: absBuf[++absVal] = runVal;
1057: absBuf[++absVal] = nextVal;
1058: } else if (absVal < 254) {
1059: // 0-254 only
1060: absBuf[++absVal] = nextVal;
1061: } else {
1062: stream.writeByte(0);
1063: stream.writeByte(absVal + 1);
1064: incCompImageSize(2);
1065: for (int a = 0; a <= absVal; a++) {
1066: stream.writeByte(absBuf[a]);
1067: incCompImageSize(1);
1068: }
1069: // padding since 255 elts is not even
1070: stream.writeByte(0);
1071: incCompImageSize(1);
1072: absVal = -1;
1073: }
1074: runVal = nextVal;
1075: runCount = 1;
1076: }
1077:
1078: if (j == scanlineBytes - 1) { // EOF scanline
1079: // Write the run
1080: if (absVal == -1) {
1081: stream.writeByte(runCount);
1082: stream.writeByte(runVal);
1083: incCompImageSize(2);
1084: runCount = 1;
1085: } else {
1086: // write the Absolute Run
1087: if (absVal >= 2) {
1088: stream.writeByte(0);
1089: stream.writeByte(absVal + 1);
1090: incCompImageSize(2);
1091: for (int a = 0; a <= absVal; a++) {
1092: stream.writeByte(absBuf[a]);
1093: incCompImageSize(1);
1094: }
1095: if (!isEven(absVal + 1)) {
1096: //Padding
1097: stream.writeByte(0);
1098: incCompImageSize(1);
1099: }
1100:
1101: } else if (absVal > -1) {
1102: for (int b = 0; b <= absVal; b++) {
1103: stream.writeByte(1);
1104: stream.writeByte(absBuf[b]);
1105: incCompImageSize(2);
1106: }
1107: }
1108: }
1109: /// EOF scanline
1110:
1111: stream.writeByte(0);
1112: stream.writeByte(0);
1113: incCompImageSize(2);
1114: }
1115: }
1116: }
1117:
1118: private void encodeRLE4(byte[] bipixels, int scanlineBytes)
1119: throws IOException {
1120:
1121: int runCount = 2, absVal = -1, j = -1, pixel = 0, q = 0;
1122: byte runVal1 = 0, runVal2 = 0, nextVal1 = 0, nextVal2 = 0;
1123: byte[] absBuf = new byte[256];
1124:
1125: runVal1 = bipixels[++j];
1126: runVal2 = bipixels[++j];
1127:
1128: while (j < scanlineBytes - 2) {
1129: nextVal1 = bipixels[++j];
1130: nextVal2 = bipixels[++j];
1131:
1132: if (nextVal1 == runVal1) {
1133:
1134: //Check if there was an existing Absolute Run
1135: if (absVal >= 4) {
1136: stream.writeByte(0);
1137: stream.writeByte(absVal - 1);
1138: incCompImageSize(2);
1139: // we need to exclude last 2 elts, similarity of
1140: // which caused to enter this part of the code
1141: for (int a = 0; a < absVal - 2; a += 2) {
1142: pixel = (absBuf[a] << 4) | absBuf[a + 1];
1143: stream.writeByte((byte) pixel);
1144: incCompImageSize(1);
1145: }
1146: // if # of elts is odd - read the last element
1147: if (!(isEven(absVal - 1))) {
1148: q = absBuf[absVal - 2] << 4 | 0;
1149: stream.writeByte(q);
1150: incCompImageSize(1);
1151: }
1152: // Padding to word align absolute encoding
1153: if (!isEven((int) Math.ceil((absVal - 1) / 2))) {
1154: stream.writeByte(0);
1155: incCompImageSize(1);
1156: }
1157: } else if (absVal > -1) {
1158: stream.writeByte(2);
1159: pixel = (absBuf[0] << 4) | absBuf[1];
1160: stream.writeByte(pixel);
1161: incCompImageSize(2);
1162: }
1163: absVal = -1;
1164:
1165: if (nextVal2 == runVal2) {
1166: // Even runlength
1167: runCount += 2;
1168: if (runCount == 256) {
1169: stream.writeByte(runCount - 1);
1170: pixel = (runVal1 << 4) | runVal2;
1171: stream.writeByte(pixel);
1172: incCompImageSize(2);
1173: runCount = 2;
1174: if (j < scanlineBytes - 1) {
1175: runVal1 = runVal2;
1176: runVal2 = bipixels[++j];
1177: } else {
1178: stream.writeByte(01);
1179: int r = runVal2 << 4 | 0;
1180: stream.writeByte(r);
1181: incCompImageSize(2);
1182: runCount = -1;/// Only EOF required now
1183: }
1184: }
1185: } else {
1186: // odd runlength and the run ends here
1187: // runCount wont be > 254 since 256/255 case will
1188: // be taken care of in above code.
1189: runCount++;
1190: pixel = (runVal1 << 4) | runVal2;
1191: stream.writeByte(runCount);
1192: stream.writeByte(pixel);
1193: incCompImageSize(2);
1194: runCount = 2;
1195: runVal1 = nextVal2;
1196: // If end of scanline
1197: if (j < scanlineBytes - 1) {
1198: runVal2 = bipixels[++j];
1199: } else {
1200: stream.writeByte(01);
1201: int r = nextVal2 << 4 | 0;
1202: stream.writeByte(r);
1203: incCompImageSize(2);
1204: runCount = -1;/// Only EOF required now
1205: }
1206:
1207: }
1208: } else {
1209: // Check for existing run
1210: if (runCount > 2) {
1211: pixel = (runVal1 << 4) | runVal2;
1212: stream.writeByte(runCount);
1213: stream.writeByte(pixel);
1214: incCompImageSize(2);
1215: } else if (absVal < 0) { // first time
1216: absBuf[++absVal] = runVal1;
1217: absBuf[++absVal] = runVal2;
1218: absBuf[++absVal] = nextVal1;
1219: absBuf[++absVal] = nextVal2;
1220: } else if (absVal < 253) { // only 255 elements
1221: absBuf[++absVal] = nextVal1;
1222: absBuf[++absVal] = nextVal2;
1223: } else {
1224: stream.writeByte(0);
1225: stream.writeByte(absVal + 1);
1226: incCompImageSize(2);
1227: for (int a = 0; a < absVal; a += 2) {
1228: pixel = (absBuf[a] << 4) | absBuf[a + 1];
1229: stream.writeByte((byte) pixel);
1230: incCompImageSize(1);
1231: }
1232: // Padding for word align
1233: // since it will fit into 127 bytes
1234: stream.writeByte(0);
1235: incCompImageSize(1);
1236: absVal = -1;
1237: }
1238:
1239: runVal1 = nextVal1;
1240: runVal2 = nextVal2;
1241: runCount = 2;
1242: }
1243: // Handle the End of scanline for the last 2 4bits
1244: if (j >= scanlineBytes - 2) {
1245: if (absVal == -1 && runCount >= 2) {
1246: if (j == scanlineBytes - 2) {
1247: if (bipixels[++j] == runVal1) {
1248: runCount++;
1249: pixel = (runVal1 << 4) | runVal2;
1250: stream.writeByte(runCount);
1251: stream.writeByte(pixel);
1252: incCompImageSize(2);
1253: } else {
1254: pixel = (runVal1 << 4) | runVal2;
1255: stream.writeByte(runCount);
1256: stream.writeByte(pixel);
1257: stream.writeByte(01);
1258: pixel = bipixels[j] << 4 | 0;
1259: stream.writeByte(pixel);
1260: int n = bipixels[j] << 4 | 0;
1261: incCompImageSize(4);
1262: }
1263: } else {
1264: stream.writeByte(runCount);
1265: pixel = (runVal1 << 4) | runVal2;
1266: stream.writeByte(pixel);
1267: incCompImageSize(2);
1268: }
1269: } else if (absVal > -1) {
1270: if (j == scanlineBytes - 2) {
1271: absBuf[++absVal] = bipixels[++j];
1272: }
1273: if (absVal >= 2) {
1274: stream.writeByte(0);
1275: stream.writeByte(absVal + 1);
1276: incCompImageSize(2);
1277: for (int a = 0; a < absVal; a += 2) {
1278: pixel = (absBuf[a] << 4) | absBuf[a + 1];
1279: stream.writeByte((byte) pixel);
1280: incCompImageSize(1);
1281: }
1282: if (!(isEven(absVal + 1))) {
1283: q = absBuf[absVal] << 4 | 0;
1284: stream.writeByte(q);
1285: incCompImageSize(1);
1286: }
1287:
1288: // Padding
1289: if (!isEven((int) Math.ceil((absVal + 1) / 2))) {
1290: stream.writeByte(0);
1291: incCompImageSize(1);
1292: }
1293:
1294: } else {
1295: switch (absVal) {
1296: case 0:
1297: stream.writeByte(1);
1298: int n = absBuf[0] << 4 | 0;
1299: stream.writeByte(n);
1300: incCompImageSize(2);
1301: break;
1302: case 1:
1303: stream.writeByte(2);
1304: pixel = (absBuf[0] << 4) | absBuf[1];
1305: stream.writeByte(pixel);
1306: incCompImageSize(2);
1307: break;
1308: }
1309: }
1310:
1311: }
1312: stream.writeByte(0);
1313: stream.writeByte(0);
1314: incCompImageSize(2);
1315: }
1316: }
1317: }
1318:
1319: private synchronized void incCompImageSize(int value) {
1320: compImageSize = compImageSize + value;
1321: }
1322:
1323: private boolean isEven(int number) {
1324: return (number % 2 == 0 ? true : false);
1325: }
1326:
1327: private void writeFileHeader(int fileSize, int offset)
1328: throws IOException {
1329: // magic value
1330: stream.writeByte('B');
1331: stream.writeByte('M');
1332:
1333: // File size
1334: stream.writeInt(fileSize);
1335:
1336: // reserved1 and reserved2
1337: stream.writeInt(0);
1338:
1339: // offset to image data
1340: stream.writeInt(offset);
1341: }
1342:
1343: private void writeInfoHeader(int headerSize, int bitsPerPixel)
1344: throws IOException {
1345: // size of header
1346: stream.writeInt(headerSize);
1347:
1348: // width
1349: stream.writeInt(w);
1350:
1351: // height
1352: if (isTopDown == true)
1353: stream.writeInt(-h);
1354: else
1355: stream.writeInt(h);
1356:
1357: // number of planes
1358: stream.writeShort(1);
1359:
1360: // Bits Per Pixel
1361: stream.writeShort(bitsPerPixel);
1362: }
1363:
1364: private void writeSize(int dword, int offset) throws IOException {
1365: stream.skipBytes(offset);
1366: stream.writeInt(dword);
1367: }
1368:
1369: public void reset() {
1370: super .reset();
1371: stream = null;
1372: }
1373:
1374: static int getCompressionType(String typeString) {
1375: for (int i = 0; i < BMPConstants.compressionTypeNames.length; i++)
1376: if (BMPConstants.compressionTypeNames[i].equals(typeString))
1377: return i;
1378: return 0;
1379: }
1380:
1381: private void writeEmbedded(IIOImage image, ImageWriteParam bmpParam)
1382: throws IOException {
1383: String format = compressionType == BMPConstants.BI_JPEG ? "jpeg"
1384: : "png";
1385: Iterator iterator = ImageIO.getImageWritersByFormatName(format);
1386: ImageWriter writer = null;
1387: if (iterator.hasNext())
1388: writer = (ImageWriter) iterator.next();
1389: if (writer != null) {
1390: if (embedded_stream == null) {
1391: throw new RuntimeException(
1392: "No stream for writing embedded image!");
1393: }
1394:
1395: writer
1396: .addIIOWriteProgressListener(new IIOWriteProgressAdapter() {
1397: public void imageProgress(ImageWriter source,
1398: float percentageDone) {
1399: processImageProgress(percentageDone);
1400: }
1401: });
1402:
1403: writer
1404: .addIIOWriteWarningListener(new IIOWriteWarningListener() {
1405: public void warningOccurred(ImageWriter source,
1406: int imageIndex, String warning) {
1407: processWarningOccurred(imageIndex, warning);
1408: }
1409: });
1410:
1411: ImageOutputStream emb_ios = ImageIO
1412: .createImageOutputStream(embedded_stream);
1413: writer.setOutput(emb_ios);
1414: ImageWriteParam param = writer.getDefaultWriteParam();
1415: //param.setDestinationBands(bmpParam.getDestinationBands());
1416: param.setDestinationOffset(bmpParam.getDestinationOffset());
1417: param.setSourceBands(bmpParam.getSourceBands());
1418: param.setSourceRegion(bmpParam.getSourceRegion());
1419: param.setSourceSubsampling(
1420: bmpParam.getSourceXSubsampling(), bmpParam
1421: .getSourceYSubsampling(), bmpParam
1422: .getSubsamplingXOffset(), bmpParam
1423: .getSubsamplingYOffset());
1424: writer.write(null, image, param);
1425: emb_ios.flush();
1426: } else
1427: throw new RuntimeException(I18N.getString("BMPImageWrite5")
1428: + " " + format);
1429:
1430: }
1431:
1432: private int firstLowBit(int num) {
1433: int count = 0;
1434: while ((num & 1) == 0) {
1435: count++;
1436: num >>>= 1;
1437: }
1438: return count;
1439: }
1440:
1441: private class IIOWriteProgressAdapter implements
1442: IIOWriteProgressListener {
1443:
1444: public void imageComplete(ImageWriter source) {
1445: }
1446:
1447: public void imageProgress(ImageWriter source,
1448: float percentageDone) {
1449: }
1450:
1451: public void imageStarted(ImageWriter source, int imageIndex) {
1452: }
1453:
1454: public void thumbnailComplete(ImageWriter source) {
1455: }
1456:
1457: public void thumbnailProgress(ImageWriter source,
1458: float percentageDone) {
1459: }
1460:
1461: public void thumbnailStarted(ImageWriter source,
1462: int imageIndex, int thumbnailIndex) {
1463: }
1464:
1465: public void writeAborted(ImageWriter source) {
1466: }
1467: }
1468:
1469: /*
1470: * Returns preferred compression type for given image.
1471: * The default compression type is BI_RGB, but some image types can't be
1472: * encoded with using default compression without changing color resolution.
1473: * For example, BufferedImage.TYPE_USHORT_555_RGB and
1474: * BufferedImage.TYPE_USHORT_565_RGB may be encoded only by using the
1475: * BI_BITFIELDS compression type.
1476: *
1477: * NB: we probably need to extend this method if we encounter other image
1478: * types which can not be encoded with BI_RGB compression type.
1479: */
1480: static int getPreferredCompressionType(ColorModel cm, SampleModel sm) {
1481: ImageTypeSpecifier imageType = new ImageTypeSpecifier(cm, sm);
1482: return getPreferredCompressionType(imageType);
1483: }
1484:
1485: static int getPreferredCompressionType(ImageTypeSpecifier imageType) {
1486: int biType = imageType.getBufferedImageType();
1487: if (biType == BufferedImage.TYPE_USHORT_565_RGB
1488: || biType == BufferedImage.TYPE_USHORT_555_RGB) {
1489: return BI_BITFIELDS;
1490: }
1491: return BI_RGB;
1492: }
1493:
1494: /*
1495: * Check whether we can encode image of given type using compression method in question.
1496: *
1497: * For example, TYPE_USHORT_565_RGB can be encodeed with BI_BITFIELDS compression only.
1498: *
1499: * NB: method should be extended if other cases when we can not encode
1500: * with given compression will be discovered.
1501: */
1502: protected boolean canEncodeImage(int compression, ColorModel cm,
1503: SampleModel sm) {
1504: ImageTypeSpecifier imgType = new ImageTypeSpecifier(cm, sm);
1505: return canEncodeImage(compression, imgType);
1506: }
1507:
1508: protected boolean canEncodeImage(int compression,
1509: ImageTypeSpecifier imgType) {
1510: ImageWriterSpi spi = this .getOriginatingProvider();
1511: if (!spi.canEncodeImage(imgType)) {
1512: return false;
1513: }
1514: int bpp = imgType.getColorModel().getPixelSize();
1515: if (compressionType == BI_RLE4 && bpp != 4) {
1516: // only 4bpp images can be encoded as BI_RLE4
1517: return false;
1518: }
1519: if (compressionType == BI_RLE8 && bpp != 8) {
1520: // only 8bpp images can be encoded as BI_RLE8
1521: return false;
1522: }
1523: if (bpp == 16) {
1524: /*
1525: * Technically we expect that we may be able to
1526: * encode only some of SinglePixelPackedSampleModel
1527: * images here.
1528: *
1529: * In addition we should take into account following:
1530: *
1531: * 1. BI_RGB case, according to the MSDN description:
1532: *
1533: * The bitmap has a maximum of 2^16 colors. If the
1534: * biCompression member of the BITMAPINFOHEADER is BI_RGB,
1535: * the bmiColors member of BITMAPINFO is NULL. Each WORD
1536: * in the bitmap array represents a single pixel. The
1537: * relative intensities of red, green, and blue are
1538: * represented with five bits for each color component.
1539: *
1540: * 2. BI_BITFIELDS case, according ot the MSDN description:
1541: *
1542: * Windows 95/98/Me: When the biCompression member is
1543: * BI_BITFIELDS, the system supports only the following
1544: * 16bpp color masks: A 5-5-5 16-bit image, where the blue
1545: * mask is 0x001F, the green mask is 0x03E0, and the red mask
1546: * is 0x7C00; and a 5-6-5 16-bit image, where the blue mask
1547: * is 0x001F, the green mask is 0x07E0, and the red mask is
1548: * 0xF800.
1549: */
1550: boolean canUseRGB = false;
1551: boolean canUseBITFIELDS = false;
1552:
1553: SampleModel sm = imgType.getSampleModel();
1554: if (sm instanceof SinglePixelPackedSampleModel) {
1555: int[] sizes = ((SinglePixelPackedSampleModel) sm)
1556: .getSampleSize();
1557:
1558: canUseRGB = true;
1559: canUseBITFIELDS = true;
1560: for (int i = 0; i < sizes.length; i++) {
1561: canUseRGB &= (sizes[i] == 5);
1562: canUseBITFIELDS &= ((sizes[i] == 5) || (i == 1 && sizes[i] == 6));
1563: }
1564: }
1565:
1566: return (((compressionType == BI_RGB) && canUseRGB) || ((compressionType == BI_BITFIELDS) && canUseBITFIELDS));
1567: }
1568: return true;
1569: }
1570:
1571: protected void writeMaskToPalette(int mask, int i, byte[] r,
1572: byte[] g, byte[] b, byte[] a) {
1573: b[i] = (byte) (0xff & (mask >> 24));
1574: g[i] = (byte) (0xff & (mask >> 16));
1575: r[i] = (byte) (0xff & (mask >> 8));
1576: a[i] = (byte) (0xff & mask);
1577: }
1578:
1579: private int roundBpp(int x) {
1580: if (x <= 8) {
1581: return 8;
1582: } else if (x <= 16) {
1583: return 16;
1584: }
1585: if (x <= 24) {
1586: return 24;
1587: } else {
1588: return 32;
1589: }
1590: }
1591: }
|