/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * (C) 2010, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.display2d.service; import java.awt.Color; import java.awt.Dimension; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.List; import java.util.logging.Level; import org.geotoolkit.storage.coverage.CoverageReference; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.geotoolkit.coverage.grid.GridCoverageBuilder; import org.geotoolkit.coverage.io.CoverageReader; import org.geotoolkit.coverage.io.CoverageStoreException; import org.geotoolkit.coverage.io.GridCoverageReadParam; import org.geotoolkit.coverage.io.GridCoverageWriteParam; import org.geotoolkit.coverage.io.GridCoverageWriter; import org.geotoolkit.coverage.io.ImageCoverageWriter; import org.geotoolkit.coverage.processing.Operations; import org.geotoolkit.display.PortrayalException; import org.geotoolkit.display.canvas.control.CanvasMonitor; import org.geotoolkit.display2d.GO2Hints; import org.geotoolkit.display2d.GO2Utilities; import static org.geotoolkit.display2d.GO2Utilities.*; import org.geotoolkit.display2d.canvas.J2DCanvasBuffered; import org.geotoolkit.display2d.canvas.painter.SolidColorPainter; import org.geotoolkit.display2d.container.ContextContainer2D; import static org.geotoolkit.display2d.service.DefaultPortrayalService.*; import org.geotoolkit.factory.Hints; import org.geotoolkit.image.io.XImageIO; import org.geotoolkit.map.CoverageMapLayer; import org.geotoolkit.map.MapBuilder; import org.geotoolkit.map.MapContext; import org.geotoolkit.map.MapLayer; import org.apache.sis.referencing.CommonCRS; import org.geotoolkit.style.MutableFeatureTypeStyle; import org.geotoolkit.style.MutableRule; import org.opengis.coverage.grid.GridCoverage; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import org.opengis.style.Symbolizer; import org.apache.sis.util.logging.Logging; /** * Portrayal data, caches the Java2D canvas for further reuse. * This class is not thread safe. * * @author Johann Sorel (geomatys) */ public final class Portrayer { private static final MapContext EMPTY_CONTEXT = MapBuilder.createContext(); /** * Cache the last CoverageWriter. */ private GridCoverageWriter coverageWriter = null; private final J2DCanvasBuffered canvas = new J2DCanvasBuffered(CommonCRS.WGS84.normalizedGeographic(), new Dimension(1, 1)); private final ContextContainer2D container = new ContextContainer2D(canvas, false); public Portrayer(){ canvas.setContainer(container); container.setContext(EMPTY_CONTEXT); } public BufferedImage portray(final CanvasDef canvasDef, final SceneDef sceneDef, final ViewDef viewDef) throws PortrayalException{ final Envelope contextEnv = viewDef.getEnvelope(); final CoordinateReferenceSystem crs = contextEnv.getCoordinateReferenceSystem(); canvas.setSize(canvasDef.getDimension()); canvas.setRenderingHints(sceneDef.getHints()); final Color bgColor = canvasDef.getBackground(); if(bgColor != null){ canvas.setBackgroundPainter(new SolidColorPainter(bgColor)); } final CanvasMonitor monitor = viewDef.getMonitor(); if(monitor != null){ canvas.setMonitor(monitor); } final MapContext context = sceneDef.getContext(); container.setContext(context); try { canvas.setObjectiveCRS(crs); } catch (TransformException ex) { throw new PortrayalException("Could not set objective crs",ex); } //we specifically say to not repect X/Y proportions if(canvasDef.isStretchImage()) canvas.setAxisProportions(Double.NaN); try { canvas.setVisibleArea(contextEnv); if (viewDef.getAzimuth() != 0) { canvas.rotate( -Math.toRadians(viewDef.getAzimuth()) ); } } catch (NoninvertibleTransformException ex) { throw new PortrayalException(ex); } catch (TransformException ex) { throw new PortrayalException(ex); } //paints all extensions final List<PortrayalExtension> extensions = sceneDef.extensions(); if(extensions != null){ for(final PortrayalExtension extension : extensions){ if(extension != null) extension.completeCanvas(canvas); } } canvas.repaint(); final BufferedImage buffer = canvas.getSnapShot(); container.setContext(EMPTY_CONTEXT); return buffer; } /** * * @param canvasDef * @param sceneDef * @param viewDef * @param outputDef : The compression parameter will not necesarly be used * if the mime type write can not support it. * @throws PortrayalException */ public void portray(final CanvasDef canvasDef, final SceneDef sceneDef, final ViewDef viewDef, final OutputDef outputDef) throws PortrayalException{ final String mime = outputDef.getMime(); if(mime.contains("jpeg") || mime.contains("jpg")){ //special case for jpeg format, the writer generate incorrect colors //if he find out an alpha channel, so we ensure to have a opaque background //which will result in at least an RGB palette final Color bgColor = canvasDef.getBackground(); if(bgColor == null){ //we set the background white canvasDef.setBackground(Color.WHITE); }else{ //we merge colors canvasDef.setBackground(mergeColors(Color.WHITE, bgColor)); } } //directly return false if hints doesnt contain the coverage writer hint enabled final Hints hints = sceneDef.getHints(); final Object val = (hints!=null)?hints.get(GO2Hints.KEY_COVERAGE_WRITER):null; final boolean useCoverageWriter = GO2Hints.COVERAGE_WRITER_ON.equals(val); if(useCoverageWriter && portrayAsCoverage(canvasDef, sceneDef, viewDef, outputDef)){ //we succeeded in writing it with coverage writer directly. return; } //use the rendering engine to generate an image final BufferedImage image = portray(canvasDef,sceneDef,viewDef); if(image == null){ throw new PortrayalException("No image created by the canvas."); } if(useCoverageWriter){ final Envelope env = viewDef.getEnvelope(); final Dimension dim = canvasDef.getDimension(); final double[] resolution = new double[]{ env.getSpan(0) / (double)dim.width, env.getSpan(1) / (double)dim.height}; final GridCoverageBuilder gcb = new GridCoverageBuilder(); gcb.setEnvelope(env); gcb.setRenderedImage(image); final GridCoverage2D coverage = gcb.getGridCoverage2D(); writeCoverage(coverage, env, resolution, outputDef,null); }else{ try { writeImage(image, outputDef); } catch (IOException ex) { throw new PortrayalException(ex); } } } /** * Detect single raster layers with a default raster style and no special parameters * if so we can directly use the grid coverage writer and avoid the rendering chain. * It significantly reduce memory usage (minus the buffered image size and graphic objects) * and time (minus ~35%). * * @return true if the optimization have been applied. * @throws PortrayalException */ private boolean portrayAsCoverage(final CanvasDef canvasDef, final SceneDef sceneDef, final ViewDef viewDef, final OutputDef outputDef) throws PortrayalException { //works for one layer only final List<MapLayer> layers = sceneDef.getContext().layers(); if(layers.size() != 1) return false; //layer must be a coverage final MapLayer layer = layers.get(0); if(!(layer instanceof CoverageMapLayer)) return false; //we must not have extensions if(!sceneDef.extensions().isEmpty()) return false; //style must be a default raster style = native original style final List<MutableFeatureTypeStyle> ftss = layer.getStyle().featureTypeStyles(); if(ftss.size() != 1) return false; final List<MutableRule> rules = ftss.get(0).rules(); if(rules.size() != 1) return false; final List<Symbolizer> symbols = rules.get(0).symbolizers(); if(symbols.size() != 1) return false; final Symbolizer s = symbols.get(0); if(!GO2Utilities.isDefaultRasterSymbolizer(s)) return false; //we can bypass the renderer final CoverageMapLayer cml = (CoverageMapLayer) layer; try{ final CoverageReference ref = cml.getCoverageReference(); final CoverageReader reader = ref.acquireReader(); final String mime = outputDef.getMime(); final Envelope env = viewDef.getEnvelope(); final Dimension dim = canvasDef.getDimension(); final double[] resolution = new double[]{ env.getSpan(0) / (double)dim.width, env.getSpan(1) / (double)dim.height}; final GridCoverageReadParam readParam = new GridCoverageReadParam(); readParam.setEnvelope(viewDef.getEnvelope()); readParam.setResolution(resolution); GridCoverage2D coverage = (GridCoverage2D)reader.read(cml.getCoverageReference().getImageIndex(), readParam); final RenderedImage image = coverage.getRenderedImage(); ref.recycle(reader); // HACK TO FIX COLOR ERROR ON JPEG ///////////////////////////////// if(mime.contains("jpeg") || mime.contains("jpg")){ if(image.getColorModel().hasAlpha()){ final int nbBands = image.getSampleModel().getNumBands(); if(nbBands > 3){ //we can remove the fourth band assuming it is the alpha coverage = (GridCoverage2D) Operations.DEFAULT.selectSampleDimension(coverage, new int[]{0,1,2}); } } } //////////////////////////////////////////////////////////////////// writeCoverage(coverage, env, resolution, outputDef, canvasDef.getBackground()); }catch(CoverageStoreException ex){ throw new PortrayalException(ex); } return true; } /** * Write a coverage using the canvas, view and output definition. * * @param coverage : coverage to write * @param canvasDef : canvas definition * @param viewDef : view definition * @param outputDef : outpout definition * @throws PortrayalException if writing failed */ private void writeCoverage(final GridCoverage coverage, final Envelope env, final double[] resolution, final OutputDef outputDef, final Color backgroundColor) throws PortrayalException{ final String mimeType = outputDef.getMime(); String javaType = MIME_CACHE.get(mimeType); if(javaType == null){ //search the mime type final String[] candidates = XImageIO.getFormatNamesByMimeType(mimeType, false, true); if(candidates.length > 0){ javaType = candidates[0]; //cache the resulting java type MIME_CACHE.put(mimeType, javaType); } } if(javaType == null){ //no related java type, incorrect mime type throw new PortrayalException("No java type found for mime type : " + mimeType); } //get a writer GridCoverageWriter writer = coverageWriter; if(writer == null){ writer = new ImageCoverageWriter(); } try{ final GridCoverageWriteParam writeParam = new GridCoverageWriteParam(); writeParam.setEnvelope(env); writeParam.setResolution(resolution); writeParam.setFormatName(javaType); writeParam.setCompressionQuality(outputDef.getCompression()); if(backgroundColor != null){ final int r = backgroundColor.getRed(); final int g = backgroundColor.getGreen(); final int b = backgroundColor.getBlue(); final int a = backgroundColor.getAlpha(); writeParam.setBackgroundValues(new double[]{r,g,b,a}); } writer.setOutput(outputDef.getOutput()); writer.write(coverage, writeParam); }catch(CoverageStoreException ex){ throw new PortrayalException(ex); }finally{ try { writer.reset(); coverageWriter = writer; } catch (CoverageStoreException ex) { //the writer has problems, we better not put in back in the cache. try { writer.dispose(); } catch (CoverageStoreException ex1) { Logging.getLogger("org.geotoolkit.display2d.service").log(Level.WARNING, null, ex1); } throw new PortrayalException(ex); } } } }