/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, 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.image.jai; import java.util.Map; import java.util.Vector; import java.util.Arrays; import java.awt.Rectangle; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import javax.media.jai.ImageLayout; import javax.media.jai.PlanarImage; import javax.media.jai.PointOpImage; import javax.media.jai.iterator.RectIter; import javax.media.jai.iterator.RectIterFactory; import javax.media.jai.iterator.WritableRectIter; import org.apache.sis.internal.util.Numerics; import org.geotoolkit.image.TransfertRectIter; import static javax.media.jai.ImageLayout.COLOR_MODEL_MASK; import static javax.media.jai.ImageLayout.SAMPLE_MODEL_MASK; /** * Applies a mask (typically a {@linkplain javax.media.jai.operator.BinarizeDescriptor binary * image}) on an source image. For every pixel in the source image there is a choice: * <p> * <ul> * <li>If the corresponding pixel in the mask is 0, then the source pixel is copied unchanged * to the destination image.</li> * <li>Otherwise there is a choice:<ul> * <li>If replacement values were explicitly given to this image operation, then those * values are copied to the destination image.</li> * <li>Otherwise the value from the mask is used as the replacement values.</li> * </ul></li> * </ul> * <p> * Example: * <p> * <table align="center" cellpadding="15" border="1"> * <tr><th>Input</th><th>output</th></tr><tr> * <td><img src="doc-files/sample-rgb.png" border="1"> , * <img src="doc-files/silhouette.png" border="1"></td> * <td><img src="doc-files/mask.png" border="1"></td> * </tr></table> * * @author Martin Desruisseaux (Geomatys) * @version 3.00 * * @since 3.00 * @module */ public class Mask extends PointOpImage { /** * The name of this operation in the JAI registry. * This is {@value}. */ public static final String OPERATION_NAME = "org.geotoolkit.Mask"; /** * The values to copy to the destination images for every masked pixels, or {@code null} for * using the mask values. If non-null, then the array length must be equals to the number of * bands in the destination image. */ private final double[] newValues; /** * Constructs a new mask for the given image. While this constructor is public, it should * usually not be invoked directly. You should use {@linkplain javax.media.jai.JAI} factory * methods instead. * * @param source The source image. * @param mask The mask. If it has smaller bounds than the source image, then every * pixels outside the mask are treated as if they were zero. * @param layout The image layout. * @param configuration The image properties and rendering hints. * @param newValues The values to copy to the destination images for every masked pixels, * or {@code null} for using the mask values. If non-null, then the array * length must be equals to the number of bands in the destination image. */ public Mask(final RenderedImage source, final RenderedImage mask, final ImageLayout layout, final Map<?,?> configuration, final double[] newValues) { super(source, mask, layout(source, layout), configuration, false); this.newValues = (newValues == null) ? null : Arrays.copyOf(newValues, source.getSampleModel().getNumBands()); permitInPlaceOperation(); } /** * If the user didn't specified explicitly a sample or a color model, creates default ones. * This method is actually a workaround for RFE #4093999 in Sun's bug database * ("Relax constraint on placement of this()/super() call in constructors"). * * @param layout The user-supplied layout. * @return A layout with at least a color model. */ private static ImageLayout layout(final RenderedImage source, ImageLayout layout) { if (layout == null) { layout = new ImageLayout(); } else if ((layout.getValidMask() & (SAMPLE_MODEL_MASK | COLOR_MODEL_MASK)) == 0) { layout = (ImageLayout) layout.clone(); } else { return layout; } return layout.setSampleModel(source.getSampleModel()).setColorModel(source.getColorModel()); } /** * Returns the source images. */ @Override @SuppressWarnings("unchecked") public Vector<RenderedImage> getSources() { return super.getSources(); } /** * Computes a rectangle of outputs. * * @param sources The source images. Should be an array of length 2. * @param dest The raster to be filled in. * @param destRect The region within the raster to be filled. */ @Override protected void computeRect(final PlanarImage[] sources, final WritableRaster dest, final Rectangle destRect) { assert sources.length == 2; final PlanarImage source = sources[0]; final PlanarImage mask = sources[1]; final RectIter mit = RectIterFactory.create(mask, destRect); final WritableRectIter iter = TransfertRectIter.create(source, dest, destRect); final boolean needCopy = (iter instanceof TransfertRectIter); if (iter.finishedLines()) { return; } /* * Below is a copy-and-paste of the same code for the 3 transfer types. While the * textual code look identical except for the 2 first lines, many methods that are * actually invoked are different because of method overloading. */ final int numBands = getNumBands(); switch (sampleModel.getDataType()) { /* * Every integer types. */ default: { final int[] replacement = Numerics.copyAsInts(newValues); final int[] buffer = new int[numBands]; do { mit.startPixels(); iter.startPixels(); if (!iter.finishedPixels()) { do { final int m = mit.getSample(); mit.nextPixel(); if (m != 0) { // Found a value to mask. final int[] samples; if (replacement != null) { samples = replacement; } else { Arrays.fill(samples = buffer, m); } iter.setPixel(samples); continue; } // Found a unmasked value to copy. if (!needCopy) continue; iter.setPixel(iter.getPixel(buffer)); } while (!iter.nextPixelDone()); } mit.nextLine(); } while (!iter.nextLineDone()); } } } }