001/*
002 * $RCSfile: WBMPImageWriter.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.1 $
042 * $Date: 2005/02/11 05:01:52 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.wbmp;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.image.DataBuffer;
050import java.awt.image.DataBufferByte;
051import java.awt.image.IndexColorModel;
052import java.awt.image.MultiPixelPackedSampleModel;
053import java.awt.image.Raster;
054import java.awt.image.RenderedImage;
055import java.awt.image.SampleModel;
056import java.awt.image.WritableRaster;
057import java.io.IOException;
058
059import javax.imageio.IIOImage;
060import javax.imageio.ImageTypeSpecifier;
061import javax.imageio.ImageWriteParam;
062import javax.imageio.ImageWriter;
063import javax.imageio.metadata.IIOMetadata;
064import javax.imageio.spi.ImageWriterSpi;
065import javax.imageio.stream.ImageOutputStream;
066
067/**
068 * The Java Image IO plugin writer for encoding a binary RenderedImage into
069 * a WBMP format.
070 *
071 * The encoding process may clip, subsample using the parameters
072 * specified in the <code>ImageWriteParam</code>.
073 *
074 * @see com.github.jaiimageio.plugins.WBMPImageWriteParam
075 */
076public class WBMPImageWriter extends ImageWriter {
077    /** The output stream to write into */
078    private ImageOutputStream stream = null;
079
080    // Get the number of bits required to represent an int.
081    private static int getNumBits(int intValue) {
082        int numBits = 32;
083        int mask = 0x80000000;
084        while(mask != 0 && (intValue & mask) == 0) {
085            numBits--;
086            mask >>>= 1;
087        }
088        return numBits;
089    }
090
091    // Convert an int value to WBMP multi-byte format.
092    private static byte[] intToMultiByte(int intValue) {
093        int numBitsLeft = getNumBits(intValue);
094        byte[] multiBytes = new byte[(numBitsLeft + 6)/7];
095
096        int maxIndex = multiBytes.length - 1;
097        for(int b = 0; b <= maxIndex; b++) {
098            multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f);
099            if(b != maxIndex) {
100                multiBytes[b] |= (byte)0x80;
101            }
102        }
103
104        return multiBytes;
105    }
106
107    /** Constructs <code>WBMPImageWriter</code> based on the provided
108     *  <code>ImageWriterSpi</code>.
109     */
110    public WBMPImageWriter(ImageWriterSpi originator) {
111        super(originator);
112    }
113
114    public void setOutput(Object output) {
115        super.setOutput(output); // validates output
116        if (output != null) {
117            if (!(output instanceof ImageOutputStream))
118                throw new IllegalArgumentException(I18N.getString("WBMPImageWriter"));
119            this.stream = (ImageOutputStream)output;
120        } else
121            this.stream = null;
122    }
123
124    public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
125        return null;
126    }
127
128    public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
129                                               ImageWriteParam param) {
130        WBMPMetadata meta = new WBMPMetadata();
131        meta.wbmpType = 0; // default wbmp level
132        return meta;
133    }
134
135    public IIOMetadata convertStreamMetadata(IIOMetadata inData,
136                                             ImageWriteParam param) {
137        return null;
138    }
139
140    public IIOMetadata convertImageMetadata(IIOMetadata metadata,
141                                            ImageTypeSpecifier type,
142                                            ImageWriteParam param) {
143        return null;
144    }
145
146    public boolean canWriteRasters() {
147        return true;
148    }
149
150    public void write(IIOMetadata streamMetadata,
151                      IIOImage image,
152                      ImageWriteParam param) throws IOException {
153        if (stream == null) {
154            throw new IllegalStateException(I18N.getString("WBMPImageWriter3"));
155        }
156                            
157        if (image == null) {
158            throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4"));
159        }
160                                                        
161        clearAbortRequest();
162        processImageStarted(0);
163        if (param == null)
164            param = getDefaultWriteParam();
165
166        RenderedImage input = null;
167        Raster inputRaster = null;
168        boolean writeRaster = image.hasRaster();
169        Rectangle sourceRegion = param.getSourceRegion();
170        SampleModel sampleModel = null;
171
172        if (writeRaster) {
173            inputRaster = image.getRaster();
174            sampleModel = inputRaster.getSampleModel();
175        } else {
176            input = image.getRenderedImage();
177            sampleModel = input.getSampleModel();
178
179            inputRaster = input.getData();
180        }
181
182        checkSampleModel(sampleModel);
183        if (sourceRegion == null)
184            sourceRegion = inputRaster.getBounds();
185        else
186            sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
187
188        if (sourceRegion.isEmpty())
189            throw new RuntimeException(I18N.getString("WBMPImageWriter1"));
190
191        int scaleX = param.getSourceXSubsampling();
192        int scaleY = param.getSourceYSubsampling();
193        int xOffset = param.getSubsamplingXOffset();
194        int yOffset = param.getSubsamplingYOffset();
195
196        sourceRegion.translate(xOffset, yOffset);
197        sourceRegion.width -= xOffset;
198        sourceRegion.height -= yOffset;
199
200        int minX = sourceRegion.x / scaleX;
201        int minY = sourceRegion.y / scaleY;
202        int w = (sourceRegion.width + scaleX - 1) / scaleX;
203        int h = (sourceRegion.height + scaleY - 1) / scaleY;
204
205        Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
206        sampleModel = sampleModel.createCompatibleSampleModel(w, h);
207
208        SampleModel destSM= sampleModel;
209
210        // If the data are not formatted nominally then reformat.
211        if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE ||
212           !(sampleModel instanceof MultiPixelPackedSampleModel) ||
213           ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) {
214           destSM =
215                new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
216                                                w, h, 1,
217                                                w + 7 >> 3, 0);
218        }
219
220        if (!destinationRegion.equals(sourceRegion)) {
221            if (scaleX == 1 && scaleY == 1)
222                inputRaster = inputRaster.createChild(inputRaster.getMinX(),
223                                                      inputRaster.getMinY(),
224                                                      w, h, minX, minY, null);
225            else {
226                WritableRaster ras = Raster.createWritableRaster(destSM,
227                                                                 new Point(minX, minY));
228
229                byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData();
230
231                for(int j = minY, y = sourceRegion.y, k = 0;
232                    j < minY + h; j++, y += scaleY) {
233
234                    for (int i = 0, x = sourceRegion.x;
235                        i <w; i++, x +=scaleX) {
236                        int v = inputRaster.getSample(x, y, 0);
237                        data[k + (i >> 3)] |= v << (7 - (i & 7));
238                    }
239                    k += w + 7 >> 3;
240                }
241                inputRaster = ras;
242            }
243        }
244
245        // If the data are not formatted nominally then reformat.
246        if(!destSM.equals(inputRaster.getSampleModel())) {
247            WritableRaster raster =
248                Raster.createWritableRaster(destSM,
249                                            new Point(inputRaster.getMinX(),
250                                                      inputRaster.getMinY()));
251            raster.setRect(inputRaster);
252            inputRaster = raster;
253        }
254
255        // Check whether the image is white-is-zero.
256        boolean isWhiteZero = false;
257        if(!writeRaster && input.getColorModel() instanceof IndexColorModel) {
258            IndexColorModel icm = (IndexColorModel)input.getColorModel();
259            isWhiteZero = icm.getRed(0) > icm.getRed(1);
260        }
261
262        // Get the line stride, bytes per row, and data array.
263        int lineStride =
264            ((MultiPixelPackedSampleModel)destSM).getScanlineStride();
265        int bytesPerRow = (w + 7)/8;
266        byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData();
267
268        // Write WBMP header.
269        stream.write(0); // TypeField
270        stream.write(0); // FixHeaderField
271        stream.write(intToMultiByte(w)); // width
272        stream.write(intToMultiByte(h)); // height
273
274        // Write the data.
275        if(!isWhiteZero && lineStride == bytesPerRow) {
276            // Write the entire image.
277            stream.write(bdata, 0, h * bytesPerRow);
278            processImageProgress(100.0F);
279        } else {
280            // Write the image row-by-row.
281            int offset = 0;
282            if(!isWhiteZero) {
283                // Black-is-zero
284                for(int row = 0; row < h; row++) {
285                    if (abortRequested())
286                        break;
287                    stream.write(bdata, offset, bytesPerRow);
288                    offset += lineStride;
289                    processImageProgress(100.0F * row / h);
290                }
291            } else {
292                // White-is-zero: need to invert data.
293                byte[] inverted = new byte[bytesPerRow];
294                for(int row = 0; row < h; row++) {
295                    if (abortRequested())
296                        break;
297                    for(int col = 0; col < bytesPerRow; col++) {
298                        inverted[col] = (byte)(~(bdata[col+offset]));
299                    }
300                    stream.write(inverted, 0, bytesPerRow);
301                    offset += lineStride;
302                    processImageProgress(100.0F * row / h);
303                }
304            }
305        }
306
307        if (abortRequested())
308            processWriteAborted();
309        else {
310            processImageComplete();
311            stream.flushBefore(stream.getStreamPosition());
312        }
313    }
314
315    public void reset() {
316        super.reset();
317        stream = null;
318    }
319
320    private void checkSampleModel(SampleModel sm) {
321        int type = sm.getDataType();
322        if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT
323            || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1)
324            throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2"));
325    }
326}