Package org.apache.sanselan.formats.pcx

Source Code of org.apache.sanselan.formats.pcx.PcxImageParser$PcxHeader

/*
* 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;
    }
}
TOP

Related Classes of org.apache.sanselan.formats.pcx.PcxImageParser$PcxHeader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.