package com.swingsane.business.image;
import java.awt.image.BufferedImage;
import com.swingsane.i18n.Localizer;
/**
* ImageDeskew (JDeskew)
*
* A Java port of this project: http://www.codeproject.com/Articles/13615/How-to-deskew-an-image
*
* @author Roland Quast (roland@formreturn.com)
*
*/
public class ImageDeskew {
// representation of a line in the image
public class HoughLine {
// count of points in the line
public int count = 0;
// index in matrix.
public int index = 0;
// the line is represented as all x, y that solve y * cos(alpha) - x *
// sin(alpha) = d
public double alpha;
public double d;
}
private static final int BLACK = 0;
// the source image
private BufferedImage sourceImage;
// the range of angles to search for lines
private double cAlphaStart = -20.0d;
private double cAlphaStep = 0.2d;
private int cSteps = 40 * 5;
// pre-calculation of sin and cos
private double[] cSinA;
private double[] cCosA;
// range of d
private double cDMin;
private double cDStep = 1.0d;
private int cDCount;
// count of points that fit in a line
private int[] cHMatrix;
private BufferedImage binarizedImage;
private int luminanceThreshold = 165;
private boolean despeckle = true;
// constructor
public ImageDeskew(BufferedImage sourceImage) throws Exception {
this.sourceImage = sourceImage;
binarizedImage = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_BINARY);
if (sourceImage.getType() == BufferedImage.TYPE_BYTE_BINARY) {
binarizedImage = sourceImage.getSubimage(0, 0, sourceImage.getWidth(),
sourceImage.getHeight());
} else {
binarizedImage = ImageBinarize.binarizeImage(this.sourceImage, luminanceThreshold, despeckle);
}
}
// Hough Transformation
private void calc() throws Exception {
int hMin = 2; // (int) ((this.cImage.getHeight()) / 4.0);
int hMax = (sourceImage.getHeight()) - 2; // (int) ((this.cImage.getHeight()) * 3.0 /
// 4.0);
init();
if (hMin >= hMax) {
throw new Exception(Localizer.localize("HoughMaxLessThanHoughMinMessageText"));
}
for (int y = hMin; y < hMax; y++) {
for (int x = 1; x < (sourceImage.getWidth() - 2); x++) {
// only lower edges are considered
if (isBlack(x, y)) {
if (!isBlack(x, (y + 1))) {
calc(x, y);
}
}
}
}
}
// calculate all lines through the point (x,y)
private void calc(int x, int y) throws Exception {
double d;
int dIndex;
int index;
for (int alpha = 0; alpha < (cSteps - 1); alpha++) {
d = (y * cCosA[alpha]) - (x * cSinA[alpha]);
dIndex = (int) (d - cDMin);
index = (dIndex * cSteps) + alpha;
cHMatrix[index] += 1;
}
}
public final double getAlpha(int index) {
return cAlphaStart + (index * cAlphaStep);
}
public final BufferedImage getBinarizedImage() {
return binarizedImage;
}
// calculate the skew angle of the image cImage
public final double getSkewAngle() throws Exception {
ImageDeskew.HoughLine[] hl;
double sum = 0.0d;
double count = 0.0d;
// perform Hough Transformation
calc();
// top 20 of the detected lines in the image
hl = getTop(20);
if (hl.length >= 20) {
// average angle of the lines
for (int i = 0; i < 19; i++) {
sum += hl[i].alpha;
count += 1.0;
}
return (sum / count);
} else {
return 0.0d;
}
}
// calculate the count lines in the image with most points
private ImageDeskew.HoughLine[] getTop(int count) {
ImageDeskew.HoughLine[] hl;
hl = new ImageDeskew.HoughLine[count];
for (int i = 0; i < count; i++) {
hl[i] = new ImageDeskew.HoughLine();
}
ImageDeskew.HoughLine tmp;
int j = 0;
int alphaIndex;
int dIndex;
for (int i = 0; i < (count - 1); i++) {
hl[i] = new ImageDeskew.HoughLine();
}
for (int i = 0; i < (cHMatrix.length - 1); i++) {
if (cHMatrix[i] > hl[count - 1].count) {
hl[count - 1].count = cHMatrix[i];
hl[count - 1].index = i;
j = count - 1;
while ((j > 0) && (hl[j].count > hl[j - 1].count)) {
tmp = hl[j];
hl[j] = hl[j - 1];
hl[j - 1] = tmp;
j -= 1;
}
}
}
for (int i = 0; i < (count - 1); i++) {
dIndex = hl[i].index / cSteps; // integer division, no
// remainder
alphaIndex = hl[i].index - (dIndex * cSteps);
hl[i].alpha = getAlpha(alphaIndex);
hl[i].d = dIndex + cDMin;
}
return hl;
}
private void init() {
double angle;
// pre-calculation of sin and cos
cSinA = new double[cSteps - 1];
cCosA = new double[cSteps - 1];
for (int i = 0; i < (cSteps - 1); i++) {
angle = (getAlpha(i) * Math.PI) / 180.0;
cSinA[i] = Math.sin(angle);
cCosA[i] = Math.cos(angle);
}
// range of d
cDMin = -sourceImage.getWidth();
cDCount = (int) ((2.0 * ((sourceImage.getWidth() + sourceImage.getHeight()))) / cDStep);
cHMatrix = new int[cDCount * cSteps];
}
private boolean isBlack(int x, int y) {
return (binarizedImage.getRaster().getSample(x, y, 0) == BLACK);
}
}