All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.skynav.ttpe.render.png.PNGRenderProcessor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-15 Skynav, Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.skynav.ttpe.render.png;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.skynav.ttpe.area.CanvasArea;
import com.skynav.ttpe.geometry.Point;
import com.skynav.ttpe.geometry.Rectangle;
import com.skynav.ttpe.render.Frame;
import com.skynav.ttpe.render.FrameImage;
import com.skynav.ttpe.render.svg.SVGDocumentFrame;
import com.skynav.ttpe.render.svg.SVGFrameRegion;
import com.skynav.ttpe.render.svg.SVGRenderProcessor;
import com.skynav.ttpe.style.Color;
import com.skynav.ttv.app.InvalidOptionUsageException;
import com.skynav.ttv.app.MissingOptionArgumentException;
import com.skynav.ttv.app.OptionSpecification;
import com.skynav.ttv.util.IOUtil;
import com.skynav.ttv.util.Reporter;
import com.skynav.ttv.verifier.util.Colors;
import com.skynav.ttx.transformer.TransformerContext;
import com.skynav.xml.helpers.Documents;

public class PNGRenderProcessor extends SVGRenderProcessor {

    public static final String NAME                             = "png";

    // static defaults
    private static final GenerationMode defaultMode             = GenerationMode.ISD;
    private static final double defaultDensity                  = 96;
    private static final String defaultOutputFileNamePattern    = "ttpi{0,number,000000}.png";

    // option and usage info
    private static final String[][] longOptionSpecifications = new String[][] {
        { "png-background",             "COLOR",    "paint background with specified color (default: transparent)" },
        { "png-generation-mode",        "MODE",     "specify generation mode, where MODE is isd|region (default: " + defaultMode.name().toLowerCase() + ")" },
        { "png-pixel-density",          "DENSITY",  "specify pixel density in pixels per inch (default: " +  defaultDensity + "ppi)" },
    };
    private static final Map longOptions;
    static {
        longOptions = new java.util.TreeMap();
        for (String[] spec : longOptionSpecifications) {
            longOptions.put(spec[0], new OptionSpecification(spec[0], spec[1], spec[2]));
        }
    }

    enum GenerationMode {
        ISD,
        REGION;
    }

    // options state
    private String backgroundOption;
    private String modeOption;
    private String outputPattern;
    private String pixelDensityOption;

    // derived options state
    private Color background;
    private GenerationMode mode;
    private double pixelDensity;

    public PNGRenderProcessor(TransformerContext context) {
        super(context);
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public String getOutputPattern() {
        return outputPattern;
    }

    @Override
    public Collection getLongOptionSpecs() {
        Collection options = new java.util.TreeSet();
        options.addAll(super.getLongOptionSpecs());
        options.addAll(longOptions.values());
        return options;
    }


    @Override
    public int parseLongOption(List args, int index) {
        String arg = args.get(index);
        int numArgs = args.size();
        String option = arg;
        assert option.length() > 2;
        option = option.substring(2);
        if (option.equals("output-pattern")) {
            if (index + 1 > numArgs)
                throw new MissingOptionArgumentException("--" + option);
            outputPattern = args.get(++index);
        } else if (option.equals("png-background")) {
            if (index + 1 > numArgs)
                throw new MissingOptionArgumentException("--" + option);
            backgroundOption = args.get(++index);
        } else if (option.equals("png-generation-mode")) {
            if (index + 1 > numArgs)
                throw new MissingOptionArgumentException("--" + option);
            modeOption = args.get(++index);
        } else if (option.equals("png-pixel-density")) {
            if (index + 1 > numArgs)
                throw new MissingOptionArgumentException("--" + option);
            pixelDensityOption = args.get(++index);
        } else {
            return super.parseLongOption(args, index);
        }
        return index + 1;
    }

    @Override
    public void processDerivedOptions() {
        super.processDerivedOptions();
        // background
        Color background;
        if (backgroundOption != null) {
            com.skynav.ttv.model.value.Color[] retColor = new com.skynav.ttv.model.value.Color[1];
            if (Colors.isColor(backgroundOption, null, context, retColor)) {
                background = new Color(retColor[0].getRed(), retColor[0].getGreen(), retColor[0].getBlue(), retColor[0].getAlpha());
            } else
                throw new InvalidOptionUsageException("png-background", "invalid color: " + backgroundOption);
        } else
            background = null;
        this.background = background;
        // (isd generation) mode
        GenerationMode mode;
        if (modeOption != null) {
            try {
                mode = GenerationMode.valueOf(modeOption.toUpperCase());
            } catch (IllegalArgumentException e) {
                throw new InvalidOptionUsageException("png-generation-mode", "invalid token: " + modeOption);
            }
        } else
            mode = defaultMode;
        this.mode = mode;
        // output pattern
        String outputPattern = this.outputPattern;
        if (outputPattern == null)
            outputPattern = defaultOutputFileNamePattern;
        this.outputPattern = outputPattern;
        // pixel density
        double pixelDensity;
        if (pixelDensityOption != null) {
            try {
                pixelDensity = Double.valueOf(pixelDensityOption);
            } catch (NumberFormatException e) {
                throw new InvalidOptionUsageException("png-pixel-density", "invalid density: " + pixelDensityOption);
            }
        } else
            pixelDensity = defaultDensity;
        this.pixelDensity = pixelDensity;
    }

    @Override
    protected Frame renderCanvas(CanvasArea a) {
        return renderImage(super.renderCanvas(a));
    }

    private Frame renderImage(Frame frame) {
        if (frame instanceof SVGDocumentFrame) {
            SVGDocumentFrame frameSVG = (SVGDocumentFrame) frame;
            Document d = frameSVG.getDocument();
            Rectangle rectRoot = new Rectangle(Point.ZERO, frameSVG.getExtent());
            List regions = frameSVG.getRegions();
            List images = new java.util.ArrayList();
            if ((mode == GenerationMode.ISD) || regions.isEmpty()) {
                FrameImage fi = renderFrameImage(d, rectRoot, null);
                if (fi != null)
                    images.add(fi);
            } else {
                for (SVGFrameRegion r : regions) {
                    Document dPruned = pruneOtherRegions(d, r.getId());
                    FrameImage fi = renderFrameImage(dPruned, rectRoot, r.getBounds());
                    if (fi != null)
                        images.add(fi);
                }
            }
            return new PNGImageFrame(frame.getBegin(), frame.getEnd(), frame.getExtent(), images);
        }
        return null;
    }

    /*
     * Create copy of SVG document having pruned all regions except for region ID. In the SVG
     * document, a region is identified as an element with @class 'region' and @id 'r[digit]+'.
     * If no regions present (or pruned), then original document is returned.
     */
    private Document pruneOtherRegions(Document d, String idRetain) {
        if (hasPrunableRegion(d, idRetain))
            return pruneRegions((Document) d.cloneNode(true), idRetain);
        else
            return d;
    }

    private boolean hasPrunableRegion(Document d, String idRetain) {
        for (Element e : Documents.findElementsByName(d, SVGDocumentFrame.svgSVGEltName)) {
            String c = Documents.getAttribute(e, SVGDocumentFrame.classAttrName, null);
            if ((c != null) && c.equals("region")) {
                String id = Documents.getAttribute(e, SVGDocumentFrame.idAttrName, null);
                if ((id != null) && !id.equals(idRetain))
                    return true;
            }
        }
        return false;
    }

    private Document pruneRegions(Document d, String idRetain) {
        List prune = new java.util.ArrayList();
        for (Element e : Documents.findElementsByName(d, SVGDocumentFrame.svgSVGEltName)) {
            String c = Documents.getAttribute(e, SVGDocumentFrame.classAttrName, null);
            if ((c != null) && c.equals("region")) {
                String id = Documents.getAttribute(e, SVGDocumentFrame.idAttrName, null);
                if ((id != null) && !id.equals(idRetain))
                    prune.add(e);
            }
        }
        for (Element e : prune) {
            Node p = e.getParentNode();
            p.removeChild(e);
        }
        return d;
    }

    private FrameImage renderFrameImage(Document d, Rectangle rectRoot, Rectangle rectRegion) {
        Reporter reporter = context.getReporter();
        ByteArrayOutputStream bas = null;
        BufferedOutputStream bos = null;
        try {
            PNGTranscoder t = new PNGTranscoder();
            TranscoderInput ti = new TranscoderInput(d);
            bas = new ByteArrayOutputStream();
            bos = new BufferedOutputStream(bas);
            TranscoderOutput to = new TranscoderOutput(bos);
            if (rectRegion == null)
                rectRegion = rectRoot;
            t.addTranscodingHint(PNGTranscoder.KEY_WIDTH, Float.valueOf((float) rectRegion.getWidth()));
            t.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, Float.valueOf((float) rectRegion.getHeight()));
            t.addTranscodingHint(PNGTranscoder.KEY_AOI, rectRegion.getAWTRectangle());
            if (background != null)
                t.addTranscodingHint(PNGTranscoder.KEY_BACKGROUND_COLOR, background.getPaint());
            if (pixelDensity != 0)
                t.addTranscodingHint(PNGTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER, Float.valueOf((float) (25.4 / pixelDensity)));
            t.transcode(ti, to);
            bos.flush();
            bos.close();
            return new PNGFrameImage(rectRegion.getExtent(), rectRegion.getOrigin(), bas.toByteArray());
        } catch (IOException e) {
            reporter.logError(e);
        } catch (TranscoderException e) {
            reporter.logError(e);
        } finally {
            IOUtil.closeSafely(bos);
        }
        return null;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy