/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2016, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.processing.image.bandcombine; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRenderedImage; import org.opengis.parameter.ParameterValueGroup; import org.apache.sis.util.ArgumentChecks; import org.geotoolkit.image.iterator.PixelIterator; import org.geotoolkit.image.iterator.PixelIteratorFactory; import org.geotoolkit.image.BufferedImages; import org.geotoolkit.image.internal.ImageUtils; import org.geotoolkit.image.internal.PlanarConfiguration; import org.geotoolkit.image.internal.SampleType; import org.geotoolkit.image.io.large.WritableLargeRenderedImage; import org.geotoolkit.parameter.Parameters; import org.geotoolkit.processing.AbstractProcess; import org.geotoolkit.process.ProcessException; /** * Process to combine some source image into same result image.<br><br> * * Result image will own same band number as sum of all source image bands.<br> * Bands order writing is the same as image order given in parameters.<br> * To combine images each source images must answer 3 criterions as follow : <br> * - same width <br> * - same height<br> * - same dataType.<br><br> * * On the other side source images may have different upper left corner coordinates, * and also internal differents tiles size.<br><br> * * To improve performance ouput image is {@link WritableLargeRenderedImage} type.<br><br> * * Moreover, new adapted {@link ColorModel} and {@link SampleModel} are computed. * * @author Remi Marechal (Geomatys) * @author Johann Sorel (Geomatys) * * @see BufferedImages#createGrayScaleColorModel(int, int, int, double, double) * @see ImageUtils#createSampleModel(org.geotoolkit.image.internal.PlanarConfiguration, org.geotoolkit.image.internal.SampleType, int, int, int) */ public class BandCombineProcess extends AbstractProcess { public BandCombineProcess(final ParameterValueGroup input) { super(BandCombineDescriptor.INSTANCE, input); } @Override protected void execute() throws ProcessException { ArgumentChecks.ensureNonNull("inputParameter", inputParameters); final RenderedImage[] inputImages = (RenderedImage[]) Parameters.getOrCreate(BandCombineDescriptor.IN_IMAGES, inputParameters).getValue(); if (inputImages.length == 0) throw new ProcessException("No image to combine", this, null); if (inputImages.length == 1) { //nothing to do Parameters.getOrCreate(BandCombineDescriptor.OUT_IMAGE, outputParameters).setValue(inputImages[0]); return; } //check and extract informations, all images should have the same size and sample type. int sampleType = -1; int nbtotalbands = 0; int width = 0; int height = 0; final int[] nbBands = new int[inputImages.length]; final int[] nbBandIndex = new int[inputImages.length]; //-- attribut use only during same tile size final PixelIterator[] readItes = new PixelIterator[inputImages.length]; //-- minimum image coordinates only use during assert final int[][] minXYs = new int[inputImages.length][2]; final Dimension[] tilesSize = new Dimension[inputImages.length]; for(int i = 0; i < inputImages.length; i++) { final RenderedImage image = inputImages[i]; final SampleModel sm = image.getSampleModel(); if (sampleType == -1) { //first image sampleType = sm.getDataType(); width = image.getWidth(); height = image.getHeight(); } else { //check same model if (sampleType != sm.getDataType()) throw new ProcessException("Images do not have the same sample type", this, null); if (width != image.getWidth() || height != image.getHeight()) throw new ProcessException("Images do not have the same size", this, null); } minXYs[i][0] = image.getMinX(); minXYs[i][1] = image.getMinY(); tilesSize[i] = new Dimension(image.getTileWidth(), image.getTileHeight()); readItes[i] = PixelIteratorFactory.createDefaultIterator(image); nbBands[i] = sm.getNumBands(); nbBandIndex[i] = nbtotalbands; nbtotalbands += sm.getNumBands(); } //-- try to reuse a java color model for better performances final ColorModel cm = BufferedImages.createGrayScaleColorModel(sampleType,nbtotalbands, 0, 0, 10); final SampleModel sm = ImageUtils.createSampleModel(PlanarConfiguration.INTERLEAVED, SampleType.valueOf(sampleType), width, height, nbtotalbands); //-- destination Images Dimensions boolean sameTileSize = sameTileSize(tilesSize); final Dimension destTileSize; //-- if all srcTiles dimensions are equals keep same tiles sizes. if (sameTileSize) { if (tilesSize[0].width >= 64 && tilesSize[0].width <= 256 && tilesSize[0].height >= 64 && tilesSize[0].height <= 256 ) { //-- keep control to destination tile size to avoid giant destination tile size. destTileSize = new Dimension(tilesSize[0]); } else { sameTileSize = false; destTileSize = new Dimension(256, 256); } } else { //-- else destination tile size (256 * 256). destTileSize = new Dimension(256, 256); } final WritableRenderedImage resultImage = new WritableLargeRenderedImage(0, 0, width, height, destTileSize, 0, 0, cm, sm); //-- 2 cases //-- SrcTiles dimensions and src dest Dimension are equals if (sameTileSize) { final PixelIterator destPix = PixelIteratorFactory.createDefaultWriteableIterator(resultImage, resultImage); int itId = -1; while (destPix.next()) { //-- when a dest sample is completely filled //-- rewind to first destination sample if (++itId >= inputImages.length) itId = 0; final PixelIterator srcPix = readItes[itId]; srcPix.next(); //-- current src iterator coordinates final int srcPX = srcPix.getX(); final int srcPY = srcPix.getY(); //-- current destination iterator coordinates final int dstPX = srcPX - minXYs[itId][0]; final int dstPY = srcPY - minXYs[itId][1]; destPix.setSampleDouble(srcPix.getSampleDouble()); int b = 0; while (++b < nbBands[itId]) { assert checkPositions("Src Pix Iterator for image "+itId+" at band index : "+nbBandIndex[itId] + b, srcPix, srcPX, srcPY); assert checkPositions("Dest Pix at Band index : "+nbBandIndex[itId] + b, destPix, dstPX, dstPY); destPix.next(); srcPix.next(); destPix.setSampleDouble(srcPix.getSampleDouble()); } assert checkPositions("Src Pix Iterator for image "+itId+" at band index : "+nbBandIndex[itId] + b, srcPix, srcPX, srcPY); assert checkPositions("Dest Pix at Band index : "+nbBandIndex[itId] + b, destPix, dstPX, dstPY); } } else { for (int i = 0; i < inputImages.length; i++) { final RenderedImage srcImage = inputImages[i]; final int srcMinX = srcImage.getMinX(); final int srcMinY = srcImage.getMinY(); //-- travel all source image tile to read tile once a time for (int ty = 0; ty < srcImage.getNumYTiles(); ty++) { for (int tx = 0; tx < srcImage.getNumXTiles(); tx++) { final Rectangle srcArea = new Rectangle(srcMinX + tx * tilesSize[i].width, srcMinY + ty * tilesSize[i].height, tilesSize[i].width, tilesSize[i].height); final PixelIterator srcPix = PixelIteratorFactory.createDefaultIterator(srcImage, srcArea); //-- dest image begin (0, 0) final Rectangle destArea = new Rectangle(tx * tilesSize[i].width, ty * tilesSize[i].height, tilesSize[i].width, tilesSize[i].height); final PixelIterator destPix = PixelIteratorFactory.createDefaultWriteableIterator(resultImage, resultImage, destArea); while (srcPix.next()) { //-- current src iterator coordinates final int srcPX = srcPix.getX(); final int srcPY = srcPix.getY(); //-- current destination iterator coordinates final int dstPX = srcPix.getX() - srcMinX; final int dstPY = srcPix.getY() - srcMinY; destPix.moveTo(dstPX, dstPY, nbBandIndex[i]); destPix.setSampleDouble(srcPix.getSampleDouble()); //-- fork other bands writing like follow //-- to avoid unnecessary multiple pixeliterator.move() call int b = 0; while (++b < nbBands[i]) { assert checkPositions("Src Pix Iterator for image "+i+" at band index : "+nbBandIndex[i] + b, srcPix, srcPX, srcPY); assert checkPositions("Dest Pix at Band index : "+nbBandIndex[i] + b, destPix, dstPX, dstPY); srcPix.next(); destPix.next(); destPix.setSampleDouble(srcPix.getSampleDouble()); } assert checkPositions("Src Pix Iterator for image "+i+" at band index : "+nbBandIndex[i] + b, srcPix, srcPX, srcPY); assert checkPositions("Dest Pix at Band index : "+nbBandIndex[i] + b, destPix, dstPX, dstPY); } } } } } Parameters.getOrCreate(BandCombineDescriptor.OUT_IMAGE, outputParameters).setValue(resultImage); } /** * Returns {@code true} if all {@link Dimension} are equals.<br> * Moreover it is use to define if all source image have the same tile size. * * @param tileSizes tile size of future aggregated src images. * @return */ private static boolean sameTileSize(final Dimension[] tileSizes) { ArgumentChecks.ensureNonNull("tileSize", tileSizes); if (tileSizes.length == 0) return false; final Dimension dimRef = tileSizes[0]; for (int d = 1; d < tileSizes.length; d++) { if (!(dimRef.equals(tileSizes[d]))) return false; } return true; } /** * Throw {@link AssertionError} if {@link PixelIterator} position doesn't * match with expected position given in parameters. * * @param pix current tested iterator. * @param expectedPosX expected raster X position. * @param expectedPosY expected raster X position. * @return true if positions match. */ private static boolean checkPositions(final String message, final PixelIterator pix, final int expectedPosX, final int expectedPosY) { ArgumentChecks.ensureNonNull("PixelIterator", pix); assert pix.getX() == expectedPosX : message+" expected position X : "+expectedPosX+", found : "+pix.getX(); assert pix.getY() == expectedPosY : message+" expected position Y : "+expectedPosY+", found : "+pix.getY(); return true; } }