/*

   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.image.ColorModel;
import java.awt.image.ConvolveOp;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Kernel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

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

This implementation of RenderableImage will render its input GraphicsNode on demand for tiles.
Author:Vincent Hardy
Version:$Id: GaussianBlurRed8Bit.java 1733416 2016-03-03 07:07:13Z gadams $
/** * This implementation of RenderableImage will render its input * GraphicsNode on demand for tiles. * * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a> * @version $Id: GaussianBlurRed8Bit.java 1733416 2016-03-03 07:07:13Z gadams $ */
public class GaussianBlurRed8Bit extends AbstractRed { int xinset, yinset; double stdDevX, stdDevY; RenderingHints hints; ConvolveOp [] convOp = new ConvolveOp [2]; int dX, dY;
Construct a blurred version of src, by blurring with a gaussian kernel with standard Deviation of stdDev pixels.
Params:
  • src – The source image to blur
  • stdDev – The Standard Deviation of the Gaussian kernel.
  • rh – Rendering hints.
/** * Construct a blurred version of <code>src</code>, by blurring with a * gaussian kernel with standard Deviation of <code>stdDev</code> pixels. * @param src The source image to blur * @param stdDev The Standard Deviation of the Gaussian kernel. * @param rh Rendering hints. */
public GaussianBlurRed8Bit(CachableRed src, double stdDev, RenderingHints rh) { this(src, stdDev, stdDev, rh); }
Construct a blurred version of src, by blurring with a gaussian kernel with standard Deviation of stdDev pixels.
Params:
  • src – The source image to blur
  • stdDevX – The Standard Deviation of the Gaussian kernel in X
  • stdDevY – The Standard Deviation of the Gaussian kernel in Y
  • rh – Rendering hints.
/** * Construct a blurred version of <code>src</code>, by blurring with a * gaussian kernel with standard Deviation of <code>stdDev</code> pixels. * @param src The source image to blur * @param stdDevX The Standard Deviation of the Gaussian kernel in X * @param stdDevY The Standard Deviation of the Gaussian kernel in Y * @param rh Rendering hints. */
public GaussianBlurRed8Bit(CachableRed src, double stdDevX, double stdDevY, RenderingHints rh) { super(); // Remember to call super.init() this.stdDevX = stdDevX; this.stdDevY = stdDevY; this.hints = rh; xinset = surroundPixels(stdDevX, rh); yinset = surroundPixels(stdDevY, rh); Rectangle myBounds = src.getBounds(); myBounds.x += xinset; myBounds.y += yinset; myBounds.width -= 2*xinset; myBounds.height -= 2*yinset; if ((myBounds.width <= 0) || (myBounds.height <= 0)) { myBounds.width=0; myBounds.height=0; } ColorModel cm = fixColorModel(src); SampleModel sm = src.getSampleModel(); int tw = sm.getWidth(); int th = sm.getHeight(); if (tw > myBounds.width) tw = myBounds.width; if (th > myBounds.height) th = myBounds.height; sm = cm.createCompatibleSampleModel(tw, th); init(src, myBounds, cm, sm, src.getTileGridXOffset()+xinset, src.getTileGridYOffset()+yinset, null); boolean highQuality = ((hints != null) && RenderingHints.VALUE_RENDER_QUALITY.equals (hints.get(RenderingHints.KEY_RENDERING))); // System.out.println("StdDev: " + stdDevX + "x" + stdDevY); if ((xinset != 0) && ((stdDevX < 2) || highQuality)) convOp[0] = new ConvolveOp(makeQualityKernelX(xinset*2+1)); else dX = (int)Math.floor(DSQRT2PI*stdDevX+0.5f); if ((yinset != 0) && ((stdDevY < 2) || highQuality)) convOp[1] = new ConvolveOp(makeQualityKernelY(yinset*2+1)); else dY = (int)Math.floor(DSQRT2PI*stdDevY+0.5f); }
Constant: sqrt(2*PI)
/** * Constant: sqrt(2*PI) */
static final float SQRT2PI = (float)Math.sqrt(2*Math.PI);
Constant: 3*sqrt(2*PI)/4
/** * Constant: 3*sqrt(2*PI)/4 */
static final float DSQRT2PI = SQRT2PI*3f/4f;
Constant: precision used in computation of the Kernel radius
/** * Constant: precision used in computation of the Kernel radius */
static final float precision = 0.499f;
Calculate the number of surround pixels required for a given standard Deviation.
/** * Calculate the number of surround pixels required for a given * standard Deviation. */
public static int surroundPixels(double stdDev) { return surroundPixels(stdDev, null); }
Calculate the number of surround pixels required for a given standard Deviation. Also takes into account rendering quality hint.
/** * Calculate the number of surround pixels required for a given * standard Deviation. Also takes into account rendering quality * hint. */
public static int surroundPixels(double stdDev, RenderingHints hints) { boolean highQuality = ((hints != null) && RenderingHints.VALUE_RENDER_QUALITY.equals (hints.get(RenderingHints.KEY_RENDERING))); if ((stdDev < 2) || highQuality) { // Start with 1/2 the zero box enery. float areaSum = (float)(0.5/(stdDev*SQRT2PI)); int i=0; while (areaSum < precision) { areaSum += (float)(Math.pow(Math.E, -i*i/(2*stdDev*stdDev)) / (stdDev*SQRT2PI)); i++; } return i; } //compute d int diam = (int)Math.floor(DSQRT2PI*stdDev+0.5f); if (diam%2 == 0) return diam-1 + diam/2; // even case else return diam-2 + diam/2; // Odd case } /* * Here we compute the data for the one-dimensional kernel of * length '2*(radius-1) + 1' * * @param radius stdDeviationX or stdDeviationY. * @see #makeQualityKernels */ private float [] computeQualityKernelData(int len, double stdDev){ final float[] kernelData = new float [len]; int mid = len/2; float sum = 0; // Used to normalise the kernel for(int i=0; i<len; i++){ kernelData[i] = (float)(Math.pow(Math.E, -(i-mid)*(i-mid)/ (2*stdDev*stdDev)) / (SQRT2PI*stdDev)); sum += kernelData[i]; } // Normalise: make elements sum to 1 for (int i=0; i<len; i++) kernelData[i] /= sum; return kernelData; } private Kernel makeQualityKernelX(int len) { return new Kernel(len, 1, computeQualityKernelData(len, stdDevX)); } private Kernel makeQualityKernelY(int len) { return new Kernel(1, len, computeQualityKernelData(len, stdDevY)); } public WritableRaster copyData(WritableRaster wr) { // Get my source. CachableRed src = (CachableRed)getSources().get(0); Rectangle r = wr.getBounds(); r.x -= xinset; r.y -= yinset; r.width += 2*xinset; r.height += 2*yinset; // System.out.println("Gaussian GenR: " + wr); // System.out.println("SrcReq: " + r); ColorModel srcCM = src.getColorModel(); WritableRaster tmpR1=null, tmpR2=null; tmpR1 = srcCM.createCompatibleWritableRaster(r.width, r.height); { WritableRaster fill; fill = tmpR1.createWritableTranslatedChild(r.x, r.y); src.copyData(fill); } if (srcCM.hasAlpha() && !srcCM.isAlphaPremultiplied()) GraphicsUtil.coerceData(tmpR1, srcCM, true); // For the blur box approx we can use dest as our intermediate // otherwise we let it default to null which means we create a new // one... // this lets the Vertical conv know how much is junk, so it // doesn't bother to convolve the top and bottom edges int skipX; // long t1 = System.currentTimeMillis(); if (xinset == 0) { skipX = 0; } else if (convOp[0] != null) { tmpR2 = getColorModel().createCompatibleWritableRaster (r.width, r.height); tmpR2 = convOp[0].filter(tmpR1, tmpR2); skipX = convOp[0].getKernel().getXOrigin(); // Swap them... WritableRaster tmp = tmpR1; tmpR1 = tmpR2; tmpR2 = tmp; } else { if ((dX&0x01) == 0){ tmpR1 = boxFilterH(tmpR1, tmpR1, 0, 0, dX, dX/2); tmpR1 = boxFilterH(tmpR1, tmpR1, dX/2, 0, dX, dX/2-1); tmpR1 = boxFilterH(tmpR1, tmpR1, dX-1, 0, dX+1, dX/2); skipX = dX-1 + dX/2; } else { tmpR1 = boxFilterH(tmpR1, tmpR1, 0, 0, dX, dX/2); tmpR1 = boxFilterH(tmpR1, tmpR1, dX/2, 0, dX, dX/2); tmpR1 = boxFilterH(tmpR1, tmpR1, dX-2, 0, dX, dX/2); skipX = dX-2 + dX/2; } } if (yinset == 0) { tmpR2 = tmpR1; } else if (convOp[1] != null) { if (tmpR2 == null) { tmpR2 = getColorModel().createCompatibleWritableRaster (r.width, r.height); } tmpR2 = convOp[1].filter(tmpR1, tmpR2); } else { if ((dY&0x01) == 0){ tmpR1 = boxFilterV(tmpR1, tmpR1, skipX, 0, dY, dY/2); tmpR1 = boxFilterV(tmpR1, tmpR1, skipX, dY/2, dY, dY/2-1); tmpR1 = boxFilterV(tmpR1, tmpR1, skipX, dY-1, dY+1, dY/2); } else { tmpR1 = boxFilterV(tmpR1, tmpR1, skipX, 0, dY, dY/2); tmpR1 = boxFilterV(tmpR1, tmpR1, skipX, dY/2, dY, dY/2); tmpR1 = boxFilterV(tmpR1, tmpR1, skipX, dY-2, dY, dY/2); } tmpR2 = tmpR1; } // long t2 = System.currentTimeMillis(); // System.out.println("Time: " + (t2-t1) + // (((convOp[0] != null) || (convOp[1] != null))? // " ConvOp":"")); // System.out.println("Rasters WR :" + wr.getBounds()); // System.out.println(" tmp:" + tmpR2.getBounds()); // System.out.println(" bounds:" + getBounds()); // System.out.println(" skipX:" + skipX + // " dx:" + dX + " Dy: " + dY); tmpR2 = tmpR2.createWritableTranslatedChild(r.x, r.y); GraphicsUtil.copyData(tmpR2, wr); return wr; } private WritableRaster boxFilterH(Raster src, WritableRaster dest, int skipX, int skipY, int boxSz, int loc) { final int w = src.getWidth(); final int h = src.getHeight(); // Check if the raster is wide enough to do _any_ work if (w < (2*skipX)+boxSz) return dest; if (h < (2*skipY)) return dest; final SinglePixelPackedSampleModel srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel(); final SinglePixelPackedSampleModel dstSPPSM = (SinglePixelPackedSampleModel)dest.getSampleModel(); // Stride is the distance between two consecutive column elements, // in the one-dimention dataBuffer final int srcScanStride = srcSPPSM.getScanlineStride(); final int dstScanStride = dstSPPSM.getScanlineStride(); // 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() + srcSPPSM.getOffset (src.getMinX()-src.getSampleModelTranslateX(), src.getMinY()-src.getSampleModelTranslateY())); final int dstOff = (dstDB.getOffset() + dstSPPSM.getOffset (dest.getMinX()-dest.getSampleModelTranslateX(), dest.getMinY()-dest.getSampleModelTranslateY())); // Access the pixel value array final int[] srcPixels = srcDB.getBankData()[0]; final int[] destPixels = dstDB.getBankData()[0]; final int [] buffer = new int [boxSz]; int curr, prev; // Fixed point normalization factor (8.24) int scale = (1<<24)/boxSz; /* * System.out.println("Info: srcOff: " + srcOff + * " x: " + skipX + * " y: " + skipY + * " w: " + w + * " h: " + h + * " boxSz " + boxSz + * " srcStride: " + srcScanStride); */ for (int y=skipY; y<(h-skipY); y++) { int sp = srcOff + y*srcScanStride; int dp = dstOff + y*dstScanStride; int rowEnd = sp + (w-skipX); int k = 0; int sumA = 0; int sumR = 0; int sumG = 0; int sumB = 0; sp += skipX; int end = sp+boxSz; while (sp < end) { curr = buffer[k] = srcPixels[sp]; sumA += (curr>>> 24); sumR += (curr >> 16)&0xFF; sumG += (curr >> 8)&0xFF; sumB += (curr )&0xFF; k++; sp++; } dp += skipX + loc; prev = destPixels[dp] = (( (sumA*scale)&0xFF000000) | (((sumR*scale)&0xFF000000)>>>8) | (((sumG*scale)&0xFF000000)>>>16) | (((sumB*scale)&0xFF000000)>>>24)); dp++; k=0; while (sp < rowEnd) { curr = buffer[k]; if (curr == srcPixels[sp]) { destPixels[dp] = prev; } else { sumA -= (curr>>> 24); sumR -= (curr >> 16)&0xFF; sumG -= (curr >> 8)&0xFF; sumB -= (curr )&0xFF; curr = buffer[k] = srcPixels[sp]; sumA += (curr>>> 24); sumR += (curr >> 16)&0xFF; sumG += (curr >> 8)&0xFF; sumB += (curr )&0xFF; prev = destPixels[dp] = (( (sumA*scale)&0xFF000000) | (((sumR*scale)&0xFF000000)>>>8) | (((sumG*scale)&0xFF000000)>>>16) | (((sumB*scale)&0xFF000000)>>>24)); } k = (k+1)%boxSz; sp++; dp++; } } return dest; } private WritableRaster boxFilterV(Raster src, WritableRaster dest, int skipX, int skipY, int boxSz, int loc) { final int w = src.getWidth(); final int h = src.getHeight(); // Check if the raster is wide enough to do _any_ work if (w < (2*skipX)) return dest; if (h < (2*skipY)+boxSz) return dest; final SinglePixelPackedSampleModel srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel(); final SinglePixelPackedSampleModel dstSPPSM = (SinglePixelPackedSampleModel)dest.getSampleModel(); // Stride is the distance between two consecutive column elements, // in the one-dimention dataBuffer final int srcScanStride = srcSPPSM.getScanlineStride(); final int dstScanStride = dstSPPSM.getScanlineStride(); // 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() + srcSPPSM.getOffset (src.getMinX()-src.getSampleModelTranslateX(), src.getMinY()-src.getSampleModelTranslateY())); final int dstOff = (dstDB.getOffset() + dstSPPSM.getOffset (dest.getMinX()-dest.getSampleModelTranslateX(), dest.getMinY()-dest.getSampleModelTranslateY())); // Access the pixel value array final int[] srcPixels = srcDB.getBankData()[0]; final int[] destPixels = dstDB.getBankData()[0]; final int [] buffer = new int [boxSz]; int curr, prev; // Fixed point normalization factor (8.24) final int scale = (1<<24)/boxSz; /* * System.out.println("Info: srcOff: " + srcOff + * " x: " + skipX + * " y: " + skipY + * " w: " + w + * " h: " + h + * " boxSz " + boxSz + * " srcStride: " + srcScanStride); */ for (int x=skipX; x<(w-skipX); x++) { int sp = srcOff + x; int dp = dstOff + x; int colEnd = sp + (h-skipY)*srcScanStride; int k=0; int sumA = 0; int sumR = 0; int sumG = 0; int sumB = 0; sp += skipY*srcScanStride; int end = sp+(boxSz*srcScanStride); while (sp < end) { curr = buffer[k] = srcPixels[sp]; sumA += (curr>>> 24); sumR += (curr >> 16)&0xFF; sumG += (curr >> 8)&0xFF; sumB += (curr )&0xFF; k++; sp+=srcScanStride; } dp += (skipY + loc)*dstScanStride; prev = destPixels[dp] = (( (sumA*scale)&0xFF000000) | (((sumR*scale)&0xFF000000)>>>8) | (((sumG*scale)&0xFF000000)>>>16) | (((sumB*scale)&0xFF000000)>>>24)); dp+=dstScanStride; k=0; while (sp < colEnd) { curr = buffer[k]; if (curr == srcPixels[sp]) { destPixels[dp] = prev; } else { sumA -= (curr>>> 24); sumR -= (curr >> 16)&0xFF; sumG -= (curr >> 8)&0xFF; sumB -= (curr )&0xFF; curr = buffer[k] = srcPixels[sp]; sumA += (curr>>> 24); sumR += (curr >> 16)&0xFF; sumG += (curr >> 8)&0xFF; sumB += (curr )&0xFF; prev = destPixels[dp] = (( (sumA*scale)&0xFF000000) | (((sumR*scale)&0xFF000000)>>>8) | (((sumG*scale)&0xFF000000)>>>16) | (((sumB*scale)&0xFF000000)>>>24)); } k = (k+1)%boxSz; sp+=srcScanStride; dp+=dstScanStride; } } return dest; } protected static ColorModel fixColorModel(CachableRed src) { ColorModel cm = src.getColorModel(); int b = src.getSampleModel().getNumBands(); int [] masks = new int[4]; switch (b) { case 1: masks[0] = 0xFF; break; case 2: masks[0] = 0x00FF; masks[3] = 0xFF00; break; case 3: masks[0] = 0xFF0000; masks[1] = 0x00FF00; masks[2] = 0x0000FF; break; case 4: masks[0] = 0x00FF0000; masks[1] = 0x0000FF00; masks[2] = 0x000000FF; masks[3] = 0xFF000000; break; default: throw new IllegalArgumentException ("GaussianBlurRed8Bit only supports one to four band images"); } ColorSpace cs = cm.getColorSpace(); return new DirectColorModel(cs, 8*b, masks[0], masks[1], masks[2], masks[3], true, DataBuffer.TYPE_INT); } }