/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.jaiext.warp; import it.geosolutions.jaiext.border.BorderDescriptor; import it.geosolutions.jaiext.interpolators.InterpolationNoData; import it.geosolutions.jaiext.iterators.RandomIterFactory; import it.geosolutions.jaiext.range.Range; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.util.Arrays; import java.util.Map; import javax.media.jai.BorderExtender; import javax.media.jai.ImageLayout; import javax.media.jai.Interpolation; import javax.media.jai.PlanarImage; import javax.media.jai.ROI; import javax.media.jai.ROIShape; import javax.media.jai.RasterAccessor; import javax.media.jai.RasterFormatTag; import javax.media.jai.RenderedOp; import javax.media.jai.Warp; import javax.media.jai.iterator.RandomIter; import com.sun.media.jai.util.ImageUtil; /** * Subclass of {@link WarpOpImage} that makes use of the provided ROI and NoData. * * @author Simone Giannecchini, GeoSolutions SAS * */ @SuppressWarnings("unchecked") public abstract class WarpOpImage extends javax.media.jai.WarpOpImage { /** {@link BorderExtender} instance for extending roi. */ protected final static BorderExtender ZERO_EXTENDER = BorderExtender .createInstance(BorderExtender.BORDER_ZERO); /** Quantity used for extending the input tile dimensions */ protected static final int TILE_EXTENDER = 1; /** Constant indicating that the inner random iterators must pre-calculate an array of the image positions */ protected static final boolean ARRAY_CALC = true; /** Constant indicating that the inner random iterators must cache the current tile position */ protected static final boolean TILE_CACHED = true; /** Current ROI object */ protected final ROI roi; /** Boolean indicating if ROI is used */ protected final boolean hasROI; /** Boolean indicating if NoData values are present */ protected final boolean hasNoData; /** Boolean indicating absence of both NoData and ROI */ protected final boolean caseA; /** Boolean indicating absence of NoData and presence of ROI */ protected final boolean caseB; /** Boolean indicating absence of ROI and presence of NoData */ protected final boolean caseC; /** Current NoData Range object */ protected Range noDataRange; /** Boolean indicating the presence of a border extender */ protected boolean extended; /** Hints used for the calculations*/ private RenderingHints hints; /** Left padding */ protected int leftPad; /** Right padding */ protected int rightPad; /** Top padding */ protected int topPad; /** Bottom padding */ protected int bottomPad; /** Image associated to the ROI*/ protected volatile PlanarImage roiImage; /** Rectangle associated to the ROI bounds*/ protected Rectangle roiBounds; public WarpOpImage(final RenderedImage source, final ImageLayout layout, final Map<?, ?> configuration, final boolean cobbleSources, final BorderExtender extender, final Interpolation interp, final Warp warp, final double[] backgroundValues, final ROI roi, final Range noData) { super(source, layout, configuration, cobbleSources, extender, interp, warp, prepareBackground(source, layout, interp, backgroundValues)); // Get the ROI as image this.roi = roi; hasROI = roi != null; // Control on the ROI if(hasROI){ roiBounds = roi.getBounds(); } hasNoData = (interp instanceof InterpolationNoData) && (((InterpolationNoData) interp).getNoDataRange() != null) || noData != null; if (hasNoData) { if(interp instanceof InterpolationNoData){ noDataRange = ((InterpolationNoData) interp).getNoDataRange(); } if (noDataRange == null) { noDataRange = noData; } } else { noDataRange = null; } // Definition of the possible cases that can be found // caseA = no ROI nor No Data // caseB = ROI present but No Data not present // caseC = No Data present but ROI not present // Last case not defined = both ROI and No Data are present caseA = !hasROI && !hasNoData; caseB = hasROI && !hasNoData; caseC = !hasROI && hasNoData; // Extender check extended = extender != null; // Save RenderingHints if(configuration instanceof RenderingHints){ this.hints = (RenderingHints)configuration; } // Definition of the Padding leftPad = 0; rightPad = 0; topPad = 0; bottomPad = 0; } /** * Computes a tile. A new <code>WritableRaster</code> is created to represent the requested tile. Its width and height equals to this image's tile * width and tile height respectively. This method assumes that the requested tile either intersects or is within the bounds of this image. * * <p> * Whether or not this method performs source cobbling is determined by the <code>cobbleSources</code> variable set at construction time. If * <code>cobbleSources</code> is <code>true</code>, cobbling is performed on the source for areas that intersect multiple tiles, and * <code>computeRect(Raster[], WritableRaster, Rectangle)</code> is called to perform the actual computation. Otherwise, * <code>computeRect(PlanarImage[], WritableRaster, Rectangle)</code> is called to perform the actual computation. * * If ROI is present, then the source mapped rectangle is checked if it intersects the input ROI; if this condition is not satisfied, then the * tile is not elaborated. * * @param tileX The X index of the tile. * @param tileY The Y index of the tile. * * @return The tile as a <code>Raster</code>. */ @Override public Raster computeTile(final int tileX, final int tileY) { // The origin of the tile. final Point org = new Point(tileXToX(tileX), tileYToY(tileY)); // Create a new WritableRaster to represent this tile. final WritableRaster dest = createWritableRaster(sampleModel, org); // Find the intersection between this tile and the writable bounds. final Rectangle destRect = new Rectangle(org.x, org.y, tileWidth, tileHeight) .intersection(computableBounds); if (destRect.isEmpty()) { if (setBackground) { ImageUtil.fillBackground(dest, destRect, backgroundValues); } return dest; // tile completely outside of computable bounds } // get the source image and check if we falls outside its bounds final PlanarImage source = getSourceImage(0); final Rectangle srcRect = mapDestRect(destRect, 0); if (!srcRect.intersects(source.getBounds())) { if (setBackground) { ImageUtil.fillBackground(dest, destRect, backgroundValues); } return dest; // outside of source bounds } // are we outside the roi if (roi != null && !roi.intersects(srcRect)) { if (setBackground) { ImageUtil.fillBackground(dest, destRect, backgroundValues); } return dest; // outside of source roi } // This image only has one source. if (cobbleSources) { // FIXME throw new UnsupportedOperationException(); } else { final PlanarImage[] srcs = { source }; computeRect(srcs, dest, destRect); } return dest; } /** * Warps a rectangle. If ROI is present, the intersection between ROI and tile bounds is calculated; The result ROI will be used for calculations * inside the computeRect() method. */ protected void computeRect(final PlanarImage[] sources, final WritableRaster dest, final Rectangle destRect) { // Retrieve format tags. final RasterFormatTag[] formatTags = getFormatTags(); final RasterAccessor dst = new RasterAccessor(dest, destRect, formatTags[1], getColorModel()); ROI roiTile = null; RandomIter roiIter = null; boolean roiContainsTile = false; boolean roiDisjointTile = false; // If a ROI is present, then only the part contained inside the current tile bounds is taken. if (hasROI) { Rectangle srcRectExpanded = mapDestRect(destRect, 0); // The tile dimension is extended for avoiding border errors srcRectExpanded.setRect( srcRectExpanded.getMinX() - leftPad, srcRectExpanded.getMinY() - topPad, srcRectExpanded.getWidth() + rightPad + leftPad, srcRectExpanded.getHeight() + bottomPad + topPad); roiTile = roi.intersect(new ROIShape(srcRectExpanded)); if(!roiBounds.intersects(srcRectExpanded)) { roiDisjointTile = true; } else { roiContainsTile = roiTile.contains(srcRectExpanded); if (!roiContainsTile) { if (!roiTile.intersects(srcRectExpanded)) { roiDisjointTile = true; }else{ PlanarImage roiIMG = getImage(); roiIter = RandomIterFactory.create(roiIMG, null, TILE_CACHED, ARRAY_CALC); } } } } if (!hasROI || !roiDisjointTile) { switch (dst.getDataType()) { case DataBuffer.TYPE_BYTE: computeRectByte(sources[0], dst, roiIter, roiContainsTile); break; case DataBuffer.TYPE_USHORT: computeRectUShort(sources[0], dst, roiIter, roiContainsTile); break; case DataBuffer.TYPE_SHORT: computeRectShort(sources[0], dst, roiIter, roiContainsTile); break; case DataBuffer.TYPE_INT: computeRectInt(sources[0], dst, roiIter, roiContainsTile); break; case DataBuffer.TYPE_FLOAT: computeRectFloat(sources[0], dst, roiIter, roiContainsTile); break; case DataBuffer.TYPE_DOUBLE: computeRectDouble(sources[0], dst, roiIter, roiContainsTile); break; } // After the calculations, the output data are copied into the WritableRaster if (dst.isDataCopy()) { dst.clampDataArrays(); dst.copyDataToRaster(); } } else { // If the tile is outside the ROI, then the destination Raster is set to backgroundValues if (setBackground) { ImageUtil.fillBackground(dest, destRect, backgroundValues); } } } /** * This method provides a lazy initialization of the image associated to the ROI. * The method uses the Double-checked locking in order to maintain thread-safety * * @return */ private PlanarImage getImage() { PlanarImage img = roiImage; if (img == null) { synchronized (this) { img = roiImage; if (img == null) { roiImage = img = roi.getAsImage(); } } } return img; } /** * Computation of the Warp operation on Byte images * * @param src * @param dst * @param roiIter * @param roiContainsTile */ protected abstract void computeRectByte(final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile); /** * Computation of the Warp operation on UShort images * * @param src * @param dst * @param roiIter * @param roiContainsTile */ protected abstract void computeRectUShort(final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile); /** * Computation of the Warp operation on Short images * * @param src * @param dst * @param roiIter * @param roiContainsTile */ protected abstract void computeRectShort(final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile); /** * Computation of the Warp operation on Integer images * * @param src * @param dst * @param roiIter * @param roiContainsTile */ protected abstract void computeRectInt(final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile); /** * Computation of the Warp operation on Float images * * @param src * @param dst * @param roiIter * @param roiContainsTile */ protected abstract void computeRectFloat(final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile); /** * Computation of the Warp operation on Double images * * @param src * @param dst * @param roiIter * @param roiContainsTile */ protected abstract void computeRectDouble(final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile); /** * Utility method used for creating an array of background values from a single value, taken from the interpolator. If the interpolation object is * not an instance of InterpolationNoData, then the optional background values array is taken. If the array is null, then an array with 0 value is * used. * * @param source * @param layout * @param interp * @param backgroundValues * @return */ public static double[] prepareBackground(final RenderedImage source, ImageLayout layout, Interpolation interp, double[] backgroundValues) { // If the interpolator is an instance of InterpolationNoData, the background value is taken from the interpolator if (interp instanceof InterpolationNoData) { SampleModel sm; if (layout != null) { sm = layout.getSampleModel(source); } else { sm = source.getSampleModel(); } int numBands = sm.getNumBands(); double[] destinationNoData = new double[numBands]; Arrays.fill(destinationNoData, ((InterpolationNoData) interp).getDestinationNoData()); return destinationNoData; // Else, check if an array is present } else if (backgroundValues != null) { return backgroundValues; // Else an input array with 0 value is returned } else { return new double[] { 0.0d }; } } /** Returns the "floor" value of a float. */ public static final int floor(final float f) { return f >= 0 ? (int) f : (int) f - 1; } /** Returns the "round" value of a float. */ public static final int round(final float f) { return f >= 0 ? (int) (f + 0.5F) : (int) (f - 0.5F); } /** * Returns a RandomIterator on the input image. * * @param src * @return */ protected RandomIter getRandomIterator(final PlanarImage src, BorderExtender extender) { return getRandomIterator(src, 0, 1, 0, 1, extender); } /** * Returns a RandomIterator on the input image. Also it handles padding if present. * * @param src * @return */ protected RandomIter getRandomIterator(final PlanarImage src, int leftPad, int rightPad, int topPad, int bottomPad, BorderExtender extender) { RandomIter iterSource; if (extended) { RenderedOp op = BorderDescriptor.create(src, leftPad, rightPad, topPad, bottomPad, extender, noDataRange, backgroundValues != null ? backgroundValues[0] : 0d, hints); iterSource = RandomIterFactory.create(op, op.getBounds(), TILE_CACHED, ARRAY_CALC); } else { iterSource = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC); } return iterSource; } }