Package org.apache.sanselan.formats.png

Source Code of org.apache.sanselan.formats.png.PngImageParser

/*
* 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.png;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.List;
import java.util.Map;
import java.util.zip.InflaterInputStream;

import org.apache.sanselan.ColorTools;
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.ImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.formats.png.chunks.PNGChunk;
import org.apache.sanselan.formats.png.chunks.PNGChunkIDAT;
import org.apache.sanselan.formats.png.chunks.PNGChunkIHDR;
import org.apache.sanselan.formats.png.chunks.PNGChunkPLTE;
import org.apache.sanselan.formats.png.chunks.PNGChunkgAMA;
import org.apache.sanselan.formats.png.chunks.PNGChunkiCCP;
import org.apache.sanselan.formats.png.chunks.PNGChunkiTXt;
import org.apache.sanselan.formats.png.chunks.PNGChunkpHYs;
import org.apache.sanselan.formats.png.chunks.PNGChunktEXt;
import org.apache.sanselan.formats.png.chunks.PNGChunkzTXt;
import org.apache.sanselan.formats.png.chunks.PNGTextChunk;
import org.apache.sanselan.formats.transparencyfilters.TransparencyFilter;
import org.apache.sanselan.formats.transparencyfilters.TransparencyFilterGrayscale;
import org.apache.sanselan.formats.transparencyfilters.TransparencyFilterIndexedColor;
import org.apache.sanselan.formats.transparencyfilters.TransparencyFilterTrueColor;
import org.apache.sanselan.icc.IccProfileParser;
import org.apache.sanselan.util.Debug;
import org.apache.sanselan.util.ParamMap;

import com.google.code.appengine.awt.Dimension;
import com.google.code.appengine.awt.color.ColorSpace;
import com.google.code.appengine.awt.color.ICC_ColorSpace;
import com.google.code.appengine.awt.color.ICC_Profile;
import com.google.code.appengine.awt.image.BufferedImage;
import com.google.code.appengine.awt.image.ColorModel;


public class PngImageParser extends ImageParser implements PngConstants
{

    public PngImageParser()
    {
        // setDebug(true);
    }

    public String getName()
    {
        return "Png-Custom";
    }

    public String getDefaultExtension()
    {
        return DEFAULT_EXTENSION;
    }

    private static final String DEFAULT_EXTENSION = ".png";

    private static final String ACCEPTED_EXTENSIONS[] = { DEFAULT_EXTENSION, };

    protected String[] getAcceptedExtensions()
    {
        return ACCEPTED_EXTENSIONS;
    }

    protected ImageFormat[] getAcceptedTypes()
    {
        return new ImageFormat[] { ImageFormat.IMAGE_FORMAT_PNG, //
        };
    }

    // private final static int tRNS = CharsToQuad('t', 'R', 'N', 's');

    public static final String getChunkTypeName(int chunkType) {
        StringBuffer result = new StringBuffer();
        result.append((char) (0xff & (chunkType >> 24)));
        result.append((char) (0xff & (chunkType >> 16)));
        result.append((char) (0xff & (chunkType >> 8)));
        result.append((char) (0xff & (chunkType >> 0)));
        return result.toString();
    }

    /**
     * @return List of String-formatted chunk types, ie. "tRNs".
     */
    public List getChuckTypes(InputStream is) throws ImageReadException, IOException {
        List chunks = readChunks(is, null, false);
        List chunkTypes = new ArrayList();
        for (int i=0; i<chunks.size(); i++) {
            PNGChunk chunk = (PNGChunk) chunks.get(i);
            chunkTypes.add(getChunkTypeName(chunk.chunkType));
        }
        return chunkTypes;
    }

    public boolean hasChuckType(ByteSource byteSource, int chunkType) throws ImageReadException, IOException
    {
        InputStream is = null;

        try
        {
            is = byteSource.getInputStream();

            ArrayList chunks = null;

            readSignature(is);
            chunks = readChunks(is, new int[] { chunkType, }, true);
            return chunks.size() > 0;
        } finally
        {
            try
            {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e)
            {
                Debug.debug(e);
            }
        }
    }


    private boolean keepChunk(int ChunkType, int chunkTypes[])
    {
        // System.out.println("keepChunk: ");
        if (chunkTypes == null)
            return true;

        for (int i = 0; i < chunkTypes.length; i++)
        {
            if (chunkTypes[i] == ChunkType)
                return true;
        }
        return false;
    }

    private ArrayList readChunks(InputStream is, int chunkTypes[],
            boolean returnAfterFirst) throws ImageReadException, IOException
    {
        ArrayList result = new ArrayList();

        while (true)
        {
            if (debug)
                System.out.println("");

            int length = read4Bytes("Length", is, "Not a Valid PNG File");
            int chunkType = read4Bytes("ChunkType", is, "Not a Valid PNG File");

            if (debug)
            {
                printCharQuad("ChunkType", chunkType);
                debugNumber("Length", length, 4);
            }
            boolean keep = keepChunk(chunkType, chunkTypes);

            byte bytes[] = null;
            if (keep)
            {
                bytes = readByteArray("Chunk Data", length, is,
                        "Not a Valid PNG File: Couldn't read Chunk Data.");
            } else
                skipBytes(is, length, "Not a Valid PNG File");

            if (debug)
                if (bytes != null)
                    debugNumber("bytes", bytes.length, 4);

            int CRC = read4Bytes("CRC", is, "Not a Valid PNG File");

            if (keep)
            {
                if (chunkType == iCCP)
                    result.add(new PNGChunkiCCP(length, chunkType, CRC, bytes));
                else if (chunkType == tEXt)
                    result.add(new PNGChunktEXt(length, chunkType, CRC, bytes));
                else if (chunkType == zTXt)
                    result.add(new PNGChunkzTXt(length, chunkType, CRC, bytes));
                else if (chunkType == IHDR)
                    result.add(new PNGChunkIHDR(length, chunkType, CRC, bytes));
                else if (chunkType == PLTE)
                    result.add(new PNGChunkPLTE(length, chunkType, CRC, bytes));
                else if (chunkType == pHYs)
                    result.add(new PNGChunkpHYs(length, chunkType, CRC, bytes));
                else if (chunkType == IDAT)
                    result.add(new PNGChunkIDAT(length, chunkType, CRC, bytes));
                else if (chunkType == gAMA)
                    result.add(new PNGChunkgAMA(length, chunkType, CRC, bytes));
                else if (chunkType == iTXt)
                    result.add(new PNGChunkiTXt(length, chunkType, CRC, bytes));
                else
                    result.add(new PNGChunk(length, chunkType, CRC, bytes));

                if (returnAfterFirst)
                    return result;
            }

            if (chunkType == IEND)
                break;

        }

        return result;

    }

    public void readSignature(InputStream is) throws ImageReadException,
            IOException
    {
        readAndVerifyBytes(is, PNG_Signature,
                "Not a Valid PNG Segment: Incorrect Signature");

    }

    private ArrayList readChunks(ByteSource byteSource, int chunkTypes[],
            boolean returnAfterFirst) throws ImageReadException, IOException
    {
        InputStream is = null;

        try
        {
            is = byteSource.getInputStream();

            ArrayList chunks = null;

            readSignature(is);
            chunks = readChunks(is, chunkTypes, returnAfterFirst);
            return chunks;
        } finally
        {
            try
            {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e)
            {
                Debug.debug(e);
            }
        }
    }

    public byte[] getICCProfileBytes(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ArrayList chunks = readChunks(byteSource, new int[] { iCCP, }, true);

        if ((chunks == null) || (chunks.size() < 1))
        {
            // throw new ImageReadException("Png: No chunks");
            return null;
        }

        if (chunks.size() > 1)
            throw new ImageReadException(
                    "PNG contains more than one ICC Profile ");

        PNGChunkiCCP pngChunkiCCP = (PNGChunkiCCP) chunks.get(0);
        byte bytes[] = pngChunkiCCP.UncompressedProfile;

        return (bytes);
    }

    public Dimension getImageSize(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ArrayList chunks = readChunks(byteSource, new int[] { IHDR, }, true);

        if ((chunks == null) || (chunks.size() < 1))
            throw new ImageReadException("Png: No chunks");

        if (chunks.size() > 1)
            throw new ImageReadException("PNG contains more than one Header");

        PNGChunkIHDR pngChunkIHDR = (PNGChunkIHDR) chunks.get(0);

        return new Dimension(pngChunkIHDR.width, pngChunkIHDR.height);
    }

    public byte[] embedICCProfile(byte image[], byte profile[])
    {
        return null;
    }

    public boolean embedICCProfile(File src, File dst, byte profile[])
    {
        return false;
    }

    public IImageMetadata getMetadata(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ArrayList chunks = readChunks(byteSource, new int[] { tEXt, zTXt, },
                true);

        if ((chunks == null) || (chunks.size() < 1))
            return null;

        ImageMetadata result = new ImageMetadata();

        for (int i = 0; i < chunks.size(); i++)
        {
            PNGTextChunk chunk = (PNGTextChunk) chunks.get(i);

            result.add(chunk.getKeyword(), chunk.getText());
        }

        return result;
    }

    private boolean isGrayscale(int colorType) throws ImageReadException
    {
        // Color type is a single-byte integer that describes the interpretation
        // of the
        // image data. Color type codes represent sums of the following values:
        // 1 (palette used), 2 (color used), and 4 (alpha channel used).
        // Valid values are 0, 2, 3, 4, and 6.
        //
        // Bit depth restrictions for each color type are imposed to simplify
        // implementations
        // and to prohibit combinations that do not compress well. Decoders must
        // support all
        // valid combinations of bit depth and color type. The allowed
        // combinations are:
        //
        // Color Allowed Interpretation
        // Type Bit Depths
        //
        // 0 1,2,4,8,16 Each pixel is a grayscale sample.
        //
        // 2 8,16 Each pixel is an R,G,B triple.
        //
        // 3 1,2,4,8 Each pixel is a palette index;
        // a PLTE chunk must appear.
        //
        // 4 8,16 Each pixel is a grayscale sample,
        // followed by an alpha sample.
        //
        // 6 8,16 Each pixel is an R,G,B triple,
        // followed by an alpha sample.
        switch (colorType)
        {
        case COLOR_TYPE_GREYSCALE:
            return true;
        case COLOR_TYPE_TRUE_COLOR:
            return false;
        case COLOR_TYPE_INDEXED_COLOR:
            return false;
        case COLOR_TYPE_GREYSCALE_WITH_ALPHA:
            return true;
        case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA:
            return false;
        }

        // return -1;
        throw new ImageReadException("PNG: unknown color type: " + colorType);
    }

    private int samplesPerPixel(int colorType) throws ImageReadException
    {
        // Color type is a single-byte integer that describes the interpretation
        // of the
        // image data. Color type codes represent sums of the following values:
        // 1 (palette used), 2 (color used), and 4 (alpha channel used).
        // Valid values are 0, 2, 3, 4, and 6.
        //
        // Bit depth restrictions for each color type are imposed to simplify
        // implementations
        // and to prohibit combinations that do not compress well. Decoders must
        // support all
        // valid combinations of bit depth and color type. The allowed
        // combinations are:
        //
        // Color Allowed Interpretation
        // Type Bit Depths
        //
        // 0 1,2,4,8,16 Each pixel is a grayscale sample.
        //
        // 2 8,16 Each pixel is an R,G,B triple.
        //
        // 3 1,2,4,8 Each pixel is a palette index;
        // a PLTE chunk must appear.
        //
        // 4 8,16 Each pixel is a grayscale sample,
        // followed by an alpha sample.
        //
        // 6 8,16 Each pixel is an R,G,B triple,
        // followed by an alpha sample.
        switch (colorType)
        {
        case COLOR_TYPE_GREYSCALE:
            return 1;
        case COLOR_TYPE_TRUE_COLOR:
            return 3;
        case COLOR_TYPE_INDEXED_COLOR:
            return 1; // is this accurate ? how may bits per index?
        case COLOR_TYPE_GREYSCALE_WITH_ALPHA:
            return 2;
        case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA:
            return 4;
        }

        // return -1;
        throw new ImageReadException("PNG: unknown color type: " + colorType);
    }

    private ArrayList filterChunks(ArrayList v, int type)
    {
        ArrayList result = new ArrayList();

        for (int i = 0; i < v.size(); i++)
        {
            PNGChunk chunk = (PNGChunk) v.get(i);
            if (chunk.chunkType == type)
                result.add(chunk);
        }

        return result;
    }

    private boolean hasAlphaChannel(int ColorType) throws ImageReadException
    {
        switch (ColorType)
        {
        case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
            // sample.
        case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
        case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
            return false;
        case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale
            // sample,
            // followed by an alpha sample.
        case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B
            // triple,
            // followed by an alpha sample.
            return true;
        default:
            throw new ImageReadException("PNG: unknown color type: "
                    + ColorType);
        }
    }

    private String getColorTypeDescription(int ColorType)
    {
        switch (ColorType)
        {
        case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
            // sample.
            return "grayscale";
        case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
            return "rgb";
        case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
            return "indexed rgb";
        case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale
            // sample,
            // followed by an alpha sample.
            return "grayscale w/ alpha";
        case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B
            // triple,
            // followed by an alpha sample.
            return "RGB w/ alpha";
        default:
            return "Unknown Color Type";
        }
    }

    // TODO: I have been too casual about making inner classes subclass of
    // BinaryFileParser
    // I may not have always preserved byte order correctly.

    private TransparencyFilter getTransparencyFilter(int ColorType,
            PNGChunk pngChunktRNS) throws ImageReadException, IOException
    {
        // this.printCharQuad("pngChunktRNS.ChunkType", pngChunktRNS.ChunkType);
        // this.debugNumber("pngChunktRNS.Length", pngChunktRNS.Length);

        switch (ColorType)
        {
        case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
            // sample.
            return new TransparencyFilterGrayscale(pngChunktRNS.bytes);
        case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
            return new TransparencyFilterTrueColor(pngChunktRNS.bytes);
        case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette index;
            return new TransparencyFilterIndexedColor(pngChunktRNS.bytes);
        case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a grayscale
            // sample,
        case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an R,G,B
            // triple,
        default:
            throw new ImageReadException(
                    "Simple Transparency not compatible with ColorType: "
                            + ColorType);
        }
    }

    public ImageInfo getImageInfo(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ArrayList chunks = readChunks(byteSource, new int[] { IHDR, pHYs, tEXt,
                zTXt, tRNS, PLTE, iTXt, }, false);

        // if(chunks!=null)
        // System.out.println("chunks: " + chunks.size());

        if ((chunks == null) || (chunks.size() < 1))
            throw new ImageReadException("PNG: no chunks");

        ArrayList IHDRs = filterChunks(chunks, IHDR);
        if (IHDRs.size() != 1)
            throw new ImageReadException("PNG contains more than one Header");

        PNGChunkIHDR pngChunkIHDR = (PNGChunkIHDR) IHDRs.get(0);
        PNGChunk pngChunktRNS = null;

        boolean isTransparent = false;

        ArrayList tRNSs = filterChunks(chunks, tRNS);
        if (tRNSs.size() > 0)
        {
            isTransparent = true;
            pngChunktRNS = (PNGChunk) IHDRs.get(0);
        } else
            hasAlphaChannel(pngChunkIHDR.colorType);

        PNGChunkpHYs pngChunkpHYs = null;

        ArrayList pHYss = filterChunks(chunks, pHYs);
        if (pHYss.size() > 1)
            throw new ImageReadException("PNG contains more than one pHYs: "
                    + pHYss.size());
        else if (pHYss.size() == 1)
            pngChunkpHYs = (PNGChunkpHYs) pHYss.get(0);

        ArrayList tEXts = filterChunks(chunks, tEXt);
        ArrayList zTXts = filterChunks(chunks, zTXt);
        ArrayList iTXts = filterChunks(chunks, iTXt);

        {
            ArrayList comments = new ArrayList();
            List textChunks = new ArrayList();

            for (int i = 0; i < tEXts.size(); i++)
            {
                PNGChunktEXt pngChunktEXt = (PNGChunktEXt) tEXts.get(i);
                comments.add(pngChunktEXt.keyword + ": " + pngChunktEXt.text);
                textChunks.add(pngChunktEXt.getContents());
            }
            for (int i = 0; i < zTXts.size(); i++)
            {
                PNGChunkzTXt pngChunkzTXt = (PNGChunkzTXt) zTXts.get(i);
                comments.add(pngChunkzTXt.keyword + ": " + pngChunkzTXt.text);
                textChunks.add(pngChunkzTXt.getContents());
            }
            for (int i = 0; i < iTXts.size(); i++)
            {
                PNGChunkiTXt pngChunkiTXt = (PNGChunkiTXt) iTXts.get(i);
                comments.add(pngChunkiTXt.keyword + ": " + pngChunkiTXt.text);
                textChunks.add(pngChunkiTXt.getContents());
            }

            int BitsPerPixel = pngChunkIHDR.bitDepth
                    * samplesPerPixel(pngChunkIHDR.colorType);
            ImageFormat Format = ImageFormat.IMAGE_FORMAT_PNG;
            String FormatName = "PNG Portable Network Graphics";
            int Height = pngChunkIHDR.height;
            String MimeType = "image/png";
            int NumberOfImages = 1;
            int Width = pngChunkIHDR.width;
            boolean isProgressive = (pngChunkIHDR.interlaceMethod != 0);

            int PhysicalHeightDpi = -1;
            float PhysicalHeightInch = -1;
            int PhysicalWidthDpi = -1;
            float PhysicalWidthInch = -1;

            // if (pngChunkpHYs != null)
            // {
            // System.out.println("\t" + "pngChunkpHYs.UnitSpecifier: " +
            // pngChunkpHYs.UnitSpecifier );
            // System.out.println("\t" + "pngChunkpHYs.PixelsPerUnitYAxis: " +
            // pngChunkpHYs.PixelsPerUnitYAxis );
            // System.out.println("\t" + "pngChunkpHYs.PixelsPerUnitXAxis: " +
            // pngChunkpHYs.PixelsPerUnitXAxis );
            // }
            if ((pngChunkpHYs != null) && (pngChunkpHYs.UnitSpecifier == 1)) // meters
            {
                double meters_per_inch = 0.0254;

                PhysicalWidthDpi = (int) Math
                        .round(pngChunkpHYs.PixelsPerUnitXAxis
                                * meters_per_inch);
                PhysicalWidthInch = (float) ((double) Width
                        * (double) pngChunkpHYs.PixelsPerUnitXAxis * meters_per_inch);
                PhysicalHeightDpi = (int) Math
                        .round(pngChunkpHYs.PixelsPerUnitYAxis
                                * meters_per_inch);
                PhysicalHeightInch = (float) ((double) Height
                        * (double) pngChunkpHYs.PixelsPerUnitYAxis * meters_per_inch);
            }

            String FormatDetails = "Png";

            boolean usesPalette = false;

            ArrayList PLTEs = filterChunks(chunks, PLTE);
            if (PLTEs.size() > 1)
                usesPalette = true;

            int ColorType;
            switch (pngChunkIHDR.colorType)
            {
            case COLOR_TYPE_GREYSCALE: // 1,2,4,8,16 Each pixel is a grayscale
                // sample.
            case COLOR_TYPE_GREYSCALE_WITH_ALPHA: // 8,16 Each pixel is a
                // grayscale sample,
                // followed by an alpha sample.
                ColorType = ImageInfo.COLOR_TYPE_GRAYSCALE;
                break;
            case COLOR_TYPE_TRUE_COLOR: // 8,16 Each pixel is an R,G,B triple.
            case COLOR_TYPE_INDEXED_COLOR: // 1,2,4,8 Each pixel is a palette
                // index;
            case COLOR_TYPE_TRUE_COLOR_WITH_ALPHA: // 8,16 Each pixel is an
                // R,G,B triple,
                // followed by an alpha sample.
                ColorType = ImageInfo.COLOR_TYPE_RGB;
                break;
            default:
                throw new ImageReadException("Png: Unknown ColorType: "
                        + pngChunkIHDR.colorType);
            }

            String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_PNG_FILTER;

            ImageInfo result = new PngImageInfo(FormatDetails, BitsPerPixel,
                    comments, Format, FormatName, Height, MimeType,
                    NumberOfImages, PhysicalHeightDpi, PhysicalHeightInch,
                    PhysicalWidthDpi, PhysicalWidthInch, Width, isProgressive,
                    isTransparent, usesPalette, ColorType,
                    compressionAlgorithm, textChunks);

            return result;
        }
    }

    public BufferedImage getBufferedImage(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        boolean verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
                false);

        if (params.containsKey(PARAM_KEY_VERBOSE))
            params.remove(PARAM_KEY_VERBOSE);

        // if (params.size() > 0) {
        // Object firstKey = params.keySet().iterator().next();
        // throw new ImageWriteException("Unknown parameter: " + firstKey);
        // }

        ArrayList chunks = readChunks(byteSource, new int[] { IHDR, PLTE, IDAT,
                tRNS, iCCP, gAMA, sRGB, }, false);

        if ((chunks == null) || (chunks.size() < 1))
            throw new ImageReadException("PNG: no chunks");

        ArrayList IHDRs = filterChunks(chunks, IHDR);
        if (IHDRs.size() != 1)
            throw new ImageReadException("PNG contains more than one Header");

        PNGChunkIHDR pngChunkIHDR = (PNGChunkIHDR) IHDRs.get(0);

        ArrayList PLTEs = filterChunks(chunks, PLTE);
        if (PLTEs.size() > 1)
            throw new ImageReadException("PNG contains more than one Palette");

        PNGChunkPLTE pngChunkPLTE = null;
        if (PLTEs.size() == 1)
            pngChunkPLTE = (PNGChunkPLTE) PLTEs.get(0);

        // -----

        ArrayList IDATs = filterChunks(chunks, IDAT);
        if (IDATs.size() < 1)
            throw new ImageReadException("PNG missing image data");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for (int i = 0; i < IDATs.size(); i++)
        {
            PNGChunkIDAT pngChunkIDAT = (PNGChunkIDAT) IDATs.get(i);
            byte bytes[] = pngChunkIDAT.bytes;
            // System.out.println(i + ": bytes: " + bytes.length);
            baos.write(bytes);
        }

        byte compressed[] = baos.toByteArray();

        baos = null;

        TransparencyFilter transparencyFilter = null;

        ArrayList tRNSs = filterChunks(chunks, tRNS);
        if (tRNSs.size() > 0)
        {
            PNGChunk pngChunktRNS = (PNGChunk) tRNSs.get(0);
            transparencyFilter = getTransparencyFilter(pngChunkIHDR.colorType,
                    pngChunktRNS);
        }

        ICC_Profile icc_profile = null;
        GammaCorrection gammaCorrection = null;
        {
            ArrayList sRGBs = filterChunks(chunks, sRGB);
            ArrayList gAMAs = filterChunks(chunks, gAMA);
            ArrayList iCCPs = filterChunks(chunks, iCCP);
            if (sRGBs.size() > 1)
                throw new ImageReadException("PNG: unexpected sRGB chunk");
            if (gAMAs.size() > 1)
                throw new ImageReadException("PNG: unexpected gAMA chunk");
            if (iCCPs.size() > 1)
                throw new ImageReadException("PNG: unexpected iCCP chunk");

            if (sRGBs.size() == 1)
            {
                // no color management neccesary.
                if (debug)
                    System.out.println("sRGB, no color management neccesary.");
            } else if (iCCPs.size() == 1)
            {
                if (debug)
                    System.out.println("iCCP.");

                PNGChunkiCCP pngChunkiCCP = (PNGChunkiCCP) iCCPs.get(0);
                byte bytes[] = pngChunkiCCP.UncompressedProfile;

                // TODO(maybe): ICC color profile
                //icc_profile = ICC_Profile.getInstance(bytes);
                icc_profile = null;
            } else if (gAMAs.size() == 1)
            {
                PNGChunkgAMA pngChunkgAMA = (PNGChunkgAMA) gAMAs.get(0);
                double gamma = pngChunkgAMA.getGamma();

                // charles: what is the correct target value here?
                // double targetGamma = 2.2;
                double targetGamma = 1.0;
                double diff = Math.abs(targetGamma - gamma);
                if (diff >= 0.5)
                    gammaCorrection = new GammaCorrection(gamma, targetGamma);

                if (gammaCorrection != null)
                    if (pngChunkPLTE != null)
                        pngChunkPLTE.correct(gammaCorrection);

            }
        }

        {
            int width = pngChunkIHDR.width;
            int height = pngChunkIHDR.height;
            int colorType = pngChunkIHDR.colorType;
            int bitDepth = pngChunkIHDR.bitDepth;

            int bitsPerSample = bitDepth;

            if (pngChunkIHDR.filterMethod != 0)
                throw new ImageReadException("PNG: unknown FilterMethod: "
                        + pngChunkIHDR.filterMethod);

            int samplesPerPixel = samplesPerPixel(pngChunkIHDR.colorType);
            boolean isGrayscale = isGrayscale(pngChunkIHDR.colorType);

            int bitsPerPixel = bitsPerSample * samplesPerPixel;

            boolean hasAlpha = colorType == COLOR_TYPE_GREYSCALE_WITH_ALPHA
                    || colorType == COLOR_TYPE_TRUE_COLOR_WITH_ALPHA;

            BufferedImage result;
            if (isGrayscale)
                result = getBufferedImageFactory(params)
                        .getGrayscaleBufferedImage(width, height, hasAlpha);
            else
                result = getBufferedImageFactory(params).getColorBufferedImage(
                        width, height, hasAlpha);

            ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
            InflaterInputStream iis = new InflaterInputStream(bais);

            ScanExpediter scanExpediter;

            if (pngChunkIHDR.interlaceMethod == 0)
                scanExpediter = new ScanExpediterSimple(width, height, iis,
                        result, colorType, bitDepth, bitsPerPixel,
                        pngChunkPLTE, gammaCorrection, transparencyFilter);
            else if (pngChunkIHDR.interlaceMethod == 1)
                scanExpediter = new ScanExpediterInterlaced(width, height, iis,
                        result, colorType, bitDepth, bitsPerPixel,
                        pngChunkPLTE, gammaCorrection, transparencyFilter);
            else
                throw new ImageReadException("Unknown InterlaceMethod: "
                        + pngChunkIHDR.interlaceMethod);

            scanExpediter.drive();

            if (icc_profile != null)
            {
                Boolean is_srgb = new IccProfileParser().issRGB(icc_profile);
                if (is_srgb == null || !is_srgb.booleanValue())
                {
                    ICC_ColorSpace cs = new ICC_ColorSpace(icc_profile);

                    ColorModel srgbCM = ColorModel.getRGBdefault();
                    ColorSpace cs_sRGB = srgbCM.getColorSpace();

                    result = new ColorTools().convertBetweenColorSpaces(result,
                            cs, cs_sRGB);
                }
            }

            return result;

        }

    }

    public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
            throws ImageReadException, IOException
    {
        ImageInfo imageInfo = getImageInfo(byteSource);
        if (imageInfo == null)
            return false;

        imageInfo.toString(pw, "");

        {
            ArrayList chunks = readChunks(byteSource, null, false);
            {
                ArrayList IHDRs = filterChunks(chunks, IHDR);
                if (IHDRs.size() != 1)
                {
                    if (debug)
                        System.out.println("PNG contains more than one Header");
                    return false;
                }
                PNGChunkIHDR pngChunkIHDR = (PNGChunkIHDR) IHDRs.get(0);
                pw.println("Color: "
                        + getColorTypeDescription(pngChunkIHDR.colorType));
            }

            pw.println("chunks: " + chunks.size());

            if ((chunks.size() < 1))
                return false;

            for (int i = 0; i < chunks.size(); i++)
            {
                PNGChunk chunk = (PNGChunk) chunks.get(i);
                printCharQuad(pw, "\t" + i + ": ", chunk.chunkType);
            }
        }

        pw.println("");

        pw.flush();

        return true;
    }

    public void writeImage(BufferedImage src, OutputStream os, Map params)
            throws ImageWriteException, IOException
    {
        new PngWriter(params).writeImage(src, os, params);
    }

    /**
     * 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
    {

        List chunks = readChunks(byteSource, new int[] { iTXt, }, false);

        if ((chunks == null) || (chunks.size() < 1))
            return null;

        List xmpChunks = new ArrayList();
        for (int i = 0; i < chunks.size(); i++)
        {
            PNGChunkiTXt chunk = (PNGChunkiTXt) chunks.get(i);
            if (!chunk.getKeyword().equals(XMP_KEYWORD))
                continue;
            xmpChunks.add(chunk);
        }

        if (xmpChunks.size() < 1)
            return null;
        if (xmpChunks.size() > 1)
            throw new ImageReadException(
                    "PNG contains more than one XMP chunk.");

        PNGChunkiTXt chunk = (PNGChunkiTXt) xmpChunks.get(0);
        return chunk.getText();
    }

}
TOP

Related Classes of org.apache.sanselan.formats.png.PngImageParser

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.