001: /*
002: * $RCSfile: WBMPCodec.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.2 $
009: * $Date: 2005/12/14 19:24:54 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codecimpl;
013:
014: import java.awt.Point;
015: import java.awt.image.BufferedImage;
016: import java.awt.image.DataBuffer;
017: import java.awt.image.DataBufferByte;
018: import java.awt.image.IndexColorModel;
019: import java.awt.image.MultiPixelPackedSampleModel;
020: import java.awt.image.Raster;
021: import java.awt.image.RenderedImage;
022: import java.awt.image.SampleModel;
023: import java.awt.image.WritableRaster;
024: import java.io.BufferedInputStream;
025: import java.io.InputStream;
026: import java.io.IOException;
027: import java.io.OutputStream;
028: import com.sun.media.jai.codec.ForwardSeekableStream;
029: import com.sun.media.jai.codec.ImageCodec;
030: import com.sun.media.jai.codec.ImageDecoder;
031: import com.sun.media.jai.codec.ImageDecoderImpl;
032: import com.sun.media.jai.codec.ImageDecodeParam;
033: import com.sun.media.jai.codec.ImageEncoder;
034: import com.sun.media.jai.codec.ImageEncoderImpl;
035: import com.sun.media.jai.codec.ImageEncodeParam;
036: import com.sun.media.jai.codec.SeekableStream;
037:
038: /**
039: * A subclass of <code>ImageCodec</code> that handles the WBMP format.
040: */
041: public final class WBMPCodec extends ImageCodec {
042:
043: public WBMPCodec() {
044: }
045:
046: public String getFormatName() {
047: return "wbmp";
048: }
049:
050: public Class getEncodeParamClass() {
051: return Object.class;
052: }
053:
054: public Class getDecodeParamClass() {
055: return Object.class;
056: }
057:
058: public boolean canEncodeImage(RenderedImage im,
059: ImageEncodeParam param) {
060: SampleModel sampleModel = im.getSampleModel();
061:
062: int dataType = sampleModel.getTransferType();
063: if (dataType == DataBuffer.TYPE_FLOAT
064: || dataType == DataBuffer.TYPE_DOUBLE
065: || sampleModel.getNumBands() != 1
066: || sampleModel.getSampleSize(0) != 1) {
067: return false;
068: }
069:
070: return true;
071: }
072:
073: protected ImageEncoder createImageEncoder(OutputStream dst,
074: ImageEncodeParam param) {
075: return new WBMPImageEncoder(dst, null);
076: }
077:
078: protected ImageDecoder createImageDecoder(InputStream src,
079: ImageDecodeParam param) {
080: // Add buffering for efficiency
081: if (!(src instanceof BufferedInputStream)) {
082: src = new BufferedInputStream(src);
083: }
084: return new WBMPImageDecoder(new ForwardSeekableStream(src),
085: null);
086: }
087:
088: protected ImageDecoder createImageDecoder(SeekableStream src,
089: ImageDecodeParam param) {
090: return new WBMPImageDecoder(src, null);
091: }
092:
093: public int getNumHeaderBytes() {
094: return 3;
095: }
096:
097: public boolean isFormatRecognized(byte[] header) {
098: // WBMP has no magic bytes at the beginning so simply check
099: // the first three bytes for known constraints.
100: return ((header[0] == (byte) 0) && // TypeField == 0
101: header[1] == 0 && // FixHeaderField == 0xxx00000; not support ext header
102: ((header[2] & 0x8f) != 0 || (header[2] & 0x7f) != 0)); // First width byte
103: //XXX: header[2] & 0x8f) != 0 for the bug in Sony Ericsson encoder.
104: }
105: }
106:
107: final class WBMPImageEncoder extends ImageEncoderImpl {
108:
109: // Get the number of bits required to represent an int.
110: private static int getNumBits(int intValue) {
111: int numBits = 32;
112: int mask = 0x80000000;
113: while (mask != 0 && (intValue & mask) == 0) {
114: numBits--;
115: mask >>>= 1;
116: }
117: return numBits;
118: }
119:
120: // Convert an int value to WBMP multi-byte format.
121: private static byte[] intToMultiByte(int intValue) {
122: int numBitsLeft = getNumBits(intValue);
123: byte[] multiBytes = new byte[(numBitsLeft + 6) / 7];
124:
125: int maxIndex = multiBytes.length - 1;
126: for (int b = 0; b <= maxIndex; b++) {
127: multiBytes[b] = (byte) ((intValue >>> ((maxIndex - b) * 7)) & 0x7f);
128: if (b != maxIndex) {
129: multiBytes[b] |= (byte) 0x80;
130: }
131: }
132:
133: return multiBytes;
134: }
135:
136: public WBMPImageEncoder(OutputStream output, ImageEncodeParam param) {
137: super (output, param);
138: }
139:
140: public void encode(RenderedImage im) throws IOException {
141: // Get the SampleModel.
142: SampleModel sm = im.getSampleModel();
143:
144: // Check the data type, band count, and sample size.
145: int dataType = sm.getTransferType();
146: if (dataType == DataBuffer.TYPE_FLOAT
147: || dataType == DataBuffer.TYPE_DOUBLE) {
148: throw new IllegalArgumentException(JaiI18N
149: .getString("WBMPImageEncoder0"));
150: } else if (sm.getNumBands() != 1) {
151: throw new IllegalArgumentException(JaiI18N
152: .getString("WBMPImageEncoder1"));
153: } else if (sm.getSampleSize(0) != 1) {
154: throw new IllegalArgumentException(JaiI18N
155: .getString("WBMPImageEncoder2"));
156: }
157:
158: // Save image dimensions.
159: int width = im.getWidth();
160: int height = im.getHeight();
161:
162: // Write WBMP header.
163: output.write(0); // TypeField
164: output.write(0); // FixHeaderField
165: output.write(intToMultiByte(width)); // width
166: output.write(intToMultiByte(height)); // height
167:
168: Raster tile = null;
169:
170: // If the data are not formatted nominally then reformat.
171: if (sm.getDataType() != DataBuffer.TYPE_BYTE
172: || !(sm instanceof MultiPixelPackedSampleModel)
173: || ((MultiPixelPackedSampleModel) sm)
174: .getDataBitOffset() != 0) {
175: MultiPixelPackedSampleModel mppsm = new MultiPixelPackedSampleModel(
176: DataBuffer.TYPE_BYTE, width, height, 1,
177: (width + 7) / 8, 0);
178: WritableRaster raster = Raster.createWritableRaster(mppsm,
179: new Point(im.getMinX(), im.getMinY()));
180: raster.setRect(im.getData());
181: tile = raster;
182: } else if (im.getNumXTiles() == 1 && im.getNumYTiles() == 1) {
183: tile = im.getTile(im.getMinTileX(), im.getMinTileY());
184: } else {
185: tile = im.getData();
186: }
187:
188: // Check whether the image is white-is-zero.
189: boolean isWhiteZero = false;
190: if (im.getColorModel() instanceof IndexColorModel) {
191: IndexColorModel icm = (IndexColorModel) im.getColorModel();
192: isWhiteZero = (icm.getRed(0) + icm.getGreen(0) + icm
193: .getBlue(0)) > (icm.getRed(1) + icm.getGreen(1) + icm
194: .getBlue(1));
195: }
196:
197: // Get the line stride, bytes per row, and data array.
198: int lineStride = ((MultiPixelPackedSampleModel) sm)
199: .getScanlineStride();
200: int bytesPerRow = (width + 7) / 8;
201: byte[] bdata = ((DataBufferByte) tile.getDataBuffer())
202: .getData();
203:
204: // Write the data.
205: if (!isWhiteZero && lineStride == bytesPerRow) {
206: // Write the entire image.
207: output.write(bdata, 0, height * bytesPerRow);
208: } else {
209: // Write the image row-by-row.
210: int offset = 0;
211: if (!isWhiteZero) {
212: // Black-is-zero
213: for (int row = 0; row < height; row++) {
214: output.write(bdata, offset, bytesPerRow);
215: offset += lineStride;
216: }
217: } else {
218: // White-is-zero: need to invert data.
219: byte[] inverted = new byte[bytesPerRow];
220: for (int row = 0; row < height; row++) {
221: for (int col = 0; col < bytesPerRow; col++) {
222: inverted[col] = (byte) (~(bdata[col + offset]));
223: }
224: output.write(inverted, 0, bytesPerRow);
225: offset += lineStride;
226: }
227: }
228: }
229: }
230: }
231:
232: final class WBMPImageDecoder extends ImageDecoderImpl {
233:
234: public WBMPImageDecoder(SeekableStream input, ImageDecodeParam param) {
235: super (input, param);
236: }
237:
238: public RenderedImage decodeAsRenderedImage(int page)
239: throws IOException {
240: if (page != 0) {
241: throw new IOException(JaiI18N.getString(JaiI18N
242: .getString("WBMPImageDecoder0")));
243: }
244:
245: input.read(); // TypeField
246: input.read(); // FixHeaderField
247:
248: // Image width
249: int value = input.read();
250: int width = value & 0x7f;
251: while ((value & 0x80) == 0x80) {
252: width <<= 7;
253: value = input.read();
254: width |= (value & 0x7f);
255: }
256:
257: // Image height
258: value = input.read();
259: int height = value & 0x7f;
260: while ((value & 0x80) == 0x80) {
261: height <<= 7;
262: value = input.read();
263: height |= (value & 0x7f);
264: }
265:
266: // Create byte-packed bilevel image width an IndexColorModel
267: BufferedImage bi = new BufferedImage(width, height,
268: BufferedImage.TYPE_BYTE_BINARY);
269:
270: // Get the image tile.
271: WritableRaster tile = bi.getWritableTile(0, 0);
272:
273: // Get the SampleModel.
274: MultiPixelPackedSampleModel sm = (MultiPixelPackedSampleModel) bi
275: .getSampleModel();
276:
277: // Read the data.
278: input.readFully(((DataBufferByte) tile.getDataBuffer())
279: .getData(), 0, height * sm.getScanlineStride());
280:
281: return bi;
282: }
283: }
|