Package org.apache.sanselan.formats.gif

Source Code of org.apache.sanselan.formats.gif.GifImageParser

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

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.sanselan.FormatCompliance;
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.BinaryOutputStream;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.common.mylzw.MyLZWCompressor;
import org.apache.sanselan.common.mylzw.MyLZWDecompressor;
import org.apache.sanselan.palette.Palette;
import org.apache.sanselan.palette.PaletteFactory;
import org.apache.sanselan.util.Debug;
import org.apache.sanselan.util.ParamMap;

public class GifImageParser extends ImageParser
{

    public GifImageParser()
    {
        super.setByteOrder(BYTE_ORDER_LSB);
    }

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

    public String getDefaultExtension()
    {
        return DEFAULT_EXTENSION;
    }

    private static final String DEFAULT_EXTENSION = ".gif";

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

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

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

    private static final byte GIF_HEADER_SIGNATURE[] = { 71, 73, 70 };

    private GIFHeaderInfo readHeader(InputStream is,
            FormatCompliance formatCompliance) throws ImageReadException,
            IOException
    {
        byte identifier1 = readByte("identifier1", is, "Not a Valid GIF File");
        byte identifier2 = readByte("identifier2", is, "Not a Valid GIF File");
        byte identifier3 = readByte("identifier3", is, "Not a Valid GIF File");

        byte version1 = readByte("version1", is, "Not a Valid GIF File");
        byte version2 = readByte("version2", is, "Not a Valid GIF File");
        byte version3 = readByte("version3", is, "Not a Valid GIF File");

        if (formatCompliance != null)
        {
            formatCompliance.compare_bytes("Signature", GIF_HEADER_SIGNATURE,
                    new byte[] { identifier1, identifier2, identifier3, });
            formatCompliance.compare("version", 56, version1);
            formatCompliance
                    .compare("version", new int[] { 55, 57, }, version2);
            formatCompliance.compare("version", 97, version3);
        }

        if (debug)
            printCharQuad("identifier: ", ((identifier1 << 16)
                    | (identifier2 << 8) | (identifier3 << 0)));
        if (debug)
            printCharQuad("version: ",
                    ((version1 << 16) | (version2 << 8) | (version3 << 0)));

        int logicalScreenWidth = read2Bytes("Logical Screen Width", is,
                "Not a Valid GIF File");
        int logicalScreenHeight = read2Bytes("Logical Screen Height", is,
                "Not a Valid GIF File");

        if (formatCompliance != null)
        {
            formatCompliance.checkBounds("Width", 1, Integer.MAX_VALUE,
                    logicalScreenWidth);
            formatCompliance.checkBounds("Height", 1, Integer.MAX_VALUE,
                    logicalScreenHeight);
        }

        byte packedFields = readByte("Packed Fields", is,
                "Not a Valid GIF File");
        byte backgroundColorIndex = readByte("Background Color Index", is,
                "Not a Valid GIF File");
        byte pixelAspectRatio = readByte("Pixel Aspect Ratio", is,
                "Not a Valid GIF File");

        if (debug)
            printByteBits("PackedFields bits", packedFields);

        boolean globalColorTableFlag = ((packedFields & 128) > 0);
        if (debug)
            System.out.println("GlobalColorTableFlag: " + globalColorTableFlag);
        byte colorResolution = (byte) ((packedFields >> 4) & 7);
        if (debug)
            System.out.println("ColorResolution: " + colorResolution);
        boolean sortFlag = ((packedFields & 8) > 0);
        if (debug)
            System.out.println("SortFlag: " + sortFlag);
        byte sizeofGlobalColorTable = (byte) (packedFields & 7);
        if (debug)
            System.out.println("SizeofGlobalColorTable: "
                    + sizeofGlobalColorTable);

        if (formatCompliance != null)
        {
            if (globalColorTableFlag && backgroundColorIndex != -1)
                formatCompliance.checkBounds("Background Color Index", 0,
                        convertColorTableSize(sizeofGlobalColorTable),
                        backgroundColorIndex);
        }

        return new GIFHeaderInfo(identifier1, identifier2, identifier3,
                version1, version2, version3, logicalScreenWidth,
                logicalScreenHeight, packedFields, backgroundColorIndex,
                pixelAspectRatio, globalColorTableFlag, colorResolution,
                sortFlag, sizeofGlobalColorTable);
    }

    private GraphicControlExtension readGraphicControlExtension(int code,
            InputStream is) throws ImageReadException, IOException
    {
        readByte("block_size", is, "GIF: corrupt GraphicControlExt");
        int packed = readByte("packed fields", is,
                "GIF: corrupt GraphicControlExt");

        int dispose = (packed & 0x1c) >> 2; // disposal method
        boolean transparency = (packed & 1) != 0;

        int delay = read2Bytes("delay in milliseconds", is,
                "GIF: corrupt GraphicControlExt");
        int transparentColorIndex = 0xff & readByte("transparent color index",
                is, "GIF: corrupt GraphicControlExt");
        readByte("block terminator", is, "GIF: corrupt GraphicControlExt");

        return new GraphicControlExtension(code, packed, dispose, transparency,
                delay, transparentColorIndex);
    }

    private byte[] readSubBlock(InputStream is) throws ImageReadException,
            IOException
    {
        int block_size = 0xff & readByte("block_size", is, "GIF: corrupt block");

        byte bytes[] = readByteArray("block", block_size, is,
                "GIF: corrupt block");

        return bytes;
    }

    protected GenericGIFBlock readGenericGIFBlock(InputStream is, int code)
            throws ImageReadException, IOException
    {
        return readGenericGIFBlock(is, code, null);
    }

    protected GenericGIFBlock readGenericGIFBlock(InputStream is, int code,
            byte first[]) throws ImageReadException, IOException
    {
        ArrayList subblocks = new ArrayList();

        if (first != null)
            subblocks.add(first);

        while (true)
        {
            byte bytes[] = readSubBlock(is);
            if (bytes.length < 1)
                break;
            subblocks.add(bytes);
        }

        return new GenericGIFBlock(code, subblocks);
    }

    private final static int EXTENSION_CODE = 0x21;
    private final static int IMAGE_SEPARATOR = 0x2C;
    private final static int GRAPHIC_CONTROL_EXTENSION = (EXTENSION_CODE << 8) | 0xf9;
    private final static int COMMENT_EXTENSION = 0xfe;
    private final static int PLAIN_TEXT_EXTENSION = 0x01;
    private final static int XMP_EXTENSION = 0xff;
    private final static int TERMINATOR_BYTE = 0x3b;
    private final static int APPLICATION_EXTENSION_LABEL = 0xff;
    private final static int XMP_COMPLETE_CODE = (EXTENSION_CODE << 8)
            | XMP_EXTENSION;

    private ArrayList readBlocks(GIFHeaderInfo ghi, InputStream is,
            boolean stopBeforeImageData, FormatCompliance formatCompliance)
            throws ImageReadException, IOException
    {
        ArrayList result = new ArrayList();

        while (true)
        {
            int code = is.read();

            switch (code)
            {
            case -1:
                throw new ImageReadException("GIF: unexpected end of data");

            case IMAGE_SEPARATOR:
                ImageDescriptor id = readImageDescriptor(ghi, code, is,
                        stopBeforeImageData, formatCompliance);
                result.add(id);
                // if(stopBeforeImageData)
                // return result;

                break;

            case EXTENSION_CODE: // extension
            {
                int extensionCode = is.read();
                int completeCode = ((0xff & code) << 8)
                        | (0xff & extensionCode);

                switch (extensionCode)
                {
                case 0xf9:
                    GraphicControlExtension gce = readGraphicControlExtension(
                            completeCode, is);
                    result.add(gce);
                    break;

                case COMMENT_EXTENSION:
                case PLAIN_TEXT_EXTENSION: {
                    GenericGIFBlock block = readGenericGIFBlock(is,
                            completeCode);
                    result.add(block);
                    break;
                }

                case APPLICATION_EXTENSION_LABEL: // 255 (hex 0xFF) Application
                    // Extension Label
                {
                    byte label[] = readSubBlock(is);

                    if (formatCompliance != null)
                        formatCompliance
                                .addComment("Unknown Application Extension ("
                                        + new String(label) + ")", completeCode);

                    // if (label == new String("ICCRGBG1"))
                    {
                        // GIF's can have embedded ICC Profiles - who knew?
                    }

                    if ((label != null) && (label.length > 0))
                    {
                        GenericGIFBlock block = readGenericGIFBlock(is,
                                completeCode, label);
                        result.add(block);
                    }
                    break;
                }

                default: {

                    if (formatCompliance != null)
                        formatCompliance.addComment("Unknown block",
                                completeCode);

                    GenericGIFBlock block = readGenericGIFBlock(is,
                            completeCode);
                    result.add(block);
                    break;
                }
                }
            }
                break;

            case TERMINATOR_BYTE:
                return result;

            case 0x00: // bad byte, but keep going and see what happens
                break;

            default:
                throw new ImageReadException("GIF: unknown code: " + code);
            }
        }
    }

    private ImageDescriptor readImageDescriptor(GIFHeaderInfo ghi,
            int blockCode, InputStream is, boolean stopBeforeImageData,
            FormatCompliance formatCompliance) throws ImageReadException,
            IOException
    {
        int ImageLeftPosition = read2Bytes("Image Left Position", is,
                "Not a Valid GIF File");
        int ImageTopPosition = read2Bytes("Image Top Position", is,
                "Not a Valid GIF File");
        int imageWidth = read2Bytes("Image Width", is, "Not a Valid GIF File");
        int imageHeight = read2Bytes("Image Height", is, "Not a Valid GIF File");
        byte PackedFields = readByte("Packed Fields", is,
                "Not a Valid GIF File");

        if (formatCompliance != null)
        {
            formatCompliance.checkBounds("Width", 1, ghi.logicalScreenWidth,
                    imageWidth);
            formatCompliance.checkBounds("Height", 1, ghi.logicalScreenHeight,
                    imageHeight);
            formatCompliance.checkBounds("Left Position", 0,
                    ghi.logicalScreenWidth - imageWidth, ImageLeftPosition);
            formatCompliance.checkBounds("Top Position", 0,
                    ghi.logicalScreenHeight - imageHeight, ImageTopPosition);
        }

        if (debug)
            printByteBits("PackedFields bits", PackedFields);

        boolean LocalColorTableFlag = (((PackedFields >> 7) & 1) > 0);
        if (debug)
            System.out.println("LocalColorTableFlag: " + LocalColorTableFlag);
        boolean InterlaceFlag = (((PackedFields >> 6) & 1) > 0);
        if (debug)
            System.out.println("Interlace Flag: " + InterlaceFlag);
        boolean SortFlag = (((PackedFields >> 5) & 1) > 0);
        if (debug)
            System.out.println("Sort  Flag: " + SortFlag);

        byte SizeofLocalColorTable = (byte) (PackedFields & 7);
        if (debug)
            System.out.println("SizeofLocalColorTable: "
                    + SizeofLocalColorTable);

        byte LocalColorTable[] = null;
        if (LocalColorTableFlag)
            LocalColorTable = readColorTable(is, SizeofLocalColorTable,
                    formatCompliance);

        byte imageData[] = null;
        if (!stopBeforeImageData)
        {
            int LZWMinimumCodeSize = is.read();

            GenericGIFBlock block = readGenericGIFBlock(is, -1);
            byte bytes[] = block.appendSubBlocks();
            InputStream bais = new ByteArrayInputStream(bytes);

            int size = imageWidth * imageHeight;
            MyLZWDecompressor myLzwDecompressor = new MyLZWDecompressor(
                    LZWMinimumCodeSize, BYTE_ORDER_LSB);
            imageData = myLzwDecompressor.decompress(bais, size);
        } else
        {
            int LZWMinimumCodeSize = is.read();
            if (debug)
                System.out.println("LZWMinimumCodeSize: " + LZWMinimumCodeSize);

            readGenericGIFBlock(is, -1);
        }

        ImageDescriptor result = new ImageDescriptor(blockCode,
                ImageLeftPosition, ImageTopPosition, imageWidth, imageHeight,
                PackedFields, LocalColorTableFlag, InterlaceFlag, SortFlag,
                SizeofLocalColorTable, LocalColorTable, imageData);

        return result;
    }

    private int simple_pow(int base, int power)
    {
        int result = 1;

        for (int i = 0; i < power; i++)
            result *= base;

        return result;
    }

    private int convertColorTableSize(int ct_size)
    {
        return 3 * simple_pow(2, ct_size + 1);
    }

    private byte[] readColorTable(InputStream is, int ct_size,
            FormatCompliance formatCompliance) throws IOException
    {
        int actual_size = convertColorTableSize(ct_size);

        byte bytes[] = readByteArray("block", actual_size, is,
                "GIF: corrupt Color Table");

        return bytes;
    }

    // TODO - unused
    private GIFHeaderInfo readHeader(ByteSource byteSource)
            throws ImageReadException, IOException
    {
        InputStream is = null;
        try
        {
            is = byteSource.getInputStream();

            return readHeader(is, FormatCompliance.getDefault());
        } finally
        {
            try
            {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e)
            {
                Debug.debug(e);
            }

        }
    }

    private GIFBlock findBlock(ArrayList v, int code)
    {
        for (int i = 0; i < v.size(); i++)
        {
            GIFBlock gifBlock = (GIFBlock) v.get(i);
            if (gifBlock.blockCode == code)
                return gifBlock;
        }
        return null;
    }

    private ImageContents readFile(ByteSource byteSource,
            boolean stopBeforeImageData) throws ImageReadException, IOException
    {
        return readFile(byteSource, stopBeforeImageData, FormatCompliance
                .getDefault());
    }

    private ImageContents readFile(ByteSource byteSource,
            boolean stopBeforeImageData, FormatCompliance formatCompliance)
            throws ImageReadException, IOException
    {
        InputStream is = null;
        try
        {
            is = byteSource.getInputStream();

            GIFHeaderInfo ghi = readHeader(is, formatCompliance);

            byte globalColorTable[] = null;
            if (ghi.globalColorTableFlag)
                globalColorTable = readColorTable(is,
                        ghi.sizeOfGlobalColorTable, formatCompliance);

            ArrayList blocks = readBlocks(ghi, is, stopBeforeImageData,
                    formatCompliance);

            ImageContents result = new ImageContents(ghi, globalColorTable,
                    blocks);

            return result;
        } finally
        {
            try
            {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e)
            {
                Debug.debug(e);
            }

        }
    }

    public byte[] getICCProfileBytes(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        return null;
    }

    public Dimension getImageSize(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ImageContents blocks = readFile(byteSource, false);

        if (blocks == null)
            throw new ImageReadException("GIF: Couldn't read blocks");

        GIFHeaderInfo bhi = blocks.gifHeaderInfo;
        if (bhi == null)
            throw new ImageReadException("GIF: Couldn't read Header");

        ImageDescriptor id = (ImageDescriptor) findBlock(blocks.blocks,
                IMAGE_SEPARATOR);
        if (id == null)
            throw new ImageReadException("GIF: Couldn't read ImageDescriptor");

        // Prefer the size information in the ImageDescriptor; it is more reliable
        // than the size information in the header.
        return new Dimension(id.imageWidth, id.imageHeight);
    }

    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
    {
        return null;
    }

    private ArrayList getComments(ArrayList v) throws IOException
    {
        ArrayList result = new ArrayList();
        int code = 0x21fe;

        for (int i = 0; i < v.size(); i++)
        {
            GIFBlock block = (GIFBlock) v.get(i);
            if (block.blockCode == code)
            {
                byte bytes[] = ((GenericGIFBlock) block).appendSubBlocks();
                result.add(new String(bytes));
            }
        }

        return result;
    }

    public ImageInfo getImageInfo(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ImageContents blocks = readFile(byteSource, false);

        if (blocks == null)
            throw new ImageReadException("GIF: Couldn't read blocks");

        GIFHeaderInfo bhi = blocks.gifHeaderInfo;
        if (bhi == null)
            throw new ImageReadException("GIF: Couldn't read Header");

        ImageDescriptor id = (ImageDescriptor) findBlock(blocks.blocks,
                IMAGE_SEPARATOR);
        if (id == null)
            throw new ImageReadException("GIF: Couldn't read ImageDescriptor");

        GraphicControlExtension gce = (GraphicControlExtension) findBlock(
                blocks.blocks, GRAPHIC_CONTROL_EXTENSION);

        // Prefer the size information in the ImageDescriptor; it is more reliable
        // than the size information in the header.
        int height = id.imageWidth;
        int width = id.imageHeight;

        ArrayList Comments;

        Comments = getComments(blocks.blocks);

        int BitsPerPixel = (bhi.colorResolution + 1) * 3;
        ImageFormat Format = ImageFormat.IMAGE_FORMAT_GIF;
        String FormatName = "GIF Graphics Interchange Format";
        String MimeType = "image/gif";
        // we ought to count images, but don't yet.
        int NumberOfImages = -1;

        boolean isProgressive = id.interlaceFlag;

        int PhysicalWidthDpi = 72;
        float PhysicalWidthInch = (float) ((double) width / (double) PhysicalWidthDpi);
        int PhysicalHeightDpi = 72;
        float PhysicalHeightInch = (float) ((double) height / (double) PhysicalHeightDpi);

        String FormatDetails = "Gif " + ((char) blocks.gifHeaderInfo.version1)
                + ((char) blocks.gifHeaderInfo.version2)
                + ((char) blocks.gifHeaderInfo.version3);

        boolean isTransparent = false;
        if (gce != null && gce.transparency)
            isTransparent = true;

        boolean usesPalette = true;
        int colorType = ImageInfo.COLOR_TYPE_RGB;
        String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_LZW;

        ImageInfo result = new ImageInfo(FormatDetails, BitsPerPixel, Comments,
                Format, FormatName, height, MimeType, NumberOfImages,
                PhysicalHeightDpi, PhysicalHeightInch, PhysicalWidthDpi,
                PhysicalWidthInch, width, isProgressive, isTransparent,
                usesPalette, colorType, compressionAlgorithm);

        return result;
    }

    public boolean dumpImageFile(PrintWriter pw, ByteSource byteSource)
            throws ImageReadException, IOException
    {
        pw.println("gif.dumpImageFile");

        {
            ImageInfo imageData = getImageInfo(byteSource);
            if (imageData == null)
                return false;

            imageData.toString(pw, "");
        }
        {
            ImageContents blocks = readFile(byteSource, false);

            if (blocks == null)
                return false;

            pw.println("gif.blocks: " + blocks.blocks.size());
            for (int i = 0; i < blocks.blocks.size(); i++)
            {
                GIFBlock gifBlock = (GIFBlock) blocks.blocks.get(i);
                this.debugNumber(pw, "\t" + i + " ("
                        + gifBlock.getClass().getName() + ")",
                        gifBlock.blockCode, 4);
            }

        }

        pw.println("");

        return true;
    }

    private int[] getColorTable(byte bytes[]) throws ImageReadException
    {
        if ((bytes.length % 3) != 0)
            throw new ImageReadException("Bad Color Table Length: "
                    + bytes.length);
        int length = bytes.length / 3;

        int result[] = new int[length];

        for (int i = 0; i < length; i++)
        {
            int red = 0xff & bytes[(i * 3) + 0];
            int green = 0xff & bytes[(i * 3) + 1];
            int blue = 0xff & bytes[(i * 3) + 2];

            int alpha = 0xff;

            int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
            result[i] = rgb;
        }

        return result;
    }

    public FormatCompliance getFormatCompliance(ByteSource byteSource)
            throws ImageReadException, IOException
    {
        FormatCompliance result = new FormatCompliance(byteSource
                .getDescription());

        readFile(byteSource, false, result);

        return result;
    }

    public BufferedImage getBufferedImage(ByteSource byteSource, Map params)
            throws ImageReadException, IOException
    {
        ImageContents imageContents = readFile(byteSource, false);

        if (imageContents == null)
            throw new ImageReadException("GIF: Couldn't read blocks");

        GIFHeaderInfo ghi = imageContents.gifHeaderInfo;
        if (ghi == null)
            throw new ImageReadException("GIF: Couldn't read Header");

        ImageDescriptor id = (ImageDescriptor) findBlock(imageContents.blocks,
                IMAGE_SEPARATOR);
        if (id == null)
            throw new ImageReadException("GIF: Couldn't read Image Descriptor");
        GraphicControlExtension gce = (GraphicControlExtension) findBlock(
                imageContents.blocks, GRAPHIC_CONTROL_EXTENSION);

        // Prefer the size information in the ImageDescriptor; it is more reliable
        // than the size information in the header.
        int width = id.imageWidth;
        int height = id.imageHeight;

        boolean hasAlpha = false;
        if (gce != null && gce.transparency)
            hasAlpha = true;

        BufferedImage result = getBufferedImageFactory(params)
                .getColorBufferedImage(width, height, hasAlpha);

        {
            int colorTable[];
            if (id.localColorTable != null)
                colorTable = getColorTable(id.localColorTable);
            else if (imageContents.globalColorTable != null)
                colorTable = getColorTable(imageContents.globalColorTable);
            else
                throw new ImageReadException("Gif: No Color Table");

            int transparentIndex = -1;
            if (hasAlpha)
                transparentIndex = gce.transparentColorIndex;

            int counter = 0;

            int rowsInPass1 = (height + 7) / 8;
            int rowsInPass2 = (height + 3) / 8;
            int rowsInPass3 = (height + 1) / 4;
            int rowsInPass4 = (height) / 2;

            DataBuffer db = result.getRaster().getDataBuffer();

            for (int row = 0; row < height; row++)
            {
                int y;
                if (id.interlaceFlag)
                {
                    int the_row = row;
                    if (the_row < rowsInPass1)
                        y = the_row * 8;
                    else
                    {
                        the_row -= rowsInPass1;
                        if (the_row < (rowsInPass2))
                            y = 4 + (the_row * 8);
                        else
                        {
                            the_row -= rowsInPass2;
                            if (the_row < (rowsInPass3))
                                y = 2 + (the_row * 4);
                            else
                            {
                                the_row -= rowsInPass3;
                                if (the_row < (rowsInPass4))
                                    y = 1 + (the_row * 2);
                                else
                                    throw new ImageReadException(
                                            "Gif: Strange Row");
                            }
                        }
                    }
                } else
                    y = row;

                for (int x = 0; x < width; x++)
                {
                    int index = 0xff & id.imageData[counter++];
                    int rgb = colorTable[index];

                    if (transparentIndex == index)
                        rgb = 0x00;

                    db.setElem(y * width + x, rgb);
                }

            }
        }

        return result;

    }

    private void writeAsSubBlocks(OutputStream os, byte bytes[])
            throws IOException
    {
        int index = 0;

        while (index < bytes.length)
        {
            int block_size = Math.min(bytes.length - index, 255);
            os.write(block_size);
            os.write(bytes, index, block_size);
            index += block_size;
        }
        os.write(0); // last block
    }

    private static final int LOCAL_COLOR_TABLE_FLAG_MASK = 1 << 7;
    private static final int INTERLACE_FLAG_MASK = 1 << 6;
    private static final int SORT_FLAG_MASK = 1 << 5;

    public void writeImage(BufferedImage src, OutputStream os, Map params)
            throws ImageWriteException, IOException
    {
        // make copy of params; we'll clear keys as we consume them.
        params = new HashMap(params);

        boolean verbose = ParamMap.getParamBoolean(params, PARAM_KEY_VERBOSE,
                false);

        // clear format key.
        if (params.containsKey(PARAM_KEY_FORMAT))
            params.remove(PARAM_KEY_FORMAT);
        if (params.containsKey(PARAM_KEY_VERBOSE))
            params.remove(PARAM_KEY_VERBOSE);

        String xmpXml = null;
        if (params.containsKey(PARAM_KEY_XMP_XML))
        {
            xmpXml = (String) params.get(PARAM_KEY_XMP_XML);
            params.remove(PARAM_KEY_XMP_XML);
        }

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

        int width = src.getWidth();
        int height = src.getHeight();

        boolean hasAlpha = new PaletteFactory().hasTransparency(src);

        int max_colors = hasAlpha ? 255 : 256;

        Palette palette2 = new PaletteFactory().makePaletteSimple(src,
                max_colors);
        // int palette[] = new PaletteFactory().makePaletteSimple(src, 256);
        // Map palette_map = paletteToMap(palette);

        if (palette2 == null)
        {
            palette2 = new PaletteFactory().makePaletteQuantized(src,
                    max_colors);
            if (verbose)
                System.out.println("quantizing");
        } else if (verbose)
            System.out.println("exact palette");

        if (palette2 == null)
            throw new ImageWriteException(
                    "Gif: can't write images with more than 256 colors");
        int palette_size = palette2.length() + (hasAlpha ? 1 : 0);

        BinaryOutputStream bos = new BinaryOutputStream(os, BYTE_ORDER_LSB);

        {
            // write Header
            os.write(0x47); // G magic numbers
            os.write(0x49); // I
            os.write(0x46); // F

            os.write(0x38); // 8 version magic numbers
            os.write(0x39); // 9
            os.write(0x61); // a

            // Logical Screen Descriptor.

            bos.write2Bytes(width);
            bos.write2Bytes(height);

            int colorTableScaleLessOne = (palette_size > 128) ? 7
                    : (palette_size > 64) ? 6 : (palette_size > 32) ? 5
                            : (palette_size > 16) ? 4 : (palette_size > 8) ? 3
                                    : (palette_size > 4) ? 2
                                            : (palette_size > 2) ? 1 : 0;

            int colorTableSizeInFormat = 1 << (colorTableScaleLessOne + 1);
            int actual_size = 3 * simple_pow(2, colorTableScaleLessOne + 1);
            {
                byte colorResolution = (byte) colorTableScaleLessOne; // TODO:

                boolean globalColorTableFlag = false;
                boolean sortFlag = false;
                int globalColorTableFlagMask = 1 << 7;
                int sortFlagMask = 8;
                int sizeOfGlobalColorTable = 0;

                int packedFields = ((globalColorTableFlag ? globalColorTableFlagMask
                        : 0)
                        | (sortFlag ? sortFlagMask : 0)
                        | ((7 & colorResolution) << 4) | (7 & sizeOfGlobalColorTable));
                bos.write(packedFields); // one byte
            }
            {
                byte BackgroundColorIndex = 0;
                bos.write(BackgroundColorIndex);
            }
            {
                byte PixelAspectRatio = 0;
                bos.write(PixelAspectRatio);
            }

            { // write Global Color Table.

            }

            { // ALWAYS write GraphicControlExtension
                bos.write(EXTENSION_CODE);
                bos.write((byte) 0xf9);
                // bos.write(0xff & (kGraphicControlExtension >> 8));
                // bos.write(0xff & (kGraphicControlExtension >> 0));

                bos.write((byte) 4); // block size;
                int packedFields = hasAlpha ? 1 : 0; // transparency flag
                bos.write((byte) packedFields);
                bos.write((byte) 0); // Delay Time
                bos.write((byte) 0); // Delay Time
                bos.write((byte) (hasAlpha ? palette2.length() : 0)); // Transparent
                // Color
                // Index
                bos.write((byte) 0); // terminator
            }

            if (null != xmpXml)
            {
                bos.write(EXTENSION_CODE);
                bos.write(APPLICATION_EXTENSION_LABEL);

                bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE.length); // 0x0B
                bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE);

                byte xmpXmlBytes[] = xmpXml.getBytes("utf-8");
                bos.write(xmpXmlBytes);

                // write "magic trailer"
                for (int magic = 0; magic <= 0xff; magic++)
                    bos.write(0xff - magic);

                bos.write((byte) 0); // terminator

            }

            { // Image Descriptor.
                bos.write(IMAGE_SEPARATOR);
                bos.write2Bytes(0); // Image Left Position
                bos.write2Bytes(0); // Image Top Position
                bos.write2Bytes(width); // Image Width
                bos.write2Bytes(height); // Image Height

                {
                    boolean LocalColorTableFlag = true;
                    // boolean LocalColorTableFlag = false;
                    boolean InterlaceFlag = false;
                    boolean SortFlag = false;
                    int SizeOfLocalColorTable = colorTableScaleLessOne;

                    // int SizeOfLocalColorTable = 0;

                    int PackedFields = ((LocalColorTableFlag ? LOCAL_COLOR_TABLE_FLAG_MASK
                            : 0)
                            | (InterlaceFlag ? INTERLACE_FLAG_MASK : 0)
                            | (SortFlag ? SORT_FLAG_MASK : 0) | (7 & SizeOfLocalColorTable));
                    bos.write(PackedFields); // one byte
                }
            }

            { // write Local Color Table.
                for (int i = 0; i < colorTableSizeInFormat; i++)
                {
                    if (i < palette2.length())
                    {
                        int rgb = palette2.getEntry(i);

                        int red = 0xff & (rgb >> 16);
                        int green = 0xff & (rgb >> 8);
                        int blue = 0xff & (rgb >> 0);

                        bos.write(red);
                        bos.write(green);
                        bos.write(blue);
                    } else
                    {
                        bos.write(0);
                        bos.write(0);
                        bos.write(0);
                    }
                }
            }

            { // get Image Data.
                int image_data_total = 0;

                int LZWMinimumCodeSize = colorTableScaleLessOne + 1;
//                LZWMinimumCodeSize = Math.max(8, LZWMinimumCodeSize);
                if (LZWMinimumCodeSize < 2)
                    LZWMinimumCodeSize = 2;

                // TODO:
                // make
                // better
                // choice
                // here.
                bos.write(LZWMinimumCodeSize);

                MyLZWCompressor compressor = new MyLZWCompressor(
                        LZWMinimumCodeSize, BYTE_ORDER_LSB, false); // GIF
                // Mode);

                byte imagedata[] = new byte[width * height];
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        int argb = src.getRGB(x, y);
                        int rgb = 0xffffff & argb;
                        int index;

                        if (hasAlpha)
                        {
                            int alpha = 0xff & (argb >> 24);
                            final int alphaThreshold = 255;
                            if (alpha < alphaThreshold)
                                index = palette2.length(); // is transparent
                            else
                                index = palette2.getPaletteIndex(rgb);
                        } else
                        {
                            index = palette2.getPaletteIndex(rgb);
                        }

                        imagedata[y * width + x] = (byte) index;
                    }
                }

                byte compressed[] = compressor.compress(imagedata);
                writeAsSubBlocks(bos, compressed);
                image_data_total += compressed.length;
            }

            // palette2.dump();

            bos.write(TERMINATOR_BYTE);
        }

        bos.close();
        os.close();
    }

    private static final byte XMP_APPLICATION_ID_AND_AUTH_CODE[] = { 0x58, // X
            0x4D, // M
            0x50, // P
            0x20, //
            0x44, // D
            0x61, // a
            0x74, // t
            0x61, // a
            0x58, // X
            0x4D, // M
            0x50, // P
    };

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

        InputStream is = null;
        try
        {
            is = byteSource.getInputStream();

            FormatCompliance formatCompliance = null;
            GIFHeaderInfo ghi = readHeader(is, formatCompliance);

            if (ghi.globalColorTableFlag)
                readColorTable(is, ghi.sizeOfGlobalColorTable, formatCompliance);

            ArrayList blocks = readBlocks(ghi, is, true, formatCompliance);

            List result = new ArrayList();
            for (int i = 0; i < blocks.size(); i++)
            {
                GIFBlock block = (GIFBlock) blocks.get(i);
                if (block.blockCode != XMP_COMPLETE_CODE)
                    continue;

                GenericGIFBlock genericBlock = (GenericGIFBlock) block;

                byte blockBytes[] = genericBlock.appendSubBlocks(true);
                if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length)
                    continue;

                if (!compareByteArrays(blockBytes, 0,
                        XMP_APPLICATION_ID_AND_AUTH_CODE, 0,
                        XMP_APPLICATION_ID_AND_AUTH_CODE.length))
                    continue;

                byte GIF_MAGIC_TRAILER[] = new byte[256];
                for (int magic = 0; magic <= 0xff; magic++)
                    GIF_MAGIC_TRAILER[magic] = (byte) (0xff - magic);

                if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length
                        + GIF_MAGIC_TRAILER.length)
                    continue;
                if (!compareByteArrays(blockBytes, blockBytes.length
                        - GIF_MAGIC_TRAILER.length, GIF_MAGIC_TRAILER, 0,
                        GIF_MAGIC_TRAILER.length))
                    throw new ImageReadException(
                            "XMP block in GIF missing magic trailer.");

                try
                {
                    // XMP is UTF-8 encoded xml.
                    String xml = new String(
                            blockBytes,
                            XMP_APPLICATION_ID_AND_AUTH_CODE.length,
                            blockBytes.length
                                    - (XMP_APPLICATION_ID_AND_AUTH_CODE.length + GIF_MAGIC_TRAILER.length),
                            "utf-8");
                    result.add(xml);
                } catch (UnsupportedEncodingException e)
                {
                    throw new ImageReadException("Invalid XMP Block in GIF.");
                }
            }

            if (result.size() < 1)
                return null;
            if (result.size() > 1)
                throw new ImageReadException("More than one XMP Block in GIF.");
            return (String) result.get(0);

        } finally
        {
            try
            {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e)
            {
                Debug.debug(e);
            }

        }
    }
}
TOP

Related Classes of org.apache.sanselan.formats.gif.GifImageParser

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.