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.ColorModel;
050import java.awt.image.DataBuffer;
051import java.awt.image.DataBufferByte;
052import java.awt.image.IndexColorModel;
053import java.awt.image.MultiPixelPackedSampleModel;
054import java.awt.image.Raster;
055import java.awt.image.RenderedImage;
056import java.awt.image.SampleModel;
057import java.awt.image.WritableRaster;
058
059import java.io.IOException;
060
061import javax.imageio.IIOImage;
062import javax.imageio.IIOException;
063import javax.imageio.ImageTypeSpecifier;
064import javax.imageio.ImageWriteParam;
065import javax.imageio.ImageWriter;
066import javax.imageio.metadata.IIOMetadata;
067import javax.imageio.metadata.IIOMetadataFormatImpl;
068import javax.imageio.metadata.IIOInvalidTreeException;
069import javax.imageio.spi.ImageWriterSpi;
070import javax.imageio.stream.ImageOutputStream;
071
072/**
073 * The Java Image IO plugin writer for encoding a binary RenderedImage into
074 * a WBMP format.
075 *
076 * The encoding process may clip, subsample using the parameters
077 * specified in the <code>ImageWriteParam</code>.
078 *
079 * @see com.github.jaiimageio.plugins.WBMPImageWriteParam
080 */
081public class WBMPImageWriter extends ImageWriter {
082    /** The output stream to write into */
083    private ImageOutputStream stream = null;
084
085    // Get the number of bits required to represent an int.
086    private static int getNumBits(int intValue) {
087        int numBits = 32;
088        int mask = 0x80000000;
089        while(mask != 0 && (intValue & mask) == 0) {
090            numBits--;
091            mask >>>= 1;
092        }
093        return numBits;
094    }
095
096    // Convert an int value to WBMP multi-byte format.
097    private static byte[] intToMultiByte(int intValue) {
098        int numBitsLeft = getNumBits(intValue);
099        byte[] multiBytes = new byte[(numBitsLeft + 6)/7];
100
101        int maxIndex = multiBytes.length - 1;
102        for(int b = 0; b <= maxIndex; b++) {
103            multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f);
104            if(b != maxIndex) {
105                multiBytes[b] |= (byte)0x80;
106            }
107        }
108
109        return multiBytes;
110    }
111
112    /** Constructs <code>WBMPImageWriter</code> based on the provided
113     *  <code>ImageWriterSpi</code>.
114     */
115    public WBMPImageWriter(ImageWriterSpi originator) {
116        super(originator);
117    }
118
119    public void setOutput(Object output) {
120        super.setOutput(output); // validates output
121        if (output != null) {
122            if (!(output instanceof ImageOutputStream))
123                throw new IllegalArgumentException(I18N.getString("WBMPImageWriter"));
124            this.stream = (ImageOutputStream)output;
125        } else
126            this.stream = null;
127    }
128
129    public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
130        return null;
131    }
132
133    public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
134                                               ImageWriteParam param) {
135        WBMPMetadata meta = new WBMPMetadata();
136        meta.wbmpType = 0; // default wbmp level
137        return meta;
138    }
139
140    public IIOMetadata convertStreamMetadata(IIOMetadata inData,
141                                             ImageWriteParam param) {
142        return null;
143    }
144
145    public IIOMetadata convertImageMetadata(IIOMetadata metadata,
146                                            ImageTypeSpecifier type,
147                                            ImageWriteParam param) {
148        return null;
149    }
150
151    public boolean canWriteRasters() {
152        return true;
153    }
154
155    public void write(IIOMetadata streamMetadata,
156                      IIOImage image,
157                      ImageWriteParam param) throws IOException {
158        if (stream == null) {
159            throw new IllegalStateException(I18N.getString("WBMPImageWriter3"));
160        }
161                            
162        if (image == null) {
163            throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4"));
164        }
165                                                        
166        clearAbortRequest();
167        processImageStarted(0);
168        if (param == null)
169            param = getDefaultWriteParam();
170
171        RenderedImage input = null;
172        Raster inputRaster = null;
173        boolean writeRaster = image.hasRaster();
174        Rectangle sourceRegion = param.getSourceRegion();
175        SampleModel sampleModel = null;
176
177        if (writeRaster) {
178            inputRaster = image.getRaster();
179            sampleModel = inputRaster.getSampleModel();
180        } else {
181            input = image.getRenderedImage();
182            sampleModel = input.getSampleModel();
183
184            inputRaster = input.getData();
185        }
186
187        checkSampleModel(sampleModel);
188        if (sourceRegion == null)
189            sourceRegion = inputRaster.getBounds();
190        else
191            sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
192
193        if (sourceRegion.isEmpty())
194            throw new RuntimeException(I18N.getString("WBMPImageWriter1"));
195
196        int scaleX = param.getSourceXSubsampling();
197        int scaleY = param.getSourceYSubsampling();
198        int xOffset = param.getSubsamplingXOffset();
199        int yOffset = param.getSubsamplingYOffset();
200
201        sourceRegion.translate(xOffset, yOffset);
202        sourceRegion.width -= xOffset;
203        sourceRegion.height -= yOffset;
204
205        int minX = sourceRegion.x / scaleX;
206        int minY = sourceRegion.y / scaleY;
207        int w = (sourceRegion.width + scaleX - 1) / scaleX;
208        int h = (sourceRegion.height + scaleY - 1) / scaleY;
209
210        Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
211        sampleModel = sampleModel.createCompatibleSampleModel(w, h);
212
213        SampleModel destSM= sampleModel;
214
215        // If the data are not formatted nominally then reformat.
216        if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE ||
217           !(sampleModel instanceof MultiPixelPackedSampleModel) ||
218           ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) {
219           destSM =
220                new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
221                                                w, h, 1,
222                                                w + 7 >> 3, 0);
223        }
224
225        if (!destinationRegion.equals(sourceRegion)) {
226            if (scaleX == 1 && scaleY == 1)
227                inputRaster = inputRaster.createChild(inputRaster.getMinX(),
228                                                      inputRaster.getMinY(),
229                                                      w, h, minX, minY, null);
230            else {
231                WritableRaster ras = Raster.createWritableRaster(destSM,
232                                                                 new Point(minX, minY));
233
234                byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData();
235
236                for(int j = minY, y = sourceRegion.y, k = 0;
237                    j < minY + h; j++, y += scaleY) {
238
239                    for (int i = 0, x = sourceRegion.x;
240                        i <w; i++, x +=scaleX) {
241                        int v = inputRaster.getSample(x, y, 0);
242                        data[k + (i >> 3)] |= v << (7 - (i & 7));
243                    }
244                    k += w + 7 >> 3;
245                }
246                inputRaster = ras;
247            }
248        }
249
250        // If the data are not formatted nominally then reformat.
251        if(!destSM.equals(inputRaster.getSampleModel())) {
252            WritableRaster raster =
253                Raster.createWritableRaster(destSM,
254                                            new Point(inputRaster.getMinX(),
255                                                      inputRaster.getMinY()));
256            raster.setRect(inputRaster);
257            inputRaster = raster;
258        }
259
260        // Check whether the image is white-is-zero.
261        boolean isWhiteZero = false;
262        if(!writeRaster && input.getColorModel() instanceof IndexColorModel) {
263            IndexColorModel icm = (IndexColorModel)input.getColorModel();
264            isWhiteZero = icm.getRed(0) > icm.getRed(1);
265        }
266
267        // Get the line stride, bytes per row, and data array.
268        int lineStride =
269            ((MultiPixelPackedSampleModel)destSM).getScanlineStride();
270        int bytesPerRow = (w + 7)/8;
271        byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData();
272
273        // Write WBMP header.
274        stream.write(0); // TypeField
275        stream.write(0); // FixHeaderField
276        stream.write(intToMultiByte(w)); // width
277        stream.write(intToMultiByte(h)); // height
278
279        // Write the data.
280        if(!isWhiteZero && lineStride == bytesPerRow) {
281            // Write the entire image.
282            stream.write(bdata, 0, h * bytesPerRow);
283            processImageProgress(100.0F);
284        } else {
285            // Write the image row-by-row.
286            int offset = 0;
287            if(!isWhiteZero) {
288                // Black-is-zero
289                for(int row = 0; row < h; row++) {
290                    if (abortRequested())
291                        break;
292                    stream.write(bdata, offset, bytesPerRow);
293                    offset += lineStride;
294                    processImageProgress(100.0F * row / h);
295                }
296            } else {
297                // White-is-zero: need to invert data.
298                byte[] inverted = new byte[bytesPerRow];
299                for(int row = 0; row < h; row++) {
300                    if (abortRequested())
301                        break;
302                    for(int col = 0; col < bytesPerRow; col++) {
303                        inverted[col] = (byte)(~(bdata[col+offset]));
304                    }
305                    stream.write(inverted, 0, bytesPerRow);
306                    offset += lineStride;
307                    processImageProgress(100.0F * row / h);
308                }
309            }
310        }
311
312        if (abortRequested())
313            processWriteAborted();
314        else {
315            processImageComplete();
316            stream.flushBefore(stream.getStreamPosition());
317        }
318    }
319
320    public void reset() {
321        super.reset();
322        stream = null;
323    }
324
325    private void checkSampleModel(SampleModel sm) {
326        int type = sm.getDataType();
327        if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT
328            || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1)
329            throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2"));
330    }
331}