/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 - 2015 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.mosaic; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.PackedColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.awt.image.renderable.ParameterBlock; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Vector; import javax.media.jai.BorderExtender; import javax.media.jai.BorderExtenderConstant; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.LookupTableJAI; import javax.media.jai.OpImage; import javax.media.jai.PlanarImage; import javax.media.jai.ROI; import javax.media.jai.RasterAccessor; import javax.media.jai.RasterFormatTag; import javax.media.jai.RenderedOp; import javax.media.jai.operator.MosaicDescriptor; import javax.media.jai.operator.MosaicType; import com.sun.media.jai.util.ImageUtil; import it.geosolutions.jaiext.lookup.LookupTable; import it.geosolutions.jaiext.lookup.LookupTableFactory; import it.geosolutions.jaiext.range.Range; import it.geosolutions.jaiext.range.RangeFactory; /** * This class takes an array of <code>RenderedImage</code> and creates a mosaic of them. If the * image pixels are No Data values, they are not calculated and the MosaicOpimage searches for the * pixels of the other source images in the same location. If all the pixels in the same location * are No Data, the destination image pixel will be a destination No Data value. This feature is * combined with the ROI support and alpha channel support(leaved unchanged). No Data support has * been added both in the BLEND and OVERLAY mosaic type. The MosaicOpimage behavior is equal to that * of the old MosaicOpimage, the only difference is the No Data support. The input values of the * first one are different because a Java Bean is used for storing all of them in a unique block * instead of different variables as the second one. This Java Bean is described in the * ImageMosaicBean class. Inside this class, other Java Beans are used for simplifying the image * data transport between the various method. */ // @SuppressWarnings("unchecked") public class MosaicOpImage extends OpImage { /** * Default value for the destination image if every pixel in the same location is a no data */ public static final double[] DEFAULT_DESTINATION_NO_DATA_VALUE = { 0 }; /** mosaic type selected */ private MosaicType mosaicTypeSelected; /** Number of bands for every image */ private int numBands; /** Bean used for storing image data, ROI, alpha channel, Nodata Range */ private final ImageMosaicBean[] imageBeans; /** Boolean for checking if the ROI is used in the mosaic */ private boolean roiPresent; /** * Boolean for checking if the alpha channel is used only for bitmask or for weighting every * pixel with is alpha value associated */ private boolean isAlphaBitmaskUsed; /** Boolean for checking if alpha channel is used in the mosaic */ private boolean alphaPresent; /** Border extender for the source data */ private BorderExtender sourceBorderExtender; /** Border extender for the ROI or alpha channel data */ private BorderExtender zeroBorderExtender; /** * No data values for the destination image if the pixel of the same location are no Data (Byte) */ private byte[] destinationNoDataByte; /** * No data values for the destination image if the pixel of the same location are no Data * (UShort) */ private short[] destinationNoDataUShort; /** * No data values for the destination image if the pixel of the same location are no Data * (Short) */ private short[] destinationNoDataShort; /** * No data values for the destination image if the pixel of the same location are no Data * (Integer) */ private int[] destinationNoDataInt; /** * No data values for the destination image if the pixel of the same location are no Data * (Float) */ private float[] destinationNoDataFloat; /** * No data values for the destination image if the pixel of the same location are no Data * (Double) */ private double[] destinationNoDataDouble; /** * Table used for checking no data values. The first index indicates the source, the second the * band, the third the value */ protected byte[][][] byteLookupTable; /** Boolean array indicating which source images has No Data and not */ private final boolean[] hasNoData; /** The format tag for the destination image */ private RasterFormatTag rasterFormatTag; /** Enumerator for the type of mosaic weigher */ public enum WeightType { WEIGHT_TYPE_ALPHA, WEIGHT_TYPE_ROI, WEIGHT_TYPE_NODATA; } /** * Static method for providing a valid layout to the OpImage constructor * * @param noDatas */ private static final ImageLayout checkLayout(List sources, ImageLayout layout, Range[] noDatas) { // Variable Initialization SampleModel targetSampleModel = null; ColorModel targetColorModel = null; // Source number int numSources = sources.size(); if (numSources > 0) { // The sample model and the color model are taken from the first image ImageLayout tmp = getTargetSampleColorModel(sources, noDatas); targetColorModel = tmp.getColorModel(null); targetSampleModel = tmp.getSampleModel(null); } else if (layout != null // If there is no Images check the validity of the layout && layout.isValid(ImageLayout.WIDTH_MASK | ImageLayout.HEIGHT_MASK | ImageLayout.SAMPLE_MODEL_MASK)) { // The sample model and the color model are taken from layout, we don't replace it targetSampleModel = layout.getSampleModel(null); } else {// Not valid layout throw new IllegalArgumentException("Layout not valid"); } // get color and sample model if (targetSampleModel == null) { throw new IllegalArgumentException("No sample model present"); } // If the source number is less than one the layout is cloned and returned if (numSources < 1) { return (ImageLayout) layout.clone(); } // If the layout is null a new one is created, else it is cloned. This new // layout // is the layout for all the images ImageLayout mosaicLayout = layout == null ? new ImageLayout() : (ImageLayout) layout.clone(); // A new Rectangle is calculated for storing the union of all the image // bounds Rectangle mosaicBounds = new Rectangle(); // If the mosaic is valid his bounds are set to the new mosaicLayout if (mosaicLayout.isValid(ImageLayout.MIN_X_MASK | ImageLayout.MIN_Y_MASK | ImageLayout.WIDTH_MASK | ImageLayout.HEIGHT_MASK)) { mosaicBounds.setBounds(mosaicLayout.getMinX(null), mosaicLayout.getMinY(null), mosaicLayout.getWidth(null), mosaicLayout.getHeight(null)); // If the layout is not valid the mosaic bounds are calculated from // every image bounds } else if (numSources > 0) { RenderedImage source = (RenderedImage) sources.get(0); mosaicBounds.setBounds(source.getMinX(), source.getMinY(), source.getWidth(), source.getHeight()); for (int i = 1; i < numSources; i++) { source = (RenderedImage) sources.get(i); Rectangle sourceBounds = new Rectangle(source.getMinX(), source.getMinY(), source.getWidth(), source.getHeight()); mosaicBounds = mosaicBounds.union(sourceBounds); } } // The mosaic bounds are stored in the new layout mosaicLayout.setMinX(mosaicBounds.x); mosaicLayout.setMinY(mosaicBounds.y); mosaicLayout.setWidth(mosaicBounds.width); mosaicLayout.setHeight(mosaicBounds.height); mosaicLayout.setSampleModel(targetSampleModel); if (targetColorModel != null) { mosaicLayout.setColorModel(targetColorModel); } return mosaicLayout; } private static ImageLayout getTargetSampleColorModel(List sources, Range[] noDatas) { final int numSources = sources.size(); // get first image as reference RenderedImage first = (RenderedImage) sources.get(0); ColorModel firstColorModel = first.getColorModel(); SampleModel firstSampleModel = first.getSampleModel(); // starting point image layout ImageLayout result = new ImageLayout(); result.setSampleModel(firstSampleModel); // easy case if (numSources == 1) { result.setColorModel(firstColorModel); return result; } // See if they all are the same int firstDataType = firstSampleModel.getDataType(); int firstBands = firstSampleModel.getNumBands(); int firstSampleSize = firstSampleModel.getSampleSize()[0]; boolean heterogeneous = false; boolean hasIndexedColorModels = firstColorModel instanceof IndexColorModel; boolean hasComponentColorModels = firstColorModel instanceof ComponentColorModel; boolean hasPackedColorModels = firstColorModel instanceof PackedColorModel; boolean hasUnrecognizedColorModels = !hasComponentColorModels && !hasIndexedColorModels && !hasPackedColorModels; boolean hasUnsupportedTypes = false; int maxBands = firstBands; for (int i = 1; i < numSources; i++) { RenderedImage source = (RenderedImage) sources.get(i); SampleModel sourceSampleModel = source.getSampleModel(); ColorModel sourceColorModel = source.getColorModel(); int sourceBands = sourceSampleModel.getNumBands(); int sourceDataType = sourceSampleModel.getDataType(); if (sourceDataType == DataBuffer.TYPE_UNDEFINED) { hasUnsupportedTypes = true; } if (sourceBands > maxBands) { maxBands = sourceBands; } if (sourceColorModel instanceof IndexColorModel) { hasIndexedColorModels = true; } else if (sourceColorModel instanceof ComponentColorModel) { hasComponentColorModels = true; } else if (sourceColorModel instanceof PackedColorModel) { hasPackedColorModels = true; } else { hasUnrecognizedColorModels = true; } if (sourceDataType != firstDataType || sourceBands != firstBands) { heterogeneous = true; } for (int j = 0; j < sourceBands; j++) { if (sourceSampleModel.getSampleSize(j) != firstSampleSize) { heterogeneous = true; } } } // see how many types we're dealing with int colorModelsTypes = (hasIndexedColorModels ? 1 : 0) + (hasComponentColorModels ? 1 : 0) + (hasPackedColorModels ? 1 : 0); // if uniform, we have it easy if (!heterogeneous && colorModelsTypes == 1) { if (hasIndexedColorModels) { boolean uniformPalettes = hasUniformPalettes(sources, noDatas); if (!uniformPalettes) { // force RGB expansion setRGBLayout(result, first); } } return result; } if (hasUnrecognizedColorModels || hasUnsupportedTypes) { throw new IllegalArgumentException("Cannot mosaic the input images, " + "the mix of provided color and sample models is not supported"); } // all gray? if (maxBands == 1 && !hasIndexedColorModels) { // push for the largest type SampleModel sm = firstSampleModel; for (int i = 1; i < numSources; i++) { RenderedImage source = (RenderedImage) sources.get(i); SampleModel sourceSampleModel = source.getSampleModel(); int sourceDataType = sourceSampleModel.getDataType(); if (sourceDataType > sm.getDataType()) { sm = sourceSampleModel; } } result.setSampleModel(sm); } else { // take a leap of faith and assume we can manage this with a RGB output setRGBLayout(result, first); } return result; } private static void setRGBLayout(ImageLayout result, RenderedImage reference) { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); int[] nBits = { 8, 8, 8 }; ColorModel cm = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); SampleModel sm = cm.createCompatibleSampleModel(reference.getSampleModel().getWidth(), reference.getSampleModel().getWidth()); result.setColorModel(cm); result.setSampleModel(sm); } private static boolean hasUniformPalettes(List sources, Range[] noDatas) { // all indexed, but are the palettes the same? RenderedImage first = (RenderedImage) sources.get(0); Range firstNoData = noDatas != null ? noDatas[0] : null; IndexColorModel reference = (IndexColorModel) first.getColorModel(); int mapSize = reference.getMapSize(); byte[] reference_reds = new byte[mapSize]; byte[] reference_greens = new byte[mapSize]; byte[] reference_blues = new byte[mapSize]; byte[] reference_alphas = new byte[mapSize]; byte[] reds = new byte[mapSize]; byte[] greens = new byte[mapSize]; byte[] blues = new byte[mapSize]; byte[] alphas = new byte[mapSize]; reference.getReds(reference_reds); reference.getGreens(reference_greens); reference.getBlues(reference_blues); reference.getAlphas(reference_alphas); boolean uniformPalettes = true; final int numSources = sources.size(); for (int i = 1; i < numSources; i++) { RenderedImage source = (RenderedImage) sources.get(i); // we need the nodata to be uniform too, if we want to avoid color expansion Range noData = noDatas == null ? null : noDatas[i]; if (firstNoData == null) { if (noData != null) { return false; } } else { if (noData == null || !noData.equals(firstNoData)) { return false; } } IndexColorModel sourceColorModel = (IndexColorModel) source.getColorModel(); // check the basics if (reference.getNumColorComponents() != sourceColorModel.getNumColorComponents()) { throw new IllegalArgumentException("Cannot mosaic togheter images with index " + "color models having different numbers of color components:\n " + reference + "\n" + sourceColorModel); } // if not the same color space, then we need to expand if (!reference.getColorSpace().equals(reference.getColorSpace())) { return false; } if (!sourceColorModel.equals(reference) || sourceColorModel.getMapSize() != mapSize) { uniformPalettes = false; break; } // the above does not compare the rgb(a) arrays, do it sourceColorModel.getReds(reds); sourceColorModel.getGreens(greens); sourceColorModel.getBlues(blues); sourceColorModel.getAlphas(alphas); if (!Arrays.equals(reds, reference_reds) || !Arrays.equals(greens, reference_greens) || !Arrays.equals(blues, reference_blues) || !Arrays.equals(alphas, reference_alphas)) { uniformPalettes = false; break; } } return uniformPalettes; } /** * This constructor takes the source images, the layout, the rendering hints, and the parameters * and initialize variables. */ public MosaicOpImage(List sources, ImageLayout layout, Map renderingHints, MosaicType mosaicTypeSelected, PlanarImage[] alphaImgs, ROI[] rois, double[][] thresholds, double[] destinationNoData, Range[] noDatas) { // OpImage constructor super((Vector) sources, checkLayout(sources, layout, noDatas), renderingHints, true); // Stores the data passed by the parameterBlock this.numBands = sampleModel.getNumBands(); int numSources = getNumSources(); this.mosaicTypeSelected = mosaicTypeSelected; this.roiPresent = false; this.alphaPresent = false; // boolean indicating if ROI, alpha bands and nodata are present boolean roiExists = rois != null; boolean alphaExists = alphaImgs != null; boolean nodataExists = noDatas != null; // Checks on the size if (roiExists && rois.length != numSources) { throw new IllegalArgumentException("roi number is not equal to the source number"); } if (alphaExists && alphaImgs.length != numSources) { throw new IllegalArgumentException( "alpha bands number is not equal to the source number"); } if (nodataExists && noDatas.length != numSources) { throw new IllegalArgumentException("no data number is not equal to the source number"); } // Definition of the Bounds Rectangle totalBounds = getBounds(); // Type of data used for every image int dataType = sampleModel.getDataType(); // BorderExtender used for filling the ROI or alpha images border values. this.zeroBorderExtender = BorderExtender.createInstance(BorderExtender.BORDER_ZERO); // Stores the destination no data values. if (destinationNoData == null) { this.destinationNoDataDouble = DEFAULT_DESTINATION_NO_DATA_VALUE; switch (dataType) { case DataBuffer.TYPE_BYTE: this.destinationNoDataInt = new int[numSources]; Arrays.fill(this.destinationNoDataInt, Integer.MIN_VALUE); this.destinationNoDataByte = new byte[numSources]; Arrays.fill(this.destinationNoDataByte, (byte) 0); break; case DataBuffer.TYPE_USHORT: this.destinationNoDataInt = new int[numSources]; Arrays.fill(this.destinationNoDataInt, Integer.MIN_VALUE); this.destinationNoDataUShort = new short[numSources]; Arrays.fill(this.destinationNoDataUShort, (short) 0); break; case DataBuffer.TYPE_SHORT: this.destinationNoDataInt = new int[numSources]; Arrays.fill(this.destinationNoDataInt, Integer.MIN_VALUE); this.destinationNoDataShort = new short[numSources]; Arrays.fill(this.destinationNoDataShort, Short.MIN_VALUE); break; case DataBuffer.TYPE_INT: this.destinationNoDataInt = new int[numSources]; Arrays.fill(this.destinationNoDataInt, Integer.MIN_VALUE); break; case DataBuffer.TYPE_FLOAT: this.destinationNoDataFloat = new float[numSources]; Arrays.fill(this.destinationNoDataFloat, -Float.MAX_VALUE); break; case DataBuffer.TYPE_DOUBLE: Arrays.fill(this.destinationNoDataDouble, -Double.MAX_VALUE); break; default: throw new IllegalArgumentException("Wrong data Type"); } } else { this.destinationNoDataDouble = new double[numBands]; if (destinationNoData.length < numBands) { Arrays.fill(this.destinationNoDataDouble, destinationNoData[0]); } else { System.arraycopy(destinationNoData, 0, this.destinationNoDataDouble, 0, numBands); } switch (dataType) { case DataBuffer.TYPE_BYTE: this.destinationNoDataByte = new byte[numBands]; if (destinationNoData.length < numBands) { Arrays.fill(this.destinationNoDataByte, (byte) destinationNoData[0]); } else { for (int i = 0; i < numBands; i++) { this.destinationNoDataByte[i] = (byte) (destinationNoData[i]); } } this.destinationNoDataInt = new int[numBands]; for (int i = 0; i < destinationNoDataInt.length; i++) { destinationNoDataInt[i] = destinationNoDataByte[i]; } break; case DataBuffer.TYPE_USHORT: this.destinationNoDataUShort = new short[numBands]; if (destinationNoData.length < numBands) { Arrays.fill(this.destinationNoDataUShort, (short) ((short) (destinationNoData[0]) & 0xffff)); } else { for (int i = 0; i < numBands; i++) { this.destinationNoDataUShort[i] = (short) ((short) (destinationNoData[i]) & 0xffff); } } this.destinationNoDataInt = new int[numBands]; for (int i = 0; i < destinationNoDataInt.length; i++) { destinationNoDataInt[i] = destinationNoDataUShort[i]; } break; case DataBuffer.TYPE_SHORT: this.destinationNoDataShort = new short[numBands]; if (destinationNoData.length < numBands) { Arrays.fill(this.destinationNoDataShort, (short) destinationNoData[0]); } else { for (int i = 0; i < numBands; i++) { this.destinationNoDataShort[i] = (short) destinationNoData[i]; } } this.destinationNoDataInt = new int[numBands]; for (int i = 0; i < destinationNoDataInt.length; i++) { destinationNoDataInt[i] = destinationNoDataShort[i]; } break; case DataBuffer.TYPE_INT: this.destinationNoDataInt = new int[numBands]; if (destinationNoData.length < numBands) { Arrays.fill(this.destinationNoDataInt, (int) destinationNoData[0]); } else { for (int i = 0; i < numBands; i++) { this.destinationNoDataInt[i] = (int) destinationNoData[i]; } } break; case DataBuffer.TYPE_FLOAT: this.destinationNoDataFloat = new float[numBands]; if (destinationNoData.length < numBands) { Arrays.fill(this.destinationNoDataFloat, (float) destinationNoData[0]); } else { for (int i = 0; i < numBands; i++) { this.destinationNoDataFloat[i] = (float) destinationNoData[i]; } } break; case DataBuffer.TYPE_DOUBLE: break; default: throw new IllegalArgumentException("Wrong data Type"); } } // Value for filling the image border double sourceExtensionBorder = (noDatas != null && noDatas.length > 0 && noDatas[0] != null) ? noDatas[0].getMin().doubleValue() : destinationNoDataDouble[0]; // BorderExtender used for filling the image border with the above // sourceExtensionBorder this.sourceBorderExtender = sourceExtensionBorder == 0.0 ? BorderExtender.createInstance(BorderExtender.BORDER_ZERO) : new BorderExtenderConstant(new double[] { sourceExtensionBorder }); hasNoData = new boolean[numSources]; // This list contains the alpha channel for every source image (if present) List<PlanarImage> alphaList = new ArrayList<PlanarImage>(); // NoDataRangeByte initialization byteLookupTable = new byte[numSources][numBands][256]; // Init the imagemosaic bean imageBeans = new ImageMosaicBean[numSources]; for (int i = 0; i < numSources; i++) { imageBeans[i] = new ImageMosaicBean(); } // This cycle is used for checking if every alpha channel is single banded // and has the same sample model of the source images. Also wrap all the source, // Alpha and ROI images(if present) with a Border operator in order to avoid // the call of the getExtendedData() method. RasterFormatTag[] tags = getRasterFormatTags(); for (int i = 0; i < numSources; i++) { // Selection of the i-th source. RenderedImage img = getSourceImage(i); // Calculation of the padding int[] padding = calculatePadding(img, totalBounds); // Extend the Source image if padding is defined if (padding != null) { ParameterBlock pb = new ParameterBlock(); pb.setSource(img, 0); pb.set(padding[0], 0); pb.set(padding[1], 1); pb.set(padding[2], 2); pb.set(padding[3], 3); pb.set(sourceBorderExtender, 4); // Setting of the padded source to the associated bean RenderedOp create = JAI.create("border", pb); imageBeans[i].setImage(create); } else { imageBeans[i].setImage(img); } // Selection of the alpha channel PlanarImage alpha = alphaExists ? alphaImgs[i] : null; alphaList.add(alpha); ROI roi = roiExists ? rois[i] : null; if (alpha != null) { alphaPresent = true; SampleModel alphaSampleModel = alpha.getSampleModel(); // Padding of the alpha channel // Calculate padding int[] pads = calculatePadding(alpha, totalBounds); // Extend the alpha image if padding is defined if (pads != null) { ParameterBlock pb = new ParameterBlock(); pb.setSource(alpha, 0); pb.set(pads[0], 0); pb.set(pads[1], 1); pb.set(pads[2], 2); pb.set(pads[3], 3); pb.set(zeroBorderExtender, 4); // Setting of the padded alpha to the associated bean RenderedOp create = JAI.create("border", pb); imageBeans[i].setAlphaChannel(create); } else { imageBeans[i].setAlphaChannel(alpha); } if (alphaSampleModel.getNumBands() != 1) { throw new IllegalArgumentException("Alpha bands number must be 1"); } else if (alphaSampleModel.getDataType() != sampleModel.getDataType()) { throw new IllegalArgumentException( "Alpha sample model dataType and Source sample model " + "dataTypes must be equal"); } else if (alphaSampleModel.getSampleSize(0) != sampleModel.getSampleSize(0)) { throw new IllegalArgumentException( "Alpha sample model sampleSize and Source sample model " + "sampleSize must be equal"); } } // If even only one ROI is present, the roiPresent flag is set to True if (roi != null) { roiPresent = true; RenderedImage roiIMG = roi.getAsImage(); // Padding of the ROI image // Calculate padding int[] pads = calculatePadding(roiIMG, totalBounds); // Extend the roi image if padding is defined if (pads != null) { ParameterBlock pb = new ParameterBlock(); pb.setSource(roiIMG, 0); pb.set(pads[0], 0); pb.set(pads[1], 1); pb.set(pads[2], 2); pb.set(pads[3], 3); pb.set(zeroBorderExtender, 4); // Setting of the padded ROI image to the associated bean RenderedOp create = JAI.create("border", pb); imageBeans[i].setRoiImage(create); } else { imageBeans[i].setRoiImage(roiIMG); } imageBeans[i].setRoi(roi); } // set the raster tag imageBeans[i].setRasterFormatTag(tags[i]); // prepare to handle nodata, if any is available Range noDataRange = nodataExists ? noDatas[i] : null; if (noDataRange != null) { RenderedImage image = imageBeans[i].getImage(); Range expandedNoDataRage = RasterAccessorExt.expandNoData(noDataRange, tags[i], image, this); // convert the range to the target type, we might find it's null and if not, // we'll get an optimized contains method to play against int formatDataType = tags[i].getFormatTagID() & RasterAccessorExt.DATATYPE_MASK; Range convertedNoDataRange = RangeFactory.convert(expandedNoDataRage, formatDataType); if (convertedNoDataRange != null) { hasNoData[i] = true; imageBeans[i].setSourceNoData(convertedNoDataRange); if (RasterAccessorExt.isPaletteExpansionRequired(image, tags[i].getFormatTagID())) { // no way to guarantee that the index in the palette can be turned into a // RGB // nodata, just a few cases where it breaks: // - the palette has a color in the nodata element that's a valid one // - the palette uses a color that's the output nodata as a valid value // We turn the nodata into a ROI, and if the ROI is already set, we // intersect it // force a binary image ImageLayout il = new ImageLayout(); byte[] arr = { (byte) 0, (byte) 0xff }; ColorModel binaryCm = new IndexColorModel(1, 2, arr, arr, arr); SampleModel binarySm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, image.getWidth(), image.getHeight(), 1); il.setColorModel(binaryCm); il.setSampleModel(binarySm); RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, il); hints.put(JAI.KEY_TRANSFORM_ON_COLORMAP, false); LookupTableJAI lt = buildNoDataLookupTable(dataType, noDataRange); ParameterBlock pb = new ParameterBlock(); pb.setSource(image, 0); pb.set(lt, 0); RenderedOp noDataMask = JAI.create("lookup", pb, hints); noDataMask.getTile(0, 0); ROI noDataRoi = new ROI(noDataMask); if (imageBeans[i].getRoi() == null) { roiPresent = true; imageBeans[i].setRoi(noDataRoi); imageBeans[i].setRoiImage(noDataRoi.getAsImage()); } else { ROI intersection = noDataRoi.intersect(imageBeans[i].getRoi()); imageBeans[i].setRoi(intersection); imageBeans[i].setRoiImage(intersection.getAsImage()); } // we transformed the nodata into the ROI imageBeans[i].setSourceNoData(null); hasNoData[i] = false; } else if (dataType == DataBuffer.TYPE_BYTE) { // selection of the no data range for byte values Range noDataByte = expandedNoDataRage; // The lookup table is filled with the related no data or valid data for // every // value for (int b = 0; b < numBands; b++) { for (int z = 0; z < byteLookupTable[i][0].length; z++) { if (noDataByte != null && noDataByte.contains(z)) { byteLookupTable[i][b][z] = destinationNoDataByte[b]; } else { byteLookupTable[i][b][z] = (byte) z; } } } } } } } // compute the destination tag rasterFormatTag = tags[getNumSources()]; if (!this.isAlphaBitmaskUsed) { for (int i = 0; i < numSources; i++) { if (alphaList.get(i) == null) { this.isAlphaBitmaskUsed = true; break; } } } } private LookupTable buildNoDataLookupTable(int dataType, Range noDataRange) { byte[] table; switch (dataType) { case DataBuffer.TYPE_BYTE: table = new byte[256]; for (int i = 0; i < table.length; i++) { if (noDataRange.contains(i)) { table[i] = 0; } else { table[i] = 1; } } break; case DataBuffer.TYPE_USHORT: table = new byte[65536]; for (int i = 0; i < table.length; i++) { if (noDataRange.contains(i)) { table[i] = 0; } else { table[i] = 1; } } break; default: throw new IllegalArgumentException( "Unable to handle a index color model based on data type " + dataType); } return LookupTableFactory.create(table); } private RasterFormatTag[] getRasterFormatTags() { final int numSources = getNumSources(); RenderedImage[] sources = new RenderedImage[numSources]; for (int i = 0; i < numSources; i++) { sources[i] = getSourceImage(i); } return RasterAccessorExt.findCompatibleTags(sources, this); } /** * Method for calculating the padding between the total mosaic bounds and the bounds of the * input source. * * @param src * @param totalBounds * @return */ private int[] calculatePadding(RenderedImage src, Rectangle totalBounds) { // Calculation of the padding to set in order toexpand the input // image to the Rectangle dimensions int deltaX0 = (src.getMinX() - totalBounds.x); int leftP = deltaX0 > 0 ? deltaX0 : 0; int deltaY0 = (src.getMinY() - totalBounds.y); int topP = deltaY0 > 0 ? deltaY0 : 0; int deltaX1 = (totalBounds.x + totalBounds.width - src.getMinX() - src.getWidth()); int rightP = deltaX1 > 0 ? deltaX1 : 0; int deltaY1 = (totalBounds.y + totalBounds.height - src.getMinY() - src.getHeight()); int bottomP = deltaY1 > 0 ? deltaY1 : 0; if ((leftP + rightP + topP + bottomP) == 0) { // no padding return null return null; } return new int[] { leftP, rightP, topP, bottomP }; } /** * This method overrides the OpImage compute tile method and calculates the mosaic operation for * the selected tile. */ public Raster computeTile(int tileX, int tileY) { // The destination raster is created as WritableRaster WritableRaster destRaster = createWritableRaster(sampleModel, new Point(tileXToX(tileX), tileYToY(tileY))); // This method calculates the tile active area. Rectangle destRectangle = getTileRect(tileX, tileY); // Stores the source image number int numSources = getNumSources(); // Initialization of a new RasterBean for passing all the raster information // to the compute rect method Raster[] sourceRasters = new Raster[numSources]; RasterFormatTag[] sourceTags = new RasterFormatTag[numSources]; ColorModel[] sourceColorModels = new ColorModel[numSources]; Raster[] alphaRasters = new Raster[numSources]; Raster[] roiRasters = new Raster[numSources]; Range[] noDataRanges = new Range[numSources]; ColorModel[] alphaChannelColorModels = new ColorModel[numSources]; // The previous array is filled with the source raster data int intersectingSourceCount = 0; for (int i = 0; i < numSources; i++) { PlanarImage source = getSourceImage(i); Rectangle srcRect = mapDestRect(destRectangle, i); Raster data = null; // First, check if the source mapped rectangle is not empty if (!(srcRect != null && srcRect.isEmpty())) { // Get the source data from the source or the padded image. if (source.getBounds().contains(destRectangle)) { data = source.getData(destRectangle); } else { data = imageBeans[i].getImage().getData(destRectangle); } } // Raster bean initialization // If the data are present then we can check if Alpha and ROI are present if (data != null) { sourceRasters[intersectingSourceCount] = data; sourceTags[intersectingSourceCount] = imageBeans[i].getRasterFormatTag(); sourceColorModels[intersectingSourceCount] = imageBeans[i].getColorModel(); noDataRanges[intersectingSourceCount] = imageBeans[i].getSourceNoData(); // Get the Alpha data from the padded alpha image if present PlanarImage alpha = imageBeans[i].getAlphaChannel(); if (alphaPresent && alpha != null) { alphaRasters[intersectingSourceCount] = alpha.getData(destRectangle); alphaChannelColorModels[intersectingSourceCount] = imageBeans[i].getAlphaChannel().getColorModel(); } // Get the ROI data from the padded ROI image if present RenderedImage roi = imageBeans[i].getRoiImage(); if (roiPresent && roi != null) { roiRasters[intersectingSourceCount] = roi.getData(destRectangle); } intersectingSourceCount++; } } // For the given source destination rasters, the mosaic is calculated computeRect(sourceRasters, sourceTags, sourceColorModels, destRaster, destRectangle, alphaRasters, roiRasters, noDataRanges, alphaChannelColorModels, intersectingSourceCount); // Tile recycling if the Recycle is present for (int i = 0; i < intersectingSourceCount; i++) { Raster sourceData = sourceRasters[i]; if (sourceData != null) { PlanarImage source = getSourceImage(i); if (source.overlapsMultipleTiles(sourceData.getBounds())) { recycleTile(sourceData); } } } return destRaster; } private void computeRect(Raster[] sourceRasters, RasterFormatTag[] rasterFormatTags, ColorModel[] sourceColorModels, WritableRaster destRaster, Rectangle destRectangle, Raster[] alphaRasters, Raster[] roiRasters, Range[] noDataRanges, ColorModel[] alphaChannelColorModels, int sourcesNumber) { // if all null, just return a constant image if (sourcesNumber == 0) { ImageUtil.fillBackground(destRaster, destRectangle, destinationNoDataDouble); return; } // Creates source accessors bean array (a new bean) RasterBeanAccessor[] sourceAccessorsArrayBean = new RasterBeanAccessor[sourcesNumber]; // The above array is filled with image data, roi, alpha and no data ranges for (int i = 0; i < sourcesNumber; i++) { // RasterAccessorBean temporary file RasterBeanAccessor helpAccessor = new RasterBeanAccessor(); if (sourceRasters[i] != null) { helpAccessor.setDataRasterAccessor(new RasterAccessorExt(sourceRasters[i], destRectangle, rasterFormatTags[i], sourceColorModels[i], getNumBands(), getSampleModel().getDataType())); } Raster alphaRaster = alphaRasters[i]; if (alphaRaster != null) { SampleModel alphaSampleModel = alphaRaster.getSampleModel(); int alphaFormatTagID = RasterAccessor.findCompatibleTag(null, alphaSampleModel); RasterFormatTag alphaFormatTag = new RasterFormatTag(alphaSampleModel, alphaFormatTagID); helpAccessor.setAlphaRasterAccessor(new RasterAccessor(alphaRaster, destRectangle, alphaFormatTag, alphaChannelColorModels[i])); } helpAccessor.setRoiRaster(roiRasters[i]); helpAccessor.setSourceNoDataRangeRasterAccessor(noDataRanges[i]); sourceAccessorsArrayBean[i] = helpAccessor; } // Create dest accessor. RasterAccessor destinationAccessor = new RasterAccessor(destRaster, destRectangle, rasterFormatTag, null); // This method calculates the mosaic of the source images and stores the // result in the destination // accessor int dataType = destinationAccessor.getDataType(); switch (dataType) { case DataBuffer.TYPE_BYTE: byteLoop(sourceAccessorsArrayBean, destinationAccessor); break; case DataBuffer.TYPE_USHORT: ushortLoop(sourceAccessorsArrayBean, destinationAccessor); break; case DataBuffer.TYPE_SHORT: shortLoop(sourceAccessorsArrayBean, destinationAccessor); break; case DataBuffer.TYPE_INT: intLoop(sourceAccessorsArrayBean, destinationAccessor); break; case DataBuffer.TYPE_FLOAT: floatLoop(sourceAccessorsArrayBean, destinationAccessor); break; case DataBuffer.TYPE_DOUBLE: doubleLoop(sourceAccessorsArrayBean, destinationAccessor); break; } // the data are copied back to the destination raster destinationAccessor.copyDataToRaster(); } private void byteLoop(RasterBeanAccessor[] srcBean, RasterAccessor dst) { // Stores the source number final int sourcesNumber = srcBean.length; // From every source all the LineStride, PixelStride, LineOffsets, // PixelOffsets and Band Offset are initialized final int[] srcLineStride = new int[sourcesNumber]; final int[] srcPixelStride = new int[sourcesNumber]; final int[][] srcBandOffsets = new int[sourcesNumber][]; final int[] sLineOffsets = new int[sourcesNumber]; final int[] sPixelOffsets = new int[sourcesNumber]; // Source data creation with null values final byte[][][] srcDataByte = new byte[sourcesNumber][][]; // Alpha Channel creation final byte[][][] alfaDataByte; // Destination data creation final byte[][] dstDataByte = dst.getByteDataArrays(); // Source data per band creation final byte[][] sBandDataByte = new byte[sourcesNumber][]; // Alpha data per band creation final byte[][] aBandDataByte; // Check if the alpha is used in the selected raster. boolean alphaPresentinRaster = false; for (int i = 0; i < sourcesNumber; i++) { if (srcBean[i].getAlphaRasterAccessor() != null) { alphaPresentinRaster = true; break; } } // LineStride, PixelStride, BandOffset, LineOffset, PixelOffset for the // alpha channel final int[] alfaLineStride; final int[] alfaPixelStride; final int[][] alfaBandOffsets; final int[] aLineOffsets; final int[] aPixelOffsets; if (alphaPresentinRaster) { // The above alpha arrays are allocated only if the alpha channel is // present alfaLineStride = new int[sourcesNumber]; alfaPixelStride = new int[sourcesNumber]; alfaBandOffsets = new int[sourcesNumber][]; aLineOffsets = new int[sourcesNumber]; aPixelOffsets = new int[sourcesNumber]; alfaDataByte = new byte[sourcesNumber][][]; aBandDataByte = new byte[sourcesNumber][]; } else { alfaLineStride = null; alfaPixelStride = null; alfaBandOffsets = null; aLineOffsets = null; aPixelOffsets = null; alfaDataByte = null; aBandDataByte = null; } // Weight type arrays can have different weight types if ROI or alpha // channel are present or not final WeightType[] weightTypesUsed = new WeightType[sourcesNumber]; // The above arrays are filled with the data from the Java Raster // AcessorBean. for (int i = 0; i < sourcesNumber; i++) { weightTypesUsed[i] = WeightType.WEIGHT_TYPE_NODATA; final RasterAccessor dataRA = srcBean[i].getDataRasterAccessor(); if (dataRA != null) { srcLineStride[i] = dataRA.getScanlineStride(); srcPixelStride[i] = dataRA.getPixelStride(); srcBandOffsets[i] = dataRA.getBandOffsets(); // Data retrieval srcDataByte[i] = dataRA.getByteDataArrays(); final RasterAccessor alphaRA = srcBean[i].getAlphaRasterAccessor(); if (alphaPresentinRaster & alphaRA != null) { alfaDataByte[i] = alphaRA.getByteDataArrays(); alfaBandOffsets[i] = alphaRA.getBandOffsets(); alfaPixelStride[i] = alphaRA.getPixelStride(); alfaLineStride[i] = alphaRA.getScanlineStride(); } if (alphaRA != null) { // If alpha channel is present alpha weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ALPHA; } else if (roiPresent && srcBean[i].getRoiRaster() != null) { // Else if ROI is present, then roi weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ROI; } } } // Destination information are taken from the destination accessor final int dstMinX = dst.getX(); final int dstMinY = dst.getY(); final int dstWidth = dst.getWidth(); final int dstHeight = dst.getHeight(); final int dstMaxX = dstMinX + dstWidth; final int dstMaxY = dstMinY + dstHeight; final int dstBands = dst.getNumBands(); final int dstLineStride = dst.getScanlineStride(); final int dstPixelStride = dst.getPixelStride(); final int[] dstBandOffsets = dst.getBandOffsets(); // COMPUTATION LEVEL for (int b = 0; b < dstBands; b++) { // For all the Bands // The data value are taken for every band for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { // source band data sBandDataByte[s] = srcDataByte[s][b]; // The offset is initialized sLineOffsets[s] = srcBandOffsets[s][b]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { // The alpha value are taken only from the first band (this // happens because the raster // accessor provides the data array with the band data even if // the alpha channel has only // one band. aBandDataByte[s] = alfaDataByte[s][0]; aLineOffsets[s] = alfaBandOffsets[s][0]; } } // The destination data band are selected byte[] dBandDataByte = dstDataByte[b]; // the destination lineOffset is initialized int dLineOffset = dstBandOffsets[b]; if (mosaicTypeSelected == MosaicDescriptor.MOSAIC_TYPE_OVERLAY) { for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // For all the Y // values // Source line Offset and pixel Offset, // Alpha line Offset and pixel Offset are initialized for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (srcBean[s].getAlphaRasterAccessor() != null) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // For all // the X // values // The destination flag is initialized to false and changes // to true only // if one pixel alpha channel is not 0 or falls into an // image ROI or is not a NoData boolean setDestinationFlag = false; for (int s = 0; s < sourcesNumber; s++) { final RasterAccessor dataRA = srcBean[s].getDataRasterAccessor(); if (dataRA == null) { continue; } // The source valuse are initialized only for the switch // method int sourceValueByte = sBandDataByte[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // the flag checks if the pixel is a noData boolean isData = true; if (hasNoData[s]) { isData = !(byteLookupTable[s][b][sourceValueByte & 0xFF] == destinationNoDataByte[b]); } if (!isData) { setDestinationFlag = false; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: setDestinationFlag = aBandDataByte[s][aPixelOffsets[s]] != 0; aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: setDestinationFlag = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0; break; default: setDestinationFlag = true; } } // If the flag is True, the related source pixel is // saved in the // destination one and exit from the cycle after // incrementing the offset if (setDestinationFlag) { dBandDataByte[dPixelOffset] = (byte) (sourceValueByte & 0xff); for (int k = s + 1; k < sourcesNumber; k++) { if (dataRA != null) { sPixelOffsets[k] += srcPixelStride[k]; } if (srcBean[k].getAlphaRasterAccessor() != null) { aPixelOffsets[k] += alfaPixelStride[k]; } } break; } } // If the flag is false for every source, the destinationb // no data value is // set to the related destination pixel and then updates the // offset if (!setDestinationFlag) { dBandDataByte[dPixelOffset] = destinationNoDataByte[b]; } dPixelOffset += dstPixelStride; } } } else { // the mosaicType is MOSAIC_TYPE_BLEND for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // Source and pixel Offset are initialized and Source and alpha // line offset are // translated (cycle accross all the sources) for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // In the blending operation the destination pixel value is // calculated // as sum of the weighted source pixel / sum of weigth. double numerator = 0.0; double denominator = 0.0; for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() == null) { continue; } // The source valuse are initialized only for the switch // method int sourceValueByte = sBandDataByte[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // The weight is calculated for every pixel double weight = 0.0F; boolean isData = true; // If no alpha channel or Roi is present, the weight // is set to 1 or 0 if the pixel has // or not a No Data value if (hasNoData[s]) { isData = !(byteLookupTable[s][b][sourceValueByte & 0xFF] == destinationNoDataByte[b]); } if (!isData) { weight = 0F; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: weight = (aBandDataByte[s][aPixelOffsets[s]] & 0xff); if (weight > 0.0F && isAlphaBitmaskUsed) { weight = 1.0F; } else { weight /= 255.0F; } aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: weight = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0 ? 1.0F : 0.0F; break; default: weight = 1.0F; } } // The above calculated weight are added to the // numerator and denominator numerator += (weight * (sourceValueByte & 0xff)); denominator += weight; } // If the weighted sum is 0 the destination pixel value // takes the destination no data. // If the sum is not 0 the value is added to the related // destination pixel if (denominator == 0.0) { dBandDataByte[dPixelOffset] = destinationNoDataByte[b]; } else { dBandDataByte[dPixelOffset] = ImageUtil .clampRoundByte(numerator / denominator); } // Offset update dPixelOffset += dstPixelStride; } } } } } private void ushortLoop(RasterBeanAccessor[] srcBean, RasterAccessor dst) { // Stores the source number final int sourcesNumber = srcBean.length; // From every source all the LineStride, PixelStride, LineOffsets, // PixelOffsets and Band Offset are initialized final int[] srcLineStride = new int[sourcesNumber]; final int[] srcPixelStride = new int[sourcesNumber]; final int[][] srcBandOffsets = new int[sourcesNumber][]; final int[] sLineOffsets = new int[sourcesNumber]; final int[] sPixelOffsets = new int[sourcesNumber]; // Source data creation with null values final short[][][] srcDataUshort = new short[sourcesNumber][][]; // Alpha Channel creation final short[][][] alfaDataUshort; // Destination data creation final short[][] dstDataUshort = dst.getShortDataArrays(); // Source data per band creation final short[][] sBandDataUshort = new short[sourcesNumber][]; // Alpha data per band creation final short[][] aBandDataUshort; // Check if the alpha is used in the selected raster. boolean alphaPresentinRaster = false; for (int i = 0; i < sourcesNumber; i++) { if (srcBean[i].getAlphaRasterAccessor() != null) { alphaPresentinRaster = true; break; } } // LineStride, PixelStride, BandOffset, LineOffset, PixelOffset for the // alpha channel final int[] alfaLineStride; final int[] alfaPixelStride; final int[][] alfaBandOffsets; final int[] aLineOffsets; final int[] aPixelOffsets; if (alphaPresentinRaster) { // The above alpha arrays are allocated only if the alpha channel is // present alfaLineStride = new int[sourcesNumber]; alfaPixelStride = new int[sourcesNumber]; alfaBandOffsets = new int[sourcesNumber][]; aLineOffsets = new int[sourcesNumber]; aPixelOffsets = new int[sourcesNumber]; alfaDataUshort = new short[sourcesNumber][][]; aBandDataUshort = new short[sourcesNumber][]; } else { alfaLineStride = null; alfaPixelStride = null; alfaBandOffsets = null; aLineOffsets = null; aPixelOffsets = null; alfaDataUshort = null; aBandDataUshort = null; } // Weight type arrays can have different weight types if ROI or alpha // channel are present or not final WeightType[] weightTypesUsed = new WeightType[sourcesNumber]; // The above arrays are filled with the data from the Java Raster // AcessorBean. for (int i = 0; i < sourcesNumber; i++) { weightTypesUsed[i] = WeightType.WEIGHT_TYPE_NODATA; final RasterAccessor dataRA = srcBean[i].getDataRasterAccessor(); if (dataRA != null) { srcLineStride[i] = dataRA.getScanlineStride(); srcPixelStride[i] = dataRA.getPixelStride(); srcBandOffsets[i] = dataRA.getBandOffsets(); // Data retrieval srcDataUshort[i] = dataRA.getShortDataArrays(); final RasterAccessor alphaRA = srcBean[i].getAlphaRasterAccessor(); if (alphaPresentinRaster & alphaRA != null) { alfaDataUshort[i] = alphaRA.getShortDataArrays(); alfaBandOffsets[i] = alphaRA.getBandOffsets(); alfaPixelStride[i] = alphaRA.getPixelStride(); alfaLineStride[i] = alphaRA.getScanlineStride(); } if (alphaRA != null) { // If alpha channel is present alpha weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ALPHA; } else if (roiPresent && srcBean[i].getRoiRaster() != null) { // Else if ROI is present, then roi weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ROI; } } } // Destination information are taken from the destination accessor final int dstMinX = dst.getX(); final int dstMinY = dst.getY(); final int dstWidth = dst.getWidth(); final int dstHeight = dst.getHeight(); final int dstMaxX = dstMinX + dstWidth; final int dstMaxY = dstMinY + dstHeight; final int dstBands = dst.getNumBands(); final int dstLineStride = dst.getScanlineStride(); final int dstPixelStride = dst.getPixelStride(); final int[] dstBandOffsets = dst.getBandOffsets(); // COMPUTATION LEVEL for (int b = 0; b < dstBands; b++) { // For all the Bands // The data value are taken for every band for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { // source band data sBandDataUshort[s] = srcDataUshort[s][b]; // The offset is initialized sLineOffsets[s] = srcBandOffsets[s][b]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { // The alpha value are taken only from the first band (this // happens because the raster // accessor provides the data array with the band data even if // the alpha channel has only // one band. aBandDataUshort[s] = alfaDataUshort[s][0]; aLineOffsets[s] = alfaBandOffsets[s][0]; } } // The destination data band are selected short[] dBandDataUshort = dstDataUshort[b]; ; // the destination lineOffset is initialized int dLineOffset = dstBandOffsets[b]; if (mosaicTypeSelected == MosaicDescriptor.MOSAIC_TYPE_OVERLAY) { for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // For all the Y // values // Source line Offset and pixel Offset, // Alpha line Offset and pixel Offset are initialized for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (srcBean[s].getAlphaRasterAccessor() != null) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // For all // the X // values // The destination flag is initialized to false and changes // to true only // if one pixel alpha channel is not 0 or falls into an // image ROI or is not a NoData boolean setDestinationFlag = false; for (int s = 0; s < sourcesNumber; s++) { final RasterAccessor dataRA = srcBean[s].getDataRasterAccessor(); if (dataRA == null) { continue; } // The source valuse are initialized only for the switch // method short value = sBandDataUshort[s][sPixelOffsets[s]]; int sourceValueUshort = (value & 0xffff); // Offset update sPixelOffsets[s] += srcPixelStride[s]; // the flag checks if the pixel is a noData boolean isData = true; if (hasNoData[s]) { Range noDataRangeUShort = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); ; isData = !noDataRangeUShort.contains(sourceValueUshort); } if (!isData) { setDestinationFlag = false; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: setDestinationFlag = aBandDataUshort[s][aPixelOffsets[s]] != 0; aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: setDestinationFlag = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0; break; default: setDestinationFlag = true; } } // If the flag is True, the related source pixel is // saved in the // destination one and exit from the cycle after // incrementing the offset if (setDestinationFlag) { dBandDataUshort[dPixelOffset] = value; for (int k = s + 1; k < sourcesNumber; k++) { if (dataRA != null) { sPixelOffsets[k] += srcPixelStride[k]; } if (srcBean[k].getAlphaRasterAccessor() != null) { aPixelOffsets[k] += alfaPixelStride[k]; } } break; } } // If the flag is false for every source, the destinationb // no data value is // set to the related destination pixel and then updates the // offset if (!setDestinationFlag) { dBandDataUshort[dPixelOffset] = destinationNoDataUShort[b]; } dPixelOffset += dstPixelStride; } } } else { // the mosaicType is MOSAIC_TYPE_BLEND for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // Source and pixel Offset are initialized and Source and alpha // line offset are // translated (cycle accross all the sources) for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // In the blending operation the destination pixel value is // calculated // as sum of the weighted source pixel / sum of weigth. double numerator = 0.0; double denominator = 0.0; for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() == null) { continue; } // The source valuse are initialized only for the switch // method short sourceValueUshort = (short) (sBandDataUshort[s][sPixelOffsets[s]] & 0xffff); // Offset update sPixelOffsets[s] += srcPixelStride[s]; // The weight is calculated for every pixel double weight = 0.0F; boolean isData = true; // If no alpha channel or Roi is present, the weight // is set to 1 or 0 if the pixel has // or not a No Data value if (hasNoData[s]) { Range noDataRangeUShort = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); isData = !noDataRangeUShort.contains(sourceValueUshort); } if (!isData) { weight = 0F; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: weight = (aBandDataUshort[s][aPixelOffsets[s]] & 0xffff); if (weight > 0.0F && isAlphaBitmaskUsed) { weight = 1.0F; } else { weight /= 255.0F; } aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: weight = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0 ? 1.0F : 0.0F; break; default: weight = 1.0F; } } // The above calculated weight are added to the // numerator and denominator numerator += (weight * (sourceValueUshort)); denominator += weight; } // If the weighted sum is 0 the destination pixel value // takes the destination no data. // If the sum is not 0 the value is added to the related // destination pixel if (denominator == 0.0) { dBandDataUshort[dPixelOffset] = destinationNoDataUShort[b]; } else { dBandDataUshort[dPixelOffset] = ImageUtil .clampRoundUShort(numerator / denominator); } // Offset update dPixelOffset += dstPixelStride; } } } } } private void shortLoop(RasterBeanAccessor[] srcBean, RasterAccessor dst) { // Stores the source number final int sourcesNumber = srcBean.length; // From every source all the LineStride, PixelStride, LineOffsets, // PixelOffsets and Band Offset are initialized final int[] srcLineStride = new int[sourcesNumber]; final int[] srcPixelStride = new int[sourcesNumber]; final int[][] srcBandOffsets = new int[sourcesNumber][]; final int[] sLineOffsets = new int[sourcesNumber]; final int[] sPixelOffsets = new int[sourcesNumber]; // Source data creation with null values final short[][][] srcDataShort = new short[sourcesNumber][][]; // Alpha Channel creation final short[][][] alfaDataShort; // Destination data creation final short[][] dstDataShort = dst.getShortDataArrays(); // Source data per band creation final short[][] sBandDataShort = new short[sourcesNumber][]; // Alpha data per band creation final short[][] aBandDataShort; // Check if the alpha is used in the selected raster. boolean alphaPresentinRaster = false; for (int i = 0; i < sourcesNumber; i++) { if (srcBean[i].getAlphaRasterAccessor() != null) { alphaPresentinRaster = true; break; } } // LineStride, PixelStride, BandOffset, LineOffset, PixelOffset for the // alpha channel final int[] alfaLineStride; final int[] alfaPixelStride; final int[][] alfaBandOffsets; final int[] aLineOffsets; final int[] aPixelOffsets; if (alphaPresentinRaster) { // The above alpha arrays are allocated only if the alpha channel is // present alfaLineStride = new int[sourcesNumber]; alfaPixelStride = new int[sourcesNumber]; alfaBandOffsets = new int[sourcesNumber][]; aLineOffsets = new int[sourcesNumber]; aPixelOffsets = new int[sourcesNumber]; alfaDataShort = new short[sourcesNumber][][]; aBandDataShort = new short[sourcesNumber][]; } else { alfaLineStride = null; alfaPixelStride = null; alfaBandOffsets = null; aLineOffsets = null; aPixelOffsets = null; alfaDataShort = null; aBandDataShort = null; } // Weight type arrays can have different weight types if ROI or alpha // channel are present or not final WeightType[] weightTypesUsed = new WeightType[sourcesNumber]; // The above arrays are filled with the data from the Java Raster // AcessorBean. for (int i = 0; i < sourcesNumber; i++) { weightTypesUsed[i] = WeightType.WEIGHT_TYPE_NODATA; final RasterAccessor dataRA = srcBean[i].getDataRasterAccessor(); if (dataRA != null) { srcLineStride[i] = dataRA.getScanlineStride(); srcPixelStride[i] = dataRA.getPixelStride(); srcBandOffsets[i] = dataRA.getBandOffsets(); // Data retrieval srcDataShort[i] = dataRA.getShortDataArrays(); final RasterAccessor alphaRA = srcBean[i].getAlphaRasterAccessor(); if (alphaPresentinRaster & alphaRA != null) { alfaDataShort[i] = alphaRA.getShortDataArrays(); alfaBandOffsets[i] = alphaRA.getBandOffsets(); alfaPixelStride[i] = alphaRA.getPixelStride(); alfaLineStride[i] = alphaRA.getScanlineStride(); } if (alphaRA != null) { // If alpha channel is present alpha weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ALPHA; } else if (roiPresent && srcBean[i].getRoiRaster() != null) { // Else if ROI is present, then roi weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ROI; } } } // Destination information are taken from the destination accessor final int dstMinX = dst.getX(); final int dstMinY = dst.getY(); final int dstWidth = dst.getWidth(); final int dstHeight = dst.getHeight(); final int dstMaxX = dstMinX + dstWidth; final int dstMaxY = dstMinY + dstHeight; final int dstBands = dst.getNumBands(); final int dstLineStride = dst.getScanlineStride(); final int dstPixelStride = dst.getPixelStride(); final int[] dstBandOffsets = dst.getBandOffsets(); // COMPUTATION LEVEL for (int b = 0; b < dstBands; b++) { // For all the Bands // The data value are taken for every band for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { // source band data sBandDataShort[s] = srcDataShort[s][b]; // The offset is initialized sLineOffsets[s] = srcBandOffsets[s][b]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { // The alpha value are taken only from the first band (this // happens because the raster // accessor provides the data array with the band data even if // the alpha channel has only // one band. aBandDataShort[s] = alfaDataShort[s][0]; aLineOffsets[s] = alfaBandOffsets[s][0]; } } // The destination data band are selected short[] dBandDataShort = dstDataShort[b]; // the destination lineOffset is initialized int dLineOffset = dstBandOffsets[b]; if (mosaicTypeSelected == MosaicDescriptor.MOSAIC_TYPE_OVERLAY) { for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // For all the Y // values // Source line Offset and pixel Offset, // Alpha line Offset and pixel Offset are initialized for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (srcBean[s].getAlphaRasterAccessor() != null) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // For all // the X // values // The destination flag is initialized to false and changes // to true only // if one pixel alpha channel is not 0 or falls into an // image ROI or is not a NoData boolean setDestinationFlag = false; for (int s = 0; s < sourcesNumber; s++) { final RasterAccessor dataRA = srcBean[s].getDataRasterAccessor(); if (dataRA == null) { continue; } // The source valuse are initialized only for the switch // method short sourceValueShort = sBandDataShort[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // the flag checks if the pixel is a noData boolean isData = true; if (hasNoData[s]) { Range noDataRangeShort = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); isData = !noDataRangeShort.contains(sourceValueShort); } if (!isData) { setDestinationFlag = false; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: setDestinationFlag = aBandDataShort[s][aPixelOffsets[s]] != 0; aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: setDestinationFlag = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0; break; default: setDestinationFlag = true; } } // If the flag is True, the related source pixel is // saved in the // destination one and exit from the cycle after // incrementing the offset if (setDestinationFlag) { dBandDataShort[dPixelOffset] = sourceValueShort; for (int k = s + 1; k < sourcesNumber; k++) { if (dataRA != null) { sPixelOffsets[k] += srcPixelStride[k]; } if (srcBean[k].getAlphaRasterAccessor() != null) { aPixelOffsets[k] += alfaPixelStride[k]; } } break; } } // If the flag is false for every source, the destinationb // no data value is // set to the related destination pixel and then updates the // offset if (!setDestinationFlag) { dBandDataShort[dPixelOffset] = destinationNoDataShort[b]; } dPixelOffset += dstPixelStride; } } } else { // the mosaicType is MOSAIC_TYPE_BLEND for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // Source and pixel Offset are initialized and Source and alpha // line offset are // translated (cycle accross all the sources) for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // In the blending operation the destination pixel value is // calculated // as sum of the weighted source pixel / sum of weigth. double numerator = 0.0; double denominator = 0.0; for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() == null) { continue; } // The source valuse are initialized only for the switch // method short sourceValueShort = sBandDataShort[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // The weight is calculated for every pixel double weight = 0.0F; boolean isData = true; // If no alpha channel or Roi is present, the weight // is set to 1 or 0 if the pixel has // or not a No Data value if (hasNoData[s]) { Range noDataRangeShort = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); isData = !noDataRangeShort.contains(sourceValueShort); } if (!isData) { weight = 0F; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: weight = (aBandDataShort[s][aPixelOffsets[s]]); if (weight > 0.0F && isAlphaBitmaskUsed) { weight = 1.0F; } else { weight /= 255.0F; } aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: weight = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0 ? 1.0F : 0.0F; break; default: weight = 1.0F; } } // The above calculated weight are added to the // numerator and denominator numerator += (weight * (sourceValueShort)); denominator += weight; } // If the weighted sum is 0 the destination pixel value // takes the destination no data. // If the sum is not 0 the value is added to the related // destination pixel if (denominator == 0.0) { dBandDataShort[dPixelOffset] = destinationNoDataShort[b]; } else { dBandDataShort[dPixelOffset] = ImageUtil .clampRoundShort(numerator / denominator); } // Offset update dPixelOffset += dstPixelStride; } } } } } private void intLoop(RasterBeanAccessor[] srcBean, RasterAccessor dst) { // Stores the source number final int sourcesNumber = srcBean.length; // From every source all the LineStride, PixelStride, LineOffsets, // PixelOffsets and Band Offset are initialized final int[] srcLineStride = new int[sourcesNumber]; final int[] srcPixelStride = new int[sourcesNumber]; final int[][] srcBandOffsets = new int[sourcesNumber][]; final int[] sLineOffsets = new int[sourcesNumber]; final int[] sPixelOffsets = new int[sourcesNumber]; // Source data creation with null values final int[][][] srcDataInt = new int[sourcesNumber][][]; // Alpha Channel creation final int[][][] alfaDataInt; // Destination data creation final int[][] dstDataInt = dst.getIntDataArrays(); // Source data per band creation final int[][] sBandDataInt = new int[sourcesNumber][]; // Alpha data per band creation final int[][] aBandDataInt; // Check if the alpha is used in the selected raster. boolean alphaPresentinRaster = false; for (int i = 0; i < sourcesNumber; i++) { if (srcBean[i].getAlphaRasterAccessor() != null) { alphaPresentinRaster = true; break; } } // LineStride, PixelStride, BandOffset, LineOffset, PixelOffset for the // alpha channel final int[] alfaLineStride; final int[] alfaPixelStride; final int[][] alfaBandOffsets; final int[] aLineOffsets; final int[] aPixelOffsets; if (alphaPresentinRaster) { // The above alpha arrays are allocated only if the alpha channel is // present alfaLineStride = new int[sourcesNumber]; alfaPixelStride = new int[sourcesNumber]; alfaBandOffsets = new int[sourcesNumber][]; aLineOffsets = new int[sourcesNumber]; aPixelOffsets = new int[sourcesNumber]; alfaDataInt = new int[sourcesNumber][][]; aBandDataInt = new int[sourcesNumber][]; } else { alfaLineStride = null; alfaPixelStride = null; alfaBandOffsets = null; aLineOffsets = null; aPixelOffsets = null; alfaDataInt = null; aBandDataInt = null; } // Weight type arrays can have different weight types if ROI or alpha // channel are present or not final WeightType[] weightTypesUsed = new WeightType[sourcesNumber]; // The above arrays are filled with the data from the Java Raster // AcessorBean. for (int i = 0; i < sourcesNumber; i++) { weightTypesUsed[i] = WeightType.WEIGHT_TYPE_NODATA; final RasterAccessor dataRA = srcBean[i].getDataRasterAccessor(); if (dataRA != null) { srcLineStride[i] = dataRA.getScanlineStride(); srcPixelStride[i] = dataRA.getPixelStride(); srcBandOffsets[i] = dataRA.getBandOffsets(); // Data retrieval srcDataInt[i] = dataRA.getIntDataArrays(); final RasterAccessor alphaRA = srcBean[i].getAlphaRasterAccessor(); if (alphaPresentinRaster & alphaRA != null) { alfaDataInt[i] = alphaRA.getIntDataArrays(); alfaBandOffsets[i] = alphaRA.getBandOffsets(); alfaPixelStride[i] = alphaRA.getPixelStride(); alfaLineStride[i] = alphaRA.getScanlineStride(); } if (alphaRA != null) { // If alpha channel is present alpha weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ALPHA; } else if (roiPresent && srcBean[i].getRoiRaster() != null) { // Else if ROI is present, then roi weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ROI; } } } // Destination information are taken from the destination accessor final int dstMinX = dst.getX(); final int dstMinY = dst.getY(); final int dstWidth = dst.getWidth(); final int dstHeight = dst.getHeight(); final int dstMaxX = dstMinX + dstWidth; final int dstMaxY = dstMinY + dstHeight; final int dstBands = dst.getNumBands(); final int dstLineStride = dst.getScanlineStride(); final int dstPixelStride = dst.getPixelStride(); final int[] dstBandOffsets = dst.getBandOffsets(); // COMPUTATION LEVEL for (int b = 0; b < dstBands; b++) { // For all the Bands // The data value are taken for every band for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { // source band data sBandDataInt[s] = srcDataInt[s][b]; // The offset is initialized sLineOffsets[s] = srcBandOffsets[s][b]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { // The alpha value are taken only from the first band (this // happens because the raster // accessor provides the data array with the band data even if // the alpha channel has only // one band. aBandDataInt[s] = alfaDataInt[s][0]; aLineOffsets[s] = alfaBandOffsets[s][0]; } } // The destination data band are selected int[] dBandDataInt = dstDataInt[b]; // the destination lineOffset is initialized int dLineOffset = dstBandOffsets[b]; if (mosaicTypeSelected == MosaicDescriptor.MOSAIC_TYPE_OVERLAY) { for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // For all the Y // values // Source line Offset and pixel Offset, // Alpha line Offset and pixel Offset are initialized for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (srcBean[s].getAlphaRasterAccessor() != null) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // For all // the X // values // The destination flag is initialized to false and changes // to true only // if one pixel alpha channel is not 0 or falls into an // image ROI or is not a NoData boolean setDestinationFlag = false; for (int s = 0; s < sourcesNumber; s++) { final RasterAccessor dataRA = srcBean[s].getDataRasterAccessor(); if (dataRA == null) { continue; } // The source valuse are initialized only for the switch // method int sourceValueInt = sBandDataInt[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // the flag checks if the pixel is a noData boolean isData = true; if (hasNoData[s]) { Range noDataRangeInt = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); isData = !noDataRangeInt.contains(sourceValueInt); } if (!isData) { setDestinationFlag = false; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: setDestinationFlag = aBandDataInt[s][aPixelOffsets[s]] != 0; aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: setDestinationFlag = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0; break; default: setDestinationFlag = true; } } // If the flag is True, the related source pixel is // saved in the // destination one and exit from the cycle after // incrementing the offset if (setDestinationFlag) { dBandDataInt[dPixelOffset] = sourceValueInt; for (int k = s + 1; k < sourcesNumber; k++) { if (dataRA != null) { sPixelOffsets[k] += srcPixelStride[k]; } if (srcBean[k].getAlphaRasterAccessor() != null) { aPixelOffsets[k] += alfaPixelStride[k]; } } break; } } // If the flag is false for every source, the destinationb // no data value is // set to the related destination pixel and then updates the // offset if (!setDestinationFlag) { dBandDataInt[dPixelOffset] = destinationNoDataInt[b]; } dPixelOffset += dstPixelStride; } } } else { // the mosaicType is MOSAIC_TYPE_BLEND for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // Source and pixel Offset are initialized and Source and alpha // line offset are // translated (cycle accross all the sources) for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // In the blending operation the destination pixel value is // calculated // as sum of the weighted source pixel / sum of weigth. double numerator = 0.0; double denominator = 0.0; for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() == null) { continue; } // The source valuse are initialized only for the switch // method int sourceValueInt = sBandDataInt[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // The weight is calculated for every pixel double weight = 0.0F; boolean isData = true; // If no alpha channel or Roi is present, the weight // is set to 1 or 0 if the pixel has // or not a No Data value if (hasNoData[s]) { Range noDataRangeInt = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); isData = !noDataRangeInt.contains(sourceValueInt); } if (!isData) { weight = 0F; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: weight = (aBandDataInt[s][aPixelOffsets[s]]); if (weight > 0.0F && isAlphaBitmaskUsed) { weight = 1.0F; } else { weight /= 255.0F; } aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: weight = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0 ? 1.0F : 0.0F; break; default: weight = 1.0F; } } // The above calculated weight are added to the // numerator and denominator numerator += (weight * (sourceValueInt)); denominator += weight; } // If the weighted sum is 0 the destination pixel value // takes the destination no data. // If the sum is not 0 the value is added to the related // destination pixel if (denominator == 0.0) { dBandDataInt[dPixelOffset] = destinationNoDataInt[b]; } else { dBandDataInt[dPixelOffset] = ImageUtil .clampRoundInt(numerator / denominator); } // Offset update dPixelOffset += dstPixelStride; } } } } } private void floatLoop(RasterBeanAccessor[] srcBean, RasterAccessor dst) { // Stores the source number final int sourcesNumber = srcBean.length; // From every source all the LineStride, PixelStride, LineOffsets, // PixelOffsets and Band Offset are initialized final int[] srcLineStride = new int[sourcesNumber]; final int[] srcPixelStride = new int[sourcesNumber]; final int[][] srcBandOffsets = new int[sourcesNumber][]; final int[] sLineOffsets = new int[sourcesNumber]; final int[] sPixelOffsets = new int[sourcesNumber]; // Source data creation with null values final float[][][] srcDataFloat = new float[sourcesNumber][][]; // Alpha Channel creation final float[][][] alfaDataFloat; // Destination data creation final float[][] dstDataFloat = dst.getFloatDataArrays(); // Source data per band creation final float[][] sBandDataFloat = new float[sourcesNumber][]; // Alpha data per band creation final float[][] aBandDataFloat; // Check if the alpha is used in the selected raster. boolean alphaPresentinRaster = false; for (int i = 0; i < sourcesNumber; i++) { if (srcBean[i].getAlphaRasterAccessor() != null) { alphaPresentinRaster = true; break; } } // LineStride, PixelStride, BandOffset, LineOffset, PixelOffset for the // alpha channel final int[] alfaLineStride; final int[] alfaPixelStride; final int[][] alfaBandOffsets; final int[] aLineOffsets; final int[] aPixelOffsets; if (alphaPresentinRaster) { // The above alpha arrays are allocated only if the alpha channel is // present alfaLineStride = new int[sourcesNumber]; alfaPixelStride = new int[sourcesNumber]; alfaBandOffsets = new int[sourcesNumber][]; aLineOffsets = new int[sourcesNumber]; aPixelOffsets = new int[sourcesNumber]; alfaDataFloat = new float[sourcesNumber][][]; aBandDataFloat = new float[sourcesNumber][]; } else { alfaLineStride = null; alfaPixelStride = null; alfaBandOffsets = null; aLineOffsets = null; aPixelOffsets = null; alfaDataFloat = null; aBandDataFloat = null; } // Weight type arrays can have different weight types if ROI or alpha // channel are present or not final WeightType[] weightTypesUsed = new WeightType[sourcesNumber]; // The above arrays are filled with the data from the Java Raster // AcessorBean. for (int i = 0; i < sourcesNumber; i++) { weightTypesUsed[i] = WeightType.WEIGHT_TYPE_NODATA; final RasterAccessor dataRA = srcBean[i].getDataRasterAccessor(); if (dataRA != null) { srcLineStride[i] = dataRA.getScanlineStride(); srcPixelStride[i] = dataRA.getPixelStride(); srcBandOffsets[i] = dataRA.getBandOffsets(); // Data retrieval srcDataFloat[i] = dataRA.getFloatDataArrays(); final RasterAccessor alphaRA = srcBean[i].getAlphaRasterAccessor(); if (alphaPresentinRaster & alphaRA != null) { alfaDataFloat[i] = alphaRA.getFloatDataArrays(); alfaBandOffsets[i] = alphaRA.getBandOffsets(); alfaPixelStride[i] = alphaRA.getPixelStride(); alfaLineStride[i] = alphaRA.getScanlineStride(); } if (alphaRA != null) { // If alpha channel is present alpha weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ALPHA; } else if (roiPresent && srcBean[i].getRoiRaster() != null) { // Else if ROI is present, then roi weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ROI; } } } // Destination information are taken from the destination accessor final int dstMinX = dst.getX(); final int dstMinY = dst.getY(); final int dstWidth = dst.getWidth(); final int dstHeight = dst.getHeight(); final int dstMaxX = dstMinX + dstWidth; final int dstMaxY = dstMinY + dstHeight; final int dstBands = dst.getNumBands(); final int dstLineStride = dst.getScanlineStride(); final int dstPixelStride = dst.getPixelStride(); final int[] dstBandOffsets = dst.getBandOffsets(); // COMPUTATION LEVEL for (int b = 0; b < dstBands; b++) { // For all the Bands // The data value are taken for every band for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { // source band data sBandDataFloat[s] = srcDataFloat[s][b]; // The offset is initialized sLineOffsets[s] = srcBandOffsets[s][b]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { // The alpha value are taken only from the first band (this // happens because the raster // accessor provides the data array with the band data even if // the alpha channel has only // one band. aBandDataFloat[s] = alfaDataFloat[s][0]; aLineOffsets[s] = alfaBandOffsets[s][0]; } } // The destination data band are selected float[] dBandDataFloat = dstDataFloat[b]; ; // the destination lineOffset is initialized int dLineOffset = dstBandOffsets[b]; if (mosaicTypeSelected == MosaicDescriptor.MOSAIC_TYPE_OVERLAY) { for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // For all the Y // values // Source line Offset and pixel Offset, // Alpha line Offset and pixel Offset are initialized for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (srcBean[s].getAlphaRasterAccessor() != null) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // For all // the X // values // The destination flag is initialized to false and changes // to true only // if one pixel alpha channel is not 0 or falls into an // image ROI or is not a NoData boolean setDestinationFlag = false; for (int s = 0; s < sourcesNumber; s++) { final RasterAccessor dataRA = srcBean[s].getDataRasterAccessor(); if (dataRA == null) { continue; } // The source valuse are initialized only for the switch // method float sourceValueFloat = sBandDataFloat[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // the flag checks if the pixel is a noData boolean isData = true; if (hasNoData[s]) { Range noDataRangeFloat = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); if (noDataRangeFloat != null) { isData = !(noDataRangeFloat.contains(sourceValueFloat)); } } if (!isData) { setDestinationFlag = false; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: setDestinationFlag = aBandDataFloat[s][aPixelOffsets[s]] != 0; aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: setDestinationFlag = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0; break; default: setDestinationFlag = true; } } // If the flag is True, the related source pixel is // saved in the // destination one and exit from the cycle after // incrementing the offset if (setDestinationFlag) { dBandDataFloat[dPixelOffset] = sourceValueFloat; for (int k = s + 1; k < sourcesNumber; k++) { if (dataRA != null) { sPixelOffsets[k] += srcPixelStride[k]; } if (srcBean[k].getAlphaRasterAccessor() != null) { aPixelOffsets[k] += alfaPixelStride[k]; } } break; } } // If the flag is false for every source, the destinationb // no data value is // set to the related destination pixel and then updates the // offset if (!setDestinationFlag) { dBandDataFloat[dPixelOffset] = destinationNoDataFloat[b]; } dPixelOffset += dstPixelStride; } } } else { // the mosaicType is MOSAIC_TYPE_BLEND for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // Source and pixel Offset are initialized and Source and alpha // line offset are // translated (cycle accross all the sources) for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // In the blending operation the destination pixel value is // calculated // as sum of the weighted source pixel / sum of weigth. double numerator = 0.0; double denominator = 0.0; for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() == null) { continue; } // The source valuse are initialized only for the switch // method float sourceValueFloat = sBandDataFloat[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // The weight is calculated for every pixel double weight = 0.0F; boolean isData = true; // If no alpha channel or Roi is present, the weight // is set to 1 or 0 if the pixel has // or not a No Data value if (hasNoData[s]) { Range noDataRangeFloat = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); if (noDataRangeFloat != null) { isData = !(noDataRangeFloat.contains(sourceValueFloat)); } } if (!isData) { weight = 0F; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: weight = aBandDataFloat[s][aPixelOffsets[s]]; if (weight > 0.0F && isAlphaBitmaskUsed) { weight = 1.0F; } else { weight /= 255.0F; } aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: weight = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0 ? 1.0F : 0.0F; break; default: weight = 1.0F; } } // The above calculated weight are added to the // numerator and denominator if (isData) { numerator += (weight * (sourceValueFloat)); } denominator += weight; } // If the weighted sum is 0 the destination pixel value // takes the destination no data. // If the sum is not 0 the value is added to the related // destination pixel if (denominator == 0.0) { dBandDataFloat[dPixelOffset] = destinationNoDataFloat[b]; } else { dBandDataFloat[dPixelOffset] = ImageUtil .clampFloat(numerator / denominator); } // Offset update dPixelOffset += dstPixelStride; } } } } } private void doubleLoop(RasterBeanAccessor[] srcBean, RasterAccessor dst) { // Stores the source number final int sourcesNumber = srcBean.length; // From every source all the LineStride, PixelStride, LineOffsets, // PixelOffsets and Band Offset are initialized final int[] srcLineStride = new int[sourcesNumber]; final int[] srcPixelStride = new int[sourcesNumber]; final int[][] srcBandOffsets = new int[sourcesNumber][]; final int[] sLineOffsets = new int[sourcesNumber]; final int[] sPixelOffsets = new int[sourcesNumber]; // Source data creation with null values final double[][][] srcDataDouble = new double[sourcesNumber][][]; // Alpha Channel creation final double[][][] alfaDataDouble; // Destination data creation final double[][] dstDataDouble = dst.getDoubleDataArrays(); // Source data per band creation final double[][] sBandDataDouble = new double[sourcesNumber][]; // Alpha data per band creation final double[][] aBandDataDouble; // Check if the alpha is used in the selected raster. boolean alphaPresentinRaster = false; for (int i = 0; i < sourcesNumber; i++) { if (srcBean[i].getAlphaRasterAccessor() != null) { alphaPresentinRaster = true; break; } } // LineStride, PixelStride, BandOffset, LineOffset, PixelOffset for the // alpha channel final int[] alfaLineStride; final int[] alfaPixelStride; final int[][] alfaBandOffsets; final int[] aLineOffsets; final int[] aPixelOffsets; if (alphaPresentinRaster) { // The above alpha arrays are allocated only if the alpha channel is // present alfaLineStride = new int[sourcesNumber]; alfaPixelStride = new int[sourcesNumber]; alfaBandOffsets = new int[sourcesNumber][]; aLineOffsets = new int[sourcesNumber]; aPixelOffsets = new int[sourcesNumber]; alfaDataDouble = new double[sourcesNumber][][]; aBandDataDouble = new double[sourcesNumber][]; } else { alfaLineStride = null; alfaPixelStride = null; alfaBandOffsets = null; aLineOffsets = null; aPixelOffsets = null; alfaDataDouble = null; aBandDataDouble = null; } // Weight type arrays can have different weight types if ROI or alpha // channel are present or not final WeightType[] weightTypesUsed = new WeightType[sourcesNumber]; // The above arrays are filled with the data from the Java Raster // AcessorBean. for (int i = 0; i < sourcesNumber; i++) { weightTypesUsed[i] = WeightType.WEIGHT_TYPE_NODATA; final RasterAccessor dataRA = srcBean[i].getDataRasterAccessor(); if (dataRA != null) { srcLineStride[i] = dataRA.getScanlineStride(); srcPixelStride[i] = dataRA.getPixelStride(); srcBandOffsets[i] = dataRA.getBandOffsets(); // Data retrieval srcDataDouble[i] = dataRA.getDoubleDataArrays(); final RasterAccessor alphaRA = srcBean[i].getAlphaRasterAccessor(); if (alphaPresentinRaster & alphaRA != null) { alfaDataDouble[i] = alphaRA.getDoubleDataArrays(); alfaBandOffsets[i] = alphaRA.getBandOffsets(); alfaPixelStride[i] = alphaRA.getPixelStride(); alfaLineStride[i] = alphaRA.getScanlineStride(); } if (alphaRA != null) { // If alpha channel is present alpha weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ALPHA; } else if (roiPresent && srcBean[i].getRoiRaster() != null) { // Else if ROI is present, then roi weight type is used weightTypesUsed[i] = WeightType.WEIGHT_TYPE_ROI; } } } // Destination information are taken from the destination accessor final int dstMinX = dst.getX(); final int dstMinY = dst.getY(); final int dstWidth = dst.getWidth(); final int dstHeight = dst.getHeight(); final int dstMaxX = dstMinX + dstWidth; final int dstMaxY = dstMinY + dstHeight; final int dstBands = dst.getNumBands(); final int dstLineStride = dst.getScanlineStride(); final int dstPixelStride = dst.getPixelStride(); final int[] dstBandOffsets = dst.getBandOffsets(); // COMPUTATION LEVEL for (int b = 0; b < dstBands; b++) { // For all the Bands // The data value are taken for every band for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { // source band data sBandDataDouble[s] = srcDataDouble[s][b]; // The offset is initialized sLineOffsets[s] = srcBandOffsets[s][b]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { // The alpha value are taken only from the first band (this // happens because the raster // accessor provides the data array with the band data even if // the alpha channel has only // one band. aBandDataDouble[s] = alfaDataDouble[s][0]; aLineOffsets[s] = alfaBandOffsets[s][0]; } } // The destination data band are selected double[] dBandDataDouble = dstDataDouble[b]; // the destination lineOffset is initialized int dLineOffset = dstBandOffsets[b]; if (mosaicTypeSelected == MosaicDescriptor.MOSAIC_TYPE_OVERLAY) { for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // For all the Y // values // Source line Offset and pixel Offset, // Alpha line Offset and pixel Offset are initialized for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (srcBean[s].getAlphaRasterAccessor() != null) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // For all // the X // values // The destination flag is initialized to false and changes // to true only // if one pixel alpha channel is not 0 or falls into an // image ROI or is not a NoData boolean setDestinationFlag = false; for (int s = 0; s < sourcesNumber; s++) { final RasterAccessor dataRA = srcBean[s].getDataRasterAccessor(); if (dataRA == null) { continue; } // The source valuse are initialized only for the switch // method double sourceValueDouble = sBandDataDouble[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // the flag checks if the pixel is a noData boolean isData = true; if (hasNoData[s]) { Range noDataRangeDouble = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); if (noDataRangeDouble != null) { isData = !(noDataRangeDouble.contains(sourceValueDouble)); } } if (!isData) { setDestinationFlag = false; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: setDestinationFlag = aBandDataDouble[s][aPixelOffsets[s]] != 0; aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: setDestinationFlag = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0; break; default: setDestinationFlag = true; } } // If the flag is True, the related source pixel is // saved in the // destination one and exit from the cycle after // incrementing the offset if (setDestinationFlag) { dBandDataDouble[dPixelOffset] = sourceValueDouble; for (int k = s + 1; k < sourcesNumber; k++) { if (dataRA != null) { sPixelOffsets[k] += srcPixelStride[k]; } if (srcBean[k].getAlphaRasterAccessor() != null) { aPixelOffsets[k] += alfaPixelStride[k]; } } break; } } // If the flag is false for every source, the destinationb // no data value is // set to the related destination pixel and then updates the // offset if (!setDestinationFlag) { dBandDataDouble[dPixelOffset] = destinationNoDataDouble[b]; } dPixelOffset += dstPixelStride; } } } else { // the mosaicType is MOSAIC_TYPE_BLEND for (int dstY = dstMinY; dstY < dstMaxY; dstY++) { // Source and pixel Offset are initialized and Source and alpha // line offset are // translated (cycle accross all the sources) for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() != null) { sPixelOffsets[s] = sLineOffsets[s]; sLineOffsets[s] += srcLineStride[s]; } if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] = aLineOffsets[s]; aLineOffsets[s] += alfaLineStride[s]; } } // The same operation is performed for the destination offsets int dPixelOffset = dLineOffset; dLineOffset += dstLineStride; for (int dstX = dstMinX; dstX < dstMaxX; dstX++) { // In the blending operation the destination pixel value is // calculated // as sum of the weighted source pixel / sum of weigth. double numerator = 0.0; double denominator = 0.0; for (int s = 0; s < sourcesNumber; s++) { if (srcBean[s].getDataRasterAccessor() == null) { continue; } // The source valuse are initialized only for the switch // method double sourceValueDouble = sBandDataDouble[s][sPixelOffsets[s]]; // Offset update sPixelOffsets[s] += srcPixelStride[s]; // The weight is calculated for every pixel double weight = 0.0F; boolean isData = true; // If no alpha channel or Roi is present, the weight // is set to 1 or 0 if the pixel has // or not a No Data value if (hasNoData[s]) { Range noDataRangeDouble = (srcBean[s] .getSourceNoDataRangeRasterAccessor()); if (noDataRangeDouble != null) { isData = !(noDataRangeDouble.contains(sourceValueDouble)); } } if (!isData) { weight = 0F; if (weightTypesUsed[s] == WeightType.WEIGHT_TYPE_ALPHA) { aPixelOffsets[s] += alfaPixelStride[s]; } } else { switch (weightTypesUsed[s]) { case WEIGHT_TYPE_ALPHA: weight = aBandDataDouble[s][aPixelOffsets[s]]; if (weight > 0.0F && isAlphaBitmaskUsed) { weight = 1.0F; } else { weight /= 255.0F; } aPixelOffsets[s] += alfaPixelStride[s]; break; case WEIGHT_TYPE_ROI: weight = srcBean[s].getRoiRaster().getSample(dstX, dstY, 0) > 0 ? 1.0F : 0.0F; break; default: weight = 1.0F; } } // The above calculated weight are added to the // numerator and denominator if (isData) { numerator += (weight * (sourceValueDouble)); } denominator += weight; } // If the weighted sum is 0 the destination pixel value // takes the destination no data. // If the sum is not 0 the value is added to the related // destination pixel if (denominator == 0.0) { dBandDataDouble[dPixelOffset] = destinationNoDataDouble[b]; } else { dBandDataDouble[dPixelOffset] = numerator / denominator; } // Offset update dPixelOffset += dstPixelStride; } } } } } // These methods simplyoverride the OpImage mapDestRect and mapSourceRect method @Override public Rectangle mapDestRect(Rectangle destRectangle, int sourceRasterIndex) { if (destRectangle == null) { throw new IllegalArgumentException("Destination rectangle is not defined"); } if (sourceRasterIndex < 0 || sourceRasterIndex >= getNumSources()) { throw new IllegalArgumentException( "Source index must be between 0 and source dimension-1"); } return destRectangle.intersection(getSourceImage(sourceRasterIndex).getBounds()); } @Override public Rectangle mapSourceRect(Rectangle sourceRectangle, int sourceRasterIndex) { if (sourceRectangle == null) { throw new IllegalArgumentException("Destination rectangle is not defined"); } if (sourceRasterIndex < 0 || sourceRasterIndex >= getNumSources()) { throw new IllegalArgumentException( "Source index must be between 0 and source dimension-1"); } return sourceRectangle.intersection(getBounds()); } @Override public synchronized void dispose() { if(imageBeans != null) { // each of these might have been extended, make sure // to dispose all of them (the super.dispose() will dispose // the sources, which eventually will re-dispose some images, // but that should be fine) for (ImageMosaicBean bean : imageBeans) { dispose(bean.getImage()); dispose(bean.getRoiImage()); dispose(bean.getAlphaChannel()); } } super.dispose(); } private void dispose(RenderedImage image) { if(image instanceof RenderedOp) { ((RenderedOp) image).dispose(); } } /** Java bean for saving all the rasterAccessor informations */ private static class RasterBeanAccessor { // RasterAccessor of image data private RasterAccessor dataRasterAccessor; // alpha rasterAccessor data private RasterAccessor alphaRasterAccessor; // Roi raster data private Raster roiRaster; // No data range private Range sourceNoDataRangeRasterAccessor; // No-argument constructor as requested for the java beans RasterBeanAccessor() { } // The methods below are setter and getter for every field as requested for the // java beans public RasterAccessor getDataRasterAccessor() { return dataRasterAccessor; } public void setDataRasterAccessor(RasterAccessor dataRasterAccessor) { this.dataRasterAccessor = dataRasterAccessor; } public RasterAccessor getAlphaRasterAccessor() { return alphaRasterAccessor; } public void setAlphaRasterAccessor(RasterAccessor alphaRasterAccessor) { this.alphaRasterAccessor = alphaRasterAccessor; } public Raster getRoiRaster() { return roiRaster; } public void setRoiRaster(Raster roiRaster) { this.roiRaster = roiRaster; } public Range getSourceNoDataRangeRasterAccessor() { return sourceNoDataRangeRasterAccessor; } public void setSourceNoDataRangeRasterAccessor(Range sourceNoDataRangeRasterAccessor) { this.sourceNoDataRangeRasterAccessor = sourceNoDataRangeRasterAccessor; } } }