Package org.geoserver.wms.map

Source Code of org.geoserver.wms.map.GIFMapResponse

/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.map;

import java.awt.Transparency;                                                  
import java.awt.image.BufferedImage;                                           
import java.awt.image.IndexColorModel;

import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedImageList;

import org.geoserver.platform.ServiceException;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.RasterCleaner;
import org.geoserver.wms.MapProducerCapabilities;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.kvp.PaletteManager;
import org.geotools.image.ImageWorker;
import org.geotools.image.palette.InverseColorMapOp;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.logging.Logging;

import com.sun.media.imageioimpl.plugins.gif.GIFImageWriter;
import com.sun.media.imageioimpl.plugins.gif.GIFImageWriterSpi;

/**
* Handles a GetMap request that spects a map in GIF format.
*
* @author Didier Richard
* @author Simone Giannecchini - GeoSolutions
* @author Alessio Fabiani - GeoSolutions
* @version $Id
*/
public final class GIFMapResponse extends RenderedImageMapResponse {

    private final static Logger LOGGER = Logging.getLogger(GIFMapResponse.class);

    public static final String IMAGE_GIF_SUBTYPE_ANIMATED = "image/gif;subtype=animated";

    private static final GIFImageWriterSpi ORIGINATING_PROVIDER = new GIFImageWriterSpi();

    /** the only MIME type this map producer supports */
    static final String MIME_TYPE = "image/gif";

    static final String[] OUTPUT_FORMATS = {MIME_TYPE, IMAGE_GIF_SUBTYPE_ANIMATED };

    /**
     * Default capabilities for GIF .
     *
     * <p>
     * <ol>
     * <li>tiled = supported</li>
     * <li>multipleValues = unsupported</li>
     * <li>paletteSupported = supported</li>
     * <li>transparency = supported</li>
     * </ol>
     *
     * <p>
     * We should soon support multipage tiff.
     */
    private static MapProducerCapabilities CAPABILITIES = new MapProducerCapabilities(true, false,
            true, true, MIME_TYPE);

   
    /**
     * Default capabilities for GIF animated.
     *
     * <p>
     * <ol>
     * <li>tiled = supported</li>
     * <li>multipleValues = supported</li>
     * <li>paletteSupported = supported</li>
     * <li>transparency = supported</li>
     * </ol>
     *
     * <p>
     * We should soon support multipage tiff.
     */   
    private static MapProducerCapabilities CAPABILITIES_ANIM = new MapProducerCapabilities(true,
            true, true, true, MIME_TYPE);

    public GIFMapResponse(WMS wms) {
        super(OUTPUT_FORMATS, wms);
    }

    /**
     * Transforms the rendered image into the appropriate format, streaming to the output stream.
     *
     * @param image The image to be formatted.
     * @param outStream The stream to write to.
     *
     * @throws ServiceException not really.
     * @throws IOException if encoding to <code>outStream</code> fails.
     */
    @Override
    public void formatImageOutputStream(RenderedImage originalImage, OutputStream outStream,
            WMSMapContent mapContent) throws ServiceException, IOException {

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("Writing gif image ...");
        }

        // get the one required by the MultidimensionalGetMapRequest
        final String format = mapContent.getRequest().getFormat();
        boolean animatedGIF = false;
        if (format.equalsIgnoreCase(IMAGE_GIF_SUBTYPE_ANIMATED)) {
            animatedGIF = true;

        }
        // the original image should always be a list of rendered images unless metatiling is
        // activated
        int numfiles = 1;
        final RenderedImageList ril;
        if (originalImage instanceof RenderedImageList) {
            // convert to list
            ril = (RenderedImageList) originalImage;

            // get number of images
            numfiles = ril.size();
        } else {
            ril = new RenderedImageList(Arrays.asList(originalImage));
        }
        if (numfiles == 1 || !animatedGIF) {
            if (LOGGER.isLoggable(Level.FINE))
                LOGGER.fine("Preparing to write a gif...");
            //
            // Now the magic
            //
            try {
                originalImage = applyPalette(originalImage, mapContent, MIME_TYPE, false);
                ImageWorker iw = new ImageWorker(originalImage);
                iw.writeGIF(outStream, "LZW", 0.75f);
                RasterCleaner.addImage(iw.getRenderedImage());
            } catch (IOException e) {
                throw new ServiceException(e);
            }
            return;
        }

        // check the number is >1
        if (numfiles <= 0) {
            throw new ServiceException("The number of frames for this GIF is less than 1");
        }

        final GIFImageWriter gifWriter = new GIFImageWriter(ORIGINATING_PROVIDER);
        // write param
        final ImageWriteParam param = gifWriter.getDefaultWriteParam();
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionType("LZW");
        param.setCompressionQuality(0.75f);

        ImageOutputStream otStream = null;
        List<RenderedImage> images = new ArrayList<RenderedImage>();
        try {
            otStream = ImageIO.createImageOutputStream(outStream);
            gifWriter.setOutput(otStream);
            gifWriter.prepareWriteSequence(null);

            // gif params
            final GetMapRequest request = (GetMapRequest) mapContent.getRequest();
            final Boolean loopContinuosly = (request.getFormatOptions().get("gif_loop_continuosly") != null ?
                    Boolean.valueOf((String)request.getFormatOptions().get("gif_loop_continuosly")) : wms.getLoopContinuously());
            final Integer delay = (request.getFormatOptions().get("gif_frames_delay") != null ?
                    Integer.valueOf((String) request.getFormatOptions().get("gif_frames_delay")) : wms.getFramesDelay());

            // check value
            if (delay <= 0)
                throw new ServiceException("Animate GIF delay invalid: " + delay);

            //
            // Getting input files
            //
            for (int i = 0; i < numfiles; i++) {
                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.fine("Writing image " + i);
                // get the image
                RenderedImage ri = (RenderedImage) ril.get(i);
                // convert it to gif compatible
                ri = applyPalette(ri, mapContent, MIME_TYPE, false);
                if (ri != null) {
                    // prepare metadata and write param
                    final IIOMetadata imageMetadata = gifWriter.getDefaultImageMetadata(
                      new ImageTypeSpecifier(ri), param);
                    prepareMetadata(ri, imageMetadata, loopContinuosly, delay);

                    // write
                    gifWriter.writeToSequence(new IIOImage(ri, null, imageMetadata), param);
                    images.add(ri);
                }
            }

            // close writing sequence
            gifWriter.endWriteSequence();

        } catch (IOException e) {
            throw new ServiceException(e);
        } finally {
            try {
                otStream.flush();
            } catch (Exception e) {
                // swallow
            }

            try {
                otStream.close();
            } catch (Exception e) {
                // swallow
            }

            try {
                gifWriter.dispose();
            } catch (Exception e) {
                // swallow
            }

            // let go of the image chain as soon as possible to free memory
            for (RenderedImage image : images) {
                if (image instanceof PlanarImage) {
                    ImageUtilities.disposePlanarImageChain((PlanarImage) image);
                } else if (image instanceof BufferedImage) {
                    ((BufferedImage) image).flush();
                }
            }
        }

        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine("Done writing animated gif");
    }

    public String getContentDisposition() {
        // can be null
        return null;
    }

    /**
     * Prepare imagemetadata for writing an animated GIF.
     * <p>
     * This process involves setting the continuos looping mode as well the delay between frames
     * @param ri The {@link RenderedImage} for which we are setting metadata.
     *
     * @param imageMetadata original {@link IIOMetadata} instance to modify.
     * @param loopContinuously <code>yes</code> in case we want to loop continuosly,
     *        <code>false</code> otherwise.
     * @param timeBetweenFramesMS the delay in ms between two frames when looping.
     * @throws IOException in case an error occurs.
     */
    private static void prepareMetadata(RenderedImage ri, IIOMetadata imageMetadata, boolean loopContinuously,
            int timeBetweenFramesMS) throws IOException {

        String metaFormatName = imageMetadata.getNativeMetadataFormatName();

        IIOMetadataNode root = (IIOMetadataNode) imageMetadata.getAsTree(metaFormatName);
        IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
        graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
        graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
        graphicsControlExtensionNode.setAttribute("delayTime", Integer.toString(timeBetweenFramesMS / 10));
       
       
        // transparency support
        final IndexColorModel icm= (IndexColorModel) ri.getColorModel();
        int transparentColorIndex=-1;
       
        if(icm.getTransparency()==Transparency.BITMASK&&(transparentColorIndex=icm.getTransparentPixel())>=0){
            graphicsControlExtensionNode.setAttribute("transparentColorIndex", String.valueOf(transparentColorIndex));
            graphicsControlExtensionNode.setAttribute("transparentColorFlag", "TRUE");             
        } else {
            graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0");
            graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");             
        }

        IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
        commentsNode.setAttribute("CommentExtension", "Created by MAH");

        IIOMetadataNode appEntensionsNode = getNode(root, "ApplicationExtensions");
        IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");

        child.setAttribute("applicationID", "NETSCAPE");
        child.setAttribute("authenticationCode", "2.0");

        int loop = loopContinuously ? 0 : 1;
        final byte[] userObject=new byte[] { 0x1, (byte) (loop & 0xFF), (byte) ((loop >> 8) & 0xFF) };
        child.setUserObject(userObject);
        appEntensionsNode.appendChild(child);

        imageMetadata.setFromTree(metaFormatName, root);

    }

    /**
     * Returns an existing child node, or creates and returns a new child node (if the requested
     * node does not exist).
     *
     * @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node.
     * @param nodeName the name of the child node.
     * @return the child node, if found or a new node created with the given name.
     */
    private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName) {
        int nNodes = rootNode.getLength();
        for (int i = 0; i < nNodes; i++) {
            if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0) {
                return ((IIOMetadataNode) rootNode.item(i));
            }
        }
        IIOMetadataNode node = new IIOMetadataNode(nodeName);
        rootNode.appendChild(node);
        return (node);
    }

    @Override
    public MapProducerCapabilities getCapabilities(String outputFormat) {
        if(IMAGE_GIF_SUBTYPE_ANIMATED.equals(outputFormat)) {
            return CAPABILITIES_ANIM;
        } else {
            return CAPABILITIES;
        }
    }
}
TOP

Related Classes of org.geoserver.wms.map.GIFMapResponse

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.