/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, 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.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 org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.ViewType; import org.geotools.metadata.iso.spatial.PixelTranslation; import org.geotools.referencing.operation.LinearTransform; import org.geotools.referencing.operation.transform.ConcatenatedTransform; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.resources.image.ImageUtilities; import org.opengis.coverage.processing.OperationNotFoundException; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.parameter.ParameterDescriptorGroup; 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 class BaseScaleOperationJAI extends OperationJAI { /** * 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"): BorderExtender.createInstance(BorderExtender.BORDER_COPY)); // ///////////////////////////////////////////////////////////////////// // // Getting the source coverage // // ///////////////////////////////////////////////////////////////////// GridCoverage2D sourceCoverage = sources[PRIMARY_SOURCE_INDEX]; // map to upper left to avoid loosing too much precision due to the fact // that the internal grid tow world maps from pixel centre. final MathTransform sourceG2W = (sourceCoverage.getGridGeometry()) .getGridToCRS2D(PixelOrientation.UPPER_LEFT); RenderedImage sourceImage = sourceCoverage.getRenderedImage(); // ///////////////////////////////////////////////////////////////////// // // Do we need to explode the Palette to RGB(A)? // // ///////////////////////////////////////////////////////////////////// ViewType strategy = CoverageUtilities.preferredViewForOperation( sourceCoverage, interpolation, false, parameters.hints); switch (strategy) { case PHOTOGRAPHIC: // // // // In this case I do not require an explicit color expansion since I // can leverage on the fact that the scale operation with latest // versions of JAI is one of the operations that perform automatic // color expansion. // // // break; case GEOPHYSICS: // in this case we need to go back the geophysics view of the // source coverage // fallthrough same code than PACKED. case PACKED: // in this case we work on the non geophysics version because it // should be faster than working on the geophysics one. We are going // to work on a single band indexed image. sourceCoverage = sourceCoverage.view(strategy); sourceImage = sourceCoverage.getRenderedImage(); break; } // ///////////////////////////////////////////////////////////////////// // // 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 NOT honored by the scale operation. The other // ImageLayout hints, like tileWidth and tileHeight, however are // honored. // ///////////////////////////////////////////////////////////////////// RenderingHints targetHints = 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. if (strategy != ViewType.PHOTOGRAPHIC) 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. // // ///////////////////////////////////////////////////////////////////// //concatenate and remap to pixel centre final PixelTranslation translationValue = PixelTranslation.getPixelTranslation(PixelOrientation.LOWER_RIGHT); final LinearTransform translation = ProjectiveTransform.create(AffineTransform.getTranslateInstance(translationValue.dx, translationValue.dy)); final LinearTransform scale = ProjectiveTransform.create( AffineTransform.getScaleInstance( sourceImage.getWidth()/ (1.0 * image.getWidth()), sourceImage.getHeight()/ (1.0 * image.getHeight()) ) ); final MathTransform finalTransform= ConcatenatedTransform.create(translation,ConcatenatedTransform.create(scale,sourceG2W)); // ///////////////////////////////////////////////////////////////////// // // 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 GridCoverage2D result = getFactory(parameters.hints) .create(name, // The grid coverage name image, // The underlying data parameters.crs, // The coordinate system (may not be 2D). finalTransform, // The grid transform (may not be 2D). (GridSampleDimension[]) (strategy == ViewType.PHOTOGRAPHIC ? 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 if (strategy == ViewType.GEOPHYSICS) return result.view(ViewType.PACKED); if (strategy == ViewType.PACKED) return result.view(ViewType.GEOPHYSICS); return result; } }