/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You 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 org.apache.batik.ext.awt.image.rendered;

import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.RasterOp;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

import org.apache.batik.ext.awt.image.GraphicsUtil;

This class provides an implementation for the SVG feMorphology filter, as defined in Chapter 15, section 20 of the SVG specification.
Author:Sheng Pei
Version:$Id: MorphologyOp.java 1733416 2016-03-03 07:07:13Z gadams $
/** * This class provides an implementation for the SVG * feMorphology filter, as defined in Chapter 15, section 20 * of the SVG specification. * * @author <a href="mailto:sheng.pei@sun.com">Sheng Pei</a> * @version $Id: MorphologyOp.java 1733416 2016-03-03 07:07:13Z gadams $ */
public class MorphologyOp implements BufferedImageOp, RasterOp {
The radius of the operation on X axis
/** * The radius of the operation on X axis */
private int radiusX;
The radius of the operation on Y axis
/** * The radius of the operation on Y axis */
private int radiusY; /* * Determine whether to do the dilation or erosion operation. * Will do dilation when it's true and erosion when it's false. */ private boolean doDilation; /* * rangeX is 2*radiusX+1, which is the width of the Kernel */ private final int rangeX; /* * rangeY is 2*radiusY+1, which is the height of the Kernel */ private final int rangeY; /* * sRGB ColorSpace instance used for compatibility checking */ private final ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); /* * Linear RGB ColorSpace instance used for compatibility checking */ private final ColorSpace lRGB = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
Params:
  • radiusX – defines the radius of filter operation on X-axis. Should not be negative. A value of zero will disable the effect of the operation on X-axis, as described in the SVG specification.
  • radiusY – defines the radius of filter operation on Y-axis. Should not be negative. A value of zero will disable the effect of the operation on Y-axis, as described in the SVG specification.
  • doDilation – defines whether to do dilation or erosion operation. Will do dilation when the value is true, erosion when false.
/** * @param radiusX defines the radius of filter operation on X-axis. Should not be negative. * A value of zero will disable the effect of the operation on X-axis, as described * in the SVG specification. * @param radiusY defines the radius of filter operation on Y-axis. Should not be negative. * A value of zero will disable the effect of the operation on Y-axis, as described * in the SVG specification. * @param doDilation defines whether to do dilation or erosion operation. Will do dilation * when the value is true, erosion when false. */
public MorphologyOp (int radiusX, int radiusY, boolean doDilation){ if (radiusX<=0 || radiusY<=0){ throw new IllegalArgumentException( "The radius of X-axis or Y-axis should not be Zero or Negatives." ); } else { this.radiusX = radiusX; this.radiusY = radiusY; this.doDilation = doDilation; rangeX = 2*radiusX + 1; rangeY = 2*radiusY + 1; } } public Rectangle2D getBounds2D(Raster src){ checkCompatible(src.getSampleModel()); return new Rectangle(src.getMinX(), src.getMinY(), src.getWidth(), src.getHeight()); } public Rectangle2D getBounds2D(BufferedImage src){ return new Rectangle(0, 0, src.getWidth(), src.getHeight()); } public Point2D getPoint2D(Point2D srcPt, Point2D destPt){ // This operation does not affect pixel location if(destPt==null) destPt = new Point2D.Float(); destPt.setLocation(srcPt.getX(), srcPt.getY()); return destPt; } private void checkCompatible(ColorModel colorModel, SampleModel sampleModel){ ColorSpace cs = colorModel.getColorSpace(); // Check that model is sRGB or linear RGB if((!cs .equals (sRGB)) && (!cs .equals( lRGB))) throw new IllegalArgumentException("Expected CS_sRGB or CS_LINEAR_RGB color model"); // Check ColorModel is of type DirectColorModel if(!(colorModel instanceof DirectColorModel)) throw new IllegalArgumentException("colorModel should be an instance of DirectColorModel"); // Check transfer type if(sampleModel.getDataType() != DataBuffer.TYPE_INT) throw new IllegalArgumentException("colorModel's transferType should be DataBuffer.TYPE_INT"); // Check red, green, blue and alpha mask DirectColorModel dcm = (DirectColorModel)colorModel; if(dcm.getRedMask() != 0x00ff0000) throw new IllegalArgumentException("red mask in source should be 0x00ff0000"); if(dcm.getGreenMask() != 0x0000ff00) throw new IllegalArgumentException("green mask in source should be 0x0000ff00"); if(dcm.getBlueMask() != 0x000000ff) throw new IllegalArgumentException("blue mask in source should be 0x000000ff"); if(dcm.getAlphaMask() != 0xff000000) throw new IllegalArgumentException("alpha mask in source should be 0xff000000"); } private boolean isCompatible(ColorModel colorModel, SampleModel sampleModel){ ColorSpace cs = colorModel.getColorSpace(); // Check that model is sRGB or linear RGB if((cs != ColorSpace.getInstance(ColorSpace.CS_sRGB)) && (cs != ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB))) return false; // Check ColorModel is of type DirectColorModel if(!(colorModel instanceof DirectColorModel)) return false; // Check transfer type if(sampleModel.getDataType() != DataBuffer.TYPE_INT) return false; // Check red, green, blue and alpha mask DirectColorModel dcm = (DirectColorModel)colorModel; if(dcm.getRedMask() != 0x00ff0000) return false; if(dcm.getGreenMask() != 0x0000ff00) return false; if(dcm.getBlueMask() != 0x000000ff) return false; if(dcm.getAlphaMask() != 0xff000000) return false; return true; } private void checkCompatible(SampleModel model){ // Check model is ok: should be SinglePixelPackedSampleModel if(!(model instanceof SinglePixelPackedSampleModel)) throw new IllegalArgumentException ("MorphologyOp only works with Rasters " + "using SinglePixelPackedSampleModels"); // Check number of bands int nBands = model.getNumBands(); if(nBands!=4) throw new IllegalArgumentException ("MorphologyOp only words with Rasters having 4 bands"); // Check that integer packed. if(model.getDataType()!=DataBuffer.TYPE_INT) throw new IllegalArgumentException ("MorphologyOp only works with Rasters using DataBufferInt"); // Check bit masks int[] bitOffsets=((SinglePixelPackedSampleModel)model).getBitOffsets(); for(int i=0; i<bitOffsets.length; i++){ if(bitOffsets[i]%8 != 0) throw new IllegalArgumentException ("MorphologyOp only works with Rasters using 8 bits " + "per band : " + i + " : " + bitOffsets[i]); } } public RenderingHints getRenderingHints(){ return null; } public WritableRaster createCompatibleDestRaster(Raster src){ checkCompatible(src.getSampleModel()); // Src Raster is OK: create a similar Raster for destination. return src.createCompatibleWritableRaster(); } public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM){ BufferedImage dest = null; if(destCM==null) destCM = src.getColorModel(); WritableRaster wr; wr = destCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()); checkCompatible(destCM, wr.getSampleModel()); dest = new BufferedImage(destCM, wr, destCM.isAlphaPremultiplied(), null); return dest; } /* * This method compares the two input variables according * to the doDilation boolean variable. */ static final boolean isBetter (final int v1, final int v2, final boolean doDilation) { if (v1 > v2) return doDilation; if (v1 < v2) return !doDilation; return true; } /* * This method deals with the condition that the Kernel is wider than * the Image */ private void specialProcessRow(Raster src, WritableRaster dest){ final int w = src.getWidth(); final int h = src.getHeight(); // Access the integer buffer for each image. DataBufferInt srcDB = (DataBufferInt)src.getDataBuffer(); DataBufferInt dstDB = (DataBufferInt)dest.getDataBuffer(); // Offset defines where in the stack the real data begin SinglePixelPackedSampleModel sppsm; sppsm = (SinglePixelPackedSampleModel)src.getSampleModel(); final int srcOff = srcDB.getOffset() + sppsm.getOffset(src.getMinX() - src.getSampleModelTranslateX(), src.getMinY() - src.getSampleModelTranslateY()); sppsm = (SinglePixelPackedSampleModel)dest.getSampleModel(); final int dstOff = dstDB.getOffset() + sppsm.getOffset(dest.getMinX() - dest.getSampleModelTranslateX(), dest.getMinY() - dest.getSampleModelTranslateY()); // Stride is the distance between two consecutive column elements, // in the one-dimention dataBuffer final int srcScanStride = ((SinglePixelPackedSampleModel)src.getSampleModel()).getScanlineStride(); final int dstScanStride = ((SinglePixelPackedSampleModel)dest.getSampleModel()).getScanlineStride(); // Access the pixel value array final int[] srcPixels = srcDB.getBankData()[0]; final int[] destPixels = dstDB.getBankData()[0]; // The pointer of src and dest indicating where the pixel values are int sp, dp; // Declaration for the circular buffer's implementation // These are the circular buffers' head pointer and // the index pointers // bufferHead points to the leftmost element in the circular buffer int bufferHead; int maxIndexA; int maxIndexR; int maxIndexG; int maxIndexB; // Temp variables int pel, currentPixel, lastPixel; int a,r,g,b; int a1,r1,g1,b1; // If image width is less than or equal to the radiusX, // all the pixels share the same max/min value if (w<=radiusX){ for (int i=0; i<h; i++){ // pointing to the first pixels of each row sp = srcOff + i*srcScanStride; dp = dstOff + i*dstScanStride; pel = srcPixels[sp++]; a = pel>>>24; r = pel&0xff0000; g = pel&0xff00; b = pel&0xff; for (int k=1; k<w; k++){ currentPixel = srcPixels[sp++]; a1 = currentPixel>>>24; r1 = currentPixel&0xff0000; g1 = currentPixel&0xff00; b1 = currentPixel&0xff; if (isBetter(a1, a, doDilation)){ a = a1; } if (isBetter(r1, r, doDilation)){ r = r1; } if (isBetter(g1, g, doDilation)){ g = g1; } if (isBetter(b1, b, doDilation)){ b = b1; } } // all the element share the same max/min value for (int k=0; k<w; k++){ destPixels[dp++] = (a << 24) | r | g | b; } } } // When radiusX < w <= 2*radiusX else { // The width of the circular buffer is w final int [] bufferA = new int [w]; final int [] bufferR = new int [w]; final int [] bufferG = new int [w]; final int [] bufferB = new int [w]; for (int i=0; i<h; i++){ // initialization of pointers, indice // at the head of each row sp = srcOff + i*srcScanStride; dp = dstOff + i*dstScanStride; bufferHead = 0; maxIndexA = 0; maxIndexR = 0; maxIndexG = 0; maxIndexB = 0; pel = srcPixels[sp++]; a = pel>>>24; r = pel&0xff0000; g = pel&0xff00; b = pel&0xff; bufferA[0] = a; bufferR[0] = r; bufferG[0] = g; bufferB[0] = b; for (int k=1; k<=radiusX; k++){ currentPixel = srcPixels[sp++]; a1 = currentPixel>>>24; r1 = currentPixel&0xff0000; g1 = currentPixel&0xff00; b1 = currentPixel&0xff; bufferA[k] = a1; bufferR[k] = r1; bufferG[k] = g1; bufferB[k] = b1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = k; } if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = k; } if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = k; } if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = k; } } destPixels[dp++] = (a << 24) | r | g | b; // // 1 <= j <= w-radiusX-1 : The left margin of each row. // for (int j=1; j<=w-radiusX-1; j++){ lastPixel = srcPixels[sp++]; // here is the Alpha channel // we retrieve the previous max/min value a = bufferA[maxIndexA]; a1 = lastPixel>>>24; bufferA[j+radiusX] = a1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = j+radiusX; } // now we deal with the Red channel r = bufferR[maxIndexR]; r1 = lastPixel&0xff0000; bufferR[j+radiusX] = r1; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = j+radiusX; } // now we deal with the Green channel g = bufferG[maxIndexG]; g1 = lastPixel&0xff00; bufferG[j+radiusX] = g1; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = j+radiusX; } // now we deal with the Blue channel b = bufferB[maxIndexB]; b1 = lastPixel&0xff; bufferB[j+radiusX] = b1; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = j+radiusX; } // now we have gone through the four channels and // updated the index array. then we'll pack the // new max/min value according to each channel's // max/min vlue destPixels[dp++] = (a << 24) | r | g | b; } // Now is the inner body of the row: // all elements in this segment share the same max/min value for (int j = w-radiusX; j<= radiusX; j++){ destPixels[dp] = destPixels[dp-1]; dp++; } // Now the circular buffer is full // Now is the right margin of the row when radiusX < w <= 2*radiusX for (int j = radiusX+1; j<w; j++){ if (maxIndexA == bufferHead){ a = bufferA[bufferHead+1]; maxIndexA = bufferHead+1; for (int m= bufferHead+2; m< w; m++){ a1 = bufferA[m]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = m; } } } else { a = bufferA[maxIndexA]; } if (maxIndexR == bufferHead){ r = bufferR[bufferHead+1]; maxIndexR = bufferHead+1; for (int m= bufferHead+2; m< w; m++){ r1 = bufferR[m]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = m; } } } else { r = bufferR[maxIndexR]; } if (maxIndexG == bufferHead){ g = bufferG[bufferHead+1]; maxIndexG = bufferHead+1; for (int m= bufferHead+2; m< w; m++){ g1 = bufferG[m]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = m; } } } // we can reuse the previous max/min value else { g = bufferG[maxIndexG]; } if (maxIndexB == bufferHead){ b = bufferB[bufferHead+1]; maxIndexB = bufferHead+1; for (int m= bufferHead+2; m< w; m++){ b1 = bufferB[m]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = m; } } } // we can reuse the previous max/min value else { b = bufferB[maxIndexB]; } // discard the leftmost element bufferHead++; destPixels[dp++] = (a << 24) | r | g | b; } // return to the first pixel of the next row } }// When radiusX < w <=2*radiusX } /* * This method deals with the condition when the Kernel is * higher than the image. */ private void specialProcessColumn(Raster src, WritableRaster dest){ final int w = src.getWidth(); final int h = src.getHeight(); // Access the integer buffer for each image. DataBufferInt dstDB = (DataBufferInt)dest.getDataBuffer(); // Offset defines where in the stack the real data begin final int dstOff = dstDB.getOffset(); // Stride is the distance between two consecutive column elements, // in the one-dimention dataBuffer final int dstScanStride = ((SinglePixelPackedSampleModel)dest.getSampleModel()).getScanlineStride(); // Access the pixel value array final int[] destPixels = dstDB.getBankData()[0]; // The pointer of src and dest indicating where the pixel values are int dp, cp; // Declaration for the circular buffer's implementation // These are the circular buffers' head pointer and // the index pointers // bufferHead points to the leftmost element in the circular buffer int bufferHead; int maxIndexA; int maxIndexR; int maxIndexG; int maxIndexB; // Temp variables int pel, currentPixel, lastPixel; int a,r,g,b; int a1,r1,g1,b1; // Here all the pixels share the same // max/min value if (h<=radiusY){ for (int j=0; j<w; j++){ dp = dstOff + j; cp = dstOff + j; pel = destPixels[cp]; cp += dstScanStride; a = pel>>>24; r = pel&0xff0000; g = pel&0xff00; b = pel&0xff; for (int k=1; k<h; k++){ currentPixel = destPixels[cp]; cp += dstScanStride; a1 = currentPixel>>>24; r1 = currentPixel&0xff0000; g1 = currentPixel&0xff00; b1 = currentPixel&0xff; if (isBetter(a1, a, doDilation)){ a = a1; } if (isBetter(r1, r, doDilation)){ r = r1; } if (isBetter(g1, g, doDilation)){ g = g1; } if (isBetter(b1, b, doDilation)){ b = b1; } } for (int k=0; k<h; k++){ destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; } // return to the first pixel of the next column } } // When radiusY < h <= 2*radiusY else { // The height of the circular buffer is h final int [] bufferA = new int [h]; final int [] bufferR = new int [h]; final int [] bufferG = new int [h]; final int [] bufferB = new int [h]; for (int j=0; j<w; j++){ // initialization of pointers, indice // at the head of each column dp = dstOff + j; cp = dstOff + j; bufferHead = 0; maxIndexA = 0; maxIndexR = 0; maxIndexG = 0; maxIndexB = 0; pel = destPixels[cp]; cp += dstScanStride; a = pel>>>24; r = pel&0xff0000; g = pel&0xff00; b = pel&0xff; bufferA[0] = a; bufferR[0] = r; bufferG[0] = g; bufferB[0] = b; for (int k=1; k<=radiusY; k++){ currentPixel = destPixels[cp]; cp += dstScanStride; a1 = currentPixel>>>24; r1 = currentPixel&0xff0000; g1 = currentPixel&0xff00; b1 = currentPixel&0xff; bufferA[k] = a1; bufferR[k] = r1; bufferG[k] = g1; bufferB[k] = b1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = k; } if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = k; } if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = k; } if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = k; } } // fill the first pixel of each column destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; // // 1 <= i <= h-1-radiusY : The upper margin of each column. // for (int i=1; i<=h-radiusY-1; i++){ lastPixel = destPixels[cp]; cp += dstScanStride; // here is the Alpha channel a = bufferA[maxIndexA]; a1 = lastPixel>>>24; bufferA[i+radiusY] = a1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = i+radiusY; } // now we deal with the Red channel r = bufferR[maxIndexR]; r1 = lastPixel&0xff0000; bufferR[i+radiusY] = r1; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = i+radiusY; } // now we deal with the Green channel g = bufferG[maxIndexG]; g1 = lastPixel&0xff00; bufferG[i+radiusY] = g1; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = i+radiusY; } // now we deal with the Blue channel b = bufferB[maxIndexB]; b1 = lastPixel&0xff; bufferB[i+radiusY] = b1; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = i+radiusY; } // now we have gone through the four channels and // updated the index array. then we'll pack the // new max/min value according to each channel's // max/min vlue destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; } // Now is the inner body of the column // when radiusY < h <= 2*radiusY for (int i = h-radiusY; i<= radiusY; i++){ destPixels[dp] = destPixels[dp-dstScanStride]; dp += dstScanStride; } // The circular buffer is full now for (int i = radiusY+1; i<h; i++){ if (maxIndexA == bufferHead){ a = bufferA[bufferHead+1]; maxIndexA = bufferHead+1; for (int m= bufferHead+2; m< h; m++){ a1 = bufferA[m]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = m; } } } else { a = bufferA[maxIndexA]; } if (maxIndexR == bufferHead){ r = bufferR[bufferHead+1]; maxIndexR = bufferHead+1; for (int m= bufferHead+2; m< h; m++){ r1 = bufferR[m]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = m; } } } else { r = bufferR[maxIndexR]; } if (maxIndexG == bufferHead){ g = bufferG[bufferHead+1]; maxIndexG = bufferHead+1; for (int m= bufferHead+2; m< h; m++){ g1 = bufferG[m]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = m; } } } // we can reuse the previous max/min value else { g = bufferG[maxIndexG]; } if (maxIndexB == bufferHead){ b = bufferB[bufferHead+1]; maxIndexB = bufferHead+1; for (int m= bufferHead+2; m< h; m++){ b1 = bufferB[m]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = m; } } } // we can reuse the previous max/min value else { b = bufferB[maxIndexB]; } // discard the leftmost element bufferHead++; destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; } // return to the first pixel of the next column } } // when radiusY < h <= 2*radiusY }
Filters src and writes result into dest. If dest if null, then a Raster is created. If dest and src refer to the same object, then the source is modified.

The filtering kernel(the operation range for each pixel) is a rectangle of width 2*radiusX+1 and height radiusY+1

Params:
  • src – the Raster to be filtered
  • dest – stores the filtered image. If null, a destination will be created. src and dest can refer to the same Raster, in which situation the src will be modified.
/** * Filters src and writes result into dest. If dest if null, then * a Raster is created. If dest and src refer to the same object, * then the source is modified. * <p> * The filtering kernel(the operation range for each pixel) is a * rectangle of width 2*radiusX+1 and height radiusY+1 * <p> * @param src the Raster to be filtered * @param dest stores the filtered image. If null, a destination will * be created. src and dest can refer to the same Raster, in * which situation the src will be modified. */
public WritableRaster filter(Raster src, WritableRaster dest){ // //This method sorts the pixel values in the kernel window in two steps: // 1. sort by row and store the result into an intermediate matrix // 2. sort the intermediate matrix by column and output the max/min value // into the destination matrix element //check destation if(dest!=null) checkCompatible(dest.getSampleModel()); else { if(src==null) throw new IllegalArgumentException("src should not be null when dest is null"); else dest = createCompatibleDestRaster(src); } final int w = src.getWidth(); final int h = src.getHeight(); // Access the integer buffer for each image. DataBufferInt srcDB = (DataBufferInt)src.getDataBuffer(); DataBufferInt dstDB = (DataBufferInt)dest.getDataBuffer(); // Offset defines where in the stack the real data begin final int srcOff = srcDB.getOffset(); final int dstOff = dstDB.getOffset(); // Stride is the distance between two consecutive column elements, // in the one-dimention dataBuffer final int srcScanStride = ((SinglePixelPackedSampleModel)src.getSampleModel()).getScanlineStride(); final int dstScanStride = ((SinglePixelPackedSampleModel)dest.getSampleModel()).getScanlineStride(); // Access the pixel value array final int[] srcPixels = srcDB.getBankData()[0]; final int[] destPixels = dstDB.getBankData()[0]; // The pointer of src and dest indicating where the pixel values are int sp, dp, cp; // Declaration for the circular buffer's implementation // These are the circular buffers' head pointer and // the index pointers // bufferHead points to the leftmost element in the circular buffer int bufferHead; int maxIndexA; int maxIndexR; int maxIndexG; int maxIndexB; // Temp variables int pel, currentPixel, lastPixel; int a,r,g,b; int a1,r1,g1,b1; // In both round, we are using an optimization approach // to reduce excessive computation to sort values around // the current pixel. The idea is as follows: // ---------------- // |*|V|V|$|N|V|V|&| // ---------------- // For example, suppose we've finished pixel"$" and come // to "N", the radius is 3. Then we must have got the max/min // value and index array for "$". If the max/min is at // "*"(using the index array to judge this), // we need to recompute a max/min and the index array // for "N"; if the max/min is not at "*", we can // reuse the current max/min: we simply compare it with // "&", and update the max/min and the index array. // // The first round: sort by row // if (w<=2*radiusX){ specialProcessRow(src, dest); } // when the size is large enough, we can // use standard optimization method else { final int [] bufferA = new int [rangeX]; final int [] bufferR = new int [rangeX]; final int [] bufferG = new int [rangeX]; final int [] bufferB = new int [rangeX]; for (int i=0; i<h; i++){ // initialization of pointers, indice // at the head of each row sp = srcOff + i*srcScanStride; dp = dstOff + i*dstScanStride; bufferHead = 0; maxIndexA = 0; maxIndexR = 0; maxIndexG = 0; maxIndexB = 0; // // j=0 : Initialization, compute the max/min and // index array for the use of other pixels. // pel = srcPixels[sp++]; a = pel>>>24; r = pel&0xff0000; g = pel&0xff00; b = pel&0xff; bufferA[0] = a; bufferR[0] = r; bufferG[0] = g; bufferB[0] = b; for (int k=1; k<=radiusX; k++){ currentPixel = srcPixels[sp++]; a1 = currentPixel>>>24; r1 = currentPixel&0xff0000; g1 = currentPixel&0xff00; b1 = currentPixel&0xff; bufferA[k] = a1; bufferR[k] = r1; bufferG[k] = g1; bufferB[k] = b1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = k; } if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = k; } if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = k; } if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = k; } } destPixels[dp++] = (a << 24) | r | g | b; // // 1 <= j <= radiusX : The left margin of each row. // for (int j=1; j<=radiusX; j++){ lastPixel = srcPixels[sp++]; // here is the Alpha channel // we retrieve the previous max/min value a = bufferA[maxIndexA]; a1 = lastPixel>>>24; bufferA[j+radiusX] = a1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = j+radiusX; } // now we deal with the Red channel r = bufferR[maxIndexR]; r1 = lastPixel&0xff0000; bufferR[j+radiusX] = r1; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = j+radiusX; } // now we deal with the Green channel g = bufferG[maxIndexG]; g1 = lastPixel&0xff00; bufferG[j+radiusX] = g1; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = j+radiusX; } // now we deal with the Blue channel b = bufferB[maxIndexB]; b1 = lastPixel&0xff; bufferB[j+radiusX] = b1; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = j+radiusX; } // now we have gone through the four channels and // updated the index array. then we'll pack the // new max/min value according to each channel's // max/min vlue destPixels[dp++] = (a << 24) | r | g | b; } // // radiusX <= j <= w-1-radiusX : Inner body of the row, between // left and right margins // for (int j=radiusX+1; j<=w-1-radiusX; j++){ lastPixel = srcPixels[sp++]; a1 = lastPixel>>>24; r1 = lastPixel&0xff0000; g1 = lastPixel&0xff00; b1 = lastPixel&0xff; bufferA[bufferHead] = a1; bufferR[bufferHead] = r1; bufferG[bufferHead] = g1; bufferB[bufferHead] = b1; // Alpha channel: // we need to recompute a local max/min // and update the max/min index if (maxIndexA == bufferHead){ a = bufferA[0]; maxIndexA = 0; for (int m= 1; m< rangeX; m++){ a1 = bufferA[m]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = m; } } } // we can reuse the previous max/min value else { a = bufferA[maxIndexA]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = bufferHead; } } // Red channel // we need to recompute a local max/min // and update the index array if (maxIndexR == bufferHead){ r = bufferR[0]; maxIndexR = 0; for (int m= 1; m< rangeX; m++){ r1 = bufferR[m]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = m; } } } // we can reuse the previous max/min value else { r = bufferR[maxIndexR]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = bufferHead; } } // Green channel // we need to recompute a local max/min // and update the index array if (maxIndexG == bufferHead){ g = bufferG[0]; maxIndexG = 0; for (int m= 1; m< rangeX; m++){ g1 = bufferG[m]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = m; } } } // we can reuse the previous max/min value else { g = bufferG[maxIndexG]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = bufferHead; } } // Blue channel // we need to recompute a local max/min // and update the index array if (maxIndexB == bufferHead){ b = bufferB[0]; maxIndexB = 0; for (int m= 1; m< rangeX; m++){ b1 = bufferB[m]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = m; } } } // we can reuse the previous max/min value else { b = bufferB[maxIndexB]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = bufferHead; } } destPixels[dp++] = (a << 24) | r | g | b; bufferHead = (bufferHead+1)%rangeX; } // // w-radiusX <= j < w : The right margin of the row // // Head will be updated to indicate the current head // of the remaining buffer int head; // Tail is where the last element is final int tail = (bufferHead == 0)?rangeX-1:bufferHead -1; int count = rangeX-1; for (int j=w-radiusX; j<w; j++){ head = (bufferHead+1)%rangeX; // Dealing with Alpha Channel: if (maxIndexA == bufferHead){ a = bufferA[tail]; int hd = head; for(int m=1; m<count; m++) { a1 = bufferA[hd]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = hd; } hd = (hd+1)%rangeX; } } // Dealing with Red Channel: if (maxIndexR == bufferHead){ r = bufferR[tail]; int hd = head; for(int m=1; m<count; m++) { r1 = bufferR[hd]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = hd; } hd = (hd+1)%rangeX; } } // Dealing with Green Channel: if (maxIndexG == bufferHead){ g = bufferG[tail]; int hd = head; for(int m=1; m<count; m++) { g1 = bufferG[hd]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = hd; } hd = (hd+1)%rangeX; } } // Dealing with Blue Channel: if (maxIndexB == bufferHead){ b = bufferB[tail]; int hd = head; for(int m=1; m<count; m++) { b1 = bufferB[hd]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = hd; } hd = (hd+1)%rangeX; } } destPixels[dp++] = (a << 24) | r | g | b; bufferHead = (bufferHead+1)%rangeX; // we throw another element count--; }// end of the right margin of this row // return to the beginning of the next row } }// end of the first round! // // Second round: sort by column // the difference from the first round is that // now we are accessing the intermediate matrix // // When the image size is smaller than the // Kernel size if (h<=2*radiusY){ specialProcessColumn(src, dest); } // when the size is large enough, we can // use standard optimization method else { final int [] bufferA = new int [rangeY]; final int [] bufferR = new int [rangeY]; final int [] bufferG = new int [rangeY]; final int [] bufferB = new int [rangeY]; for (int j=0; j<w; j++){ // initialization of pointers, indice // at the head of each column dp = dstOff + j; cp = dstOff + j; bufferHead = 0; maxIndexA = 0; maxIndexR = 0; maxIndexG = 0; maxIndexB = 0; // i=0 : The first pixel pel = destPixels[cp]; cp += dstScanStride; a = pel>>>24; r = pel&0xff0000; g = pel&0xff00; b = pel&0xff; bufferA[0] = a; bufferR[0] = r; bufferG[0] = g; bufferB[0] = b; for (int k=1; k<=radiusY; k++){ currentPixel = destPixels[cp]; cp += dstScanStride; a1 = currentPixel>>>24; r1 = currentPixel&0xff0000; g1 = currentPixel&0xff00; b1 = currentPixel&0xff; bufferA[k] = a1; bufferR[k] = r1; bufferG[k] = g1; bufferB[k] = b1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = k; } if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = k; } if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = k; } if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = k; } } destPixels[dp] = (a << 24) | r | g | b; // go to the next element in the column. dp += dstScanStride; // 1 <= i <= radiusY : The upper margin of each row for (int i=1; i<=radiusY; i++){ int maxI = i+radiusY; // we can reuse the previous max/min value lastPixel = destPixels[cp]; cp += dstScanStride; // here is the Alpha channel a = bufferA[maxIndexA]; a1 = lastPixel>>>24; bufferA[maxI] = a1; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = maxI; } // now we deal with the Red channel r = bufferR[maxIndexR]; r1 = lastPixel&0xff0000; bufferR[maxI] = r1; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = maxI; } // now we deal with the Green channel g = bufferG[maxIndexG]; g1 = lastPixel&0xff00; bufferG[maxI] = g1; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = maxI; } // now we deal with the Blue channel b = bufferB[maxIndexB]; b1 = lastPixel&0xff; bufferB[maxI] = b1; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = maxI; } destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; } // // radiusY +1 <= i <= h-1-radiusY: // inner body of the column between upper and lower margins // for (int i=radiusY+1; i<=h-1-radiusY; i++){ lastPixel = destPixels[cp]; cp += dstScanStride; a1 = lastPixel>>>24; r1 = lastPixel&0xff0000; g1 = lastPixel&0xff00; b1 = lastPixel&0xff; bufferA[bufferHead] = a1; bufferR[bufferHead] = r1; bufferG[bufferHead] = g1; bufferB[bufferHead] = b1; // here we check if the previous max/min value can be // reused safely and, if possible, reuse the previous // maximum value // Alpha channel: // Recompute the local max/min if (maxIndexA == bufferHead){ a = bufferA[0]; maxIndexA = 0; for (int m= 1; m<= 2*radiusY; m++){ a1 = bufferA[m]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = m; } } } // we can reuse the previous max/min value else { a = bufferA[maxIndexA]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = bufferHead; } } // Red channel: if (maxIndexR == bufferHead){ r = bufferR[0]; maxIndexR = 0; for (int m= 1; m<= 2*radiusY; m++){ r1 = bufferR[m]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = m; } } } // we can reuse the previous max/min value else { r = bufferR[maxIndexR]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = bufferHead; } } // Green channel if (maxIndexG == bufferHead){ g = bufferG[0]; maxIndexG = 0; for (int m= 1; m<= 2*radiusY; m++){ g1 = bufferG[m]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = m; } } } // we can reuse the previous max/min value else { g = bufferG[maxIndexG]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = bufferHead; } } // Blue channel: if (maxIndexB == bufferHead){ b = bufferB[0]; maxIndexB = 0; for (int m= 1; m<= 2*radiusY; m++){ b1 = bufferB[m]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = m; } } } // we can reuse the previous max/min value else { b = bufferB[maxIndexB]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = bufferHead; } } destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; bufferHead = (bufferHead+1)%rangeY; } // // h-radiusY <= i <= h-1 : The lower margin of the column // // head will be updated to indicate the current head // of the remaining buffer: int head; // tail is where the last element in the buffer is final int tail = (bufferHead == 0)?2*radiusY:bufferHead -1; int count = rangeY-1; for (int i= h-radiusY; i<h-1; i++){ head = (bufferHead +1)%rangeY; if (maxIndexA == bufferHead){ a = bufferA[tail]; int hd = head; for (int m=1; m<count; m++){ a1 = bufferA[hd]; if (isBetter(a1, a, doDilation)){ a = a1; maxIndexA = hd; } hd = (hd+1)%rangeY; } } if (maxIndexR == bufferHead){ r = bufferR[tail]; int hd = head; for (int m=1; m<count; m++){ r1 = bufferR[hd]; if (isBetter(r1, r, doDilation)){ r = r1; maxIndexR = hd; } hd = (hd+1)%rangeY; } } if (maxIndexG == bufferHead){ g = bufferG[tail]; int hd = head; for (int m=1; m<count; m++){ g1 = bufferG[hd]; if (isBetter(g1, g, doDilation)){ g = g1; maxIndexG = hd; } hd = (hd+1)%rangeY; } } if (maxIndexB == bufferHead){ b = bufferB[tail]; int hd = head; for (int m=1; m<count; m++){ b1 = bufferB[hd]; if (isBetter(b1, b, doDilation)){ b = b1; maxIndexB = hd; } hd = (hd+1)%rangeY; } } destPixels[dp] = (a << 24) | r | g | b; dp += dstScanStride; bufferHead = (bufferHead+1)%rangeY; // we throw out this useless element count--; } // return to the beginning of the next column } }// end of the second round! return dest; }// end of the filter() method for Raster
This implementation of filter does the morphology operation on a premultiplied alpha image. This tends to muddy the colors. so something that is supposed to be a mostly transparent bright red may well become a muddy opaque red. Where as I think it should become a bright opaque red. Which is the result you would get if you were using unpremult data.
/** * This implementation of filter does the morphology operation * on a premultiplied alpha image. This tends to muddy the * colors. so something that is supposed to be a mostly * transparent bright red may well become a muddy opaque red. * Where as I think it should become a bright opaque red. Which * is the result you would get if you were using unpremult data. */
public BufferedImage filter(BufferedImage src, BufferedImage dest){ if (src == null) throw new NullPointerException("Source image should not be null"); BufferedImage origSrc = src; BufferedImage finalDest = dest; if (!isCompatible(src.getColorModel(), src.getSampleModel())) { src = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); GraphicsUtil.copyData(origSrc, src); } else if (!src.isAlphaPremultiplied()) { // Get a Premultipled CM. ColorModel srcCM, srcCMPre; srcCM = src.getColorModel(); srcCMPre = GraphicsUtil.coerceColorModel(srcCM, true); src = new BufferedImage(srcCMPre, src.getRaster(), true, null); GraphicsUtil.copyData(origSrc, src); } if (dest == null) { dest = createCompatibleDestImage(src, null); finalDest = dest; } else if (!isCompatible(dest.getColorModel(), dest.getSampleModel())) { dest = createCompatibleDestImage(src, null); } else if (!dest.isAlphaPremultiplied()) { // Get a Premultipled CM. ColorModel dstCM, dstCMPre; dstCM = dest.getColorModel(); dstCMPre = GraphicsUtil.coerceColorModel(dstCM, true); dest = new BufferedImage(dstCMPre, finalDest.getRaster(), true, null); } filter(src.getRaster(), dest.getRaster()); // Check to see if we need to 'fix' our source (divide out alpha). if ((src.getRaster() == origSrc.getRaster()) && (src.isAlphaPremultiplied() != origSrc.isAlphaPremultiplied())) { // Copy our source back the way it was... GraphicsUtil.copyData(src, origSrc); } // Check to see if we need to store our result... if ((dest.getRaster() != finalDest.getRaster()) || (dest.isAlphaPremultiplied() != finalDest.isAlphaPremultiplied())){ // Coerce our source back the way it was requested... GraphicsUtil.copyData(dest, finalDest); } return finalDest; } /* * This commented out implementation of filter does the * morphology operation on unpremultiplied alpha image data. * This tends to leave colors bright. */ /* public BufferedImage filter(BufferedImage src, BufferedImage dest){ if (src == null && dest == null) throw new NullPointerException("Source image should not be null"); BufferedImage origSrc = src; BufferedImage finalDest = dest; if (!isCompatible(src.getColorModel(), src.getSampleModel())) { src = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB); GraphicsUtil.copyData(origSrc, src); } else if (src.isAlphaPremultiplied()) { ColorModel srcCM, srcCMUnpre; srcCM = src.getColorModel(); srcCMUnpre = GraphicsUtil.coerceColorModel(srcCM, false); src = new BufferedImage(srcCMUnpre, src.getRaster(), false, null); GraphicsUtil.copyData(origSrc, src); } if (dest == null) { dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB); finalDest = dest; } else if (!isCompatible(dest.getColorModel(), dest.getSampleModel())) { dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB); } else if (dest.isAlphaPremultiplied()) { ColorModel dstCM, dstCMUnpre; dstCM = dest.getColorModel(); dstCMUnpre = GraphicsUtil.coerceColorModel(dstCM, false); dest = new BufferedImage(dstCMUnpre, finalDest.getRaster(), false, null); } // We now have two compatible images. We can safely filter the rasters filter(src.getRaster(), dest.getRaster()); // Check to see if we need to 'fix' our source (divide out alpha). if ((src.getRaster() == origSrc.getRaster()) && (src.isAlphaPremultiplied() != origSrc.isAlphaPremultiplied())) { GraphicsUtil.copyData(src, origSrc); } // Check to see if we need to store our result... if ((dest.getRaster() != finalDest.getRaster()) || (dest.isAlphaPremultiplied() != finalDest.isAlphaPremultiplied())){ // Coerce our source back the way it was... System.out.println("Dest: " + dest.isAlphaPremultiplied() + " finalDest: " + finalDest.isAlphaPremultiplied()); GraphicsUtil.copyData(dest, finalDest); } return finalDest; } */ }