001/*
002 * $RCSfile: PCXImageWriter.java,v $
003 *
004 * 
005 * Copyright (c) 2007 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.2 $
042 * $Date: 2007/09/11 20:45:42 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.pcx;
046
047import java.awt.Rectangle;
048import java.awt.color.ColorSpace;
049import java.awt.image.*;
050import java.io.IOException;
051import java.nio.ByteOrder;
052
053import javax.imageio.*;
054import javax.imageio.metadata.IIOMetadata;
055import javax.imageio.stream.ImageOutputStream;
056
057import com.github.jaiimageio.impl.common.ImageUtil;
058
059public class PCXImageWriter extends ImageWriter implements PCXConstants {
060
061    private ImageOutputStream ios;
062    private Rectangle sourceRegion;
063    private Rectangle destinationRegion;
064    private int colorPlanes,bytesPerLine;
065    private Raster inputRaster = null;
066    private int scaleX,scaleY;
067
068    public PCXImageWriter(PCXImageWriterSpi imageWriterSpi) {
069        super(imageWriterSpi);
070    }
071
072    public void setOutput(Object output) {
073        super.setOutput(output); // validates output
074        if (output != null) {
075            if (!(output instanceof ImageOutputStream))
076                throw new IllegalArgumentException("output not instance of ImageOutputStream");
077            ios = (ImageOutputStream) output;
078            ios.setByteOrder(ByteOrder.LITTLE_ENDIAN);
079        } else
080            ios = null;
081    }
082
083    public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
084        if(inData instanceof PCXMetadata)
085            return inData;
086        return null;
087    }
088
089    public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
090        return null;
091    }
092
093    public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
094        PCXMetadata md = new PCXMetadata();
095        md.bitsPerPixel = (byte) imageType.getSampleModel().getSampleSize()[0];
096        return md;
097    }
098
099    public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
100        return null;
101    }
102
103    public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
104        if (ios == null) {
105            throw new IllegalStateException("output stream is null");
106        }
107
108        if (image == null) {
109            throw new IllegalArgumentException("image is null");
110        }
111
112        clearAbortRequest();
113        processImageStarted(0);
114        if (param == null)
115            param = getDefaultWriteParam();
116
117        boolean writeRaster = image.hasRaster();
118        
119        sourceRegion = param.getSourceRegion();
120        
121        SampleModel sampleModel = null;
122        ColorModel colorModel = null;
123
124        if (writeRaster) {
125            inputRaster = image.getRaster();
126            sampleModel = inputRaster.getSampleModel();
127            colorModel = ImageUtil.createColorModel(null, sampleModel);
128            if (sourceRegion == null)
129                sourceRegion = inputRaster.getBounds();
130            else
131                sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
132        } else {
133            RenderedImage input = image.getRenderedImage();
134            inputRaster = input.getData();
135            sampleModel = input.getSampleModel();
136            colorModel = input.getColorModel();
137            Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(),
138                                           input.getWidth(), input.getHeight());
139            if (sourceRegion == null)
140                sourceRegion = rect;
141            else
142                sourceRegion = sourceRegion.intersection(rect);
143        }
144        
145        if (sourceRegion.isEmpty())
146            throw new IllegalArgumentException("source region is empty");
147
148        IIOMetadata imageMetadata = image.getMetadata();
149        PCXMetadata pcxImageMetadata = null;
150        
151        ImageTypeSpecifier imageType = new ImageTypeSpecifier(colorModel, sampleModel);
152        if(imageMetadata != null) {
153            // Convert metadata.
154            pcxImageMetadata = (PCXMetadata)convertImageMetadata(imageMetadata, imageType, param);
155        } else {
156            // Use default.
157            pcxImageMetadata = (PCXMetadata)getDefaultImageMetadata(imageType, param);
158        }
159
160        scaleX = param.getSourceXSubsampling();
161        scaleY = param.getSourceYSubsampling();
162        
163        int xOffset = param.getSubsamplingXOffset();
164        int yOffset = param.getSubsamplingYOffset();
165
166        // cache the data type;
167        int dataType = sampleModel.getDataType();
168
169        sourceRegion.translate(xOffset, yOffset);
170        sourceRegion.width -= xOffset;
171        sourceRegion.height -= yOffset;
172
173        int minX = sourceRegion.x / scaleX;
174        int minY = sourceRegion.y / scaleY;
175        int w = (sourceRegion.width + scaleX - 1) / scaleX;
176        int h = (sourceRegion.height + scaleY - 1) / scaleY;
177        
178        xOffset = sourceRegion.x % scaleX;
179        yOffset = sourceRegion.y % scaleY;
180
181        destinationRegion = new Rectangle(minX, minY, w, h);
182        
183        boolean noTransform = destinationRegion.equals(sourceRegion);
184
185        // Raw data can only handle bytes, everything greater must be ASCII.
186        int[] sourceBands = param.getSourceBands();
187        boolean noSubband = true;
188        int numBands = sampleModel.getNumBands();
189
190        if (sourceBands != null) {
191            sampleModel = sampleModel.createSubsetSampleModel(sourceBands);
192            colorModel = null;
193            noSubband = false;
194            numBands = sampleModel.getNumBands();
195        } else {
196            sourceBands = new int[numBands];
197            for (int i = 0; i < numBands; i++)
198                sourceBands[i] = i;
199        }
200        
201        ios.writeByte(MANUFACTURER);
202        ios.writeByte(VERSION_3_0);
203        ios.writeByte(ENCODING);
204        
205        int bitsPerPixel = sampleModel.getSampleSize(0);
206        ios.writeByte(bitsPerPixel);
207        
208        ios.writeShort(destinationRegion.x); // xmin
209        ios.writeShort(destinationRegion.y); // ymin
210        ios.writeShort(destinationRegion.x+destinationRegion.width-1); // xmax
211        ios.writeShort(destinationRegion.y+destinationRegion.height-1); // ymax
212        
213        ios.writeShort(pcxImageMetadata.hdpi);
214        ios.writeShort(pcxImageMetadata.vdpi);
215        
216        byte[] smallpalette = createSmallPalette(colorModel);
217        ios.write(smallpalette);
218        ios.writeByte(0); // reserved
219        
220        colorPlanes = sampleModel.getNumBands();
221        
222        ios.writeByte(colorPlanes);
223        
224        bytesPerLine = destinationRegion.width*bitsPerPixel/8;
225        bytesPerLine += bytesPerLine %2;
226        
227        ios.writeShort(bytesPerLine);
228        
229        if(colorModel.getColorSpace().getType()==ColorSpace.TYPE_GRAY)
230            ios.writeShort(PALETTE_GRAYSCALE);
231        else
232            ios.writeShort(PALETTE_COLOR);
233        
234        ios.writeShort(pcxImageMetadata.hsize);
235        ios.writeShort(pcxImageMetadata.vsize);
236        
237        for(int i=0;i<54;i++)
238            ios.writeByte(0);
239        
240        // write image data
241        
242        if(colorPlanes==1 && bitsPerPixel==1) {
243            write1Bit();
244        }
245        else if(colorPlanes==1 && bitsPerPixel==4) {
246            write4Bit();
247        }
248        else {
249            write8Bit();
250        }
251        
252        // write 256 color palette if needed
253        if(colorPlanes==1 && bitsPerPixel==8 &&
254           colorModel.getColorSpace().getType()!=ColorSpace.TYPE_GRAY){
255            ios.writeByte(12); // Magic number preceding VGA 256 Color Palette Information
256            ios.write(createLargePalette(colorModel));
257        }
258        
259        if (abortRequested()) {
260            processWriteAborted();
261        } else {
262            processImageComplete();
263        }
264    }
265    
266    private void write4Bit() throws IOException {
267        int[] unpacked = new int[sourceRegion.width];
268        int[] samples = new int[bytesPerLine];
269
270        for (int line = 0; line < sourceRegion.height; line += scaleY) {
271            inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, 0, unpacked);
272            
273            int val=0,dst=0;
274            for(int x=0,nibble=0;x<sourceRegion.width;x+=scaleX){
275                val = val | (unpacked[x] & 0x0F);
276                if(nibble==1) {
277                    samples[dst++]=val;
278                    nibble=0;
279                    val=0;
280                } else {
281                    nibble=1;
282                    val = val << 4;
283                }
284            }
285
286            int last = samples[0];
287            int count = 0;
288
289            for (int x = 0; x < bytesPerLine; x += scaleX) {
290                int sample = samples[x];
291                if (sample != last || count == 63) {
292                    writeRLE(last, count);
293                    count = 1;
294                    last = sample;
295                } else
296                    count++;
297            }
298            if (count >= 1) {
299                writeRLE(last, count);
300            }
301
302            processImageProgress(100.0F * line / sourceRegion.height);
303        }
304    }
305    
306    private void write1Bit() throws IOException {
307        int[] unpacked = new int[sourceRegion.width];
308        int[] samples = new int[bytesPerLine];
309
310        for (int line = 0; line < sourceRegion.height; line += scaleY) {
311            inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, 0, unpacked);
312            
313            int val=0,dst=0;
314            for(int x=0,bit=1<<7;x<sourceRegion.width;x+=scaleX){
315                if(unpacked[x]>0)
316                    val = val | bit;
317                if(bit==1) {
318                    samples[dst++]=val;
319                    bit=1<<7;
320                    val=0;
321                } else {
322                    bit = bit >> 1;
323                }
324            }
325
326            int last = samples[0];
327            int count = 0;
328
329            for (int x = 0; x < bytesPerLine; x += scaleX) {
330                int sample = samples[x];
331                if (sample != last || count == 63) {
332                    writeRLE(last, count);
333                    count = 1;
334                    last = sample;
335                } else
336                    count++;
337            }
338            if (count >= 1) {
339                writeRLE(last, count);
340            }
341
342            processImageProgress(100.0F * line / sourceRegion.height);
343        }
344    }
345    
346    private void write8Bit() throws IOException {
347        int[][] samples = new int[colorPlanes][bytesPerLine];
348        
349        for(int line=0;line<sourceRegion.height;line+=scaleY) {
350            for(int band=0;band<colorPlanes;band++) {
351                inputRaster.getSamples(sourceRegion.x, line+sourceRegion.y,sourceRegion.width,1,band,samples[band]);
352            }
353            
354            int last = samples[0][0];
355            int count=0;
356            
357            for(int band=0;band<colorPlanes;band++) {
358                for(int x=0;x<bytesPerLine;x+=scaleX) {
359                    int sample = samples[band][x];
360                    if(sample!=last || count==63) {
361                        writeRLE(last,count);
362                        count=1;
363                        last=sample;
364                    } else
365                        count++;
366                }
367            }
368            if(count>=1) {
369                writeRLE(last,count);
370            }
371
372            processImageProgress(100.0F * line / sourceRegion.height);
373        }
374    }
375    
376    private void writeRLE(int val,int count) throws IOException{
377        if(count==1 && (val & 0xC0) != 0xC0) {
378            ios.writeByte(val);
379        } else {
380            ios.writeByte(0xC0 | count);
381            ios.writeByte(val);
382        }
383    }
384    
385    private byte[] createSmallPalette(ColorModel cm){
386        byte[] palette = new byte[16*3];
387        
388        if(!(cm instanceof IndexColorModel))
389            return palette;
390        
391        IndexColorModel icm = (IndexColorModel) cm;
392        if(icm.getMapSize()>16)
393            return palette;
394        
395        for(int i=0,offset=0;i<icm.getMapSize();i++) {
396            palette[offset++] = (byte) icm.getRed(i);
397            palette[offset++] = (byte) icm.getGreen(i);
398            palette[offset++] = (byte) icm.getBlue(i);
399        }
400        
401        return palette;
402    }
403    private byte[] createLargePalette(ColorModel cm){
404        byte[] palette = new byte[256*3];
405        
406        if(!(cm instanceof IndexColorModel))
407            return palette;
408        
409        IndexColorModel icm = (IndexColorModel) cm;
410        
411        for(int i=0,offset=0;i<icm.getMapSize();i++) {
412            palette[offset++] = (byte) icm.getRed(i);
413            palette[offset++] = (byte) icm.getGreen(i);
414            palette[offset++] = (byte) icm.getBlue(i);
415        }
416        
417        return palette;
418    }
419}