/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2017, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.processing.jai; import java.awt.Rectangle; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.util.Map; import javax.media.jai.AreaOpImage; import javax.media.jai.BorderExtender; import javax.media.jai.ImageLayout; import javax.media.jai.RasterAccessor; import javax.media.jai.RasterFormatTag; import org.geotools.processing.jai.TransparencyFillDescriptor.FillType; public class TransparencyFillOpImage extends AreaOpImage { private FillType type = TransparencyFillDescriptor.FILL_AVERAGE; /** * Transparency Fill algorithms. More can be added in the future for * different logics */ static enum TransparencyFillAlgorithm { // Fill the empty pixel by taking sourrounding pixels and computing the average. FILL_AVERAGE { @Override public void fillPixel(int numBands, byte[][] srcData, byte[][] dstData, int srcOffset, int srcStride, int dstOffset) { for (int k = 0; k < numBands; k++) { if (k != numBands - 1) { int left = (srcData[k][srcOffset - srcStride + k] & 0xFF); int right = (srcData[k][srcOffset + srcStride + k] & 0xFF); dstData[k][dstOffset + k] = (byte) ((left + right) / 2); } else { dstData[k][dstOffset + k] = ((byte) ((srcData[k][srcOffset - srcStride + k]))); } } } }, // Fill the empty pixel by taking data from the adjacent pixel (left/above pixel) FILL_CLONE_FIRST { @Override public void fillPixel(int numBands, byte[][] srcData, byte[][] dstData, int srcOffset, int srcStride, int dstOffset) { for (int k = 0; k < numBands; k++) { dstData[k][dstOffset + k] = ((byte) ((srcData[k][srcOffset - srcStride + k]))); } } }, // Fill the empty pixel by taking data from the adjacent pixel (right/below pixel) FILL_CLONE_SECOND { @Override public void fillPixel(int numBands, byte[][] srcData, byte[][] dstData, int srcOffset, int srcStride, int dstOffset) { for (int k = 0; k < numBands; k++) { dstData[k][dstOffset + k] = ((byte) ((srcData[k][srcOffset + srcStride + k]))); } } }; public abstract void fillPixel(int numBands, byte[][] srcData, byte[][] dstData, int srcOffset, int srcStride, int dstOffset); }; private TransparencyFillAlgorithm fillAlgorithm = null; /** * Creates a TransparencyFillOpImage given a ParameterBlock containing the image source. The image dimensions are derived * from the source image. The tile grid layout, SampleModel, and ColorModel may optionally be specified by an ImageLayout object. * * @param source a RenderedImage. * @param extender a BorderExtender, or null. * @param type a {@link FillType} type to be used for transparency filling * @param layout an ImageLayout optionally containing the tile grid layout, SampleModel, and ColorModel, or null. */ public TransparencyFillOpImage(RenderedImage source, BorderExtender extender, FillType type, Map config, ImageLayout layout) { super(source, layout, config, true, extender, 1, 1, 1, 1); if (type == null) { // Setting up default type = TransparencyFillDescriptor.FILL_AVERAGE; } this.type = type; switch (this.type.getValue()) { case 0: fillAlgorithm = TransparencyFillAlgorithm.FILL_AVERAGE; break; case 1: fillAlgorithm = TransparencyFillAlgorithm.FILL_CLONE_FIRST; break; case 2: fillAlgorithm = TransparencyFillAlgorithm.FILL_CLONE_SECOND; break; } } /** * Performs fill on a specified rectangle. The sources are cobbled. * * @param sources an array of source Rasters, guaranteed to provide all necessary source data for computing the output. * @param dest a WritableRaster tile containing the area to be computed. * @param destRect the rectangle within dest to be processed. */ protected void computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect) { // Retrieve format tags. RasterFormatTag[] formatTags = getFormatTags(); Raster source = sources[0]; Rectangle srcRect = mapDestRect(destRect, 0); RasterAccessor srcAccessor = new RasterAccessor(source, srcRect, formatTags[0], getSourceImage(0).getColorModel()); RasterAccessor dstAccessor = new RasterAccessor(dest, destRect, formatTags[1], getColorModel()); switch (dstAccessor.getDataType()) { case DataBuffer.TYPE_BYTE: byteLoop(srcAccessor, dstAccessor); break; default: // Transparency fill is only supported on byte datatype throw new UnsupportedOperationException("Only byte databuffer is currently supported"); } // If the RasterAccessor object set up a temporary buffer for the // op to write to, tell the RasterAccessor to write that data // to the raster no that we're done with it. if (dstAccessor.isDataCopy()) { dstAccessor.clampDataArrays(); dstAccessor.copyDataToRaster(); } } private void byteLoop(RasterAccessor src, RasterAccessor dst) { int dwidth = dst.getWidth(); int dheight = dst.getHeight(); int numBands = dst.getNumBands(); int dstOffsetsForBands[] = dst.getOffsetsForBands(); int dstBandOffsets[] = dst.getBandOffsets(); int dstPixelStride = dst.getPixelStride(); int dstScanlineStride = dst.getScanlineStride(); int srcOffsetsForBands[] = src.getOffsetsForBands(); int srcBandOffsets[] = src.getBandOffsets(); int srcPixelStride = src.getPixelStride(); int srcScanlineStride = src.getScanlineStride(); byte dstDataArrays[][] = dst.getByteDataArrays(); byte srcDataArrays[][] = src.getByteDataArrays(); byte srcData[] = srcDataArrays[numBands - 1]; int srcScanlineOffset = srcBandOffsets[numBands - 1]; int srcScanlineDataOffset = srcBandOffsets[srcOffsetsForBands[0]]; int dstScanlineOffset[] = new int[numBands]; for (int i = 0; i < numBands; i++) { dstScanlineOffset[i] = dstBandOffsets[i]; } for (int j = 0; j < dheight; j++) { int srcAlphaCentralPixelOffset = srcScanlineOffset + srcScanlineStride + srcPixelStride; int srcDataLeftPixelOffset = srcScanlineDataOffset + srcScanlineStride; int srcDataPixelOffset = srcDataLeftPixelOffset + srcPixelStride; int dstPixelOffset = dstScanlineOffset[dstOffsetsForBands[0]]; int imageOffset = srcAlphaCentralPixelOffset; int imageDataOffset = srcDataPixelOffset; for (int i = 0; i < dwidth; i++) { int imageVerticalOffset = imageOffset; int imageDataVerticalOffset = imageDataOffset; int centralPixel = srcData[imageVerticalOffset]; boolean copySource = true; if (centralPixel == 0) { // Check if previous and next pixels are zero. int rightPixel = srcData[imageVerticalOffset + srcPixelStride]; int leftPixel = srcData[imageVerticalOffset - srcPixelStride]; if (rightPixel != 0 && leftPixel != 0) { // This has been identified as a pixel of a vertical transparent stripe fillAlgorithm.fillPixel(numBands, srcDataArrays, dstDataArrays, imageDataVerticalOffset, srcPixelStride, dstPixelOffset); copySource = false; } else { // A transparent pixel with adjacent transparent pixels along x int upperPixel = srcData[imageVerticalOffset - srcScanlineStride]; if (upperPixel != 0) { int lowerPixel = srcData[imageVerticalOffset + srcScanlineStride]; if (lowerPixel != 0) { // This has been identified as a pixel of an horizontal transparent stripe fillAlgorithm.fillPixel(numBands, srcDataArrays, dstDataArrays, imageDataVerticalOffset, srcScanlineStride, dstPixelOffset); copySource = false; } } } } if (copySource) { for (int k = 0; k < numBands; k++) { dstDataArrays[k][dstPixelOffset + k] = srcDataArrays[k][imageDataVerticalOffset + k]; } } imageOffset += srcPixelStride; imageDataOffset += srcPixelStride; dstPixelOffset += dstPixelStride; } srcScanlineOffset += srcScanlineStride; srcScanlineDataOffset += srcScanlineStride; for (int i = 0; i < numBands; i++) { dstScanlineOffset[i] += dstScanlineStride; } } } }