//------------------------------------------------------------------------------------------------// // // // G a u s s i a n G r a y F i l t e r // // // //------------------------------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Audiveris 2018. All rights reserved. // // This program is free software: you can redistribute it and/or modify it under the terms of the // GNU Affero General Public License as published by the Free Software Foundation, either version // 3 of the License, or (at your option) any later version. // // This program 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License along with this // program. If not, see <http://www.gnu.org/licenses/>. //------------------------------------------------------------------------------------------------// // </editor-fold> package org.audiveris.omr.image; import ij.process.ByteProcessor; import java.awt.image.Kernel; /** * Class {@code GaussianGrayFilter} allows to run a Gaussian filter on an input image, * assumed to contain only gray values [0..255]. * <p> * This implementation is derived from Jerry Huxtable more general filter but limited to * BufferedImage class. * * @author Hervé Bitteur */ public class GaussianGrayFilter extends AbstractGrayFilter { /** Radius of the kernel. */ private final float radius; /** The kernel to apply. */ private final Kernel kernel; /** * Creates a new GaussianGrayFilter object with a default radius value. */ public GaussianGrayFilter () { this(2); } /** * Creates a new GaussianGrayFilter object with a specified radius. * * @param radius kernel radius in pixels */ public GaussianGrayFilter (float radius) { this.radius = radius; kernel = makeKernel(radius); } //--------// // filter // //--------// @Override public void filter (ByteProcessor input, ByteProcessor output) { final int width = input.getWidth(); final int height = input.getHeight(); final byte[] inPixels = new byte[width * height]; final byte[] outPixels = new byte[width * height]; // Read pixels for (int y = 0; y < height; y++) { final int offset = y * width; for (int x = 0; x < width; x++) { inPixels[offset + x] = (byte) input.get(x, y); } } convolveAndTranspose(inPixels, outPixels, width, height); convolveAndTranspose(outPixels, inPixels, height, width); // Write pixels for (int y = 0; y < height; y++) { final int offset = y * width; for (int x = 0; x < width; x++) { output.set(x, y, inPixels[offset + x] & 0xff); } } } //-----------// // getRadius // //-----------// /** * Get the radius of the kernel. * * @return the kernel radius */ public float getRadius () { return radius; } //----------------------// // convolveAndTranspose // //----------------------// private void convolveAndTranspose (byte[] inPixels, byte[] outPixels, int width, int height) { float[] matrix = kernel.getKernelData(null); int cols = kernel.getWidth(); int cols2 = cols / 2; for (int y = 0; y < height; y++) { int index = y; int ioffset = y * width; for (int x = 0; x < width; x++) { float p = 0; int moffset = cols2; for (int col = -cols2; col <= cols2; col++) { float f = matrix[moffset + col]; if (f != 0) { int ix = x + col; if (ix < 0) { ix = 0; } else if (ix >= width) { ix = width - 1; } int pix = inPixels[ioffset + ix] & 0xff; p += (f * pix); } } int ip = clamp((int) (p + 0.5)); outPixels[index] = (byte) ip; index += height; } } } //------------// // makeKernel // //------------// /** * Make a Gaussian blur kernel. * * @param radius desired kernel radius specified in pixels around center * @return the Gaussian kernel of desired radius */ public static Kernel makeKernel (float radius) { final int r = (int) Math.ceil(radius); final int rows = (r * 2) + 1; final float[] matrix = new float[rows]; final float sigma = 1f; //HB: was radius / 3; final float sigmaSq2 = 2 * sigma * sigma; final float radiusSq = radius * radius; float total = 0; int index = 0; for (int row = -r; row <= r; row++) { float distanceSq = row * row; if (distanceSq > radiusSq) { matrix[index] = 0; } else { matrix[index] = (float) Math.exp(-distanceSq / sigmaSq2); } total += matrix[index]; index++; } // Normalize all matrix items for (int i = 0; i < rows; i++) { matrix[i] /= total; } return new Kernel(rows, 1, matrix); } //-------// // clamp // //-------// /** * Clamp a value to the range 0..255. * * @param val the input value * @return the value constrained in [0..255] */ private static int clamp (int val) { if (val < 0) { return 0; } if (val > 255) { return 255; } return val; } }