You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by da...@apache.org on 2011/04/02 10:22:31 UTC

svn commit: r1087970 - in /commons/proper/sanselan/trunk/src: main/java/org/apache/sanselan/ main/java/org/apache/sanselan/formats/pcx/ test/data/images/pcx/ test/data/images/pcx/1/ test/java/org/apache/sanselan/common/byteSources/ test/java/org/apache...

Author: damjan
Date: Sat Apr  2 08:22:30 2011
New Revision: 1087970

URL: http://svn.apache.org/viewvc?rev=1087970&view=rev
Log:
Added support for reading and writing PCX images, and thorough tests.

Added:
    commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/
    commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxConstants.java   (with props)
    commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxImageParser.java   (with props)
    commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxWriter.java   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppUncompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane24bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane2bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane32bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane4bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppUncompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/2plane1bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane1bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppUncompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/4plane1bppCompressed.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/Oregon Scientific DS6639 - DSC_0307 - small.pcx   (with props)
    commons/proper/sanselan/trunk/src/test/data/images/pcx/1/info.txt   (with props)
    commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/
    commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxBaseTest.java   (with props)
    commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxReadTest.java   (with props)
Modified:
    commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java
    commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java
    commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java
    commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java

Modified: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java?rev=1087970&r1=1087969&r2=1087970&view=diff
==============================================================================
--- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java (original)
+++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageFormat.java Sat Apr  2 08:22:30 2011
@@ -74,6 +74,7 @@ public class ImageFormat
     public static final ImageFormat IMAGE_FORMAT_JBIG2 = new ImageFormat("JBig2");
     public static final ImageFormat IMAGE_FORMAT_ICNS = new ImageFormat("ICNS");
     public static final ImageFormat IMAGE_FORMAT_WBMP = new ImageFormat("WBMP");
+    public static final ImageFormat IMAGE_FORMAT_PCX = new ImageFormat("PCX");
 
     public static final ImageFormat[] getAllFormats()
     {
@@ -83,6 +84,7 @@ public class ImageFormat
                 IMAGE_FORMAT_PSD, IMAGE_FORMAT_PBM, IMAGE_FORMAT_PGM,
                 IMAGE_FORMAT_PPM, IMAGE_FORMAT_PNM, IMAGE_FORMAT_TGA,
                 IMAGE_FORMAT_JBIG2, IMAGE_FORMAT_ICNS, IMAGE_FORMAT_WBMP,
+                IMAGE_FORMAT_PCX,
         };
 
         return result;

Modified: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java?rev=1087970&r1=1087969&r2=1087970&view=diff
==============================================================================
--- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java (original)
+++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/ImageParser.java Sat Apr  2 08:22:30 2011
@@ -38,6 +38,7 @@ import org.apache.sanselan.formats.gif.G
 import org.apache.sanselan.formats.icns.IcnsImageParser;
 import org.apache.sanselan.formats.ico.IcoImageParser;
 import org.apache.sanselan.formats.jpeg.JpegImageParser;
+import org.apache.sanselan.formats.pcx.PcxImageParser;
 import org.apache.sanselan.formats.png.PngImageParser;
 import org.apache.sanselan.formats.pnm.PNMImageParser;
 import org.apache.sanselan.formats.psd.PsdImageParser;
@@ -56,6 +57,7 @@ public abstract class ImageParser extend
                 new GifImageParser(), new PsdImageParser(),
                 new PNMImageParser(), new IcoImageParser(),
                 new IcnsImageParser(), new WbmpImageParser(),
+                new PcxImageParser(),
         // new JBig2ImageParser(),
         // new TgaImageParser(),
         };

Added: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxConstants.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxConstants.java?rev=1087970&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxConstants.java (added)
+++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxConstants.java Sat Apr  2 08:22:30 2011
@@ -0,0 +1,26 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *  under the License.
+ */
+package org.apache.sanselan.formats.pcx;
+
+import org.apache.sanselan.SanselanConstants;
+
+public interface PcxConstants extends SanselanConstants
+{
+    public static final String PARAM_KEY_PCX_COMPRESSION = "PCX_COMPRESSION";
+    public static final int PCX_COMPRESSION_UNCOMPRESSED = 0;
+    public static final int PCX_COMPRESSION_RLE = 1;
+
+    public static final String PARAM_KEY_PCX_BIT_DEPTH = "PCX_BIT_DEPTH";
+}

Propchange: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxConstants.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxImageParser.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxImageParser.java?rev=1087970&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxImageParser.java (added)
+++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxImageParser.java Sat Apr  2 08:22:30 2011
@@ -0,0 +1,589 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sanselan.formats.pcx;
+
+import java.awt.Dimension;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DirectColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.sanselan.ImageFormat;
+import org.apache.sanselan.ImageInfo;
+import org.apache.sanselan.ImageParser;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.ImageWriteException;
+import org.apache.sanselan.common.IImageMetadata;
+import org.apache.sanselan.common.byteSources.ByteSource;
+
+public class PcxImageParser extends ImageParser implements PcxConstants
+{
+    // ZSoft's official spec is at http://www.qzx.com/pc-gpe/pcx.txt
+    // (among other places) but it's pretty thin. The fileformat.info document
+    // at http://www.fileformat.info/format/pcx/egff.htm is a little better
+    // but their gray sample image seems corrupt. PCX files themselves are
+    // the ultimate test but pretty hard to find nowdays, so the best
+    // test is against other image viewers (Irfanview is pretty good).
+    //
+    // Open source projects are generally poor at parsing PCX,
+    // SDL_Image/gdk-pixbuf/Eye of Gnome/GIMP/F-Spot all only do some formats,
+    // don't support uncompressed PCX, and/or don't handle black and white
+    // images properly.
+
+    public PcxImageParser()
+    {
+        super.setByteOrder(BYTE_ORDER_LSB);
+    }
+
+    public String getName()
+    {
+        return "Pcx-Custom";
+    }
+
+    public String getDefaultExtension()
+    {
+        return DEFAULT_EXTENSION;
+    }
+    private static final String DEFAULT_EXTENSION = ".pcx";
+    private static final String ACCEPTED_EXTENSIONS[] =
+    {
+        ".pcx", ".pcc",
+    };
+
+    protected String[] getAcceptedExtensions()
+    {
+        return ACCEPTED_EXTENSIONS;
+    }
+
+    protected ImageFormat[] getAcceptedTypes()
+    {
+        return new ImageFormat[]
+        {
+            ImageFormat.IMAGE_FORMAT_PCX, //
+        };
+    }
+
+    public boolean embedICCProfile(File src, File dst, byte profile[])
+    {
+        return false;
+    }
+
+    public IImageMetadata getMetadata(ByteSource byteSource, Map params)
+            throws ImageReadException, IOException
+    {
+        return null;
+    }
+
+    public ImageInfo getImageInfo(ByteSource byteSource, Map params)
+            throws ImageReadException, IOException
+    {
+        PcxHeader pcxHeader = readPcxHeader(byteSource);
+        Dimension size = getImageSize(byteSource, params);
+        int metricHDpi = (int) (pcxHeader.hDpi * 1000.0 / 2.54);
+        int metricVDpi = (int) (pcxHeader.vDpi * 1000.0 / 2.54);
+        return new ImageInfo("PCX", pcxHeader.nPlanes * pcxHeader.bitsPerPixel, new ArrayList(),
+                ImageFormat.IMAGE_FORMAT_PCX, "ZSoft PCX Image", size.height, "image/x-pcx", 1,
+                metricVDpi, pcxHeader.vDpi / metricVDpi, metricHDpi, pcxHeader.hDpi / metricHDpi,
+                size.width, false, false,
+                !(pcxHeader.nPlanes == 3 && pcxHeader.bitsPerPixel == 8),
+                ImageInfo.COLOR_TYPE_RGB,
+                pcxHeader.encoding == PcxHeader.ENCODING_RLE ?
+                    ImageInfo.COMPRESSION_ALGORITHM_RLE : ImageInfo.COMPRESSION_ALGORITHM_NONE);
+    }
+
+    public Dimension getImageSize(ByteSource byteSource,
+            Map params)
+            throws ImageReadException, IOException
+    {
+        PcxHeader pcxHeader = readPcxHeader(byteSource);
+        int xSize = pcxHeader.xMax - pcxHeader.xMin + 1;
+        if (xSize < 0)
+            throw new ImageReadException("Image width is negative");
+        int ySize = pcxHeader.yMax - pcxHeader.yMin + 1;
+        if (ySize < 0)
+            throw new ImageReadException("Image height is negative");
+        return new Dimension(xSize, ySize);
+    }
+
+    public byte[] getICCProfileBytes(ByteSource byteSource,
+            Map params)
+            throws ImageReadException, IOException
+    {
+        return null;
+    }
+
+    static class PcxHeader
+    {
+
+        public static final int ENCODING_UNCOMPRESSED = 0;
+        public static final int ENCODING_RLE = 1;
+        public static final int PALETTE_INFO_COLOR = 1;
+        public static final int PALETTE_INFO_GRAYSCALE = 2;
+        public final int manufacturer; // Always 10 = ZSoft .pcx
+        public final int version; // 0 = PC Paintbrush 2.5
+                                  // 2 = PC Paintbrush 2.8 with palette
+                                  // 3 = PC Paintbrush 2.8 w/o palette
+                                  // 4 = PC Paintbrush for Windows
+                                  // 5 = PC Paintbrush >= 3.0
+        public final int encoding; // 0 = very old uncompressed format, 1 = .pcx run length encoding
+        public final int bitsPerPixel; // Bits ***PER PLANE*** for each pixel
+        public final int xMin; // window
+        public final int yMin;
+        public final int xMax;
+        public final int yMax;
+        public final int hDpi; // horizontal dpi
+        public final int vDpi; // vertical dpi
+        public final int[] colormap; // palette for <= 16 colors
+        public final int reserved; // Always 0
+        public final int nPlanes; // Number of color planes
+        public final int bytesPerLine; // Number of bytes per scanline plane, must be an even number.
+        public final int paletteInfo; // 1 = Color/BW, 2 = Grayscale, ignored in Paintbrush IV/IV+
+        public final int hScreenSize; // horizontal screen size, in pixels. PaintBrush >= IV only.
+        public final int vScreenSize; // vertical screen size, in pixels. PaintBrush >= IV only.
+
+        public PcxHeader(final int manufacturer, final int version,
+                final int encoding, final int bitsPerPixel,
+                final int xMin, final int yMin,
+                final int xMax, final int yMax,
+                final int hDpi, final int vDpi,
+                final int[] colormap,
+                final int reserved, final int nPlanes,
+                final int bytesPerLine, final int paletteInfo,
+                final int hScreenSize, final int vScreenSize)
+        {
+            this.manufacturer = manufacturer;
+            this.version = version;
+            this.encoding = encoding;
+            this.bitsPerPixel = bitsPerPixel;
+            this.xMin = xMin;
+            this.yMin = yMin;
+            this.xMax = xMax;
+            this.yMax = yMax;
+            this.hDpi = hDpi;
+            this.vDpi = vDpi;
+            this.colormap = colormap;
+            this.reserved = reserved;
+            this.nPlanes = nPlanes;
+            this.bytesPerLine = bytesPerLine;
+            this.paletteInfo = paletteInfo;
+            this.hScreenSize = hScreenSize;
+            this.vScreenSize = vScreenSize;
+        }
+
+        public void dump(PrintWriter pw)
+        {
+            pw.println("PcxHeader");
+            pw.println("Manufacturer: " + manufacturer);
+            pw.println("Version: " + version);
+            pw.println("Encoding: " + encoding);
+            pw.println("BitsPerPixel: " + bitsPerPixel);
+            pw.println("xMin: " + xMin);
+            pw.println("yMin: " + yMin);
+            pw.println("xMax: " + xMax);
+            pw.println("yMax: " + yMax);
+            pw.println("hDpi: " + hDpi);
+            pw.println("vDpi: " + vDpi);
+            pw.print("ColorMap: ");
+            for (int i = 0; i < colormap.length; i++)
+            {
+                if (i > 0)
+                    pw.print(",");
+                pw.print("("
+                        + (0xff & (colormap[i] >> 16)) + ","
+                        + (0xff & (colormap[i] >> 8)) + ","
+                        + (0xff & colormap[i]) + ")");
+            }
+            pw.println();
+            pw.println("Reserved: " + reserved);
+            pw.println("nPlanes: " + nPlanes);
+            pw.println("BytesPerLine: " + bytesPerLine);
+            pw.println("PaletteInfo: " + paletteInfo);
+            pw.println("hScreenSize: " + hScreenSize);
+            pw.println("vScreenSize: " + vScreenSize);
+            pw.println();
+        }
+    }
+
+    private PcxHeader readPcxHeader(ByteSource byteSource)
+            throws ImageReadException, IOException
+    {
+        InputStream is = null;
+        try
+        {
+            is = byteSource.getInputStream();
+            return readPcxHeader(is, false);
+        }
+        finally
+        {
+            try
+            {
+                if (is != null)
+                    is.close();
+            }
+            catch (IOException ignored)
+            {
+            }
+        }
+    }
+
+    private PcxHeader readPcxHeader(InputStream is, boolean isStrict)
+            throws ImageReadException, IOException
+    {
+        byte[] pcxHeaderBytes = readByteArray("PcxHeader", 128, is, "Not a Valid PCX File");
+        int manufacturer = 0xff & pcxHeaderBytes[0];
+        int version = 0xff & pcxHeaderBytes[1];
+        int encoding = 0xff & pcxHeaderBytes[2];
+        int bitsPerPixel = 0xff & pcxHeaderBytes[3];
+        int xMin = convertByteArrayToShort("xMin", 4, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int yMin = convertByteArrayToShort("yMin", 6, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int xMax = convertByteArrayToShort("xMax", 8, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int yMax = convertByteArrayToShort("yMax", 10, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int hDpi = convertByteArrayToShort("hDpi", 12, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int vDpi = convertByteArrayToShort("vDpi", 14, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int[] colormap = new int[16];
+        for (int i = 0; i < 16; i++)
+        {
+            colormap[i] = 0xff000000
+                    | ((0xff & pcxHeaderBytes[16 + 3 * i]) << 16)
+                    | ((0xff & pcxHeaderBytes[16 + 3 * i + 1]) << 8)
+                    | (0xff & pcxHeaderBytes[16 + 3 * i + 2]);
+        }
+        int reserved = 0xff & pcxHeaderBytes[64];
+        int nPlanes = 0xff & pcxHeaderBytes[65];
+        int bytesPerLine = convertByteArrayToShort("BytesPerLine", 66, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int paletteInfo = convertByteArrayToShort("PaletteInfo", 68, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int hScreenSize = convertByteArrayToShort("hScreenSize", 70, pcxHeaderBytes, BYTE_ORDER_LSB);
+        int vScreenSize = convertByteArrayToShort("vScreenSize", 72, pcxHeaderBytes, BYTE_ORDER_LSB);
+
+        if (manufacturer != 10)
+            throw new ImageReadException("Not a Valid PCX File: manufacturer is " + manufacturer);
+        if (isStrict)
+        {
+            // Note that reserved is sometimes set to a non-zero value
+            // by Paintbrush itself, so it shouldn't be enforced.
+            if (bytesPerLine % 2 != 0)
+                throw new ImageReadException("Not a Valid PCX File: bytesPerLine is odd");
+        }
+
+        return new PcxHeader(manufacturer, version, encoding, bitsPerPixel,
+                xMin, yMin, xMax, yMax, hDpi, vDpi, colormap, reserved,
+                nPlanes, bytesPerLine, paletteInfo, hScreenSize, vScreenSize);
+    }
+
+    public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
+            throws ImageReadException, IOException
+    {
+        readPcxHeader(byteSource).dump(pw);
+        return true;
+    }
+
+    private void readScanLine(PcxHeader pcxHeader, InputStream is, byte[] samples)
+            throws IOException, ImageReadException
+    {
+        if (pcxHeader.encoding == PcxHeader.ENCODING_UNCOMPRESSED)
+        {
+            int r;
+            for (int bytesRead = 0; bytesRead < samples.length; bytesRead += r)
+            {
+                r = is.read(samples, bytesRead, samples.length - bytesRead);
+                if (r < 0)
+                    throw new ImageReadException("Premature end of file reading image data");
+            }
+        }
+        else
+        {
+            if (pcxHeader.encoding == PcxHeader.ENCODING_RLE)
+            {
+                for (int bytesRead = 0; bytesRead < samples.length;)
+                {
+                    byte b = readByte("Pixel", is, "Error reading image data");
+                    int count;
+                    byte sample;
+                    if ((b & 0xc0) == 0xc0)
+                    {
+                        count = b & 0x3f;
+                        sample = readByte("Pixel", is, "Error reading image data");
+                    }
+                    else
+                    {
+                        count = 1;
+                        sample = b;
+                    }
+                    for (int i = 0; i < count && bytesRead + i < samples.length; i++)
+                        samples[bytesRead + i] = sample;
+                    bytesRead += count;
+                }
+            }
+            else
+                throw new ImageReadException("Invalid PCX encoding " + pcxHeader.encoding);
+        }
+    }
+
+    private int[] read256ColorPalette(InputStream stream)
+            throws IOException
+    {
+        byte[] paletteBytes = readByteArray("Palette", 769, stream, "Error reading palette");
+        if (paletteBytes[0] != 12)
+            return null;
+        int[] palette = new int[256];
+        for (int i = 0; i < palette.length; i++)
+        {
+            palette[i] = ((0xff & paletteBytes[1 + 3*i]) << 16)
+                    | ((0xff & paletteBytes[1 + 3*i + 1]) << 8)
+                    | (0xff & paletteBytes[1 + 3*i + 2]);
+        }
+        return palette;
+    }
+
+    private int[] read256ColorPaletteFromEndOfFile(ByteSource byteSource)
+            throws IOException, ImageReadException
+    {
+        InputStream stream = null;
+        try
+        {
+            stream = byteSource.getInputStream();
+            long toSkip = byteSource.getLength() - 769;
+            while (toSkip > 0)
+                toSkip -= stream.skip(toSkip);
+            return read256ColorPalette(stream);
+        }
+        finally
+        {
+            try
+            {
+                if (stream != null)
+                    stream.close();
+            }
+            catch (IOException closeException)
+            {
+            }
+        }
+    }
+
+    private BufferedImage readImage(PcxHeader pcxHeader, InputStream is, ByteSource byteSource)
+            throws ImageReadException, IOException
+    {
+        int xSize = pcxHeader.xMax - pcxHeader.xMin + 1;
+        if (xSize < 0)
+            throw new ImageReadException("Image width is negative");
+        int ySize = pcxHeader.yMax - pcxHeader.yMin + 1;
+        if (ySize < 0)
+            throw new ImageReadException("Image height is negative");
+
+        int scanlineLength = pcxHeader.bytesPerLine * pcxHeader.nPlanes;
+        byte[] scanline = new byte[scanlineLength];
+        if ((pcxHeader.bitsPerPixel == 1 || pcxHeader.bitsPerPixel == 2
+                || pcxHeader.bitsPerPixel == 4 || pcxHeader.bitsPerPixel == 8) &&
+                pcxHeader.nPlanes == 1)
+        {
+            int bytesPerImageRow = (xSize * pcxHeader.bitsPerPixel + 7) / 8;
+            byte[] image = new byte[ySize * bytesPerImageRow];
+            for (int y = 0; y < ySize; y++)
+            {
+                readScanLine(pcxHeader, is, scanline);
+                System.arraycopy(scanline, 0, image, y*bytesPerImageRow, bytesPerImageRow);
+            }
+            DataBufferByte dataBuffer = new DataBufferByte(image, image.length);
+            int[] palette;
+            if (pcxHeader.bitsPerPixel == 1)
+                palette = new int[] { 0x000000, 0xffffff };
+            else if (pcxHeader.bitsPerPixel == 8)
+            {
+                // Normally the palette is read 769 bytes from the end of the file.
+                // However DCX files have multiple PCX images in one file, so
+                // there could be extra data before the end! So try look for the palette
+                // immediately after the image data first.
+                palette = read256ColorPalette(is);
+                if (palette == null)
+                    palette = read256ColorPaletteFromEndOfFile(byteSource);
+                if (palette == null)
+                    throw new ImageReadException(
+                            "No 256 color palette found in image that needs it");
+            }
+            else
+                palette = pcxHeader.colormap;
+            WritableRaster raster;
+            if (pcxHeader.bitsPerPixel == 8)
+            {
+                raster = WritableRaster.createInterleavedRaster(dataBuffer,
+                        xSize, ySize, bytesPerImageRow, 1, new int[]{0}, null);
+            }
+            else
+            {
+                raster =  WritableRaster.createPackedRaster(dataBuffer,
+                        xSize, ySize, pcxHeader.bitsPerPixel, null);
+            }
+            IndexColorModel colorModel = new IndexColorModel(pcxHeader.bitsPerPixel,
+                    1 << pcxHeader.bitsPerPixel, palette, 0, false, -1, DataBuffer.TYPE_BYTE);
+            return new BufferedImage(colorModel, raster,
+                    colorModel.isAlphaPremultiplied(), new Properties());
+        }
+        else if (pcxHeader.bitsPerPixel == 1 && 2 <= pcxHeader.nPlanes
+                && pcxHeader.nPlanes <= 4)
+        {
+            IndexColorModel colorModel = new IndexColorModel(pcxHeader.nPlanes,
+                    1 << pcxHeader.nPlanes, pcxHeader.colormap, 0, false, -1, DataBuffer.TYPE_BYTE);
+            BufferedImage image = new BufferedImage(xSize, ySize, BufferedImage.TYPE_BYTE_BINARY, colorModel);
+            byte[] unpacked = new byte[xSize];
+            for (int y = 0; y < ySize; y++)
+            {
+                readScanLine(pcxHeader, is, scanline);
+                int nextByte = 0;
+                Arrays.fill(unpacked, (byte) 0);
+                for (int plane = 0; plane < pcxHeader.nPlanes; plane++)
+                {
+                    for (int i = 0; i < pcxHeader.bytesPerLine; i++)
+                    {
+                        int b = 0xff & scanline[nextByte++];
+                        for (int j = 0; j < 8 && 8*i + j < unpacked.length; j++)
+                            unpacked[8*i + j] |= (byte) (((b >> (7 - j)) & 0x1) << plane);
+                    }
+                }
+                image.getRaster().setDataElements(0, y, xSize, 1, unpacked);
+            }
+            return image;
+        }
+        else if (pcxHeader.bitsPerPixel == 8 && pcxHeader.nPlanes == 3)
+        {
+            byte[][] image = new byte[3][];
+            image[0] = new byte[xSize*ySize];
+            image[1] = new byte[xSize*ySize];
+            image[2] = new byte[xSize*ySize];
+            for (int y = 0; y < ySize; y++)
+            {
+                readScanLine(pcxHeader, is, scanline);
+                System.arraycopy(scanline, 0, image[0], y*xSize, xSize);
+                System.arraycopy(scanline, pcxHeader.bytesPerLine,
+                        image[1], y*xSize, xSize);
+                System.arraycopy(scanline, 2*pcxHeader.bytesPerLine,
+                        image[2], y*xSize, xSize);
+            }
+            DataBufferByte dataBuffer = new DataBufferByte(image, image[0].length);
+            WritableRaster raster = WritableRaster.createBandedRaster(dataBuffer,
+                    xSize, ySize, xSize, new int[]{0,1,2},
+                    new int[]{0,0,0}, null);
+            ColorModel colorModel = new ComponentColorModel(
+                    ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false,
+                    Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
+            return new BufferedImage(colorModel, raster,
+                    colorModel.isAlphaPremultiplied(), new Properties());
+        }
+        else if ((pcxHeader.bitsPerPixel == 24 && pcxHeader.nPlanes == 1) ||
+                (pcxHeader.bitsPerPixel == 32 && pcxHeader.nPlanes == 1))
+        {
+            int rowLength = 3 * xSize;
+            byte[] image = new byte[rowLength * ySize];
+            for (int y = 0; y < ySize; y++)
+            {
+                readScanLine(pcxHeader, is, scanline);
+                if (pcxHeader.bitsPerPixel == 24)
+                    System.arraycopy(scanline, 0, image, y*rowLength, rowLength);
+                else
+                {
+                    for (int x = 0; x < xSize; x++)
+                    {
+                        image[y*rowLength + 3*x] = scanline[4*x];
+                        image[y*rowLength + 3*x + 1] = scanline[4*x + 1];
+                        image[y*rowLength + 3*x + 2] = scanline[4*x + 2];
+                    }
+                }
+            }
+            DataBufferByte dataBuffer = new DataBufferByte(image, image.length);
+            WritableRaster raster = WritableRaster.createInterleavedRaster(
+                    dataBuffer, xSize, ySize, rowLength, 3,
+                    new int[]{2,1,0}, null);
+            ColorModel colorModel = new ComponentColorModel(
+                    ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false,
+                    Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
+            return new BufferedImage(colorModel, raster,
+                    colorModel.isAlphaPremultiplied(), new Properties());
+        }
+        else
+        {
+            throw new ImageReadException("Invalid/unsupported image with bitsPerPixel "
+                    + pcxHeader.bitsPerPixel + " and planes " + pcxHeader.nPlanes);
+        }
+    }
+
+    public final BufferedImage getBufferedImage(ByteSource byteSource,
+            Map params) throws ImageReadException, IOException
+    {
+        params = (params == null) ? new HashMap() : new HashMap(params);
+        boolean isStrict = false;
+        Object strictness = params.get(PARAM_KEY_STRICT);
+        if (strictness != null)
+            isStrict = ((Boolean) strictness).booleanValue();
+
+        InputStream is = null;
+        try
+        {
+            is = byteSource.getInputStream();
+            PcxHeader pcxHeader = readPcxHeader(is, isStrict);
+            return readImage(pcxHeader, is, byteSource);
+        }
+        finally
+        {
+            try
+            {
+                if (is != null)
+                    is.close();
+            }
+            catch (IOException ignored)
+            {
+            }
+        }
+    }
+
+    public void writeImage(BufferedImage src, OutputStream os, Map params)
+            throws ImageWriteException, IOException
+    {
+        new PcxWriter(params).writeImage(src, os);
+    }
+
+    /**
+     * Extracts embedded XML metadata as XML string.
+     * <p>
+     *
+     * @param byteSource
+     *            File containing image data.
+     * @param params
+     *            Map of optional parameters, defined in SanselanConstants.
+     * @return Xmp Xml as String, if present.  Otherwise, returns null.
+     */
+    public String getXmpXml(ByteSource byteSource, Map params)
+            throws ImageReadException, IOException
+    {
+        return null;
+    }
+}

Propchange: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxImageParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxWriter.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxWriter.java?rev=1087970&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxWriter.java (added)
+++ commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxWriter.java Sat Apr  2 08:22:30 2011
@@ -0,0 +1,401 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *  under the License.
+ */
+
+package org.apache.sanselan.formats.pcx;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.sanselan.ImageWriteException;
+import org.apache.sanselan.common.BinaryOutputStream;
+import org.apache.sanselan.palette.PaletteFactory;
+import org.apache.sanselan.palette.SimplePalette;
+
+public class PcxWriter implements PcxConstants
+{
+    private int encoding;
+    private int bitDepth = -1;
+
+    public PcxWriter(Map params) throws ImageWriteException
+    {
+        // make copy of params; we'll clear keys as we consume them.
+        params = (params == null) ? new HashMap() : new HashMap(params);
+
+        // clear format key.
+        if (params.containsKey(PARAM_KEY_FORMAT))
+            params.remove(PARAM_KEY_FORMAT);
+
+        // uncompressed PCX files are not even documented in ZSoft's spec,
+        // let alone supported by most image viewers
+        encoding = PcxImageParser.PcxHeader.ENCODING_RLE;
+        if (params.containsKey(PARAM_KEY_PCX_COMPRESSION))
+        {
+            Object value = params.remove(PARAM_KEY_PCX_COMPRESSION);
+            if (value != null)
+            {
+                if (!(value instanceof Number))
+                    throw new ImageWriteException(
+                            "Invalid compression parameter: " + value);
+                int compression = ((Number) value).intValue();
+                if (compression == PCX_COMPRESSION_UNCOMPRESSED)
+                    encoding = PcxImageParser.PcxHeader.ENCODING_UNCOMPRESSED;
+            }
+        }
+
+        if (params.containsKey(PARAM_KEY_PCX_BIT_DEPTH))
+        {
+            Object value = params.remove(PARAM_KEY_PCX_BIT_DEPTH);
+            if (value != null)
+            {
+                if (!(value instanceof Number))
+                    throw new ImageWriteException(
+                            "Invalid bit depth parameter: " + value);
+                bitDepth = ((Number)value).intValue();
+            }
+        }
+
+        if (params.size() > 0)
+        {
+            Object firstKey = params.keySet().iterator().next();
+            throw new ImageWriteException("Unknown parameter: " + firstKey);
+        }
+    }
+
+    private void writeScanLine(BinaryOutputStream bos, byte[] scanline)
+            throws IOException, ImageWriteException
+    {
+        if (encoding == PcxImageParser.PcxHeader.ENCODING_UNCOMPRESSED)
+            bos.writeByteArray(scanline);
+        else
+        {
+            if (encoding == PcxImageParser.PcxHeader.ENCODING_RLE)
+            {
+                int previousByte = -1;
+                int repeatCount = 0;
+                for (int i = 0; i < scanline.length; i++)
+                {
+                    if ((scanline[i] & 0xff) == previousByte && repeatCount < 63)
+                        ++repeatCount;
+                    else
+                    {
+                        if (repeatCount > 0)
+                        {
+                            if (repeatCount == 1 && (previousByte & 0xc0) != 0xc0)
+                                bos.write(previousByte);
+                            else
+                            {
+                                bos.write(0xc0 | repeatCount);
+                                bos.write(previousByte);
+                            }
+                        }
+                        previousByte = 0xff & scanline[i];
+                        repeatCount = 1;
+                    }
+                }
+                if (repeatCount > 0)
+                {
+                    if (repeatCount == 1 && (previousByte & 0xc0) != 0xc0)
+                        bos.write(previousByte);
+                    else
+                    {
+                        bos.write(0xc0 | repeatCount);
+                        bos.write(previousByte);
+                    }
+                }
+            }
+            else
+                throw new ImageWriteException("Invalid PCX encoding " + encoding);
+        }
+    }
+
+    public void writeImage(BufferedImage src, OutputStream os)
+            throws ImageWriteException, IOException
+    {
+        final PaletteFactory paletteFactory = new PaletteFactory();
+        final SimplePalette palette = paletteFactory.makePaletteSimple(src, 256);
+        BinaryOutputStream bos = new BinaryOutputStream(os, BinaryOutputStream.BYTE_ORDER_INTEL);
+        if (palette == null || bitDepth == 24 || bitDepth == 32)
+        {
+            if (bitDepth == 32)
+                write32BppPCX(src, bos);
+            else
+                write24BppPCX(src, bos);
+        }
+        else if (palette.length() > 16 || bitDepth == 8)
+            write256ColorPCX(src, palette, bos);
+        else if (palette.length() > 2 || bitDepth == 4)
+            write16ColorPCX(src, palette, bos);
+        else
+        {
+            boolean onlyBlackAndWhite = true;
+            if (palette.length() >= 1)
+            {
+                int rgb = palette.getEntry(0);
+                if (rgb != 0 && rgb != 0xffffff)
+                    onlyBlackAndWhite = false;
+            }
+            if (palette.length() == 2)
+            {
+                int rgb = palette.getEntry(1);
+                if (rgb != 0 && rgb != 0xffffff)
+                    onlyBlackAndWhite = false;
+            }
+            if (onlyBlackAndWhite)
+                writeBlackAndWhitePCX(src, palette, bos);
+            else
+                write16ColorPCX(src, palette, bos);
+        }
+    }
+
+    private void write32BppPCX(BufferedImage src, BinaryOutputStream bos)
+            throws ImageWriteException, IOException
+    {
+        final int bytesPerLine = src.getWidth() % 2 == 0 ?
+            src.getWidth() : src.getWidth() + 1;
+
+        // PCX header
+        bos.write(10); // manufacturer
+        bos.write(5); // version
+        bos.write(encoding); // encoding
+        bos.write(32); // bits per pixel
+        bos.write2Bytes(0); // xMin
+        bos.write2Bytes(0); // yMin
+        bos.write2Bytes(src.getWidth() - 1); // xMax
+        bos.write2Bytes(src.getHeight() - 1); // yMax
+        bos.write2Bytes(300); // hDpi
+        bos.write2Bytes(300); // vDpi
+        bos.writeByteArray(new byte[48]); // 16 color palette
+        bos.write(0); // reserved
+        bos.write(1); // planes
+        bos.write2Bytes(bytesPerLine); // bytes per line
+        bos.write2Bytes(1); // palette info
+        bos.write2Bytes(0); // hScreenSize
+        bos.write2Bytes(0); // vScreenSize
+        bos.writeByteArray(new byte[54]);
+
+        int rgbs[] = new int[src.getWidth()];
+        byte rgbBytes[] = new byte[4 * bytesPerLine];
+        for (int y = 0; y < src.getHeight(); y++)
+        {
+            src.getRGB(0, y, src.getWidth(), 1, rgbs, 0, src.getWidth());
+            for (int x = 0; x < rgbs.length; x++)
+            {
+                rgbBytes[4*x + 0] = (byte) (rgbs[x] & 0xff);
+                rgbBytes[4*x + 1] = (byte) ((rgbs[x] >> 8) & 0xff);
+                rgbBytes[4*x + 2] = (byte) ((rgbs[x] >> 16) & 0xff);
+                rgbBytes[4*x + 3] = 0;
+            }
+            writeScanLine(bos, rgbBytes);
+        }
+    }
+
+    private void write24BppPCX(BufferedImage src, BinaryOutputStream bos)
+            throws ImageWriteException, IOException
+    {
+        final int bytesPerLine = src.getWidth() % 2 == 0 ?
+            src.getWidth() : src.getWidth() + 1;
+        
+        // PCX header
+        bos.write(10); // manufacturer
+        bos.write(5); // version
+        bos.write(encoding); // encoding
+        bos.write(8); // bits per pixel
+        bos.write2Bytes(0); // xMin
+        bos.write2Bytes(0); // yMin
+        bos.write2Bytes(src.getWidth() - 1); // xMax
+        bos.write2Bytes(src.getHeight() - 1); // yMax
+        bos.write2Bytes(300); // hDpi
+        bos.write2Bytes(300); // vDpi
+        bos.writeByteArray(new byte[48]); // 16 color palette
+        bos.write(0); // reserved
+        bos.write(3); // planes
+        bos.write2Bytes(bytesPerLine); // bytes per line
+        bos.write2Bytes(1); // palette info
+        bos.write2Bytes(0); // hScreenSize
+        bos.write2Bytes(0); // vScreenSize
+        bos.writeByteArray(new byte[54]);
+
+        int rgbs[] = new int[src.getWidth()];
+        byte rgbBytes[] = new byte[3 * bytesPerLine];
+        for (int y = 0; y < src.getHeight(); y++)
+        {
+            src.getRGB(0, y, src.getWidth(), 1, rgbs, 0, src.getWidth());
+            for (int x = 0; x < rgbs.length; x++)
+            {
+                rgbBytes[x] = (byte) ((rgbs[x] >> 16) & 0xff);
+                rgbBytes[bytesPerLine + x] = (byte) ((rgbs[x] >> 8) & 0xff);
+                rgbBytes[2 * bytesPerLine + x] = (byte) (rgbs[x] & 0xff);
+            }
+            writeScanLine(bos, rgbBytes);
+        }
+    }
+
+    private void writeBlackAndWhitePCX(BufferedImage src, SimplePalette palette, BinaryOutputStream bos)
+            throws ImageWriteException, IOException
+    {
+        int bytesPerLine = (src.getWidth() + 7) / 8;
+        if (bytesPerLine % 2 != 0)
+            ++bytesPerLine;
+
+        // PCX header
+        bos.write(10); // manufacturer
+        bos.write(3); // version - it seems some apps only open
+        // black and white files if the version is 3...
+        bos.write(encoding); // encoding
+        bos.write(1); // bits per pixel
+        bos.write2Bytes(0); // xMin
+        bos.write2Bytes(0); // yMin
+        bos.write2Bytes(src.getWidth() - 1); // xMax
+        bos.write2Bytes(src.getHeight() - 1); // yMax
+        bos.write2Bytes(300); // hDpi
+        bos.write2Bytes(300); // vDpi
+        bos.writeByteArray(new byte[48]); // 16 color palette
+        bos.write(0); // reserved
+        bos.write(1); // planes
+        bos.write2Bytes(bytesPerLine); // bytes per line
+        bos.write2Bytes(1); // palette info
+        bos.write2Bytes(0); // hScreenSize
+        bos.write2Bytes(0); // vScreenSize
+        bos.writeByteArray(new byte[54]);
+
+        byte[] row = new byte[bytesPerLine];
+        for (int y = 0; y < src.getHeight(); y++)
+        {
+            Arrays.fill(row, (byte)0);
+            for (int x = 0; x < src.getWidth(); x++)
+            {
+                int rgb = 0xffffff & src.getRGB(x, y);
+                int bit;
+                if (rgb == 0x000000)
+                    bit = 0;
+                else if (rgb == 0xffffff)
+                    bit = 1;
+                else
+                    throw new ImageWriteException("Pixel neither black nor white");
+                row[x / 8] |= (bit << (7 - (x % 8)));
+            }
+            writeScanLine(bos, row);
+        }
+    }
+
+    private void write16ColorPCX(BufferedImage src, SimplePalette palette, BinaryOutputStream bos)
+            throws ImageWriteException, IOException
+    {
+        int bytesPerLine = (src.getWidth() + 1) / 2;
+        if (bytesPerLine % 2 != 0)
+            ++bytesPerLine;
+
+        byte[] palette16 = new byte[16*3];
+        for (int i = 0; i < 16; i++)
+        {
+            int rgb;
+            if (i < palette.length())
+                rgb = palette.getEntry(i);
+            else
+                rgb = 0;
+            palette16[3*i + 0] = (byte) (0xff & (rgb >> 16));
+            palette16[3*i + 1] = (byte) (0xff & (rgb >> 8));
+            palette16[3*i + 2] = (byte) (0xff & rgb);
+        }
+
+        // PCX header
+        bos.write(10); // manufacturer
+        bos.write(5); // version
+        bos.write(encoding); // encoding
+        bos.write(4); // bits per pixel
+        bos.write2Bytes(0); // xMin
+        bos.write2Bytes(0); // yMin
+        bos.write2Bytes(src.getWidth() - 1); // xMax
+        bos.write2Bytes(src.getHeight() - 1); // yMax
+        bos.write2Bytes(300); // hDpi
+        bos.write2Bytes(300); // vDpi
+        bos.writeByteArray(palette16); // 16 color palette
+        bos.write(0); // reserved
+        bos.write(1); // planes
+        bos.write2Bytes(bytesPerLine); // bytes per line
+        bos.write2Bytes(1); // palette info
+        bos.write2Bytes(0); // hScreenSize
+        bos.write2Bytes(0); // vScreenSize
+        bos.writeByteArray(new byte[54]);
+
+        byte[] indeces = new byte[bytesPerLine];
+        for (int y = 0; y < src.getHeight(); y++)
+        {
+            Arrays.fill(indeces, (byte)0);
+            for (int x = 0; x < src.getWidth(); x++)
+            {
+                int argb = src.getRGB(x, y);
+                int index = palette.getPaletteIndex(0xffffff & argb);
+                indeces[x / 2] |= (index << 4*(1 - (x % 2)));
+            }
+            writeScanLine(bos, indeces);
+        }
+    }
+
+    private void write256ColorPCX(BufferedImage src, SimplePalette palette, BinaryOutputStream bos)
+            throws ImageWriteException, IOException
+    {
+        final int bytesPerLine = src.getWidth() % 2 == 0 ?
+            src.getWidth() : src.getWidth() + 1;
+
+        // PCX header
+        bos.write(10); // manufacturer
+        bos.write(5); // version
+        bos.write(encoding); // encoding
+        bos.write(8); // bits per pixel
+        bos.write2Bytes(0); // xMin
+        bos.write2Bytes(0); // yMin
+        bos.write2Bytes(src.getWidth() - 1); // xMax
+        bos.write2Bytes(src.getHeight() - 1); // yMax
+        bos.write2Bytes(300); // hDpi
+        bos.write2Bytes(300); // vDpi
+        bos.writeByteArray(new byte[48]); // 16 color palette
+        bos.write(0); // reserved
+        bos.write(1); // planes
+        bos.write2Bytes(bytesPerLine); // bytes per line
+        bos.write2Bytes(1); // palette info
+        bos.write2Bytes(0); // hScreenSize
+        bos.write2Bytes(0); // vScreenSize
+        bos.writeByteArray(new byte[54]);
+
+        byte[] indeces = new byte[bytesPerLine];
+        for (int y = 0; y < src.getHeight(); y++)
+        {
+            for (int x = 0; x < src.getWidth(); x++)
+            {
+                int argb = src.getRGB(x, y);
+                int index = palette.getPaletteIndex(0xffffff & argb);
+                indeces[x] = (byte) index;
+            }
+            writeScanLine(bos, indeces);
+        }
+        // palette
+        bos.write(12);
+        for (int i = 0; i < 256; i++)
+        {
+            int rgb;
+            if (i < palette.length())
+                rgb = palette.getEntry(i);
+            else
+                rgb = 0;
+            bos.write((rgb >> 16) & 0xff);
+            bos.write((rgb >> 8) & 0xff);
+            bos.write(rgb & 0xff);
+        }
+    }
+}

Propchange: commons/proper/sanselan/trunk/src/main/java/org/apache/sanselan/formats/pcx/PcxWriter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppUncompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppUncompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane1bppUncompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane24bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane24bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane24bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane2bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane2bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane2bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane32bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane32bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane32bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane4bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane4bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane4bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppUncompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppUncompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/1plane8bppUncompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/2plane1bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/2plane1bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/2plane1bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane1bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane1bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane1bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppUncompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppUncompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/3plane8bppUncompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/4plane1bppCompressed.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/4plane1bppCompressed.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/4plane1bppCompressed.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/Oregon Scientific DS6639 - DSC_0307 - small.pcx
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/Oregon%20Scientific%20DS6639%20-%20DSC_0307%20-%20small.pcx?rev=1087970&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/Oregon Scientific DS6639 - DSC_0307 - small.pcx
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/info.txt
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/data/images/pcx/1/info.txt?rev=1087970&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/test/data/images/pcx/1/info.txt (added)
+++ commons/proper/sanselan/trunk/src/test/data/images/pcx/1/info.txt Sat Apr  2 08:22:30 2011
@@ -0,0 +1,3 @@
+Contributed to the project by Damjan Jovanovic.
+Oregon Scientific DS6639 - DSC_0307 - small.pcx was converted from ../../bmp/1/Oregon Scientific DS6639 - DSC_0307 - small.bmp
+The XplaneYbpp[Un]compressed.pcx files were programmatically generated.

Propchange: commons/proper/sanselan/trunk/src/test/data/images/pcx/1/info.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java?rev=1087970&r1=1087969&r2=1087970&view=diff
==============================================================================
--- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java (original)
+++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/common/byteSources/ByteSourceImageTest.java Sat Apr  2 08:22:30 2011
@@ -59,6 +59,7 @@ public class ByteSourceImageTest extends
             if (imageFile.getName().toLowerCase().endsWith(".ico")
                     || imageFile.getName().toLowerCase().endsWith(".tga")
                     || imageFile.getName().toLowerCase().endsWith(".jb2")
+                    || imageFile.getName().toLowerCase().endsWith(".pcx")
                     || imageFile.getName().toLowerCase().endsWith(".psd")
                     || imageFile.getName().toLowerCase().endsWith(".wbmp"))
             {

Added: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxBaseTest.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxBaseTest.java?rev=1087970&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxBaseTest.java (added)
+++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxBaseTest.java Sat Apr  2 08:22:30 2011
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sanselan.formats.pcx;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.SanselanTest;
+
+public abstract class PcxBaseTest extends SanselanTest
+{
+
+    private static boolean isPcx(File file) throws IOException, ImageReadException
+    {
+        return file.getName().toLowerCase().endsWith(".pcx");
+    }
+
+    private static final ImageFilter IMAGE_FILTER = new ImageFilter() {
+        public boolean accept(File file) throws IOException, ImageReadException
+        {
+            return isPcx(file);
+        }
+    };
+
+    protected List getPcxImages() throws IOException, ImageReadException
+    {
+        return getTestImages(IMAGE_FILTER);
+    }
+}

Propchange: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxBaseTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxReadTest.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxReadTest.java?rev=1087970&view=auto
==============================================================================
--- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxReadTest.java (added)
+++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxReadTest.java Sat Apr  2 08:22:30 2011
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sanselan.formats.pcx;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sanselan.ImageInfo;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.Sanselan;
+import org.apache.sanselan.common.IImageMetadata;
+import org.apache.sanselan.util.Debug;
+
+public class PcxReadTest extends PcxBaseTest
+{
+
+    public void test() throws IOException, ImageReadException
+    {
+        Debug.debug("start");
+
+        List images = getPcxImages();
+        for (int i = 0; i < images.size(); i++)
+        {
+            if (i % 10 == 0)
+                Debug.purgeMemory();
+
+            File imageFile = (File) images.get(i);
+            Debug.debug("imageFile", imageFile);
+
+            IImageMetadata metadata = Sanselan.getMetadata(imageFile);
+            // assertNotNull(metadata);
+
+            Map params = new HashMap();
+            ImageInfo imageInfo = Sanselan.getImageInfo(imageFile, params);
+            assertNotNull(imageInfo);
+
+            BufferedImage image = Sanselan.getBufferedImage(imageFile);
+            assertNotNull(image);
+        }
+    }
+
+}

Propchange: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/formats/pcx/PcxReadTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java
URL: http://svn.apache.org/viewvc/commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java?rev=1087970&r1=1087969&r2=1087970&view=diff
==============================================================================
--- commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java (original)
+++ commons/proper/sanselan/trunk/src/test/java/org/apache/sanselan/roundtrip/RoundtripTest.java Sat Apr  2 08:22:30 2011
@@ -88,6 +88,8 @@ public class RoundtripTest extends Sanse
                     COLOR_FULL_RGB, true), //
             new FormatInfo(ImageFormat.IMAGE_FORMAT_WBMP, true, true,
                     COLOR_BITMAP, true), //
+            new FormatInfo(ImageFormat.IMAGE_FORMAT_PCX, true, true,
+                    COLOR_FULL_RGB, true), //
     };
 
     private BufferedImage createArgbBitmapImage(int width, int height)