60 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
Erik Auerswald 5439679c83 bump version number to 2.24.1 2024-12-11 15:43:49 +01:00
Erik Auerswald e7a20fbb2d man page: fix two typos 2024-12-11 15:38:10 +01:00
Erik Auerswald 88a627050a print warning when ignoring unknown luminance formula 2024-11-18 17:55:02 +01:00
Erik Auerswald 6e8c8361fd improve wording of unknown charset warning 2024-11-18 17:43:55 +01:00
Erik Auerswald ad5b6da118 NEWS: mention recent changes 2024-11-17 19:44:57 +01:00
Erik Auerswald 7485ba3ffc man page: option argument separation description 2024-11-17 19:33:35 +01:00
Erik Auerswald acdf26c6bf print warning when ignoring unknown charset 2024-11-17 19:25:33 +01:00
Erik Auerswald 4ab3869eea add '+' to version to indicate unreleased changes 2024-11-17 19:13:33 +01:00
Erik Auerswald 70692aa5c4 suppress debug output without -P, --debug-output 2024-11-17 19:10:34 +01:00
Erik Auerswald 9964c8ce85 fix copy & paste error in a comment 2024-11-17 19:09:00 +01:00
Erik Auerswald 895b0cb2b5 bump version number to 2.24.0 2024-06-22 20:05:30 +02:00
Erik Auerswald 894f3035fd fix a special case for decimal point recognition
When the widest digit found in the image is a one, it is likely
that a decimal separator is nearly as wide as this digit.  Thus
it cannot be recognized, because the decimal separator needs to
be at most half as wide as the widest digit (before this commit).

Thus add an additional pass over the digits for this special case.
This pass comes after the existing recognition passes for the
digit one, decimal separator, and minus sign.  In the new pass,
the width of the digit is ignored.

This addresses GitHub issue #26.
2024-06-22 20:04:08 +02:00
Erik Auerswald 40c34ad6d7 tweak --help and --version output 2024-05-19 01:05:22 +02:00
Erik Auerswald bae7381e34 add lower case r to character set "full" 2024-05-18 23:51:13 +02:00
Erik Auerswald d6be469e14 add lower case h to character set "full" 2024-05-18 23:49:07 +02:00
Erik Auerswald af6c53c3f3 add lower case n for consistency with lower case c
Neither lower case c nor lower case n can happen with correct
image segmentation.  Lower case c was already used in the code,
but lower case n was only found as a comment.

I am not sure if I want to keep this or rather remove both lower
case variants.  I do want to have this consistent, though.
2024-05-18 21:44:42 +02:00
Erik Auerswald b05410efae man page: fix "white_border" description
The command name stems from the time when ssocr always considered
"white" as background and "black" as foreground.  The intention
has always been to ensure some background border around the seven
segment displays, and the implementation always followed this.  The
output of --help correctly uses "background" for the "white_border"
description.  Now, the man page description is correct, too.
2024-05-09 16:19:36 +02:00
Erik Auerswald 6569ca9f6b update copyright years 2024-02-23 19:03:58 +01:00
Erik Auerswald 48da4ee8c1 help: align equal signs in Defaults section 2024-02-23 18:58:58 +01:00
Erik Auerswald 1a8c4d7701 help: mention that rotate is clockwise 2024-02-23 18:57:04 +01:00
Erik Auerswald 624e998efb NEWS: mention message improvements 2023-09-10 14:54:27 +02:00
Erik Auerswald 81c5472b69 add program name to warning messages 2023-09-10 14:49:12 +02:00
Erik Auerswald 6fc62729e9 improve error message for wrong number of digits
* Use different messages for single digit and digit ranges.
* Use singular when looking for one digit, plural otherwise.
2023-09-10 14:35:49 +02:00
Erik Auerswald 19f93dcb58 fix debug output regarding flag DEBUG_OUTPUT 2023-07-26 19:50:15 +02:00
Erik Auerswald 5ca3455873 add ssocr version to debug output 2023-07-26 19:47:01 +02:00
Erik Auerswald 2026f39ee7 INSTALL: tweak prerequisites list 2023-05-21 18:45:08 +02:00
Erik Auerswald 73e7a69b4c INSTALL: mention (GNU) make as prerequisite 2023-05-20 19:37:29 +02:00
Erik Auerswald f25047745d INSTALL: mention gcc and man under prerequisites 2023-05-20 16:58:38 +02:00
Erik Auerswald 658230679a bump version number to 2.23.1 2023-05-18 16:13:18 +02:00
Erik Auerswald 1fe392c772 use latest release date as man page date
The latest release date is extracted from the NEWS file,
i.e., it depends only on the sources, not the build date.
This is intended to help in creating reproducible builds
by avoiding timestamps.  It is also closer to the date of
the contents of the man page than using just the latest
copyright year.

I do not have a perfect solution that works for both a git
clone and a downloaded tar ball.  This solution works well
for released tar balls of the ssocr sources.
2023-05-14 17:37:48 +02:00
Erik Auerswald 2ff3d882b9 Makefile: remove quotes from inside VERSION variable 2023-05-14 17:21:37 +02:00
Erik Auerswald f06f8441fd Makefile: use single quote when possible 2023-05-14 17:01:26 +02:00
Erik Auerswald a5c550b72b Makefile: man page also depends on help.c 2023-05-14 16:26:34 +02:00
Erik Auerswald 0c3adc9002 avoid useless digit memory copy
There is no need to copy digit memory if all potential
digits are kept.
2023-05-13 20:05:23 +02:00
Erik Auerswald ad9027b6a0 guard against some integer overflows
When determining memory allocation sizes, integer overflow can
lead to memory safety errors.  Add some guards to prevent this.
2023-05-13 19:58:16 +02:00
Erik Auerswald 66dbee3591 typo fix in NEWS 2023-05-12 18:28:16 +02:00
Erik Auerswald c6e09b8f70 INSTALL, README: add commas around "e.g." 2023-05-12 18:27:19 +02:00
Erik Auerswald 7e056835fa man page: mention -- to end option-scanning 2023-05-11 21:25:31 +02:00
Erik Auerswald 050830a709 man page: use latest copyright year, not build date
In order to help creating reproducible builds of ssocr,
do not use the build day of the man page as the date
inside the man page.  Instead, use the latest copyright
year of ssocr.

Using the man page build date has always been problematic,
because it is misleading.  But I do not have a general
automatic way to maintain the last change date for the
man page that works for both git clones and tar balls.
This seems like an improvement to me.  It provides some
idea of how old the man page is, and this date depends
only on the ssocr source code, not the build date.

This should help with one of the two problems reported
in GitHub issue #22.
2023-05-09 18:11:53 +02:00
Erik Auerswald 27e7913135 append '+' to version to indicate changes 2023-05-09 18:10:25 +02:00
15 changed files with 437 additions and 259 deletions
+8 -4
View File
@@ -23,9 +23,10 @@ build a .deb package by typing:
Prerequisites
-------------
- A C compiler compatible with GCC.
- A system sufficiently compatible to POSIX and GNU.
- POSIX utilities: chmod, printf, sh, sed (or compatible)
- GNU utilities: date, gzip, install, tar (or compatible)
- POSIX utilities: chmod, head, printf, sh, sed (or compatible)
- GNU utilities: date, gzip, install, make, tar (or compatible)
- Other utilities: bzip2
- Imlib2 shared library and development headers. On a Debian or Ubuntu system
they are provided by the packages libimlib2 and libimlib2-dev.
@@ -37,8 +38,11 @@ Prerequisites
- Imlib2 requires the X11/Xlib.h header and links against some X11 libraries,
at least on GNU/Linux with X11 GUI, thus X11 shared libraries and development
headers are required for ssocr, too, although ssocr does not use X11 itself
- Build tools, e.g. build-essential on a Debian (or Ubuntu) system.
- Build tools, e.g., build-essential on a Debian (or Ubuntu) system, usually
contain both make and a C compiler.
- To build a .deb package, you probably need the debhelper package.
- To create an HTML version of the man page, you need a man utility that can
produce HTML output.
Additional Makefile Targets
---------------------------
@@ -93,6 +97,6 @@ https://www.unix-ag.uni-kl.de/~auerswal/ssocr/
Third Party Packages
--------------------
There exist third party packaging efforts for e.g. GNU/Linux distributions
There exist third party packaging efforts for, e.g., GNU/Linux distributions
and FreeBSD. Thus you can search the packaging system of your distribution
for an ssocr package.
+5 -4
View File
@@ -8,8 +8,9 @@ BINDIR := $(PREFIX)/bin
MANDIR := $(PREFIX)/share/man/man1
DOCDIR := $(PREFIX)/share/doc/ssocr
DOCS := AUTHORS COPYING INSTALL README THANKS NEWS
VERSION := $(shell sed -n 's/^.*VERSION.*\(".*"\).*/\1/p' defines.h)
VERSION := $(shell sed -n 's/^.*VERSION.*"\(.*\)".*/\1/p' defines.h)
CRYEARS := $(shell sed -n 's/^.*fprintf.*Copyright.*\(2004-2[0-9][0-9][0-9]\).*Erik.*Auerswald.*$$/\1/p' help.c)
RELDATE := $(shell sed -n 's/^Version [.0-9]* .\([-0-9]*\).*$$/\1/p' NEWS | head -n1)
all: ssocr ssocr.1
@@ -20,9 +21,9 @@ imgproc.o: imgproc.c defines.h imgproc.h help.h Makefile
help.o: help.c defines.h imgproc.h help.h Makefile
charset.o: charset.c charset.h defines.h help.h Makefile
ssocr.1: ssocr.1.in Makefile defines.h
ssocr.1: ssocr.1.in Makefile defines.h help.c NEWS
sed -e 's/@VERSION@/$(VERSION)/' \
-e "s/@DATE@/$(shell date +%Y-%m-%d)/" \
-e 's/@DATE@/$(RELDATE)/' \
-e 's/@CRYEARS@/$(CRYEARS)/' <$< >$@
ssocr-manpage.html: ssocr.1
@@ -43,7 +44,7 @@ ssocr-dir:
chmod +x ssocr-$(VERSION)/notdebian/rules
notdebian/changelog:
printf "ssocr ($(VERSION)-1) unstable; urgency=low\n\n * self built package of current ssocr version in .deb format\n\n -- $(USER) $(shell date -R)\n" >$@
printf 'ssocr ($(VERSION)-1) unstable; urgency=low\n\n * self built package of current ssocr version in .deb format\n\n -- $(USER) $(shell date -R)\n' >$@
selfdeb: notdebian/changelog notdebian/control notdebian/rules ssocr-dir
(cd ssocr-$(VERSION); ln -sv notdebian debian; fakeroot debian/rules binary; fakeroot debian/rules clean; rm -f debian)
+37
View File
@@ -1,6 +1,43 @@
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
* Print warning when an unknown luminance formula name is ignored
* Fix one debug message to be printed only with -P, --debug-output
* Documentation improvements
Version 2.24.0 (2024-06-22):
----------------------------
* Fix decimal separator recognition when widest digit is a one
* Add recognition of lower case variant of character 'h' to full charset
* Add recognition of lower case variant of character 'r' to full charset
* Documentation improvements
* Error, warning, and debug message improvements
Version 2.23.1 (2023-05-18):
----------------------------
* The man page uses the latest ssocr release date as its date (to help
in creating reproducible builds)
* Documentation improvements
Version 2.23.0 (2023-05-01):
----------------------------
* New option -N, --min-segment to specify the minimum width and height of
+2 -2
View File
@@ -7,7 +7,7 @@ as well), Mac OS X (Homebrew can be used to install the library Imlib2,
used by ssocr), and even on Windows (using Cygwin). ssocr should work
on any UNIX-like or POSIX compatible operating system.
Unless ssocr is installed via some packaging system, e.g. from a GNU/Linux
Unless ssocr is installed via some packaging system, e.g., from a GNU/Linux
distribution, it is distributed in source form and needs to be built
before it can be used. See the INSTALL file for instructions on how to
build ssocr.
@@ -19,7 +19,7 @@ quotes).
You can get the current ssocr version from the official ssocr website:
https://www.unix-ag.uni-kl.de/~auerswal/ssocr/
(Links to ssocr should point to the official website, not to a convenience
copy of the development repository, e.g. on GitHub.)
copy of the development repository, e.g., on GitHub.)
I am usually quicker to reply to emails than to GitHub issues. But
increasingly Google blocks emails sent by me, so if you do not receive
+6 -2
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2018-2023 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 */
@@ -44,7 +44,7 @@ charset_t parse_charset(char *keyword)
} else if(strncasecmp(keyword, "tt_robot", 8) == 0) {
return CS_TT_ROBOT;
} else {
return DEFAULT_CHARSET;
return CS_PARSE_ERROR;
}
}
@@ -86,9 +86,13 @@ void init_charset(charset_t cs)
charset_array[D_T] = 't';
charset_array[D_L] = 'l';
charset_array[D_H] = 'h';
charset_array[D_h] = 'h';
charset_array[D_R] = 'r';
charset_array[D_ALT_R] = 'r';
charset_array[D_r] = 'r';
charset_array[D_P] = 'p';
charset_array[D_N] = 'n';
charset_array[D_n] = 'n';
charset_array[D_Y] = 'y';
charset_array[D_J] = 'j';
break;
+1 -1
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2018-2023 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
+18 -5
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2023 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.23.0"
#define VERSION "2.25.1"
/* states */
#define FIND_DARK 0
@@ -76,6 +76,7 @@
#define D_HEX_A (ALL_SEGS & ~HORIZ_DOWN)
#define D_HEX_b (ALL_SEGS & ~(HORIZ_UP | VERT_RIGHT_UP))
#define D_HEX_C (ALL_SEGS & ~(VERT_RIGHT_UP | HORIZ_MID | VERT_RIGHT_DOWN))
/* a C in the lower half can only happen when digit boundary detection fails */
#define D_HEX_c (HORIZ_MID | VERT_LEFT_DOWN | HORIZ_DOWN)
#define D_HEX_d (ALL_SEGS & ~(HORIZ_UP | VERT_LEFT_UP))
#define D_HEX_E (ALL_SEGS & ~(VERT_RIGHT_UP | VERT_RIGHT_DOWN))
@@ -84,10 +85,15 @@
#define D_T (ALL_SEGS & ~(HORIZ_UP | VERT_RIGHT_UP | VERT_RIGHT_DOWN))
#define D_L (D_T & ~HORIZ_MID)
#define D_H (ALL_SEGS & ~(HORIZ_UP | HORIZ_DOWN))
#define D_h (VERT_LEFT_UP | VERT_LEFT_DOWN | HORIZ_MID | VERT_RIGHT_DOWN)
#define D_R (D_ZERO & ~(VERT_RIGHT_DOWN | HORIZ_DOWN))
#define D_ALT_R (VERT_LEFT_UP | VERT_LEFT_DOWN | HORIZ_UP)
/* an r in the lower half can only happen when digit boundary detection fails */
#define D_r (VERT_LEFT_DOWN | HORIZ_MID)
#define D_P (D_HEX_F | VERT_RIGHT_UP)
#define D_N (D_ZERO & ~HORIZ_DOWN)
/* an N in the lower half can only happen when digit boundary detection fails *//* define D_LOW_N (VERT_LEFT_DOWN | VERT_RIGHT_DOWN | HORIZ_MID) */
/* an N in the lower half can only happen when digit boundary detection fails */
#define D_n (VERT_LEFT_DOWN | VERT_RIGHT_DOWN | HORIZ_MID)
#define D_Y (ALL_SEGS & ~(HORIZ_UP | VERT_LEFT_DOWN))
#define D_J (HORIZ_DOWN | VERT_RIGHT_UP | VERT_RIGHT_DOWN)
/* add two "wrong" 7 definitions used in a character set for a Chinese
@@ -150,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
@@ -166,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,
@@ -181,7 +192,8 @@ typedef enum luminance_e {
MAXIMUM,
RED,
GREEN,
BLUE
BLUE,
LUM_PARSE_ERROR
} luminance_t;
/* direction, to mirror horizontally or vertically, or for a scanline */
@@ -196,7 +208,8 @@ typedef enum charset_e {
CS_DIGITS,
CS_DECIMAL,
CS_HEXADECIMAL,
CS_TT_ROBOT
CS_TT_ROBOT,
CS_PARSE_ERROR
} charset_t;
#define DEFAULT_CHARSET CS_FULL
+20 -18
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2023 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,11 +92,11 @@ void print_version(FILE *f)
{
fprintf(f, "Seven Segment Optical Character Recognition Version %s\n",
VERSION);
fprintf(f, "Copyright (C) 2004-2023 by 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 program comes with ABSOLUTELY NO WARRANTY.\n");
fprintf(f, "This is free software, and you are welcome to redistribute it"
" under the terms\nof the GNU GPL (version 3 or later)\n");
" under the terms\nof the GNU GPL (version 3 or later).\n");
}
/* print short usage */
@@ -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");
@@ -179,7 +181,7 @@ void usage(char *name, FILE *f)
fprintf(f, " color\n");
fprintf(f, " shear OFFSET shear image OFFSET pixels (at bottom) to the\n");
fprintf(f, " right\n");
fprintf(f, " rotate THETA rotate image by THETA degrees\n");
fprintf(f, " rotate THETA rotate image clockwise by THETA degrees\n");
fprintf(f, " mirror {horiz|vert} mirror image horizontally or vertically\n");
fprintf(f, " crop X Y W H crop image with upper left corner (X,Y) with\n");
fprintf(f, " width W and height H\n");
@@ -187,26 +189,26 @@ void usage(char *name, FILE *f)
fprintf(f, " pixels set (including checked position)\n");
fprintf(f, " keep_pixels_filter MASK keeps pixels that have at least MASK neighbor\n");
fprintf(f, " pixels set (not counting the checked pixel)\n");
fprintf(f, "\nDefaults: needed pixels = %2d\n", NEED_PIXELS);
fprintf(f, " minimum segment size = %2d\n", MIN_SEGMENT);
fprintf(f, " minimum character width = %2d\n", MIN_CHAR_W);
fprintf(f, " minimum character height = %2d\n", MIN_CHAR_H);
fprintf(f, " ignored pixels = %2d\n", IGNORE_PIXELS);
fprintf(f, " minimum number of digits = %2d\n", NUMBER_OF_DIGITS);
fprintf(f, " maximum number of digits = %2d\n", NUMBER_OF_DIGITS);
fprintf(f, " threshold = %5.2f\n", THRESHOLD);
fprintf(f, " foreground = %s\n",
fprintf(f, "\nDefaults: needed pixels = %2d\n", NEED_PIXELS);
fprintf(f, " minimum segment size = %2d\n", MIN_SEGMENT);
fprintf(f, " minimum character width = %2d\n", MIN_CHAR_W);
fprintf(f, " minimum character height = %2d\n", MIN_CHAR_H);
fprintf(f, " ignored pixels = %2d\n", IGNORE_PIXELS);
fprintf(f, " minimum number of digits = %2d\n", NUMBER_OF_DIGITS);
fprintf(f, " maximum number of digits = %2d\n", NUMBER_OF_DIGITS);
fprintf(f, " threshold = %5.2f\n", THRESHOLD);
fprintf(f, " foreground = %s\n",
(SSOCR_DEFAULT_FOREGROUND == SSOCR_BLACK) ? "black" : "white");
fprintf(f, " background = %s\n",
fprintf(f, " background = %s\n",
(SSOCR_DEFAULT_BACKGROUND == SSOCR_BLACK) ? "black" : "white");
fprintf(f, " luminance = ");
fprintf(f, " luminance = ");
print_lum_key(DEFAULT_LUM_FORMULA, f); fprintf(f, "\n");
fprintf(f, " height/width threshold for digit one = %2d\n", ONE_RATIO);
fprintf(f, " width/height threshold for minus sign = %2d\n", MINUS_RATIO);
fprintf(f, " max_dig_h/h threshold for decimal sep = %2d\n", DEC_H_RATIO);
fprintf(f, " max_dig_w/w threshold for decimal sep = %2d\n", DEC_W_RATIO);
fprintf(f, " space width factor = %.2f\n", SPC_FAC);
fprintf(f, " character set = ");
fprintf(f, " space width factor = %.2f\n", SPC_FAC);
fprintf(f, " character set = ");
print_cs_key(DEFAULT_CHARSET, f); fputs("\n", f);
fprintf(f, "\nOperation: The IMAGE is read, the COMMANDs are processed in the sequence\n");
fprintf(f, " they are given, in the resulting image the given number of digits\n");
+1 -1
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2023 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
+65 -128
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2023 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);
}
}
}
@@ -1126,7 +1063,7 @@ luminance_t parse_lum(char *keyword)
} else if(strcasecmp(keyword, "blue") == 0) {
return BLUE;
} else {
return DEFAULT_LUM_FORMULA;
return LUM_PARSE_ERROR;
}
}
+11 -12
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2023 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-2023 Erik Auerswald
Copyright (C) 2004-2025 Erik Auerswald
License:
+39 -4
View File
@@ -1,4 +1,4 @@
.TH ssocr 1 "@DATE@" @VERSION@ "OCR for seven segment displays"
.TH ssocr 1 "@DATE@" "@VERSION@" "OCR for seven segment displays"
.SH NAME
ssocr \- optical recognition of seven segment displays
.SH SYNOPSIS
@@ -18,10 +18,24 @@ provides several image manipulation algorithms to enhance noisy images.
can be used to change
.B ssocr
behavior.
.PP
.B Commands
can be used to manipulate the input
.I IMAGE
before starting the recognition algorithm.
.PP
Two hyphens
.RB ( \-\- )
can be used as a special argument to end option-scanning, e.g.,
in order to use a negative number as argument to a command.
.PP
When using a single character (i.e., short) option,
arguments can either directly follow the option character,
or can be separated from the option character by whitespace.
When using a multi character (i.e., long) option,
arguments must be separated from the option by either an equals sign
.RB ( = ),
or whitespace.
.SH OPTIONS
.SS \-h, \-\-help
Write a help message to standard output.
@@ -39,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.
@@ -276,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
@@ -360,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
@@ -422,7 +457,7 @@ or
.B dynamic_threshold
instead.
.SS white_border [WIDTH]
The border of the image is set to the foreground color.
The border of the image is set to the background color.
This border is one pixel wide unless a
.B WIDTH
>
+222 -76
View File
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Copyright (C) 2004-2023 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 */
@@ -22,6 +22,7 @@
#include <Imlib2.h>
/* standard things */
#include <limits.h> /* INT_MAX */
#include <stdint.h> /* SIZE_MAX */
#include <stdio.h> /* puts, printf, BUFSIZ, perror, FILE */
#include <stdlib.h> /* exit */
@@ -31,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 */
@@ -56,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;
@@ -75,6 +76,10 @@ static char * tmp_imgfile(unsigned int flags)
}
dir_len = strlen(dir);
pattern_len = dir_len + pat_suffix_len + 1;
if(pattern_len <= dir_len) {
fputs(PROG ": error: temporary file name length overflow\n", stderr);
exit(99);
}
name = calloc(pattern_len, sizeof(char));
if(!name) {
perror(PROG ": could not allocate memory for name of temporary file");
@@ -94,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);
@@ -109,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;
@@ -126,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++;
}
@@ -243,6 +253,7 @@ int main(int argc, char **argv)
int use_tmpfile=0; /* flag to know if temporary image file is used */
int i, j, d; /* iteration variables */
size_t cur_digit_mem, new_digit_mem; /* for overflow checks */
int unknown_digit=0; /* was one of the 6 found digits an unknown one? */
int need_pixels = NEED_PIXELS; /* pixels needed to set segment in scanline */
int min_segment = MIN_SEGMENT; /* minimum pixels needed for a segment */
@@ -272,6 +283,7 @@ int main(int argc, char **argv)
int dig_w; /* width of digit part of image */
int dig_h; /* height of digit part of image */
int max_dig_h=0, max_dig_w=0; /* maximum height & width of digits found */
int widest_dig_is_one=0; /* set to one if the widest digit is a one */
Imlib_Color color; /* Imlib2 RGBA color structure */
/* state of search */
int state = (ssocr_foreground == SSOCR_BLACK) ? FIND_DARK : FIND_LIGHT;
@@ -321,16 +333,17 @@ int main(int argc, char **argv)
{"ascii-art-segments", 0, 0, 'S'}, /* print found segments in ASCII art */
{"print-as-hex", 0, 0, 'X'}, /* change output format to hex */
{"omit-decimal-point", 0, 0, 'C'}, /* omit decimal points from output */
{"charset", 1, 0, 'c'}, /* omit decimal points from output */
{"charset", 1, 0, 'c'}, /* select character set of display */
{"dec-h-ratio", 1, 0, 'H'}, /* height ratio for decimal point detection */
{"dec-w-ratio", 1, 0, 'W'}, /* width ratio for decimal point detection */
{"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) {
@@ -373,7 +386,8 @@ int main(int argc, char **argv)
if(optarg) {
need_pixels = atoi(optarg);
if(need_pixels < 1) {
fprintf(stderr, "warning: ignoring --number-pixels=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --number-pixels=%s\n",
optarg);
need_pixels = NEED_PIXELS;
}
if(flags & DEBUG_OUTPUT) {
@@ -385,7 +399,8 @@ int main(int argc, char **argv)
if(optarg) {
min_segment = atoi(optarg);
if(min_segment < 1) {
fprintf(stderr, "warning: ignoring --min-segment=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --min-segment=%s\n",
optarg);
min_segment = MIN_SEGMENT;
if(flags & DEBUG_OUTPUT) {
fprintf(stderr, "min_segment = %d\n", min_segment);
@@ -403,7 +418,8 @@ int main(int argc, char **argv)
int ret;
ret = parse_width_height(optarg, &min_char_dims);
if (ret) {
fprintf(stderr, "warning: ignoring --min-char-dims=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --min-char-dims=%s\n",
optarg);
}
if(flags & DEBUG_OUTPUT) {
fprintf(stderr, "min_char_dims = %dx%d\n", min_char_dims.w,
@@ -415,7 +431,8 @@ int main(int argc, char **argv)
if(optarg) {
ignore_pixels = atoi(optarg);
if(ignore_pixels < 0) {
fprintf(stderr, "warning: ignoring --ignore-pixels=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --ignore-pixels=%s\n",
optarg);
ignore_pixels = IGNORE_PIXELS;
}
}
@@ -425,7 +442,8 @@ int main(int argc, char **argv)
int ret;
ret = parse_interval(optarg, &expected_digits);
if(ret) {
fprintf(stderr, "warning: ignoring --number-digits=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --number-digits=%s\n",
optarg);
expected_digits.min = expected_digits.max = NUMBER_OF_DIGITS;
}
if (flags & DEBUG_OUTPUT) {
@@ -438,7 +456,7 @@ int main(int argc, char **argv)
if(optarg) {
one_ratio = atoi(optarg);
if(one_ratio < 2) {
fprintf(stderr, "warning: ignoring --one-ratio=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --one-ratio=%s\n",optarg);
one_ratio = ONE_RATIO;
}
}
@@ -447,7 +465,8 @@ int main(int argc, char **argv)
if(optarg) {
minus_ratio = atoi(optarg);
if(minus_ratio < 1) {
fprintf(stderr, "warning: ignoring --minus-ratio=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --minus-ratio=%s\n",
optarg);
minus_ratio = MINUS_RATIO;
}
}
@@ -519,6 +538,12 @@ int main(int argc, char **argv)
case 'l':
if(optarg) {
lt = parse_lum(optarg);
if(lt == LUM_PARSE_ERROR) {
fprintf(stderr,
PROG ": warning: ignoring unknown luminance formula '%s'\n",
optarg);
lt = DEFAULT_LUM_FORMULA;
}
}
break;
case 'S':
@@ -545,13 +570,19 @@ int main(int argc, char **argv)
case 'c':
if(optarg) {
charset = parse_charset(optarg);
if(charset == CS_PARSE_ERROR) {
fprintf(stderr, PROG ": warning: ignoring unknown charset '%s'\n",
optarg);
charset = DEFAULT_CHARSET;
}
}
break;
case 'H':
if(optarg) {
dec_h_ratio = atoi(optarg);
if(dec_h_ratio < 2) {
fprintf(stderr, "warning: ignoring --dec-h-ratio=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --dec-h-ratio=%s\n",
optarg);
dec_h_ratio = DEC_H_RATIO;
}
}
@@ -560,7 +591,8 @@ int main(int argc, char **argv)
if(optarg) {
dec_w_ratio = atoi(optarg);
if(dec_w_ratio < 1) {
fprintf(stderr, "warning: ignoring --dec-w-ratio=%s\n", optarg);
fprintf(stderr, PROG ": warning: ignoring --dec-w-ratio=%s\n",
optarg);
dec_w_ratio = DEC_W_RATIO;
}
}
@@ -592,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);
@@ -608,8 +647,11 @@ 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);
fprintf(stderr, "flags & VERBOSE=%d\nthresh=%f\n", flags & VERBOSE, thresh);
fprintf(stderr, "flags & PRINT_INFO=%d\nflags & ADJUST_GRAY=%d\n",
flags & PRINT_INFO, flags & ADJUST_GRAY);
@@ -617,7 +659,7 @@ int main(int argc, char **argv)
fprintf(stderr, "flags & DO_ITERATIVE_THRESHOLD=%d\n",
flags & DO_ITERATIVE_THRESHOLD);
fprintf(stderr, "flags & USE_DEBUG_IMAGE=%d\n", flags & USE_DEBUG_IMAGE);
fprintf(stderr, "flags & DEBUG_OUTPUT=%d\n", flags & PRINT_INFO);
fprintf(stderr, "flags & DEBUG_OUTPUT=%d\n", flags & DEBUG_OUTPUT);
fprintf(stderr, "flags & PROCESS_ONLY=%d\n", flags & PROCESS_ONLY);
fprintf(stderr, "flags & ASCII_ART_SEGMENTS=%d\n",
flags & ASCII_ART_SEGMENTS);
@@ -625,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);
@@ -695,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) {
@@ -732,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;
@@ -751,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;
@@ -770,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;
@@ -789,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();
@@ -819,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;
@@ -839,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();
@@ -858,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();
@@ -878,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();
@@ -900,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();
@@ -911,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();
@@ -957,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) {
@@ -966,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();
@@ -983,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]);
@@ -999,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();
@@ -1014,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);
@@ -1035,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();
@@ -1084,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);
@@ -1147,6 +1209,11 @@ int main(int argc, char **argv)
}
digits[d].x2 = i;
digits[d].y2 = h-1;
if((d >= INT_MAX - 1) || (d < 0)) {
fputs(PROG ": error: too many potential digits (integer overflow)\n",
stderr);
exit(99);
}
d++;
if(flags & USE_DEBUG_IMAGE) {
imlib_context_set_image(debug_image);
@@ -1155,7 +1222,13 @@ int main(int argc, char **argv)
imlib_context_set_image(image);
}
/* add memory for another digit */
if(!(digits = realloc(digits, (d+1) * sizeof(digit_struct)))) {
cur_digit_mem = d * sizeof(digit_struct);
new_digit_mem = (d+1) * sizeof(digit_struct);
if(new_digit_mem <= cur_digit_mem) {
fputs(PROG ": error: size_t overflow (memory for digits)\n", stderr);
exit(99);
}
if(!(digits = realloc(digits, new_digit_mem))) {
perror(PROG ": digits = realloc()");
exit(99);
}
@@ -1293,35 +1366,57 @@ int main(int argc, char **argv)
fprintf(stderr, "keeping %d of %d potential digits\n", digit_count,
potential_digits);
}
/* allocate memory for sufficiently large digits */
if(!(tmp = calloc(digit_count, sizeof(digit_struct)))) {
perror(PROG ": tmp = calloc()");
/* at least one digit is required */
if (digit_count < 1) {
fputs(PROG ": error: no sufficiently large digits found\n", stderr);
exit(1);
}
/* ensure we do not try to keep more digits than we have found */
if(digit_count > potential_digits) {
fprintf(stderr,
PROG ": error: trying to keep more digits (%d) than found (%d)\n",
digit_count, potential_digits);
exit(99);
}
/* keep only sufficiently large digits */
pos = 0;
for (d = 0; d < potential_digits; d++) {
if (digits[d].x2 - digits[d].x1 >= min_char_dims.w &&
digits[d].y2 - digits[d].y1 >= min_char_dims.h) {
if (pos >= digit_count) {
fputs(PROG ": error copying digit information", stderr);
exit(99);
}
memcpy(tmp + pos, digits + d, sizeof(digit_struct));
pos++;
/* if potential digits are discarded, copy remaining ones to new memory */
if(digit_count < potential_digits) {
/* allocate memory for sufficiently large digits we want to keep */
if(!(tmp = calloc(digit_count, sizeof(digit_struct)))) {
perror(PROG ": tmp = calloc()");
exit(99);
}
/* keep only sufficiently large digits */
pos = 0;
for (d = 0; d < potential_digits; d++) {
if (digits[d].x2 - digits[d].x1 >= min_char_dims.w &&
digits[d].y2 - digits[d].y1 >= min_char_dims.h) {
if (pos >= digit_count) {
fputs(PROG ": error copying digit information", stderr);
exit(99);
}
memcpy(tmp + pos, digits + d, sizeof(digit_struct));
pos++;
}
}
free(digits);
digits = tmp;
potential_digits = digit_count;
}
free(digits);
digits = tmp;
potential_digits = digit_count;
}
/* check if expected number of digits have been found */
if ((expected_digits.min > -1) &&
((potential_digits < expected_digits.min) ||
(potential_digits > expected_digits.max))) {
fprintf(stderr, PROG ": expected between %d and %d digits, but found %d\n",
expected_digits.min, expected_digits.max, potential_digits);
if (expected_digits.min != expected_digits.max) {
fprintf(stderr,
PROG ": expected between %d and %d digits, but found %d\n",
expected_digits.min, expected_digits.max, potential_digits);
} else {
fprintf(stderr, PROG ": expected %d digit%s, but found %d\n",
expected_digits.min, expected_digits.min > 1 ? "s" : "",
potential_digits);
}
imlib_free_image_and_decache();
if(flags & USE_DEBUG_IMAGE) {
save_image("debug", debug_image, output_fmt,debug_image_file,flags);
@@ -1473,6 +1568,57 @@ int main(int argc, char **argv)
}
}
/* If the widest digit is a one, decimal points may be of the same width,
* and may thus not be detected. Now that minus signs have been selected,
* if the widest digit still is a one (i.e., no minus signs), then decimal
* separators may also be recognized by checking only the height, not the
* width. */
if(flags & DEBUG_OUTPUT)
fputs("checking for special case of a one as widest character\n",stderr);
/* check if the widest digit is a one */
for(d=0; d<number_of_digits; d++) {
/* skip digits with zero width or height */
if((digits[d].x1 == digits[d].x2) || (digits[d].y1 == digits[d].y2)) {
if(flags & DEBUG_OUTPUT)
fprintf(stderr, " skipping digit %d with zero width or height\n", d);
continue;
}
if((digits[d].digit == D_ONE) && (digits[d].x2-digits[d].x1 >= max_dig_w)) {
widest_dig_is_one = 1;
if(flags & DEBUG_OUTPUT)
fputs(" widest digit is a one -> additional decimal point search\n",
stderr);
break;
}
}
if(!widest_dig_is_one) {
if(flags & DEBUG_OUTPUT)
fputs(" widest digit is not a one, skipping extra decimal point search\n",
stderr);
} else {
/* widest digit is a one, thus decimal seperators may have been missed:
* identify a decimal point (or thousands separator) by relative height */
if(flags & DEBUG_OUTPUT)
fputs("looking for decimal points again\n",stderr);
for(d=0; d<number_of_digits; d++) {
/* skip digits with zero width or height */
if((digits[d].x1 == digits[d].x2) || (digits[d].y1 == digits[d].y2)) {
if(flags & DEBUG_OUTPUT)
fprintf(stderr, " skipping digit %d with zero width or height\n", d);
continue;
}
/* if height of a digit is less than 1/5 of the maximum digit height,
* and its width is less than 1/2 of the maximum digit width (the widest
* digit might be a one), assume it is a decimal point */
if((digits[d].digit == D_UNKNOWN) &&
(max_dig_h / (digits[d].y2 - digits[d].y1) > dec_h_ratio)) {
digits[d].digit = D_DECIMAL;
if(flags & DEBUG_OUTPUT)
fprintf(stderr, " digit %d is a decimal point\n", d);
}
}
}
/* now the digits are located and they have to be identified */
if(flags & DEBUG_OUTPUT)
fputs("starting scanline based recognition for remaining digits\n", stderr);
@@ -1496,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) {
@@ -1511,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) {
@@ -1520,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) {
@@ -1528,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);
@@ -1538,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) {
@@ -1546,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-2023 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