/* 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.imagefunction; import it.geosolutions.jaiext.range.Range; import it.geosolutions.jaiext.range.RangeFactory; import java.awt.Rectangle; import java.awt.image.DataBuffer; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.util.Map; import javax.media.jai.ImageFunction; import javax.media.jai.ImageLayout; import javax.media.jai.PlanarImage; import javax.media.jai.ROI; import javax.media.jai.ROIShape; import javax.media.jai.RasterFactory; import javax.media.jai.SourcelessOpImage; import com.sun.media.jai.util.ImageUtil; /** * An OpImage class to generate an image from a functional description. */ public class ImageFunctionOpImage extends SourcelessOpImage { /** * Constant indicating that the inner random iterators must pre-calculate an array of the image positions */ public static final boolean ARRAY_CALC = true; /** * Constant indicating that the inner random iterators must cache the current tile position */ public static final boolean TILE_CACHED = true; /** The functional description of the image. */ protected ImageFunctionJAIEXT function; /** The X scale factor. */ protected float xScale; /** The Y scale factor. */ protected float yScale; /** The X translation. */ protected float xTrans; /** The Y translation. */ protected float yTrans; /** ROI used for reducing calculation area */ private ROI roi; /** NoData used for checking if input pixels are NoData */ private Range nodata; /** Boolean indicating if ROI is present */ private boolean hasROI; /** Value to set as output NoData */ private float destNoData; /** {@link PlanarImage} containing ROI data */ private PlanarImage roiImage; /** Rectangle defining ROI bounds */ private Rectangle roiBounds; /** Helper function for creating a suitable sample model for the final image */ private static SampleModel sampleModelHelper(int numBands, ImageLayout layout) { SampleModel sampleModel; if (layout != null && layout.isValid(ImageLayout.SAMPLE_MODEL_MASK)) { sampleModel = layout.getSampleModel(null); if (sampleModel.getNumBands() != numBands) { throw new RuntimeException(JaiI18N.getString("ImageFunctionRIF0")); } } else { // Create a SampleModel. // Use a dummy width and height, OpImage will fix them sampleModel = RasterFactory.createBandedSampleModel(DataBuffer.TYPE_FLOAT, 1, 1, numBands); } return sampleModel; } /** * Constructs an ImageFunctionOpImage. * * @param width The output image width. * @param height The output image height. */ public ImageFunctionOpImage(ImageFunction function, int minX, int minY, int width, int height, float xScale, float yScale, float xTrans, float yTrans, ROI roi, Range nodata, float destNoData, Map config, ImageLayout layout) { super(layout, config, sampleModelHelper(function.getNumElements() * (function.isComplex() ? 2 : 1), layout), minX, minY, width, height); // Cache the parameters. this.function = function instanceof ImageFunctionJAIEXT ? (ImageFunctionJAIEXT) function : new ImageFunctionJAIEXTWrapper(function); this.xScale = xScale; this.yScale = yScale; this.xTrans = xTrans; this.yTrans = yTrans; // Check if ROI is present hasROI = roi != null; if (hasROI) { this.roiBounds = roi.getBounds(); this.roi = roi; } // Check on NoData if (nodata != null) { this.nodata = RangeFactory.convertToFloatRange(nodata); } this.destNoData = destNoData; } /** * Compute a Rectangle of output data based on the ImageFunction. Note that the sources parameter is not used. */ protected void computeRect(PlanarImage[] sources, WritableRaster dest, Rectangle destRect) { // Cache some info. int dataType = sampleModel.getTransferType(); int numBands = sampleModel.getNumBands(); // ROI check boolean roiDisjointTile = false; ROI roiTile = null; // If a ROI is present, then only the part contained inside the current // tile bounds is taken. if (hasROI) { Rectangle srcRectExpanded = destRect.getBounds(); // The tile dimension is extended for avoiding border errors srcRectExpanded.setRect(srcRectExpanded.getMinX() - 1, srcRectExpanded.getMinY() - 1, srcRectExpanded.getWidth() + 2, srcRectExpanded.getHeight() + 2); roiTile = roi.intersect(new ROIShape(srcRectExpanded)); if (!roiBounds.intersects(srcRectExpanded)) { roiDisjointTile = true; } } if (roiDisjointTile) { double[] bkg = new double[function.isComplex() ? 2 : 1]; bkg[0] = destNoData; if (function.isComplex()) { bkg[1] = destNoData; } ImageUtil.fillBackground(dest, destRect, bkg); return; } // Allocate the actual data memory. int length = width * height; Object data; if (dataType == DataBuffer.TYPE_DOUBLE) { data = function.isComplex() ? (Object) new double[2][length] : (Object) new double[length]; } else { data = function.isComplex() ? (Object) new float[2][length] : (Object) new float[length]; } if (dataType == DataBuffer.TYPE_DOUBLE) { double[] real = function.isComplex() ? ((double[][]) data)[0] : ((double[]) data); double[] imag = function.isComplex() ? ((double[][]) data)[1] : null; int element = 0; for (int band = 0; band < numBands; band++) { function.getElements(xScale * (destRect.x - xTrans), yScale * (destRect.y - yTrans), xScale, yScale, destRect.width, destRect.height, element++, real, imag, destRect, roiTile, nodata, destNoData); dest.setSamples(destRect.x, destRect.y, destRect.width, destRect.height, band, (double[]) real); if (function.isComplex()) { dest.setSamples(destRect.x, destRect.y, destRect.width, destRect.height, ++band, imag); } } // for (band ... } else { // not double precision float[] real = function.isComplex() ? ((float[][]) data)[0] : ((float[]) data); float[] imag = function.isComplex() ? ((float[][]) data)[1] : null; int element = 0; for (int band = 0; band < numBands; band++) { function.getElements(xScale * (destRect.x - xTrans), yScale * (destRect.y - yTrans), xScale, yScale, destRect.width, destRect.height, element++, real, imag, destRect, roiTile, nodata, destNoData); dest.setSamples(destRect.x, destRect.y, destRect.width, destRect.height, band, real); if (function.isComplex()) { dest.setSamples(destRect.x, destRect.y, destRect.width, destRect.height, ++band, imag); } } // for (band ... } } /** * 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 a PlanarImage representing ROI image */ private PlanarImage getImage() { PlanarImage img = roiImage; if (img == null) { synchronized (this) { img = roiImage; if (img == null) { roiImage = img = roi.getAsImage(); } } } return img; } }