Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

719 lines (631 sloc) 22.171 kb
/*
* This file is part of Unpaper.
*
* Copyright © 2005-2007 Jens Gulden
* Copyright © 2011-2011 Diego Elio Pettenò
*
* Unpaper 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, version 2 of the License.
*
* Unpaper 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/>.
*/
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "unpaper.h"
/****************************************************************************
* tool functions *
* - arithmetic tool functions *
* - tool functions for image handling *
****************************************************************************/
/* --- arithmetic tool functions ------------------------------------------ */
/**
* Returns the quadratic square of a number.
*/
double sqr(double d) {
return d*d;
}
/**
* Converts degrees to radians.
*/
double degreesToRadians(double d) {
return d * M_PI / 180.0;
}
/**
* Converts radians to degrees.
*/
double radiansToDegrees(double r) {
return r * 180.0 / M_PI;
}
/**
* Limits an integer value to a maximum.
*/
void limit(int* i, int max) {
if (*i > max) {
*i = max;
}
}
/* --- tool functions for image handling ---------------------------------- */
/**
* Allocates a memory block for storing image data and fills the IMAGE-struct
* with the specified values.
*/
void initImage(struct IMAGE* image, int width, int height, int bitdepth, bool color, int background) {
int size;
size = width * height;
if ( color ) {
image->bufferGrayscale = (uint8_t*)malloc(size);
image->bufferLightness = (uint8_t*)malloc(size);
image->bufferDarknessInverse = (uint8_t*)malloc(size);
memset(image->bufferGrayscale, background, size);
memset(image->bufferLightness, background, size);
memset(image->bufferDarknessInverse, background, size);
size *= 3;
}
image->buffer = (uint8_t*)malloc(size);
memset(image->buffer, background, size);
if ( ! color ) {
image->bufferGrayscale = image->buffer;
image->bufferLightness = image->buffer;
image->bufferDarknessInverse = image->buffer;
}
image->width = width;
image->height = height;
image->bitdepth = bitdepth;
image->color = color;
image->background = background;
}
/**
* Frees an image.
*/
void freeImage(struct IMAGE* image) {
free(image->buffer);
if (image->color) {
free(image->bufferGrayscale);
free(image->bufferLightness);
free(image->bufferDarknessInverse);
}
}
/**
* Replaces one image with another.
*/
void replaceImage(struct IMAGE* image, struct IMAGE* newimage) {
freeImage(image);
// pass-back new image
*image = *newimage; // copy whole struct
}
/**
* Sets the color/grayscale value of a single pixel.
*
* @return true if the pixel has been changed, false if the original color was the one to set
*/
bool setPixel(int pixel, int x, int y, struct IMAGE* image) {
uint8_t* p;
int w, h;
int pos;
bool result;
uint8_t r, g, b;
w = image->width;
h = image->height;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return false; //nop
} else {
pos = (y * w) + x;
r = (pixel >> 16) & 0xff;
g = (pixel >> 8) & 0xff;
b = pixel & 0xff;
if ( ! image->color ) {
p = &image->buffer[pos];
if ((r == g) && (r == b)) { // optimization (avoid division by 3)
pixel = r;
} else {
pixel = pixelGrayscale(r, g, b); // convert to gray (will already be in most cases, but we can't be sure)
}
if (*p != (uint8_t)pixel) {
*p = (uint8_t)pixel;
return true;
} else {
return false;
}
} else { // color
result = false;
p = &image->buffer[pos*3];
if (*p != r) {
*p = r;
result = true;
}
p++;
if (*p != g) {
*p = g;
result = true;
}
p++;
if (*p != b) {
*p = b;
result = true;
}
if ( result ) { // modified: update cached grayscale, lightness and brightnessInverse values
image->bufferGrayscale[pos] = pixelGrayscale(r, g, b);
image->bufferLightness[pos] = pixelLightness(r, g, b);
image->bufferDarknessInverse[pos] = pixelDarknessInverse(r, g, b);
}
return result;
}
}
}
/**
* Returns the color or grayscale value of a single pixel.
* Always returns a color-compatible value (which may be interpreted as 8-bit grayscale)
*
* @return color or grayscale-value of the requested pixel, or WHITE if the coordinates are outside the image
*/
int getPixel(int x, int y, struct IMAGE* image) {
const int w = image->width;
const int h = image->height;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return pixelValue(WHITE, WHITE, WHITE);
} else {
int pos = (y * w) + x;
if ( ! image->color ) {
const int pix = (uint8_t)image->buffer[pos];
return pixelValue(pix, pix, pix);
} else { // color
pos *= 3;
const uint8_t r = (uint8_t)image->buffer[pos++];
const uint8_t g = (uint8_t)image->buffer[pos++];
const uint8_t b = (uint8_t)image->buffer[pos];
return pixelValue(r, g, b);
}
}
}
/**
* Returns a color component of a single pixel (0-255).
*
* @param colorComponent either RED, GREEN or BLUE
* @return color or grayscale-value of the requested pixel, or WHITE if the coordinates are outside the image
*/
/* (currently not used)
int getPixelComponent(int x, int y, int colorComponent, struct IMAGE* image) {
int w, h;
int pos;
w = image->width;
h = image->height;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return WHITE;
} else {
pos = (y * w) + x;
if ( ! image->color ) {
return (uint8_t)image->buffer[pos];
} else { // color
return (uint8_t)image->buffer[(pos * 3) + colorComponent];
}
}
}
*/
/**
* Returns the grayscale (=brightness) value of a single pixel.
*
* @return grayscale-value of the requested pixel, or WHITE if the coordinates are outside the image
*/
int getPixelGrayscale(int x, int y, struct IMAGE* image) {
const int w = image->width;
const int h = image->height;
const int pos = (y * w) + x;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return WHITE;
} else {
return image->bufferGrayscale[pos];
}
}
/**
* Returns the 'lightness' value of a single pixel. For color images, this
* value denotes the minimum brightness of a single color-component in the
* total color, which means that any color is considered 'dark' which has
* either the red, the green or the blue component (or, of course, several
* of them) set to a high value. In some way, this is a measure how close a
* color is to white.
* For grayscale images, this value is equal to the pixel brightness.
*
* @return lightness-value (the higher, the lighter) of the requested pixel, or WHITE if the coordinates are outside the image
*/
static int getPixelLightness(int x, int y, struct IMAGE* image) {
const int w = image->width;
const int h = image->height;
const int pos = (y * w) + x;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return WHITE;
} else {
return image->bufferLightness[pos];
}
}
/**
* Returns the 'inverse-darkness' value of a single pixel. For color images, this
* value denotes the maximum brightness of a single color-component in the
* total color, which means that any color is considered 'light' which has
* either the red, the green or the blue component (or, of course, several
* of them) set to a high value. In some way, this is a measure how far away a
* color is to black.
* For grayscale images, this value is equal to the pixel brightness.
*
* @return inverse-darkness-value (the LOWER, the darker) of the requested pixel, or WHITE if the coordinates are outside the image
*/
int getPixelDarknessInverse(int x, int y, struct IMAGE* image) {
const int w = image->width;
const int h = image->height;
const int pos = (y * w) + x;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return WHITE;
} else {
return image->bufferDarknessInverse[pos];
}
}
/**
* Sets the color/grayscale value of a single pixel to either black or white.
*
* @return true if the pixel has been changed, false if the original color was the one to set
*/
static bool setPixelBW(int x, int y, struct IMAGE* image, int blackwhite) {
const int w = image->width;
const int h = image->height;
const int pos = (y * w) + x;
if ( (x < 0) || (x >= w) || (y < 0) || (y >= h) ) {
return false; //nop
} else {
if ( ! image->color ) {
uint8_t *p = &image->buffer[pos];
if (*p != blackwhite) {
*p = blackwhite;
return true;
} else {
return false;
}
} else { // color
uint8_t *p = &image->buffer[pos * 3];
bool result = false;
if (*p != blackwhite) {
*p = blackwhite;
result = true;
}
p++;
if (*p != blackwhite) {
*p = blackwhite;
result = true;
}
p++;
if (*p != blackwhite) {
*p = blackwhite;
result = true;
}
return result;
}
}
}
/**
* Sets the color/grayscale value of a single pixel to white.
*
* @return true if the pixel has been changed, false if the original color was the one to set
*/
static bool clearPixel(int x, int y, struct IMAGE* image) {
return setPixelBW(x, y, image, WHITE);
}
/**
* Clears a rectangular area of pixels with either black or white.
* @return The number of pixels actually changed from black (dark) to white.
*/
int clearRect(int left, int top, int right, int bottom, struct IMAGE* image, int blackwhite) {
int x;
int y;
int count = 0;
for (y = top; y <= bottom; y++) {
for (x = left; x <= right; x++) {
if (setPixelBW(x, y, image, blackwhite)) {
count++;
}
}
}
return count;
}
/**
* Copies one area of an image into another.
*/
void copyImageArea(int x, int y, int width, int height, struct IMAGE* source, int toX, int toY, struct IMAGE* target) {
int row;
int col;
// naive but generic implementation
for (row = 0; row < height; row++) {
for (col = 0; col < width; col++) {
const int pixel = getPixel(x+col, y+row, source);
setPixel(pixel, toX+col, toY+row, target);
}
}
}
/**
* Copies a whole image into another.
*/
void copyImage(struct IMAGE* source, int toX, int toY, struct IMAGE* target) {
copyImageArea(0, 0, source->width, source->height, source, toX, toY, target);
}
/**
* Centers one area of an image inside an area of another image.
* If the source area is smaller than the target area, is is equally
* surrounded by a white border, if it is bigger, it gets equally cropped
* at the edges.
*/
static void centerImageArea(int x, int y, int w, int h, struct IMAGE* source, int toX, int toY, int ww, int hh, struct IMAGE* target) {
if ((w < ww) || (h < hh)) { // white rest-border will remain, so clear first
clearRect(toX, toY, toX + ww - 1, toY + hh - 1, target, target->background);
}
if (w < ww) {
toX += (ww - w) / 2;
}
if (h < hh) {
toY += (hh - h) / 2;
}
if (w > ww) {
x += (w - ww) / 2;
w = ww;
}
if (h > hh) {
y += (h - hh) / 2;
h = hh;
}
copyImageArea(x, y, w, h, source, toX, toY, target);
}
/**
* Centers a whole image inside an area of another image.
*/
void centerImage(struct IMAGE* source, int toX, int toY, int ww, int hh, struct IMAGE* target) {
centerImageArea(0, 0, source->width, source->height, source, toX, toY, ww, hh, target);
}
/**
* Returns the average brightness of a rectagular area.
*/
int brightnessRect(int x1, int y1, int x2, int y2, struct IMAGE* image) {
int x;
int y;
int total = 0;
const int count = (x2-x1+1)*(y2-y1+1);
for (x = x1; x <= x2; x++) {
for (y = y1; y <= y2; y++) {
const int pixel = getPixelGrayscale(x, y, image);
total += pixel;
}
}
return total / count;
}
/**
* Returns the average lightness of a rectagular area.
*/
int lightnessRect(int x1, int y1, int x2, int y2, struct IMAGE* image) {
int x;
int y;
int total = 0;
const int count = (x2-x1+1)*(y2-y1+1);
for (x = x1; x <= x2; x++) {
for (y = y1; y <= y2; y++) {
const int pixel = getPixelLightness(x, y, image);
total += pixel;
}
}
return total / count;
}
/**
* Returns the average darkness of a rectagular area.
*/
int darknessInverseRect(int x1, int y1, int x2, int y2, struct IMAGE* image) {
int x;
int y;
int total = 0;
const int count = (x2-x1+1)*(y2-y1+1);
for (x = x1; x <= x2; x++) {
for (y = y1; y <= y2; y++) {
const int pixel = getPixelDarknessInverse(x, y, image);
total += pixel;
}
}
return total / count;
}
/**
* Counts the number of pixels in a rectangular area whose grayscale
* values ranges between minColor and maxBrightness. Optionally, the area can get
* cleared with white color while counting.
*/
int countPixelsRect(int left, int top, int right, int bottom, int minColor, int maxBrightness, bool clear, struct IMAGE* image) {
int x;
int y;
int count = 0;
for (y = top; y <= bottom; y++) {
for (x = left; x <= right; x++) {
const int pixel = getPixelGrayscale(x, y, image);
if ((pixel>=minColor) && (pixel <= maxBrightness)) {
if (clear==true) {
clearPixel(x, y, image);
}
count++;
}
}
}
return count;
}
/**
* Counts the number of dark pixels around the pixel at (x,y), who have a
* square-metric distance of 'level' to the (x,y) (thus, testing the values
* of 9 pixels if level==1, 16 pixels if level==2 and so on).
* Optionally, the pixels can get cleared after counting.
*/
static int countPixelNeighborsLevel(int x, int y, bool clear, int level, int whiteMin, struct IMAGE* image) {
int xx;
int yy;
int count = 0;
int pixel;
// upper and lower rows
for (xx = x - level; xx <= x + level; xx++) {
// upper row
pixel = getPixelLightness(xx, y - level, image);
if (pixel < whiteMin) { // non-light pixel found
if (clear == true) {
clearPixel(xx, y - level, image);
}
count++;
}
// lower row
pixel = getPixelLightness(xx, y + level, image);
if (pixel < whiteMin) {
if (clear == true) {
clearPixel(xx, y + level, image);
}
count++;
}
}
// middle rows
for (yy = y-(level-1); yy <= y+(level-1); yy++) {
// first col
pixel = getPixelLightness(x - level, yy, image);
if (pixel < whiteMin) {
if (clear == true) {
clearPixel(x - level, yy, image);
}
count++;
}
// last col
pixel = getPixelLightness(x + level, yy, image);
if (pixel < whiteMin) {
if (clear == true) {
clearPixel(x + level, yy, image);
}
count++;
}
}
/* old version, not optimized:
for (yy = y-level; yy <= y+level; yy++) {
for (xx = x-level; xx <= x+level; xx++) {
if (abs(xx-x)==level || abs(yy-y)==level) {
pixel = getPixelLightness(xx, yy, image);
if (pixel < whiteMin) {
if (clear==true) {
clearPixel(xx, yy, image);
}
count++;
}
}
}
}*/
return count;
}
/**
* Count all dark pixels in the distance 0..intensity that are directly
* reachable from the dark pixel at (x,y), without having to cross bright
* pixels.
*/
int countPixelNeighbors(int x, int y, int intensity, int whiteMin, struct IMAGE* image) {
int level;
int count = 1; // assume self as set
int lCount = -1;
for (level = 1; (lCount != 0) && (level <= intensity); level++) { // can finish when one level is completely zero
lCount = countPixelNeighborsLevel(x, y, false, level, whiteMin, image);
count += lCount;
}
return count;
}
/**
* Clears all dark pixels that are directly reachable from the dark pixel at
* (x,y). This should be called only if it has previously been detected that
* the amount of pixels to clear will be reasonable small.
*/
void clearPixelNeighbors(int x, int y, int whiteMin, struct IMAGE* image) {
int level;
int lCount = -1;
clearPixel(x, y, image);
for (level = 1; lCount != 0; level++) { // lCount will become 0, otherwise countPixelNeighbors() would previously have delivered a bigger value (and this here would not have been called)
lCount = countPixelNeighborsLevel(x, y, true, level, whiteMin, image);
}
}
/**
* Flood-fill an area of pixels.
* (Declaration of header for indirect recursive calls.)
*/
void floodFill(int x, int y, int color, int maskMin, int maskMax, int intensity, struct IMAGE* image);
/**
* Solidly fills a line of pixels heading towards a specified direction
* until color-changes in the pixels to overwrite exceed the 'intensity'
* parameter.
*
* @param stepX either -1 or 1, if stepY is 0, else 0
* @param stepY either -1 or 1, if stepX is 0, else 0
*/
static int fillLine(int x, int y, int stepX, int stepY, int color, int maskMin, int maskMax, int intensity, struct IMAGE* image) {
int distance = 0;
int intensityCount = 1; // first pixel must match, otherwise directly exit
const int w = image->width;
const int h = image->height;
while (true) {
int pixel;
x += stepX;
y += stepY;
pixel = getPixelGrayscale(x, y, image);
if ((pixel>=maskMin) && (pixel<=maskMax)) {
intensityCount = intensity; // reset counter
} else {
intensityCount--; // allow maximum of 'intensity' pixels to be bright, until stop
}
if ((intensityCount > 0) && (x>=0) && (x<w) && (y>=0) && (y<h)) {
setPixel(color, x, y, image);
distance++;
} else {
return distance; // exit here
}
}
}
/**
* Start flood-filling around the edges of a line which has previously been
* filled using fillLine(). Here, the flood-fill algorithm performs its
* indirect recursion.
*
* @param stepX either -1 or 1, if stepY is 0, else 0
* @param stepY either -1 or 1, if stepX is 0, else 0
* @see fillLine()
*/
static void floodFillAroundLine(int x, int y, int stepX, int stepY, int distance, int color, int maskMin, int maskMax, int intensity, struct IMAGE* image) {
int d;
for (d = 0; d < distance; d++) {
if (stepX != 0) {
x += stepX;
floodFill(x, y + 1, color, maskMin, maskMax, intensity, image); // indirect recursion
floodFill(x, y - 1, color, maskMin, maskMax, intensity, image); // indirect recursion
} else { // stepY != 0
y += stepY;
floodFill(x + 1, y, color, maskMin, maskMax, intensity, image); // indirect recursion
floodFill(x - 1, y, color, maskMin, maskMax, intensity, image); // indirect recursion
}
}
}
/**
* Flood-fill an area of pixels. (Naive implementation, optimizable.)
*
* @see earlier header-declaration to enable indirect recursive calls
*/
void floodFill(int x, int y, int color, int maskMin, int maskMax, int intensity, struct IMAGE* image) {
// is current pixel to be filled?
const int pixel = getPixelGrayscale(x, y, image);
if ((pixel>=maskMin) && (pixel<=maskMax)) {
// first, fill a 'cross' (both vertical, horizontal line)
setPixel(color, x, y, image);
const int left = fillLine(x, y, -1, 0, color, maskMin, maskMax, intensity, image);
const int top = fillLine(x, y, 0, -1, color, maskMin, maskMax, intensity, image);
const int right = fillLine(x, y, 1, 0, color, maskMin, maskMax, intensity, image);
const int bottom = fillLine(x, y, 0, 1, color, maskMin, maskMax, intensity, image);
// now recurse on each neighborhood-pixel of the cross (most recursions will immediately return)
floodFillAroundLine(x, y, -1, 0, left, color, maskMin, maskMax, intensity, image);
floodFillAroundLine(x, y, 0, -1, top, color, maskMin, maskMax, intensity, image);
floodFillAroundLine(x, y, 1, 0, right, color, maskMin, maskMax, intensity, image);
floodFillAroundLine(x, y, 0, 1, bottom, color, maskMin, maskMax, intensity, image);
}
}
Jump to Line
Something went wrong with that request. Please try again.