/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2015, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.coverage.processing; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.IndexColorModel; import java.awt.image.RenderedImage; import java.util.Map; import javax.media.jai.BorderExtender; import javax.media.jai.ImageLayout; import javax.media.jai.Interpolation; import javax.media.jai.InterpolationNearest; import javax.media.jai.JAI; import javax.media.jai.OperationDescriptor; import javax.media.jai.PlanarImage; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.referencing.operation.transform.ConcatenatedTransform; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.resources.image.ImageUtilities; import org.opengis.coverage.processing.OperationNotFoundException; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.util.InternationalString; /** * Base class for providing capabilities to scale {@link GridCoverage2D} objects * using JAI scale operations. * * <p> * This class tries to handles all the problems related to scaling index-color * images in order to avoid strange results in the smoothest possible way by * performing color expansions under the hood as needed. It may also apply some * optimizations in case we were dealing with non-geo view of coverage. * * @author Simone Giannecchini, GeoSolutions. * * * @source $URL$ * @since 2.5 */ public abstract class BaseScaleOperationJAI extends OperationJAI { public static final String AFFINE = "Affine"; public static final String SCALE = "Scale"; public static final String TRANSLATE = "Translate"; public static final String WARP = "Warp"; public static final String ROI = "roi"; /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = 1L; /** * Constructor for {@link BaseScaleOperationJAI}. * * @param operation name of the {@link JAI} operation we wrap. * @throws OperationNotFoundException */ public BaseScaleOperationJAI(String operation) throws OperationNotFoundException { super(operation); } /** * Constructor for {@link BaseScaleOperationJAI}. * * @param operation {@link OperationDescriptor} of the {@link JAI} operation we wrap. */ public BaseScaleOperationJAI(OperationDescriptor operation) { super(operation); } /** * Constructor for {@link BaseScaleOperationJAI}. * @param operation {@link OperationDescriptor} of the {@link JAI} operation we wrap. * @param descriptor */ public BaseScaleOperationJAI(OperationDescriptor operation, ParameterDescriptorGroup descriptor) { super(operation, descriptor); } @Override protected GridCoverage2D deriveGridCoverage(GridCoverage2D[] sources, Parameters parameters) { // // Getting the input parameters we might need // int indexOfInterpolationParam; try{ indexOfInterpolationParam=parameters.parameters .indexOfParam("interpolation"); } catch (IllegalArgumentException e) { indexOfInterpolationParam=-1; } int indexOfBorderExtenderParam; try{ indexOfBorderExtenderParam=parameters.parameters .indexOfParam("BorderExtender"); } catch (IllegalArgumentException e) { indexOfBorderExtenderParam=-1; } final Interpolation interpolation =(Interpolation) (indexOfInterpolationParam==-1? new InterpolationNearest(): parameters.parameters.getObjectParameter("interpolation")); ; final BorderExtender borderExtender= (BorderExtender) (indexOfBorderExtenderParam!=-1? parameters.parameters.getObjectParameter("BorderExtender"): ImageUtilities.DEFAULT_BORDER_EXTENDER); // ///////////////////////////////////////////////////////////////////// // // Getting the source coverage // // ///////////////////////////////////////////////////////////////////// GridCoverage2D sourceCoverage = sources[PRIMARY_SOURCE_INDEX]; RenderedImage sourceImage = sourceCoverage.getRenderedImage(); // ///////////////////////////////////////////////////////////////////// // // Do we need to explode the Palette to RGB(A)? // // ///////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////// // // Managing Hints, especially for output coverage's layout purposes. // // It is worthwhile to point out that layout hints for minx, miny, width // and height are honored by the warp and affine operation. The other // ImageLayout hints, like tileWidth and tileHeight, are also // honored. // ///////////////////////////////////////////////////////////////////// RenderingHints targetHints = parameters.hints!=null?parameters.hints:ImageUtilities.getRenderingHints(sourceImage); if (targetHints == null) targetHints = new RenderingHints(null); if (parameters.hints != null) targetHints.add(parameters.hints); ImageLayout layout = (ImageLayout) targetHints.get(JAI.KEY_IMAGE_LAYOUT); if (layout != null) { layout = (ImageLayout) layout.clone(); } else { layout = new ImageLayout(sourceImage); layout.unsetTileLayout(); // At this point, only the color model and sample model are left // valid. } if ((layout.getValidMask() & (ImageLayout.TILE_WIDTH_MASK | ImageLayout.TILE_HEIGHT_MASK | ImageLayout.TILE_GRID_X_OFFSET_MASK | ImageLayout.TILE_GRID_Y_OFFSET_MASK)) == 0) { layout.setTileGridXOffset(layout.getMinX(sourceImage)); layout.setTileGridYOffset(layout.getMinY(sourceImage)); final int width = layout.getWidth(sourceImage); final int height = layout.getHeight(sourceImage); if (layout.getTileWidth(sourceImage) > width) layout.setTileWidth(width); if (layout.getTileHeight(sourceImage) > height) layout.setTileHeight(height); } targetHints.put(JAI.KEY_IMAGE_LAYOUT, layout); targetHints.put(JAI.KEY_BORDER_EXTENDER,borderExtender); // it is crucial to correctly manage the Hints to control the // replacement of IndexColorModel. It is worth to point out that setting // the JAI.KEY_REPLACE_INDEX_COLOR_MODEL hint to true is not enough to // force the operators to do an expansion. // If we explicitly provide an ImageLayout built with the source image // where the CM and the SM are valid. those will be employed overriding // a the possibility to expand the color model. final boolean asPhotographicStrategy = sourceImage.getColorModel() instanceof IndexColorModel; if (!(asPhotographicStrategy)) targetHints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL); else { targetHints.add(ImageUtilities.REPLACE_INDEX_COLOR_MODEL); layout.unsetValid(ImageLayout.COLOR_MODEL_MASK); layout.unsetValid(ImageLayout.SAMPLE_MODEL_MASK); } // ///////////////////////////////////////////////////////////////////// // // Creating final grid coverage. // // ///////////////////////////////////////////////////////////////////// RenderedImage image= createRenderedImage(parameters.parameters,targetHints); // ///////////////////////////////////////////////////////////////////// // // Preparing the resulting grid to world transformation // // //// // // This step is crucial for making a leap towards a more robust // implementation of the scaling operations and also towards their // integration as operation JAI subclasses. // // What we do here is quite trivial, we take into account the initial // Grid to World transform and then we concatenate to it a // transformation that takes into account the scaling we just performed. // // ///////////////////////////////////////////////////////////////////// final double scaleX=image.getWidth()/ (1.0 * sourceImage.getWidth()); final double scaleY=image.getHeight()/ (1.0 * sourceImage.getHeight()); final double tX=image.getMinX()-sourceImage.getMinX()*scaleX; final double tY=image.getMinY()-sourceImage.getMinY()*scaleY; final AffineTransform scaleTranslate= new AffineTransform( scaleX, 0, 0, scaleY, tX, tY); final MathTransform finalTransform; try{ scaleTranslate.invert(); scaleTranslate.preConcatenate(CoverageUtilities.CENTER_TO_CORNER); final AffineTransform2D tr= new AffineTransform2D(scaleTranslate); finalTransform= ConcatenatedTransform.create(tr,sourceCoverage.getGridGeometry().getGridToCRS2D()); } catch (Exception e) { throw new RuntimeException(e); } // ///////////////////////////////////////////////////////////////////// // // Preparing the resulting coverage // // ///////////////////////////////////////////////////////////////////// /* * Performs the operation using JAI and construct the new grid coverage. * Uses the coordinate system from the main source coverage in order to * preserve the extra dimensions (if any). The first two dimensions should * be equal to the coordinate system set in the 'parameters' block. */ final InternationalString name = deriveName(sources, 0, parameters); final Map<String,?> properties = getProperties(image,parameters.crs,name,finalTransform,sources,parameters); final GridGeometry2D gg2D = new GridGeometry2D( new GridEnvelope2D(PlanarImage.wrapRenderedImage(image).getBounds()), PixelInCell.CELL_CORNER, finalTransform, parameters.crs, null); final GridCoverage2D result = getFactory(parameters.hints) .create(name, // The grid coverage name image, // The underlying data gg2D, (GridSampleDimension[]) (asPhotographicStrategy ? null : sourceCoverage.getSampleDimensions().clone()), // The sample dimensions sources, // The source grid coverage. properties); // Properties // now let's see what we need to do in order to clean things up return result; } }