/*
* This file is part of JGrasstools (http://www.jgrasstools.org)
*
* JGrasstools is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jgrasstools.gears.modules.r.morpher;
import java.awt.Point;
import java.util.HashSet;
import java.util.Iterator;
import org.jgrasstools.gears.modules.utils.BinaryFast;
/*
R. B. Fisher, K. Koryllos,
``Interactive Textbooks; Embedding Image Processing Operator Demonstrations
in Text'', Int. J. of Pattern Recognition and Artificial Intelligence,
Vol 12, No 8, pp 1095-1123, 1998.
*/
/**
* Thin is an algorithm to thin a binary image using a 3x3 kernel.
* @author Simon Horne.
*/
public class Thin {
/**
* Takes an image and a kernel and thins it once.
*
* @param b the BinaryFast input image
* @param kernel the thinning kernel
* @return the thinned BinaryFast image
*/
private BinaryFast thinBinaryRep( BinaryFast b, int[] kernel ) {
Point p;
HashSet<Point> inputHashSet = new HashSet<Point>();
int[][] pixels = b.getPixels();
if (kernelNo0s(kernel)) {
for( int j = 0; j < b.getHeight(); ++j ) {
for( int i = 0; i < b.getWidth(); ++i ) {
if (pixels[i][j] == BinaryFast.FOREGROUND) {
inputHashSet.add(new Point(i, j));
}
}
}
} else {
Iterator<Point> it = b.getForegroundEdgePixels().iterator();
while( it.hasNext() ) {
inputHashSet.add(it.next());
}
}
HashSet<Point> result = hitMissHashSet(b, inputHashSet, kernel);
Iterator<Point> it = result.iterator();
while( it.hasNext() ) {
p = new Point(it.next());
// make p a background pixel and update the edge sets
b.removePixel(p);
b.getForegroundEdgePixels().remove(p);
b.getBackgroundEdgePixels().add(p);
// check if new foreground pixels are exposed as edges
for( int j = -1; j < 2; ++j ) {
for( int k = -1; k < 2; ++k ) {
if (p.x + j >= 0 && p.y + k > 0 && p.x + j < b.getWidth() && p.y + k < b.getHeight()
&& pixels[p.x + j][p.y + k] == BinaryFast.FOREGROUND) {
Point p2 = new Point(p.x + j, p.y + k);
b.getForegroundEdgePixels().add(p2);
}
}
}
}
return b;
}
/**
* Takes an image and a kernel and thins it the specified number of times.
*
* @param binary the BinaryFast input image.
* @param kernel the kernel to apply.
*/
public void processSkeleton( BinaryFast binary, int[][] kernel ) {
int oldForeEdge = 0;
int oldBackEdge = 0;
while( !(binary.getForegroundEdgePixels().size() == oldForeEdge && binary.getBackgroundEdgePixels().size() == oldBackEdge) ) {
oldForeEdge = binary.getForegroundEdgePixels().size();
oldBackEdge = binary.getBackgroundEdgePixels().size();
for( int i = 0; i < kernel.length; ++i ) {
binary = thinBinaryRep(binary, kernel[i]);
binary.generateBackgroundEdgeFromForegroundEdge();
}
}
}
public void processPruning( BinaryFast binary, int iterations, int[][] kernels ) {
for( int j = 0; j < iterations; j++ ) {
for( int i = 0; i < kernels.length; ++i ) {
binary = thinBinaryRep(binary, kernels[i]);
binary.generateBackgroundEdgeFromForegroundEdge();
}
}
}
public void processLineendings( BinaryFast binary, int[][] kernels ) {
for( int i = 0; i < kernels.length; ++i ) {
binary = thinBinaryRep(binary, kernels[i]);
binary.generateBackgroundEdgeFromForegroundEdge();
}
}
/**
*Returns true if the 8 neighbours of p match the kernel
*0 is background
*1 is foreground
*2 is don't care.
*
* @param p the point at the centre of the
* 9 pixel neighbourhood
* @param pixels the 2D array of the image
* @param w the width of the image
* @param h the height of the image
* @param kernel the array of the kernel values
* @return True if the kernel and image match.
*/
private boolean kernelMatch( Point p, int[][] pixels, int w, int h, int[] kernel ) {
int matched = 0;
for( int j = -1; j < 2; ++j ) {
for( int i = -1; i < 2; ++i ) {
if (kernel[((j + 1) * 3) + (i + 1)] == 2) {
++matched;
} else if ((p.x + i >= 0)
&& (p.x + i < w)
&& (p.y + j >= 0)
&& (p.y + j < h)
&& (((pixels[p.x + i][p.y + j] == BinaryFast.FOREGROUND) && (kernel[((j + 1) * 3) + (i + 1)] == 1)) || ((pixels[p.x
+ i][p.y + j] == BinaryFast.BACKGROUND) && (kernel[((j + 1) * 3) + (i + 1)] == 0)))) {
++matched;
}
}
}
if (matched == 9) {
return true;
} else
return false;
}
/**
* Applies the hitmiss operation to a set of pixels
* stored in a hash table.
*
* @param b the BinaryFast input image
* @param input the set of pixels requiring matching
* @param kernel the kernel to match them with
* @return A hash table containing all the successful matches.
*/
private HashSet<Point> hitMissHashSet( BinaryFast b, HashSet<Point> input, int[] kernel ) {
HashSet<Point> output = new HashSet<Point>();
Iterator<Point> it = input.iterator();
while( it.hasNext() ) {
Point p = it.next();
if (kernelMatch(p, b.getPixels(), b.getWidth(), b.getHeight(), kernel)) {
// System.out.println("Match "+p.x+" "+p.y);
output.add(p);
}
}
// System.out.println(output.size());
return output;
}
/**
* Returns true if the kernel has no 0s.
*
* @param kernel the array storing the kernel values.
* @return True if no 0s (false otherwise)
*/
private boolean kernelNo0s( int[] kernel ) {
for( int i = 0; i < kernel.length; ++i ) {
if (kernel[i] == 0)
return false;
}
return true;
}
}