/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2015, 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; * version 2.1 of the License. * * 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.ext.dynamicrange; import java.awt.AlphaComposite; import java.awt.geom.AffineTransform; import java.awt.image.RenderedImage; import java.awt.image.WritableRenderedImage; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import org.apache.sis.referencing.operation.transform.LinearTransform; import org.geotoolkit.storage.coverage.CoverageReference; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.geotoolkit.coverage.grid.ViewType; import org.geotoolkit.display.PortrayalException; import org.geotoolkit.display2d.GO2Utilities; import org.geotoolkit.display2d.canvas.RenderingContext2D; import org.geotoolkit.display2d.primitive.ProjectedCoverage; import org.geotoolkit.display2d.style.renderer.AbstractCoverageSymbolizerRenderer; import org.geotoolkit.display2d.style.renderer.SymbolizerRendererService; import org.geotoolkit.math.Histogram; import org.geotoolkit.metadata.DefaultSampleDimensionExt; import org.geotoolkit.processing.image.dynamicrange.DynamicRangeStretchProcess; import org.opengis.filter.expression.Expression; import org.opengis.metadata.content.AttributeGroup; import org.opengis.metadata.content.CoverageDescription; import org.opengis.metadata.content.RangeDimension; import org.opengis.metadata.content.SampleDimension; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.referencing.operation.MathTransform2D; /** * * @author Johann Sorel (Geomatys) */ public class DynamicRangeSymbolizerRenderer extends AbstractCoverageSymbolizerRenderer<CachedDynamicRangeSymbolizer>{ public DynamicRangeSymbolizerRenderer(SymbolizerRendererService service, CachedDynamicRangeSymbolizer symbol, RenderingContext2D context) { super(service, symbol, context); } @Override public void portray(ProjectedCoverage projectedCoverage) throws PortrayalException { try{ GridCoverage2D dataCoverage = getObjectiveCoverage(projectedCoverage); if(dataCoverage == null){ return; } final CoverageReference covref = projectedCoverage.getCandidate().getCoverageReference(); final CoverageDescription covdesc = covref.getMetadata(); if (dataCoverage.getViewTypes().contains(ViewType.GEOPHYSICS)) dataCoverage = dataCoverage.view(ViewType.GEOPHYSICS); final RenderedImage ri = dataCoverage.getRenderedImage(); final DynamicRangeSymbolizer symbolizer = symbol.getSource(); final int[] bands = new int[]{-1,-1,-1,-1}; final double[][] ranges = new double[][]{{-1,-1},{-1,-1},{-1,-1},{-1,-1}}; final Map<String,Object> stats = new HashMap<>(); for(DynamicRangeSymbolizer.DRChannel channel : symbolizer.getChannels()){ final Integer bandIdx; try{ bandIdx = Integer.valueOf(channel.getBand()); }catch(NumberFormatException ex){ //not a number index continue; } final String cs = channel.getColorSpaceComponent().trim(); final int idx; if(DynamicRangeSymbolizer.DRChannel.BAND_RED.equalsIgnoreCase(cs)) idx=0; else if(DynamicRangeSymbolizer.DRChannel.BAND_GREEN.equalsIgnoreCase(cs)) idx=1; else if(DynamicRangeSymbolizer.DRChannel.BAND_BLUE.equalsIgnoreCase(cs)) idx=2; else if(DynamicRangeSymbolizer.DRChannel.BAND_ALPHA.equalsIgnoreCase(cs)) idx=3; else { //no mapping continue; } bands[idx] = bandIdx; //search for band statistics stats.clear(); search: for(AttributeGroup attg : covdesc.getAttributeGroups()){ for(RangeDimension rd : attg.getAttributes()){ if(!(rd instanceof SampleDimension)) continue; final int i = Integer.parseInt(rd.getSequenceIdentifier().tip().toString()); if(i==bandIdx){ final SampleDimension sd = (SampleDimension) rd; stats.put(DynamicRangeSymbolizer.PROPERTY_MIN, sd.getMinValue()); stats.put(DynamicRangeSymbolizer.PROPERTY_MAX, sd.getMaxValue()); stats.put(DynamicRangeSymbolizer.PROPERTY_MEAN, sd.getMeanValue()); stats.put(DynamicRangeSymbolizer.PROPERTY_STD, sd.getStandardDeviation()); if(sd instanceof DefaultSampleDimensionExt){ final DefaultSampleDimensionExt dsd = (DefaultSampleDimensionExt) sd; stats.put(DynamicRangeSymbolizer.PROPERTY_HISTO, dsd.getHistogram()); stats.put(DynamicRangeSymbolizer.PROPERTY_HISTO_MIN, dsd.getHistogramMin()); stats.put(DynamicRangeSymbolizer.PROPERTY_HISTO_MAX, dsd.getHistogramMax()); } break search; } } } ranges[idx][0] = evaluate(channel.getLower(), stats); ranges[idx][1] = evaluate(channel.getUpper(), stats); } final DynamicRangeStretchProcess p = new DynamicRangeStretchProcess(ri, bands, ranges); RenderedImage img = p.executeNow(); if (img instanceof WritableRenderedImage) GO2Utilities.removeBlackBorder((WritableRenderedImage)img); final MathTransform2D trs2D = dataCoverage.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT); renderCoverage(img, trs2D); } catch (Exception e) { monitor.exceptionOccured(e, Level.WARNING); } } /** * {@inheritDoc } * <br> * Note : do nothing only return coverageSource. * In attempt to particulary comportement if exist. */ @Override protected GridCoverage2D prepareCoverageToResampling(GridCoverage2D coverageSource, CachedDynamicRangeSymbolizer symbolizer) { return coverageSource; } private static double evaluate(DynamicRangeSymbolizer.DRBound bound, Map<String,Object> stats) throws PortrayalException{ final String mode = bound.getMode(); if(DynamicRangeSymbolizer.DRBound.MODE_EXPRESSION.equalsIgnoreCase(mode)){ final Expression exp = bound.getValue(); final Number val = exp.evaluate(stats, Number.class); return (val==null) ? Double.NaN : val.doubleValue(); }else if(DynamicRangeSymbolizer.DRBound.MODE_PERCENT.equalsIgnoreCase(mode)){ final long[] histo = (long[]) stats.get(DynamicRangeSymbolizer.PROPERTY_HISTO); final Double histoMin = (Double) stats.get(DynamicRangeSymbolizer.PROPERTY_HISTO_MIN); final Double histoMax = (Double) stats.get(DynamicRangeSymbolizer.PROPERTY_HISTO_MAX); if(histo==null || histoMin==null || histoMax==null){ //we don't have the informations LOGGER.log(Level.INFO, "Missing histogram information for correct rendering."); return Double.NaN; }else{ final Expression exp = bound.getValue(); final Number val = exp.evaluate(stats, Number.class); final Histogram h = new Histogram(histo, histoMin, histoMax); return h.getValueAt(val.doubleValue()/100.0); } }else{ throw new PortrayalException("Unknwoned mode "+mode); } } private void renderCoverage(RenderedImage img, MathTransform2D trs2D) throws PortrayalException{ renderingContext.switchToObjectiveCRS(); if (trs2D instanceof AffineTransform) { g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,1)); try { g2d.drawRenderedImage(img, (AffineTransform)trs2D); } catch (Exception ex) { //plenty of errors can happen when painting an image monitor.exceptionOccured(ex, Level.WARNING); } }else if (trs2D instanceof LinearTransform) { final LinearTransform lt = (LinearTransform) trs2D; final int col = lt.getMatrix().getNumCol(); final int row = lt.getMatrix().getNumRow(); //TODO using only the first parameters of the linear transform throw new PortrayalException("Could not render image, GridToCRS is a not an AffineTransform, found a " + trs2D.getClass()); }else{ throw new PortrayalException("Could not render image, GridToCRS is a not an AffineTransform, found a " + trs2D.getClass() ); } } }