20 Commits

Author SHA1 Message Date
Erik Auerswald 443b100ab9 bump version number to 2.25.1 2025-10-31 19:44:16 +01:00
Erik Auerswald 63495ef4a8 suppress debug output without -P, --debug-output
Again, a debug message was not properly guarded with a check
for the debug-flag.
2025-10-30 00:04:42 +01:00
Erik Auerswald 9d9b2253e1 bump version number to 2.25.0 2025-03-23 19:06:24 +01:00
Erik Auerswald e914669079 add option -F, --adapt-after-crop
When this option is used, the threshold is adapted to the cropped
image, i.e., after the "crop" command, but not directly before.
This allows to avoid adjusting the threshold to the full image,
and thus potentially reduce the time needed for recognition.
2025-03-23 19:03:48 +01:00
Erik Auerswald 94ce321060 lazily adapt threshold to image
Instead of adapting the threshold to the image before executing
commands, adapt the threshold just before it is needed.

This allows to avoid theshold adaptation when -p, --process-only
is used with only the "grayscale" and/or "mirror" commands.

This also prepares the code to allow introduction of a new option
to avoid adapting the threshold to the original image before the
"crop" command is applied.
2025-03-23 18:54:13 +01:00
Erik Auerswald 7cae796188 ssocr.c: code maintenance
Fix two minor issues with crop command execution:

 * correct two variable names in a comment, and
 * remove a useless imlib_context_set_image() call.
2025-03-23 14:27:52 +01:00
Erik Auerswald 7f2a9f3f22 warn when options -a and -T are used together 2025-03-22 18:45:15 +01:00
Erik Auerswald 00312ee70e man page: describe interaction of -a and -T
Option -a, --absolute-threshold inhibts the effect of option
-T, --iter-threshold.
2025-03-22 18:38:04 +01:00
Erik Auerswald e309861f24 man page: suggest -a when using gray_stretch 2025-03-20 21:23:33 +01:00
Erik Auerswald 4b5c6ea770 NEWS: mention speed up of -g with gray_stretch
Commit 67abe0fba8 improved the
speed for determining both minimum and maximum luminance (gray)
values for the image.  This speeds up some debugging output,
and also using the gray_stretch command together with option -g.
(I did not notice that initially.)
2025-03-20 21:15:01 +01:00
Erik Auerswald 56d1b22118 simplify some command execution code paths
Commands with optional argument had two code paths leading
to the respective function application, one of those with
hard-coded argument "1".  Instead, ensure the variable for
the optional argument is always set, and have just one
function call, always using this variable, per command.
2025-03-20 21:12:00 +01:00
Erik Auerswald eaf0f32850 NEWS: mention performance improvement 2025-03-18 21:45:00 +01:00
Erik Auerswald fa30f473e7 simplify adapt_threshold() and iterative_threshold()
Both functions are always called with the same arguments.
Only the get_threshold() function is also called with
different x, y, w, h arguments (when used during dynamic
thresholding).
2025-03-18 21:31:19 +01:00
Erik Auerswald 67abe0fba8 combine get_minval() and get_maxval()
This simplifies the code a bit, and slightly speeds up using
option "-P, --debug-output".
2025-03-18 20:42:38 +01:00
Erik Auerswald ac060de96b speed up reading from standard input
Instead of reading one byte at a time, read data in chunks
of BUFSIZ bytes.

For one example, this improves recognition time for image
data read via standard input from 281ms to 53ms.  YMMV.
2025-03-18 20:10:51 +01:00
Erik Auerswald 74aeb44f94 rename draw_pixel() to draw_fg_bg_pixel() 2025-02-02 20:42:52 +01:00
Erik Auerswald f012c14d93 refactoring and type consistency fixes
* The draw_pixel() function was called with an "image" parameter
  of type "Imlib_Image" instead of "Imlib_Image *".  This type
  error did not result in a compilation error, and thus stayed
  undetected in the code.

* Introduce a new function draw_color_pixel() similar to draw_pixel(),
  and use it instead of repeatedly open-coding this operation.
2025-02-02 20:32:48 +01:00
Erik Auerswald b44a4ad72a update copyright years 2025-02-01 19:33:29 +01:00
Erik Auerswald 13a5ec3802 imgproc.c: remove some useless code
The removed code was intended to clear the image to allow
only drawing foreground pixels in the following loop.  But
it only drew the outline of a rectangle and thus did not
clear the image as intended.

Since the image is not cleared, the following loop already
draws every pixel, overwriting any previously drawn rectangle
anyway.
2025-02-01 19:26:09 +01:00
Erik Auerswald 2824d0aea7 ensure M_PI is defined
Some image manipulation functions use trigonometric functions.
This includes using the number Pi.  Pi is often available as
M_PI via including <math.h>.  This constant is part of the Unix98
standard, not the C standard itself.  According to the GCC
documentation[1], M_PI is only defined when the feature selection
macro _XOPEN_SOURCE=500 is used.  This seems to be default in
many versions of GNU/Linux, since ssocr could always use M_PI
without explicitly setting _XOPEN_SOURCE=500.

An MPlayer bug report[2] reported a build failure because M_PI
was not defined.  This lead to a patch[3] to work around this
problem.

I want to avoid running into this specific problem in the future,
without introducing significant changes to ssocr.  Thus I check
if M_PI is defined after inlcuding <math.h>, and define it myself
if it is missing.  I use the value found in /usr/include/math.h
from GNU Libc on my current system (Ubuntu GNU/Linux 20.04.6 LTS).

[1] https://www.gnu.org/software/libc/manual/html_node/Mathematical-Constants.html
[2] https://trac.mplayerhq.hu/ticket/2423
[3] https://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2024-December/074244.html

Also add '+' to the version number to indicate changes after the
latest release.
2024-12-14 14:56:13 +01:00
12 changed files with 210 additions and 195 deletions
+16
View File
@@ -1,6 +1,22 @@
Noteworthy Changes in ssocr Releases
====================================
Version 2.25.1 (2025-10-31):
----------------------------
* Fix one debug message to be printed only with -P, --debug-output
Version 2.25.0 (2025-03-23):
----------------------------
* New option -F, --adapt-after-crop to skip threshold adjustment directly
before cropping the image
* Print warning when the two options -a, --absolute-threshold and -T,
--iter-threshold are used together because -a inhibits -T
* Improved performance when reading image data via standard input
* Improved performance when using gray_stretch together with -g
* Improved performance when using option -p, --process-only together
with only the grayscale and/or mirror commands
* Documentation improvements
Version 2.24.1 (2024-12-11):
----------------------------
* Print warning when an unknown charset name is ignored
+1 -1
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2018-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2018-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* standard things */
#include <stdio.h> /* puts, printf, BUFSIZ, perror, FILE */
+1 -1
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2018-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2018-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
#ifndef SSOCR2_CHARSET_H
#define SSOCR2_CHARSET_H
+7 -2
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2013 Cristiano Fontana <fontanacl@ornl.gov> */
#define PROG "ssocr"
@@ -23,7 +23,7 @@
#define SSOCR2_DEFINES_H
/* version number */
#define VERSION "2.24.1"
#define VERSION "2.25.1"
/* states */
#define FIND_DARK 0
@@ -156,6 +156,7 @@
#define OMIT_DECIMAL (1<<10)
#define PRINT_SPACES (1<<11)
#define SPC_USE_AVG_DST (1<<12)
#define ADAPT_AFTER_CROP (1<<13)
/* colors used by ssocr */
#define SSOCR_BLACK 0
@@ -172,6 +173,10 @@
/* default luminance formula */
#define DEFAULT_LUM_FORMULA REC709
/* when to adapt threshold values to the image */
#define INITIAL 0 /* adapt threshold unless is was already adapted */
#define UPDATE 1 /* adapt threshold even if it was adapted before */
/* foreground and background */
typedef enum fg_bg_e {
FG,
+4 -2
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2013 Cristiano Fontana <fontanacl@ornl.gov> */
/* standard things */
@@ -92,7 +92,7 @@ void print_version(FILE *f)
{
fprintf(f, "Seven Segment Optical Character Recognition Version %s\n",
VERSION);
fprintf(f, "Copyright (C) 2004-2024 Erik Auerswald"
fprintf(f, "Copyright (C) 2004-2025 Erik Auerswald"
" <auerswal@unix-ag.uni-kl.de>\n");
fprintf(f, "This program comes with ABSOLUTELY NO WARRANTY.\n");
fprintf(f, "This is free software, and you are welcome to redistribute it"
@@ -151,6 +151,8 @@ void usage(char *name, FILE *f)
fprintf(f, " -C, --omit-decimal-point omit decimal points from output\n");
fprintf(f, " -c, --charset=KEYWORD select recognized characters\n");
fprintf(f, " use -c help for list of KEYWORDS\n");
fprintf(f, " -F, --adapt-after-crop do not adapt threshold to image directly\n"
" before, only after, cropping\n");
fprintf(f, "\nCommands: dilation [N] [N times] dilation algorithm"
"\n (set_pixels_filter with mask"
" of 1 pixel)\n");
+1 -1
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
#ifndef SSOCR2_HELP_H
#define SSOCR2_HELP_H
+64 -127
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* ImLib2 Header */
#include <X11/Xlib.h> /* needed by Imlib2.h */
@@ -27,8 +27,11 @@
/* string manipulation */
#include <string.h> /* strcasecmp, strcmp, strrchr */
/* sin, cos */
#include <math.h>
/* trigonometry */
#include <math.h> /* sin, cos, M_PI */
#ifndef M_PI /* sometimes, M_PI is not defined */
#define M_PI 3.14159265358979323846
#endif
/* my headers */
#include "defines.h" /* defines */
@@ -76,12 +79,12 @@ void ssocr_set_color(fg_bg_t color)
}
/* draw a fore- or background pixel */
void draw_pixel(Imlib_Image *image, int x, int y, fg_bg_t color)
void draw_fg_bg_pixel(Imlib_Image *image, int x, int y, fg_bg_t color)
{
Imlib_Image *current_image; /* save current image */
current_image = imlib_context_get_image();
imlib_context_set_image(image);
imlib_context_set_image(*image);
ssocr_set_color(color);
imlib_image_draw_pixel(x,y,0);
imlib_context_set_image(current_image);
@@ -90,13 +93,25 @@ void draw_pixel(Imlib_Image *image, int x, int y, fg_bg_t color)
/* draw a foreground pixel */
void draw_fg_pixel(Imlib_Image *image, int x, int y)
{
draw_pixel(image, x, y, FG);
draw_fg_bg_pixel(image, x, y, FG);
}
/* draw a background pixel */
void draw_bg_pixel(Imlib_Image *image, int x, int y)
{
draw_pixel(image, x, y, BG);
draw_fg_bg_pixel(image, x, y, BG);
}
/* draw a pixel of a given color */
void draw_color_pixel(Imlib_Image *image, int x, int y, Imlib_Color color)
{
Imlib_Image *current_image; /* save current image */
current_image = imlib_context_get_image();
imlib_context_set_image(*image);
imlib_context_set_color(color.red, color.green, color.blue, color.alpha);
imlib_image_draw_pixel(x, y, 0);
imlib_context_set_image(current_image);
}
/* check if a pixel is set regarding current foreground/background colors */
@@ -165,9 +180,9 @@ Imlib_Image set_pixels_filter(Imlib_Image *source_image, double thresh,
}
/* set pixel if at least mask pixels around it are set */
if(set_pixel >= mask) {
draw_fg_pixel(new_image, x, y);
draw_fg_pixel(&new_image, x, y);
} else {
draw_bg_pixel(new_image, x, y);
draw_bg_pixel(&new_image, x, y);
}
}
}
@@ -257,12 +272,6 @@ Imlib_Image keep_pixels_filter(Imlib_Image *source_image, double thresh,
width = imlib_image_get_width();
new_image = imlib_clone_image();
/* draw white (background) rectangle to clear new image */
imlib_context_set_image(new_image);
ssocr_set_color(BG);
imlib_image_draw_rectangle(0, 0, width, height);
imlib_context_set_image(*source_image);
/* check for every pixel if it should be set in filtered image */
for(x=0; x<width; x++) {
for(y=0; y<height; y++) {
@@ -285,9 +294,9 @@ Imlib_Image keep_pixels_filter(Imlib_Image *source_image, double thresh,
/* set pixel if at least mask pixels around it are set */
/* mask = 1 keeps all pixels */
if(set_pixel > mask) {
draw_fg_pixel(new_image, x, y);
draw_fg_pixel(&new_image, x, y);
} else {
draw_bg_pixel(new_image, x, y);
draw_bg_pixel(&new_image, x, y);
}
}
}
@@ -402,9 +411,9 @@ Imlib_Image dynamic_threshold(Imlib_Image *source_image,double t,luminance_t lt,
lum = get_lum(&color, lt);
thresh = get_threshold(source_image, t/100.0, lt, x-ww/2, y-ww/2, ww, wh);
if(is_pixel_set(lum, thresh)) {
draw_fg_pixel(new_image, x, y);
draw_fg_pixel(&new_image, x, y);
} else {
draw_bg_pixel(new_image, x, y);
draw_bg_pixel(&new_image, x, y);
}
}
}
@@ -441,9 +450,9 @@ Imlib_Image make_mono(Imlib_Image *source_image, double thresh, luminance_t lt)
imlib_image_query_pixel(x, y, &color);
lum = get_lum(&color, lt);
if(is_pixel_set(lum, thresh)) {
draw_fg_pixel(new_image, x, y);
draw_fg_pixel(&new_image, x, y);
} else {
draw_bg_pixel(new_image, x, y);
draw_bg_pixel(&new_image, x, y);
}
}
}
@@ -456,23 +465,28 @@ Imlib_Image make_mono(Imlib_Image *source_image, double thresh, luminance_t lt)
}
/* adapt threshold to image values values */
double adapt_threshold(Imlib_Image *image, double thresh, luminance_t lt, int x,
int y, int w, int h, unsigned int flags)
double adapt_threshold(Imlib_Image *image, double thresh, luminance_t lt,
unsigned int flags, int force_update)
{
double t = thresh;
if(!(flags & ABSOLUTE_THRESHOLD)) {
static int is_adapted = 0;
if(is_adapted && !force_update) {
if(flags & DEBUG_OUTPUT)
fprintf(stderr, "threshold is already adjusted to image\n");
} else if(!(flags & ABSOLUTE_THRESHOLD)) {
if(flags & DEBUG_OUTPUT)
fprintf(stderr, "adjusting threshold to image: %f ->", t);
t = get_threshold(image, thresh/100.0, lt, x, y, w, h);
t = get_threshold(image, thresh/100.0, lt, 0, 0, -1, -1);
if(flags & DEBUG_OUTPUT)
fprintf(stderr, " %f\n", t);
if(flags & DO_ITERATIVE_THRESHOLD) {
if(flags & DEBUG_OUTPUT)
fprintf(stderr, "doing iterative_thresholding: %f ->", t);
t = iterative_threshold(image, t, lt, x, y, w, h);
t = iterative_threshold(image, t, lt);
if(flags & DEBUG_OUTPUT)
fprintf(stderr, " %f\n", t);
}
is_adapted = 1;
}
if((flags & VERBOSE) || (flags & DEBUG_OUTPUT)) {
fprintf(stderr, "using threshold %.2f\n", t);
@@ -528,7 +542,7 @@ double get_threshold(Imlib_Image *source_image, double fraction, luminance_t lt,
/* determine threshold by an iterative method */
double iterative_threshold(Imlib_Image *source_image, double thresh,
luminance_t lt, int x, int y, int w, int h)
luminance_t lt)
{
Imlib_Image current_image; /* save image pointer */
int height, width; /* image dimensions */
@@ -553,23 +567,13 @@ double iterative_threshold(Imlib_Image *source_image, double thresh,
height = imlib_image_get_height();
width = imlib_image_get_width();
/* special value -1 for width or height means image width/height */
if(w == -1) w = width;
if(h == -1) h = width;
/* assure valid coordinates */
if(x+w > width) x = width-w;
if(y+h > height) y = height-h;
if(x<0) x=0;
if(y<0) y=0;
/* find the threshold value to differentiate between dark and light */
do {
thresh_lum = MAXRGB * new_thresh;
old_thresh = new_thresh;
size_black = sum_black = size_white = sum_white = 0;
for(xi=0; (xi<w) && (xi<width); xi++) {
for(yi=0; (yi<h) && (yi<height); yi++) {
for(xi=0; xi<width; xi++) {
for(yi=0; yi<height; yi++) {
imlib_image_query_pixel(xi, yi, &color);
lum = get_lum(&color, lt);
if(lum <= thresh_lum) {
@@ -604,92 +608,39 @@ double iterative_threshold(Imlib_Image *source_image, double thresh,
return new_thresh * 100;
}
/* get minimum lum value */
double get_minval(Imlib_Image *source_image, int x, int y, int w, int h,
luminance_t lt)
/* get minimum and maximum lum values */
void get_minmaxval(Imlib_Image *source_image, luminance_t lt,
double *min, double *max)
{
Imlib_Image current_image; /* save image pointer */
int height, width; /* image dimensions */
int w, h; /* image dimensions */
int xi,yi; /* iteration variables */
Imlib_Color color; /* Imlib2 RGBA color structure */
int minval = MAXRGB;
int lum = 0;
*min = MAXRGB;
*max = 0;
/* save pointer to current image */
current_image = imlib_context_get_image();
/* get image dimensions */
imlib_context_set_image(*source_image);
height = imlib_image_get_height();
width = imlib_image_get_width();
/* special value -1 for width or height means image width/height */
if(w == -1) w = width;
if(h == -1) h = width;
/* assure valid coordinates */
if(x+w > width) x = width-w;
if(y+h > height) y = height-h;
if(x<0) x=0;
if(y<0) y=0;
h = imlib_image_get_height();
w = imlib_image_get_width();
/* find the minimum value in the image */
for(xi=0; (xi<w) && (xi<width); xi++) {
for(yi=0; (yi<h) && (yi<height); yi++) {
for(xi=0; xi<w; xi++) {
for(yi=0; yi<h; yi++) {
imlib_image_query_pixel(xi, yi, &color);
lum = clip(get_lum(&color, lt),0,255);
if(lum < minval) minval = lum;
if(lum < *min) *min = lum;
if(lum > *max) *max = lum;
}
}
/* restore image from before function call */
imlib_context_set_image(current_image);
return minval;
}
/* get maximum luminance value */
double get_maxval(Imlib_Image *source_image, int x, int y, int w, int h,
luminance_t lt)
{
Imlib_Image current_image; /* save image pointer */
int height, width; /* image dimensions */
int xi,yi; /* iteration variables */
Imlib_Color color; /* Imlib2 RGBA color structure */
int lum = 0;
int maxval = 0;
/* save pointer to current image */
current_image = imlib_context_get_image();
/* get image dimensions */
imlib_context_set_image(*source_image);
height = imlib_image_get_height();
width = imlib_image_get_width();
/* special value -1 for width or height means image width/height */
if(w == -1) w = width;
if(h == -1) h = width;
/* assure valid coordinates */
if(x+w > width) x = width-w;
if(y+h > height) y = height-h;
if(x<0) x=0;
if(y<0) y=0;
/* find the minimum value in the image */
for(xi=0; (xi<w) && (xi<width); xi++) {
for(yi=0; (yi<h) && (yi<height); yi++) {
imlib_image_query_pixel(xi, yi, &color);
lum = clip(get_lum(&color, lt),0,255);
if(lum > maxval) maxval = lum;
}
}
/* restore image from before function call */
imlib_context_set_image(current_image);
return maxval;
}
/* draw a white (background) border around image, overwriting image contents
@@ -757,15 +708,11 @@ Imlib_Image shear(Imlib_Image *source_image, int offset)
/* copy pixels */
for(x=width-1; x>=shift; x--) {
imlib_image_query_pixel(x-shift, y, &color_return);
imlib_context_set_image(new_image);
imlib_context_set_color(color_return.red, color_return.green,
color_return.blue, color_return.alpha);
imlib_image_draw_pixel(x,y,0);
imlib_context_set_image(*source_image);
draw_color_pixel(&new_image, x, y, color_return);
}
/* fill with background */
for(x=0; x<shift; x++) {
draw_bg_pixel(new_image, x, y);
draw_bg_pixel(&new_image, x, y);
}
}
@@ -806,14 +753,10 @@ Imlib_Image rotate(Imlib_Image *source_image, double theta)
sy = (y-height/2) * cos(theta) - (x-width/2) * sin(theta) + height/2;
if((sx >= 0) && (sx <= width) && (sy >= 0) && (sy <= height)) {
imlib_image_query_pixel(sx, sy, &c);
imlib_context_set_image(new_image);
imlib_context_set_color(c.red, c.green, c.blue, c.alpha);
draw_color_pixel(&new_image, x, y, c);
} else {
imlib_context_set_image(new_image);
ssocr_set_color(BG);
draw_bg_pixel(&new_image, x, y);
}
imlib_image_draw_pixel(x,y,0);
imlib_context_set_image(*source_image);
}
}
@@ -847,20 +790,14 @@ Imlib_Image mirror(Imlib_Image *source_image, direction_t direction)
for(x = width-1; x>=0; x--) {
for(y = 0; y < height; y++) {
imlib_image_query_pixel(width - 1 - x, y, &c);
imlib_context_set_image(new_image);
imlib_context_set_color(c.red, c.green, c.blue, c.alpha);
imlib_image_draw_pixel(x,y,0);
imlib_context_set_image(*source_image);
draw_color_pixel(&new_image, x, y, c);
}
}
} else if(direction == VERTICAL) {
for(x = 0; x < width; x++) {
for(y = height-1; y >= 0; y--) {
imlib_image_query_pixel(x, height - 1 - y, &c);
imlib_context_set_image(new_image);
imlib_context_set_color(c.red, c.green, c.blue, c.alpha);
imlib_image_draw_pixel(x,y,0);
imlib_context_set_image(*source_image);
draw_color_pixel(&new_image, x, y, c);
}
}
}
@@ -935,9 +872,9 @@ Imlib_Image invert(Imlib_Image *source_image, double thresh, luminance_t lt)
imlib_image_query_pixel(x, y, &color);
lum = get_lum(&color, lt);
if(is_pixel_set(lum, thresh)) {
draw_bg_pixel(new_image, x, y);
draw_bg_pixel(&new_image, x, y);
} else {
draw_fg_pixel(new_image, x, y);
draw_fg_pixel(&new_image, x, y);
}
}
}
+11 -12
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
#ifndef SSOCR2_IMGPROC_H
#define SSOCR2_IMGPROC_H
@@ -32,7 +32,7 @@ void set_bg_color(int color);
void ssocr_set_color(fg_bg_t color);
/* draw a fore- or background pixel */
void draw_pixel(Imlib_Image *image, int x, int y, fg_bg_t color);
void draw_fg_bg_pixel(Imlib_Image *image, int x, int y, fg_bg_t color);
/* draw a foreground pixel */
void draw_fg_pixel(Imlib_Image *image, int x, int y);
@@ -40,6 +40,9 @@ void draw_fg_pixel(Imlib_Image *image, int x, int y);
/* draw a background pixel */
void draw_bg_pixel(Imlib_Image *image, int x, int y);
/* draw a pixel of a given color */
void draw_color_pixel(Imlib_Image *image, int x, int y, Imlib_Color color);
/* check if a pixel is set regarding current foreground/background colors */
int is_pixel_set(int value, double threshold);
@@ -126,8 +129,8 @@ Imlib_Image grayscale(Imlib_Image *source_image, luminance_t lt);
Imlib_Image crop(Imlib_Image *source_image, int x, int y, int w, int h);
/* adapt threshold to image values values */
double adapt_threshold(Imlib_Image *image, double thresh, luminance_t lt, int x,
int y, int w, int h, unsigned int flags);
double adapt_threshold(Imlib_Image *image, double thresh, luminance_t lt,
unsigned int flags, int force_update);
/* compute dynamic threshold value from the rectangle (x,y),(x+w,y+h) of
* source_image */
@@ -136,15 +139,11 @@ double get_threshold(Imlib_Image *source_image, double fraction, luminance_t lt,
/* determine threshold by an iterative method */
double iterative_threshold(Imlib_Image *source_image, double thresh,
luminance_t lt, int x, int y, int w, int h);
luminance_t lt);
/* get minimum gray value */
double get_minval(Imlib_Image *source_image, int x, int y, int w, int h,
luminance_t lt);
/* get maximum gray value */
double get_maxval(Imlib_Image *source_image, int x, int y, int w, int h,
luminance_t lt);
/* get minimum and maximum gray (luminace) values */
void get_minmaxval(Imlib_Image *source_image, luminance_t lt,
double *min, double *max);
/* compute luminance from RGB values */
int get_lum(Imlib_Color *color, luminance_t lt);
+1 -1
View File
@@ -12,7 +12,7 @@ Upstream Author(s):
Copyright:
Copyright (C) 2004-2024 Erik Auerswald
Copyright (C) 2004-2025 Erik Auerswald
License:
+23 -2
View File
@@ -53,14 +53,21 @@ is used. The default threshold is
.IR 50 .
.SS \-a, \-\-absolute\-threshold
Do not adjust the threshold to the luminance values occurring in the image.
Using this option also inhibits iterative thresholding using option
.BR \-\-iter\-threshold .
Consider this option when using the
.B dynamic_threshold
command.
or
.B gray_stretch
commands.
.SS \-T, \-\-iter\-threshold
Use an iterative method (one-dimensional k-means clustering) to determine the
threshold. The starting value can be specified with the
.B \-\-threshold
option.
Option
.B \-\-absolute\-threshold
inhibits iterative threshold determination.
.SS \-n, \-\-number\-pixels NUMBER
Set the number of foreground pixels that have to be found in a scanline to
recognize a segment.
@@ -290,6 +297,15 @@ of the included characters.
The default is
.I full
(recognizing all characters known to ssocr in the image).
.SS \-F, \-\-adapt\-after\-crop
When using the
.B crop
command,
adjust (adapt) the threshold to image luminance values only after cropping,
not also directly before.
Using other commands before
.B crop
can still lead to adapting the threshold to the original image.
.SH COMMANDS
Most commands do not change the image dimensions.
The
@@ -374,8 +390,13 @@ the values
and
.B T2
are interpreted as percentages.
Consider using the
.B \-\-absolute\-threshold
option together with a manually adjusted
.B \-\-threshold
for predictable results.
.SS dynamic_threshold W H
Convert the image to monochrome using dynamic thresholding a.k.a local
Convert the image to monochrome using dynamic thresholding a.k.a. local
adaptive thresholding.
A window of width
.B W
+80 -45
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2013 Cristiano Fontana <fontanacl@ornl.gov> */
/* ImLib2 Header */
@@ -32,7 +32,7 @@
/* option parsing */
#include <getopt.h> /* getopt */
#include <unistd.h> /* getopt */
#include <unistd.h> /* getopt, read, write, STDIN_FILENO */
/* file permissions */
#include <sys/stat.h> /* umask */
@@ -57,8 +57,8 @@ static char * tmp_imgfile(unsigned int flags)
char *name;
size_t pattern_len;
int handle;
unsigned char buf;
ssize_t count = 0;
unsigned char buf[BUFSIZ];
ssize_t read_count = 0, write_count = 0;
size_t pat_suffix_len = strlen(DIR_SEP TMP_FILE_PATTERN);
size_t dir_len;
@@ -99,12 +99,16 @@ static char * tmp_imgfile(unsigned int flags)
}
/* copy image data from stdin to tmp file */
while((fread(&buf, sizeof(char), 1, stdin)) > 0) {
count = write(handle, &buf, 1);
if (count <= 0) break;
while((read_count = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
write_count = write(handle, buf, read_count);
if (write_count <= 0) break;
}
close(handle); /* filehandle is no longer needed, Imlib2 uses filename */
if(ferror(stdin) || (count <= 0)) {
if(read_count < 0) {
perror(PROG ": could not read image from standard input");
exit(99);
}
if(write_count <= 0) {
perror(PROG ": could not copy image data to temporary file");
unlink(name);
exit(99);
@@ -114,16 +118,20 @@ static char * tmp_imgfile(unsigned int flags)
}
/* return number of foreground pixels in a scanline */
static unsigned int scanline(Imlib_Image *image, Imlib_Image *debug_image,
static unsigned int scanline(Imlib_Image *debug_image,
int x, int y, int len, direction_t dir,
color_struct d_color, double thresh,
luminance_t lt, unsigned int flags)
{
Imlib_Color imlib_color;
Imlib_Color imlib_color, debug_color;
int lum, i, ix=x, iy=y, start, end;
unsigned int found_pixels = 0;
start = (dir == HORIZONTAL) ? x : y;
end = start + len;
debug_color.red = d_color.R;
debug_color.green = d_color.G;
debug_color.blue = d_color.B;
debug_color.alpha = d_color.A;
for (i = start; i <= end; i++) {
if (dir == HORIZONTAL) ix = i;
else iy = i;
@@ -131,10 +139,7 @@ static unsigned int scanline(Imlib_Image *image, Imlib_Image *debug_image,
lum = get_lum(&imlib_color, lt);
if(is_pixel_set(lum, thresh)) {
if(flags & USE_DEBUG_IMAGE) {
imlib_context_set_image(*debug_image);
imlib_context_set_color(d_color.R, d_color.G, d_color.B, d_color.A);
imlib_image_draw_pixel(ix, iy, 0);
imlib_context_set_image(*image);
draw_color_pixel(debug_image, ix, iy, debug_color);
}
found_pixels++;
}
@@ -334,10 +339,11 @@ int main(int argc, char **argv)
{"print-spaces", 0, 0, 's'}, /* print spaces between distant digits */
{"space-factor", 1, 0, 'A'}, /* relative distance to add spaces */
{"space-average", 0, 0, 'G'}, /* avg instead of min dst for spaces */
{"adapt-after-crop", 0, 0, 'F'}, /* don't adapt threshold before crop */
{0, 0, 0, 0} /* terminate long options */
};
c = getopt_long (argc, argv,
"hVt:vaTn:N:i:d:r:m:M:o:O:D::pPf:b:Igl:SXCc:H:W:sA:G",
"hVt:vaTn:N:i:d:r:m:M:o:O:D::pPf:b:Igl:SXCc:H:W:sA:GF",
long_options, &option_index);
if (c == -1) break; /* leaves while (1) loop */
switch (c) {
@@ -618,6 +624,13 @@ int main(int argc, char **argv)
flags & SPC_USE_AVG_DST);
}
break;
case 'F':
flags |= ADAPT_AFTER_CROP;
if(flags & DEBUG_OUTPUT) {
fprintf(stderr, "flags & ADAPT_AFTER_CROP=%d\n",
flags & ADAPT_AFTER_CROP);
}
break;
case '?': /* missing argument or character not in optstring */
short_usage(PROG,stderr);
exit (2);
@@ -634,6 +647,8 @@ int main(int argc, char **argv)
exit(99);
}
}
if((flags & ABSOLUTE_THRESHOLD) && (flags & DO_ITERATIVE_THRESHOLD))
fprintf(stderr, "%s: warning: -T has no effect due to -a\n", PROG);
if(flags & DEBUG_OUTPUT) {
fprintf(stderr, "================================================================================\n");
fprintf(stderr, "VERSION=%s\n", VERSION);
@@ -652,6 +667,7 @@ int main(int argc, char **argv)
fprintf(stderr, "flags & OMIT_DECIMAL=%d\n", flags & OMIT_DECIMAL);
fprintf(stderr, "flags & PRINT_SPACES=%d\n", flags & PRINT_SPACES);
fprintf(stderr, "flags & SPC_USE_AVG_DST=%d\n", flags & SPC_USE_AVG_DST);
fprintf(stderr, "flags & ADAPT_AFTER_CROP=%d\n", flags & ADAPT_AFTER_CROP);
fprintf(stderr, "need_pixels = %d\n", need_pixels);
fprintf(stderr, "min_segment = %d\n", min_segment);
fprintf(stderr, "min_char_dims = %dx%d\n",min_char_dims.w,min_char_dims.h);
@@ -722,14 +738,12 @@ int main(int argc, char **argv)
/* get minimum and maximum "value" values */
if((flags & DEBUG_OUTPUT) || (flags & PRINT_INFO)) {
double min, max;
get_minmaxval(&image, lt, &min, &max);
fprintf(stderr, "%.2f <= lum <= %.2f (lum should be in [0,255])\n",
get_minval(&image, 0, 0, -1, -1, lt),
get_maxval(&image, 0, 0, -1, -1, lt));
min, max);
}
/* adapt threshold to image */
thresh = adapt_threshold(&image, thresh, lt, 0, 0, -1, -1, flags);
/* process commands */
if(flags & VERBOSE) /* then print found commands */ {
if(optind >= argc-1) {
@@ -759,11 +773,12 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
i++;
new_image = dilation(&image, thresh, lt, n);
} else {
n = 1;
if(flags & VERBOSE) fputs(" processing dilation (1)\n", stderr);
new_image = dilation(&image, thresh, lt, 1);
}
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = dilation(&image, thresh, lt, n);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
@@ -778,11 +793,12 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
i++;
new_image = erosion(&image, thresh, lt, n);
} else {
n = 1;
if(flags & VERBOSE) fputs(" processing erosion (1)\n", stderr);
new_image = erosion(&image, thresh, lt, 1);
}
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = erosion(&image, thresh, lt, n);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
@@ -797,11 +813,12 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
i++;
new_image = opening(&image, thresh, lt, n);
} else {
n = 1;
if(flags & VERBOSE) fputs(" processing opening (1)\n", stderr);
new_image = opening(&image, thresh, lt, 1);
}
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = opening(&image, thresh, lt, n);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
@@ -816,22 +833,25 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
i++;
new_image = closing(&image, thresh, lt, n);
} else {
n = 1;
if(flags & VERBOSE) fputs(" processing closing (1)\n", stderr);
new_image = closing(&image, thresh, lt, 1);
}
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = closing(&image, thresh, lt, n);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
} else if(strcasecmp("remove_isolated",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing remove_isolated\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = remove_isolated(&image, thresh, lt);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
} else if(strcasecmp("make_mono",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing make_mono\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = make_mono(&image, thresh, lt);
imlib_context_set_image(image);
imlib_free_image();
@@ -846,13 +866,14 @@ int main(int argc, char **argv)
}
fprintf(stderr, "\n");
}
new_image = white_border(&image, bdwidth);
i++;
} else {
bdwidth = 1;
if(flags & VERBOSE)
fputs(" processing white_border (1)\n", stderr);
new_image = white_border(&image, 1);
}
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = white_border(&image, bdwidth);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
@@ -866,6 +887,7 @@ int main(int argc, char **argv)
}
if(i+1<argc-1) {
offset = (int) atoi(argv[++i]); /* sideeffect: increment i */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = shear(&image, offset);
imlib_context_set_image(image);
imlib_free_image();
@@ -885,6 +907,7 @@ int main(int argc, char **argv)
}
if(i+1<argc-1) {
mask = (int) atoi(argv[++i]); /* sideeffect: increment i */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = set_pixels_filter(&image, thresh, lt, mask);
imlib_context_set_image(image);
imlib_free_image();
@@ -905,6 +928,7 @@ int main(int argc, char **argv)
}
if(i+1<argc-1) {
mask = (int) atoi(argv[++i]); /* sideeffect: increment i */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = keep_pixels_filter(&image, thresh, lt, mask);
imlib_context_set_image(image);
imlib_free_image();
@@ -927,6 +951,7 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
i+=2; /* skip the arguments to dynamic_threshold */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = dynamic_threshold(&image, thresh, lt, ww, wh);
imlib_context_set_image(image);
imlib_free_image();
@@ -938,30 +963,35 @@ int main(int argc, char **argv)
}
} else if(strcasecmp("rgb_threshold",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing rgb_threshold\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = make_mono(&image, thresh, MINIMUM);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
} else if(strcasecmp("r_threshold",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing r_threshold\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = make_mono(&image, thresh, RED);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
} else if(strcasecmp("g_threshold",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing g_threshold\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = make_mono(&image, thresh, GREEN);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
} else if(strcasecmp("b_threshold",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing b_threshold\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = make_mono(&image, thresh, BLUE);
imlib_context_set_image(image);
imlib_free_image();
image = new_image;
} else if(strcasecmp("invert",argv[i]) == 0) {
if(flags & VERBOSE) fputs(" processing invert\n", stderr);
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = invert(&image, thresh, lt);
imlib_context_set_image(image);
imlib_free_image();
@@ -984,8 +1014,7 @@ int main(int argc, char **argv)
fprintf(stderr, " adjusting T1=%.2f and T2=%.2f to image\n",
t1, t2);
}
min = get_minval(&image, 0, 0, -1, -1, lt);
max = get_maxval(&image, 0, 0, -1, -1, lt);
get_minmaxval(&image, lt, &min, &max);
t1 = min + t1/100.0 * (max - min);
t2 = min + t2/100.0 * (max - min);
if(flags & VERBOSE) {
@@ -993,6 +1022,7 @@ int main(int argc, char **argv)
}
}
i+=2; /* skip the arguments to gray_stretch */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = gray_stretch(&image, t1, t2, lt);
imlib_context_set_image(image);
imlib_free_image();
@@ -1010,7 +1040,7 @@ int main(int argc, char **argv)
image = new_image;
} else if(strcasecmp("crop",argv[i]) == 0) {
if(i+4<argc-1) {
int x, y, cw, ch; /* lw = crop width, lh = crop height */
int x, y, cw, ch; /* cw = crop width, ch = crop height */
x = atoi(argv[i+1]);
y = atoi(argv[i+2]);
cw = atoi(argv[i+3]);
@@ -1026,7 +1056,8 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
i += 4; /* skip the arguments to crop */
imlib_context_set_image(image);
if(!(flags & ADAPT_AFTER_CROP))
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = crop(&image, x, y, cw, ch);
imlib_context_set_image(image);
imlib_free_image();
@@ -1041,13 +1072,13 @@ int main(int argc, char **argv)
}
/* get minimum and maximum "value" values in cropped image */
if((flags&DEBUG_OUTPUT) || (flags&PRINT_INFO) || (flags&VERBOSE)) {
double min, max;
get_minmaxval(&image, lt, &min, &max);
fprintf(stderr, " %.2f <= lum <= %.2f in cropped image"
" (lum should be in [0,255])\n",
get_minval(&image, 0, 0, -1, -1, lt),
get_maxval(&image, 0, 0, -1, -1, lt));
" (lum should be in [0,255])\n", min, max);
}
/* adapt threshold to cropped image */
thresh = adapt_threshold(&image, thresh, lt, 0, 0, -1, -1, flags);
thresh = adapt_threshold(&image, thresh, lt, flags, UPDATE);
} else {
fprintf(stderr, "%s: error: crop command needs 4 arguments\n", PROG);
exit(99);
@@ -1062,6 +1093,7 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
theta = atof(argv[++i]); /* sideeffect: increment i */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
new_image = rotate(&image, theta);
imlib_context_set_image(image);
imlib_free_image();
@@ -1111,6 +1143,9 @@ int main(int argc, char **argv)
/* exit if only image processing shall be done */
if(flags & PROCESS_ONLY) exit(3);
/* adapt threshold to image (unless this is already done) */
thresh = adapt_threshold(&image, thresh, lt, flags, INITIAL);
if(flags & USE_DEBUG_IMAGE) {
/* copy processed image to debug image */
debug_image = make_mono(&image, thresh, lt);
@@ -1607,14 +1642,14 @@ int main(int argc, char **argv)
/* check horizontal segments (vertical scan, x == middle) */
d_color.R = d_color.A = 255;
d_color.G = d_color.B = 0;
found_pixels = scanline(&image, &debug_image, middle, digits[d].y1,
found_pixels = scanline(&debug_image, middle, digits[d].y1,
d_height/3, VERTICAL, d_color, thresh, lt, flags);
if(found_pixels >= need_pixels) {
digits[d].digit |= HORIZ_UP; /* add upper segment */
}
d_color.G = d_color.A = 255;
d_color.R = d_color.B = 0;
found_pixels = scanline(&image, &debug_image, middle,
found_pixels = scanline(&debug_image, middle,
digits[d].y1 + d_height/3, d_height/3, VERTICAL,
d_color, thresh, lt, flags);
if(found_pixels >= need_pixels) {
@@ -1622,7 +1657,7 @@ int main(int argc, char **argv)
}
d_color.B = d_color.A = 255;
d_color.R = d_color.G = 0;
found_pixels = scanline(&image, &debug_image, middle,
found_pixels = scanline(&debug_image, middle,
digits[d].y1 + 2*d_height/3, d_height/3, VERTICAL,
d_color, thresh, lt, flags);
if(found_pixels >= need_pixels) {
@@ -1631,7 +1666,7 @@ int main(int argc, char **argv)
/* check upper vertical segments (horizontal scan, y == quarter) */
d_color.R = d_color.A = 255;
d_color.G = d_color.B = 0;
found_pixels = scanline(&image, &debug_image, digits[d].x1, quarter,
found_pixels = scanline(&debug_image, digits[d].x1, quarter,
(digits[d].x2 - digits[d].x1) / 2, HORIZONTAL,
d_color, thresh, lt, flags);
if (found_pixels >= need_pixels) {
@@ -1639,7 +1674,7 @@ int main(int argc, char **argv)
}
d_color.G = d_color.A = 255;
d_color.R = d_color.B = 0;
found_pixels = scanline(&image, &debug_image,
found_pixels = scanline(&debug_image,
(digits[d].x1 + digits[d].x2) / 2 + 1,
quarter, (digits[d].x2 - digits[d].x1) / 2 - 1,
HORIZONTAL, d_color, thresh, lt, flags);
@@ -1649,7 +1684,7 @@ int main(int argc, char **argv)
/* check lower vertical segments (horizontal scan, y == three_quarters) */
d_color.R = d_color.A = 255;
d_color.G = d_color.B = 0;
found_pixels = scanline(&image, &debug_image, digits[d].x1,
found_pixels = scanline(&debug_image, digits[d].x1,
three_quarters, (digits[d].x2 - digits[d].x1) / 2,
HORIZONTAL, d_color, thresh, lt, flags);
if (found_pixels >= need_pixels) {
@@ -1657,7 +1692,7 @@ int main(int argc, char **argv)
}
d_color.G = d_color.A = 255;
d_color.R = d_color.B = 0;
found_pixels = scanline(&image, &debug_image,
found_pixels = scanline(&debug_image,
(digits[d].x1 + digits[d].x2) / 2 + 1,
three_quarters, (digits[d].x2-digits[d].x1)/2 - 1,
HORIZONTAL, d_color, thresh, lt, flags);
+1 -1
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2024 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
/* Copyright (C) 2004-2025 Erik Auerswald <auerswal@unix-ag.uni-kl.de> */
#ifndef SSOCR2_H
#define SSOCR2_H