ocrad-0.29/0000775000175000017500000000000014726507103012437 5ustar andriusandriusocrad-0.29/rational.cc0000644000175000017500000001722114544652010014554 0ustar andriusandrius/* Rational - Rational number class with overflow detection Copyright (C) 2005-2024 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include "rational.h" #ifndef LLONG_MAX #define LLONG_MAX 0x7FFFFFFFFFFFFFFFLL #endif #ifndef LLONG_MIN #define LLONG_MIN (-LLONG_MAX - 1LL) #endif #ifndef ULLONG_MAX #define ULLONG_MAX 0xFFFFFFFFFFFFFFFFULL #endif namespace { long long gcd( long long n, long long m ) // Greatest Common Divisor { if( n < 0 ) n = -n; if( m < 0 ) m = -m; while( true ) { if( m ) n %= m; else return n; if( n ) m %= n; else return m; } } std::string overflow_string( const int n ) { if( n > 0 ) return "+INF"; if( n < 0 ) return "-INF"; return "NAN"; } int overflow_value( const long long n, const bool negate = false ) { if( negate ) { if( n > 0 ) return -INT_MAX; if( n < 0 ) return INT_MAX; return 0; } else { if( n > 0 ) return INT_MAX; if( n < 0 ) return -INT_MAX; return 0; } } } // end namespace void Rational::normalize( long long n, long long d ) { if( d == 0 ) { num = overflow_value( n ); den = 0; return; } // set error if( n == 0 ) { num = 0; den = 1; return; } if( d != 1 ) { const long long tmp = gcd( n, d ); n /= tmp; d /= tmp; } if( n <= INT_MAX && n >= -INT_MAX && d <= INT_MAX && d >= -INT_MAX ) { if( d >= 0 ) { num = n; den = d; } else { num = -n; den = -d; } } else { num = overflow_value( n, d < 0 ); den = 0; } } void Rational::normalize() { if( den == 0 ) { num = overflow_value( num ); return; } if( num == 0 ) { den = 1; return; } if( den != 1 ) { const int tmp = gcd( num, den ); num /= tmp; den /= tmp; } if( num < -INT_MAX ) { num = overflow_value( den, true ); den = 0; return; } if( den < 0 ) { if( den < -INT_MAX ) { num = overflow_value( num, true ); den = 0; return; } num = -num; den = -den; } } Rational Rational::inverse() const { if( den <= 0 ) return *this; // no-op on error Rational tmp; if( num > 0 ) { tmp.num = den; tmp.den = num; } else if( num < 0 ) { tmp.num = -den; tmp.den = -num; } else { tmp.num = overflow_value( den ); tmp.den = 0; } // set error return tmp; } Rational & Rational::operator+=( const Rational & r ) { if( den <= 0 ) return *this; // no-op on error if( r.den <= 0 ) { num = r.num; den = 0; return *this; } // set error long long new_den = den; new_den *= r.den; long long new_num1 = num; new_num1 *= r.den; long long new_num2 = r.num; new_num2 *= den; normalize( new_num1 + new_num2, new_den ); return *this; } Rational & Rational::operator*=( const Rational & r ) { if( den <= 0 ) return *this; // no-op on error if( r.den <= 0 ) { num = r.num; den = 0; return *this; } // set error long long new_num = num; new_num *= r.num; long long new_den = den; new_den *= r.den; normalize( new_num, new_den ); return *this; } int Rational::round() const { if( den <= 0 ) return num; int result = num / den; const int rest = std::abs( num ) % den; if( rest > 0 && rest >= den - rest ) { if( num >= 0 ) ++result; else --result; } return result; } /* Recognized formats: 123 123/456 123.456 .123 12% 12/3% 12.3% .12% Values may be preceded by an optional '+' or '-' sign. Return the number of chars read from 's', or 0 if input is invalid. In case of invalid input, the Rational is not changed. */ int Rational::parse( const char * const s ) { if( !s || !s[0] ) return 0; long long n = 0, d = 1; // restrain intermediate overflow int c = 0; bool minus = false; while( std::isspace( s[c] ) ) ++c; if( s[c] == '+' ) ++c; else if( s[c] == '-' ) { ++c; minus = true; } if( !std::isdigit( s[c] ) && s[c] != '.' ) return 0; while( std::isdigit( s[c] ) ) { if( ( LLONG_MAX - (s[c] - '0') ) / 10 < n ) return 0; n = (n * 10) + (s[c] - '0'); ++c; } if( s[c] == '.' ) { ++c; if( !std::isdigit( s[c] ) ) return 0; while( std::isdigit( s[c] ) ) { if( ( LLONG_MAX - (s[c] - '0') ) / 10 < n || LLONG_MAX / 10 < d ) return 0; n = (n * 10) + (s[c] - '0'); d *= 10; ++c; } } else if( s[c] == '/' ) { ++c; d = 0; while( std::isdigit( s[c] ) ) { if( ( LLONG_MAX - (s[c] - '0') ) / 10 < d ) return 0; d = (d * 10) + (s[c] - '0'); ++c; } if( d == 0 ) return 0; } if( s[c] == '%' ) { ++c; if( n % 100 == 0 ) n /= 100; else if( n % 10 == 0 && LLONG_MAX / 10 >= d ) { n /= 10; d *= 10; } else if( LLONG_MAX / 100 >= d ) d *= 100; else return 0; } if( minus ) n = -n; Rational tmp; tmp.normalize( n, d ); if( !tmp.error() ) { *this = tmp; return c; } return 0; } /* Return a string representing the value 'num/den' in decimal point format with 'prec' decimals. 'iwidth' is the minimum width of the integer part, prefixed with spaces if needed. If 'prec' is negative, produce only the decimals needed. If 'rounding', round up the last digit if the next one would be >= 5. */ std::string Rational::to_decimal( const unsigned iwidth, int prec, const bool rounding ) const { if( den <= 0 ) return overflow_string( num ); std::string s; int ipart = std::abs( num / den ); const bool truncate = ( prec < 0 ); if( prec < 0 ) prec = -prec; do { s += ( ipart % 10 ) + '0'; ipart /= 10; } while( ipart > 0 ); if( num < 0 ) s += '-'; if( iwidth > s.size() ) s.append( iwidth - s.size(), ' ' ); std::reverse( s.begin(), s.end() ); long long rest = std::abs( num ) % den; if( prec > 0 && ( rest > 0 || !truncate ) ) { s += '.'; while( prec > 0 && ( rest > 0 || !truncate ) ) { rest *= 10; s += ( rest / den ) + '0'; rest %= den; --prec; } } if( rounding && rest * 2 >= den ) // round last decimal up for( int j = s.size() - 1; j >= 0; --j ) { if( s[j] == '.' ) continue; if( s[j] >= '0' && s[j] < '9' ) { ++s[j]; break; } if( s[j] == '9' ) s[j] = '0'; if( j > 0 && s[j-1] == '.' ) continue; if( j > 0 && s[j-1] == ' ' ) { s[j-1] = '1'; break; } if( j > 1 && s[j-2] == ' ' && s[j-1] == '-' ) { s[j-2] = '-'; s[j-1] = '1'; break; } // no prev digit, prepend '1' to the first digit if( j == 0 || s[j-1] < '0' || s[j-1] > '9' ) { s.insert( s.begin() + j, '1' ); break; } } return s; } /* Return a string representing the value 'num/den' in fractional form. 'width' is the minimum width to be produced, prefixed with spaces if needed. */ std::string Rational::to_fraction( const unsigned width ) const { if( den <= 0 ) return overflow_string( num ); std::string s; int n = std::abs( num ), d = den; do { s += ( d % 10 ) + '0'; d /= 10; } while( d > 0 ); s += '/'; do { s += ( n % 10 ) + '0'; n /= 10; } while( n > 0 ); if( num < 0 ) s += '-'; if( width > s.size() ) s.append( width - s.size(), ' ' ); std::reverse( s.begin(), s.end() ); return s; } ocrad-0.29/profile.cc0000644000175000017500000004470214545576754014434 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "bitmap.h" #include "blob.h" #include "profile.h" Profile::Profile( const Bitmap & bm_, const Type t ) : bm( bm_ ), type( t ), limit_( -1 ), max_( -1 ), min_( -1 ), mean_( -1 ), isconcave_( -1 ), isconvex_( -1 ), isflat_( -1 ), isflats_( -1 ), ispit_( -1 ), istpit_( -1 ), isupit_( -1 ), isvpit_( -1 ), istip_( -1 ) {} void Profile::initialize() { switch( type ) { case left : data.resize( bm.height() ); limit_ = bm.width(); for( int row = bm.top(); row <= bm.bottom(); ++row ) { int j = bm.left(); while( j <= bm.right() && !bm.get_bit( row, j ) ) ++j; data[row-bm.top()] = j - bm.left(); } break; case top : data.resize( bm.width() ); limit_ = bm.height(); for( int col = bm.left(); col <= bm.right(); ++col ) { int j = bm.top(); while( j <= bm.bottom() && !bm.get_bit( j, col ) ) ++j; data[col-bm.left()] = j - bm.top(); } break; case right : data.resize( bm.height() ); limit_ = bm.width(); for( int row = bm.top(); row <= bm.bottom(); ++row ) { int j = bm.right(); while( j >= bm.left() && !bm.get_bit( row, j ) ) --j; data[row-bm.top()] = bm.right() - j; } break; case bottom : data.resize( bm.width() ); limit_ = bm.height(); for( int col = bm.left(); col <= bm.right(); ++col ) { int j = bm.bottom(); while( j >= bm.top() && !bm.get_bit( j, col ) ) --j; data[col-bm.left()] = bm.bottom() - j; } break; case height : data.resize( bm.width() ); limit_ = bm.height(); for( int col = bm.left(); col <= bm.right(); ++col ) { int u = bm.top(), d = bm.bottom(); while( u <= d && !bm.get_bit( u, col ) ) ++u; while( u <= d && !bm.get_bit( d, col ) ) --d; data[col-bm.left()] = d - u + 1; } break; case width : data.resize( bm.height() ); limit_ = bm.width(); for( int row = bm.top(); row <= bm.bottom(); ++row ) { int l = bm.left(), r = bm.right(); while( l <= r && !bm.get_bit( row, l ) ) ++l; while( l <= r && !bm.get_bit( row, r ) ) --r; data[row-bm.top()] = r - l + 1; } break; } } int Profile::mean() { if( mean_ < 0 ) { if( limit_ < 0 ) initialize(); mean_ = 0; for( int i = 0; i < samples(); ++i ) mean_ += data[i]; if( samples() > 1 ) mean_ /= samples(); } return mean_; } int Profile::max() { if( max_ < 0 ) { if( limit_ < 0 ) initialize(); max_ = data[0]; for( int i = 1; i < samples(); ++i ) if( data[i] > max_ ) max_ = data[i]; } return max_; } int Profile::max( const int l, int r ) { if( limit_ < 0 ) initialize(); if( r < 0 ) r = samples() - 1; int m = 0; for( int i = l; i <= r; ++i ) if( data[i] > m ) m = data[i]; return m; } int Profile::min() { if( min_ < 0 ) { if( limit_ < 0 ) initialize(); min_ = data[0]; for( int i = 1; i < samples(); ++i ) if( data[i] < min_ ) min_ = data[i]; } return min_; } int Profile::min( const int l, int r ) { if( limit_ < 0 ) initialize(); if( r < 0 ) r = samples() - 1; int m = limit_; for( int i = l; i <= r; ++i ) if( data[i] < m ) m = data[i]; return m; } int Profile::operator[]( int i ) { if( limit_ < 0 ) initialize(); if( i < 0 ) i = 0; else if( i >= samples() ) i = samples() - 1; return data[i]; } int Profile::area( const int l, int r ) { if( limit_ < 0 ) initialize(); if( r < 0 || r >= samples() ) r = samples() - 1; int a = 0; for( int i = l; i <= r; ++i ) a += data[i]; return a; } bool Profile::increasing( int i, const int min_delta ) { if( limit_ < 0 ) initialize(); if( i < 0 || i > samples() - 2 || data[samples()-1] - data[i] < min_delta ) return false; while( ++i < samples() ) if( data[i] < data[i-1] ) return false; return true; } bool Profile::decreasing( int i, int end ) { if( limit_ < 0 ) initialize(); const int dnoise = ( samples() / 20 ) + 1; // domain noise const int rnoise = ( std::min( samples(), limit_ ) / 20 ) + 1; // range noise if( end < 0 || end > samples() - dnoise ) end = samples() - dnoise; if( i < 0 || end - i <= 2 * rnoise || data[i] - data[end-1] <= rnoise ) return false; while( ++i < end ) if( data[i] > data[i-1] ) return false; return true; } bool Profile::isconcave() { if( isconcave_ < 0 ) { isconcave_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return isconcave_; int dmax = -1, l = 0, r = 0; for( int i = pos( 10 ); i <= pos( 90 ); ++i ) { if( data[i] > dmax ) { dmax = data[i]; l = r = i; } else if( data[i] == dmax ) { r = i; } } if( l > r || l < pos( 25 ) || r > pos( 75 ) ) return isconcave_; if( data[pos(10)] >= dmax || data[pos(90)] >= dmax ) return isconcave_; int imax = ( l + r ) / 2; for( int i = pos( 10 ); i < imax; ++i ) if( data[i] > data[i+1] ) return isconcave_; for( int i = pos( 90 ); i > imax; --i ) if( data[i] > data[i-1] ) return isconcave_; isconcave_ = true; } return isconcave_; } bool Profile::isconvex() { if( isconvex_ < 0 ) { isconvex_ = false; if( limit_ < 0 ) initialize(); if( samples() < 9 || limit_ < 5 ) return isconvex_; int min = limit_, min_begin = 0, min_end = 0; int lmin = limit_, rmax = -limit_, l = 0, r = 0; for( int i = 1; i < samples(); ++i ) { int d = data[i] - data[i-1]; if( d < lmin ) { lmin = d; l = i - 1; } if( d >= rmax ) { rmax = d; r = i; } if( data[i] <= min ) { min_end = i; if( data[i] < min ) { min = data[i]; min_begin = i; } } } if( l >= r || l >= pos( 25 ) || r <= pos( 75 ) ) return isconvex_; if( lmin >= 0 || rmax <= 0 || data[l] < 2 || data[r] < 2 || 3 * ( data[l] + data[r] ) <= std::min( samples(), limit_ ) ) return isconvex_; if( 3 * ( min_end - min_begin + 1 ) > 2 * samples() ) return isconvex_; if( 2 * l >= min_begin || 2 * r <= min_end + samples() - 1 ) return isconvex_; if( min_begin < pos( 10 ) || min_end > pos( 90 ) ) return isconvex_; const int noise = ( std::min( samples(), limit_ ) / 30 ) + 1; int dmax = -limit_; for( int i = l + 1; i <= r; ++i ) { if( i >= min_begin && i <= min_end ) { if( data[i] <= noise ) continue; else return isconvex_; } int d = data[i] - data[i-1]; if( d == 0 ) continue; if( d > dmax ) { if( std::abs( d ) <= noise ) ++dmax; else dmax = d; } else if( d < dmax - noise ) return isconvex_; } if( 2 * ( min_end - min_begin + 1 ) < samples() ) { int varea = ( min_begin - l + 1 ) * data[l] / 2; varea += ( r - min_end + 1 ) * data[r] / 2; if( this->area( l, min_begin - 1 ) + this->area( min_end + 1, r ) >= varea ) return isconvex_; } isconvex_ = true; } return isconvex_; } bool Profile::isflat() { if( isflat_ < 0 ) { isflat_ = false; if( limit_ < 0 ) initialize(); if( samples() < 10 ) return isflat_; int mn = data[samples()/2], mx = mn; for( int i = 1; i < samples() - 1; ++i ) { int d = data[i]; if( d < mn ) mn = d; else if( d > mx ) mx = d; } isflat_ = (bool)( mx - mn <= 1 + ( samples() / 30 ) ); } return isflat_; } bool Profile::isflats() { if( isflats_ < 0 ) { isflats_ = false; if( limit_ < 0 ) initialize(); if( samples() < 12 ) return isflats_; const int s1 = std::max( pos( 15 ), 3 ); const int s2 = std::min( pos( 85 ), samples() - 4 ); int mn = -1, mx = 0; for( int i = s1 + 2; i < s2; ++i ) if( data[i-1] == data[i] ) { mn = mx = data[i]; break; } if( mn < 0 ) return isflats_; for( int i = 1; i <= s1; ++i ) if( data[i] > mx ) mx = data[i]; for( int i = s1 + 1; i < s2; ++i ) { int d = data[i]; if( d < mn ) mn = d; else if( d > mx ) mx = d; } for( int i = s2; i < samples() - 1; ++i ) if( data[i] > mx ) mx = data[i]; isflats_ = (bool)( mx - mn <= 1 + ( samples() / 30 ) ); } return isflats_; } bool Profile::ispit() { if( ispit_ < 0 ) { ispit_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return ispit_; const int noise = ( std::min( samples(), limit_ ) / 25 ) + 1; for( int i = 0; i < noise; ++i ) if( data[i] <= noise - i || data[samples()-i-1] <= noise - i ) return ispit_; const int dmin = min(), dmax = limit_ / 2; int begin = 0, end = 0, i, ref; for( i = 0, ref = dmax; i < samples(); ++i ) { int d = data[i]; if( d == dmin ) { begin = i; break; } if( d < ref ) ref = d; else if( d > ref + noise && ref < dmax ) return ispit_; } if( begin < 2 || begin > samples() - 3 ) return ispit_; for( i = samples() - 1, ref = dmax; i >= begin; --i ) { int d = data[i]; if( d == dmin ) { end = i; break; } if( d < ref ) ref = d; else if( d > ref + noise && ref < dmax ) return ispit_; } if( end < begin || end > samples() - 3 ) return ispit_; for( i = begin + 1; i < end; ++i ) if( data[i] > dmin + noise ) return ispit_; ispit_ = true; } return ispit_; } bool Profile::iscpit( const int cpos ) { if( limit_ < 0 ) initialize(); if( samples() < 5 || cpos < 25 || cpos > 75 ) return false; const int mid = ( ( samples() - 1 ) * cpos ) / 100; const int iend = std::min( samples() / 4, std::min( mid, samples() - mid ) ); const int th = ( ( mean() < 2 ) ? 2 : mean() ); int imin = -1; for( int i = 0; i < iend; ++i ) { if( data[mid+i] < th ) { imin = mid + i; break; } if( data[mid-i-1] < th ) { imin = mid - i - 1; break; } } if( imin < 0 ) return false; for( int i = imin + 1; i < samples(); ++i ) if( data[i] > th ) { for( int j = imin - 1; j >= 0; --j ) if( data[j] > th ) return true; break; } return false; } bool Profile::istpit() { if( istpit_ < 0 ) { if( limit_ < 0 ) initialize(); if( limit_ < 5 || samples() < 5 || !ispit() ) { istpit_ = false; return istpit_; } const int noise = ( std::min( samples(), limit_ ) / 30 ) + 1; int l = -1, r = 0; for( int i = 0; i < samples(); ++i ) if( data[i] <= noise ) { r = i; if( l < 0 ) l = i; } istpit_ = (bool)( l > 0 && 4 * ( r - l + 1 ) < samples() ); } return istpit_; } bool Profile::isupit() { if( isupit_ < 0 ) { isupit_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return isupit_; int th = ( mean() < 2 && range() > 2 ) ? 2 : mean(); int status = 0, ucount = 0, lcount = 0, umean =0, lmean = 0; for( int i = 0; i < samples(); ++i ) { int d = data[i]; switch( status ) { case 0: if( d < th ) { if( i < pos( 25 ) || i > pos( 70 ) ) return isupit_; status = 1; break; } if( d > th ) { ++ucount; umean += d; } break; case 1: if( d > th ) { if( i < pos( 30 ) || i > pos( 75 ) ) return isupit_; status = 2; break; } if( d < th ) { ++lcount; lmean += d; } break; case 2: if( d < th ) return isupit_; if( d > th ) { ++ucount; umean += d; } break; } } if( ucount > 1 ) umean /= ucount; if( lcount > 1 ) lmean /= lcount; isupit_ = (bool)( status == 2 && umean - lmean > range() / 2 ); } return isupit_; } bool Profile::isvpit() { if( isvpit_ < 0 ) { if( limit_ < 0 ) initialize(); if( limit_ < 5 || samples() < 5 || !ispit() ) { isvpit_ = false; return isvpit_; } const int noise = limit_ / 20; const int level = ( limit_ / 10 ) + 2; int ll = -1, ln = -1, rl = -1, rn = -1; for( int i = 0; i < samples(); ++i ) if( data[i] <= level ) { rl = i; if( ll < 0 ) ll = i; if( data[i] <= noise ) { rn = i; if( ln < 0 ) ln = i; } } const int wl = rl - ll + 1, wn = rn - ln + 1; isvpit_ = (bool)( ln > 0 && 2 * wl <= samples() + 1 && wl - wn <= 2 * ( level - noise ) ); } return isvpit_; } bool Profile::istip() { if( istip_ < 0 ) { istip_ = false; if( limit_ < 0 ) initialize(); if( samples() < 5 ) return istip_; int th = ( mean() < 2 && range() > 2 ) ? 2 : mean(); if( th < 2 ) ++th; int lth = data[0], rth = data[samples()-1]; int begin = 0, end = samples() - 1; const int tries = std::max( 2, ( samples() + 5 ) / 10 ); for( int i = 1; i < tries; ++i ) { if( data[i] < lth ) { lth = data[i]; begin = i; } if( data[samples()-1-i] < rth ) { rth = data[samples()-1-i]; end = samples() - 1 - i; } } if( lth >= th || rth >= th ) return istip_; if( 3 * lth >= 2 * range() || 3 * rth >= 2 * range() ) return istip_; th = std::max( lth, rth ); int status = 0; for( int i = begin + 1; i < end; ++i ) switch( status ) { case 0: if( data[i] > th + 1 ) status = 1; break; case 1: if( data[i] > th + 1 ) status = 2; else status = 0; break; case 2: if( data[i] <= th ) status = 3; break; case 3: if( data[i] > th + 1 ) return istip_; } istip_ = (bool)( status >= 2 ); } return istip_; } bool Profile::isctip( const int cpos ) { if( limit_ < 0 ) initialize(); if( samples() < 5 || cpos < 25 || cpos > 75 ) return false; const int mid = ( ( samples() - 1 ) * cpos ) / 100; const int iend = std::min( samples() / 4, std::min( mid, samples() - mid ) ); int th = std::max( 2, std::min( mean(), limit_ / 3 ) ); int imax = -1; for( int i = 0; i < iend; ++i ) { if( data[mid+i] > th ) { imax = mid + i; break; } if( data[mid-i-1] > th ) { imax = mid - i - 1; break; } } if( imax < 0 && mean() == 0 ) { --th; for( int i = 0; i < iend; ++i ) { if( data[mid+i] > th ) { imax = mid + i; break; } if( data[mid-i-1] > th ) { imax = mid - i - 1; break; } } } if( imax < 0 ) return false; th = std::max( th, data[imax] / 2 ); for( int i = imax + 1; i < samples(); ++i ) if( data[i] < th ) { for( int j = imax - 1; j >= 0; --j ) if( data[j] < th ) return true; break; } return false; } bool Profile::isltip() { if( limit_ < 0 ) initialize(); if( samples() < 5 ) return false; const int noise = ( samples() / 30 ) + 1; if( data[0] <= noise ) return false; const int dmin = min(); int begin = 0, ref = limit_; for( int i = 0; i < samples() - noise; ++i ) { int d = data[i]; if( d == dmin ) { begin = i; break; } if( d < ref ) ref = d; else if( d > ref + noise ) return false; } if( begin <= noise || 2 * begin > samples() ) return false; return true; } bool Profile::isrtip() { if( limit_ < 0 ) initialize(); if( samples() < 5 ) return false; const int noise = ( samples() / 30 ) + 1; if( data[samples()-1] <= noise ) return false; const int dmin = min(); int begin = 0, ref = limit_; for( int i = samples() - 1; i >= noise; --i ) { int d = data[i]; if( d == dmin ) { begin = samples() - 1 - i; break; } if( d < ref ) ref = d; else if( d > ref + noise ) return false; } if( begin <= noise || 2 * begin > samples() ) return false; return true; } int Profile::imaximum() { if( limit_ < 0 ) initialize(); const int margin = ( samples() / 30 ) + 1; int mbegin = 0, mend, mvalue = 0; for( int i = margin; i < samples() - margin; ++i ) if( data[i] > mvalue ) { mvalue = data[i]; mbegin = i; } for( mend = mbegin + 1; mend < samples(); ++mend ) if( data[mend] < mvalue ) break; return ( mbegin + mend - 1 ) / 2; } int Profile::iminimum( const int m, int th ) { if( limit_ < 0 ) initialize(); const int margin = ( samples() / 30 ) + 1; if( samples() < 2 * margin ) return 0; if( th < 2 ) th = ( mean() < 2 ) ? 2 : mean(); int minima = 0, status = 0; int begin = 0, end, value = limit_ + 1; for( end = margin; end < samples() - margin; ++end ) { if( status == 0 ) { if( data[end] < th ) { status = 1; ++minima; begin = end; } } else if( data[end] > th ) { if( minima == m + 1 ) { --end; break; } else status = 0; } } if( end >= samples() ) --end; if( minima != m + 1 ) return 0; for( int i = begin; i <= end; ++i ) if( data[i] < value ) { value = data[i]; begin = i; } for( ; end >= begin; --end ) if( data[end] == value ) break; return ( begin + end ) / 2; } int Profile::minima( int th ) { if( limit_ < 0 ) initialize(); if( !samples() ) return 0; if( th < 1 ) th = ( mean() < 2 ) ? 2 : mean(); const int noise = limit_ / 40; const int dth = th - ( ( noise + 1 ) / 2 ), uth = th + ( noise / 2 ); if( dth < 1 ) return 1; int minima = ( data[0] < dth ) ? 1 : 0; int status = ( minima ) ? 1 : 0; for( int i = 1; i < samples(); ++i ) switch( status ) { case 0: if( data[i] < dth ) { status = 1; ++minima; } break; case 1: if( data[i] > uth ) status = 0; break; } return minima; } bool Profile::straight( int * const dyp ) { if( limit_ < 0 ) initialize(); if( samples() < 5 ) return false; const int xl = ( samples() / 30 ) + 1, yl = ( data[xl] + data[xl+1] ) / 2 ; const int xr = samples() - xl - 1, yr = ( data[xr-1] + data[xr] ) / 2 ; const int dx = xr - xl, dy = yr - yl; if( dx <= 0 ) return false; const int dmax = dx * ( ( samples() / 20 ) + 2 ); int faults = samples() / 10; for( int i = 0; i < samples(); ++i ) { int y = ( dx * yl ) + ( ( i - xl ) * dy ); int d = std::abs( ( dx * data[i] ) - y ); if( d >= dmax && ( ( dx * data[i] ) < y || ( i >= xl && i <= xr ) ) ) if( d > dmax || ( d == dmax && --faults < 0 ) ) return false; } if( dyp ) *dyp = dy; return true; } ocrad-0.29/feats.cc0000644000175000017500000002540514551023774014057 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "profile.h" #include "feats.h" Features::Features( const Blob & b_ ) : b( b_ ), hbar_initialized( false ), vbar_initialized( false ), lp( b, Profile::left ), tp( b, Profile::top ), rp( b, Profile::right ), bp( b, Profile::bottom ), hp( b, Profile::height ), wp( b, Profile::width ) {} void Features::row_scan_init() const { int l = -1; // begin of segment. -1 means no segment row_scan.resize( b.height() ); for( int row = b.top(); row <= b.bottom(); ++row ) for( int col = b.left(); col <= b.right(); ++col ) { bool black = b.get_bit( row, col ); if( l < 0 && black ) l = col; // begin of segment if( l >= 0 && ( !black || col == b.right() ) ) // end of segment { row_scan[row-b.top()].push_back( Csegment( l, col - !black ) ); l = -1; } } } void Features::col_scan_init() const { int t = -1; // begin of segment. -1 means no segment col_scan.resize( b.width() ); for( int col = b.left(); col <= b.right(); ++col ) for( int row = b.top(); row <= b.bottom(); ++row ) { bool black = b.get_bit( row, col ); if( t < 0 && black ) t = row; // begin of segment if( t >= 0 && ( !black || row == b.bottom() ) ) // end of segment { col_scan[col-b.left()].push_back( Csegment( t, row - !black ) ); t = -1; } } } int Features::hbars() const { if( !hbar_initialized ) { hbar_initialized = true; if( row_scan.empty() ) row_scan_init(); std::vector< Csegment > segv; segv.reserve( b.height() ); for( unsigned i = 0; i < row_scan.size(); ++i ) { if( row_scan[i].size() == 1 ) { segv.push_back( row_scan[i][0] ); continue; } int maxsize = 0, jmax = -1; for( unsigned j = 0; j < row_scan[i].size(); ++j ) { const int size = row_scan[i][j].size(); if( maxsize < size ) { maxsize = size; jmax = j; } } if( jmax >= 0 ) segv.push_back( row_scan[i][jmax] ); else segv.push_back( Csegment() ); } const int limit = ( wp.max() + 1 ) / 2; int state = 0, begin = 0, l = 0, r = 0; for( int i = 0; i < b.height(); ++i ) { Csegment & seg = segv[i]; switch( state ) { case 0: if( seg.size() <= limit ) break; state = 1; begin = i; l = seg.left; r = seg.right; if( i < b.height() - 1 ) break; case 1: if( seg.size() > limit && ( i <= begin || seg.overlaps( segv[i-1] ) ) ) { if( seg.left < l ) l = seg.left; if( seg.right > r ) r = seg.right; if( i < b.height() - 1 ) break; } state = 0; int end = ( seg.size() <= limit ) ? i - 1 : i; const int width = r - l + 1; while( begin <= end && 3 * segv[begin].size() < 2 * width ) ++begin; while( begin <= end && 3 * segv[end].size() < 2 * width ) --end; const int height = end - begin + 1; if( height < 1 || height > width ) break; const int margin = std::max( height, ( b.height() / 10 ) + 1 ); if( begin >= margin ) { bool good = false; for( int j = margin; j > 0; --j ) if( 3 * segv[begin-j].size() <= 2 * width ) { good = true; break; } if( !good ) break; } if( end + margin < b.height() ) { bool good = false; for( int j = margin; j > 0; --j ) if( 3 * segv[end+j].size() <= 2 * width ) { good = true; break; } if( !good ) break; } hbar_.push_back( Rectangle( l, begin+b.top(), r, end+b.top() ) ); break; } } while( hbar_.size() > 3 ) // remove noise hbars { int wmin = hbar_[0].width(); for( unsigned i = 1; i < hbar_.size(); ++i ) if( hbar_[i].width() < wmin ) wmin = hbar_[i].width(); for( int i = hbar_.size() - 1; i >= 0; --i ) if( hbar_[i].width() == wmin ) hbar_.erase( hbar_.begin() + i ); } } return hbar_.size(); } int Features::vbars() const // FIXME small gaps not detected { if( !vbar_initialized ) { vbar_initialized = true; int state = 0, begin = 0, limit = b.height(); limit -= ( b.height() < 40 ) ? 3 : b.height() / 10; for( int col = b.left(); col <= b.right(); ++col ) { int c = 0, c2 = 0, count = 0; for( int row = b.top() + 1; row < b.bottom(); ++row ) { if( b.get_bit( row, col ) ) { ++c; if( row < b.bottom() - 1 ) continue; } else if( ( col > b.left() && b.get_bit( row, col - 1 ) ) || ( col < b.right() && b.get_bit( row, col + 1 ) ) ) { ++c; ++c2; if( row < b.bottom() - 1 ) continue; } if( c > count ) { count = c; } c = 0; } if( ( count - c2 ) * 3 < limit * 2 ) count = 0; switch( state ) { case 0: if( count >= limit ) { state = 3; begin = col; } else if( count * 4 >= limit * 3 ) { state = 2; begin = col; } else if( count * 3 >= limit * 2 ) { state = 1; begin = col; } break; case 1: if( count >= limit ) state = 3; else if( count * 4 >= limit * 3 ) state = 2; else if( count * 3 < limit * 2 ) state = 0; else begin = col; break; case 2: if( count >= limit ) state = 3; else if( count * 3 < limit * 2 ) state = 0; else if( count * 4 < limit * 3 ) state = 1; break; case 3: if( count * 3 < limit * 2 || col == b.right() ) { int end = ( count * 3 < limit * 2 ) ? col - 1 : col; vbar_.push_back( Rectangle( begin, b.top(), end, b.bottom() ) ); state = 0; } } } } return vbar_.size(); } Csegment Features::v_segment( const int row, const int col ) const { const int segments = segments_in_col( col ); for( int i = 0; i < segments; ++i ) if( col_scan[col-b.left()][i].includes( row ) ) return col_scan[col-b.left()][i]; return Csegment(); } int Features::test_misc( const Rectangle & charbox ) const { if( bp.minima() == 1 ) { if( hbars() == 1 && hbar(0).top() <= b.top() + ( b.height() / 10 ) && 4 * hbar(0).height() <= b.height() && 5 * hbar(0).width() >= 4 * b.width() && rp[hbar(0).bottom()-b.top()+2] - rp[hbar(0).bottom()-b.top()] < b.width() / 4 && rp.increasing( hbar(0).vcenter() - b.top() + 1 ) ) return '7'; if( b.height() > b.width() && rp.increasing() && !tp.decreasing() && b.seek_left( b.vcenter(), b.hcenter() ) <= b.left() ) return '7'; } if( tp.minima( b.height() / 4 ) == 1 && bp.minima( b.height() / 4 ) == 1 ) { if( b.height() > 2 * b.width() && rp.increasing() && tp.decreasing() && lp.iscpit( 25 ) ) return '1'; if( hbars() == 1 || ( hbars() == 2 && hbar(1).bottom() >= b.bottom() - 1 && 3 * hbar(0).width() > 4 * hbar(1).width() ) ) if( 3 * hbar(0).height() < b.height() && hbar(0).top() <= b.top() + 1 ) { int i = lp.pos( 40 ); if( 3 * wp[i] < b.width() && 5 * lp[i] > b.width() && 5 * rp[i] > b.width() ) return 'T'; } if( 3 * b.height() > 4 * b.width() && vbars() == 1 && vbar(0).width() >= 2 ) { const int lg = vbar(0).left() - b.left(); const int rg = b.right() - vbar(0).right(); if( 2 * lg < b.width() && 2 * rg < b.width() && Ocrad::similar( lg, rg, 40 ) && 4 * bp[bp.pos(25)] > 3 * b.height() && 4 * tp[tp.pos(75)] > 3 * b.height() ) return 'l'; } if( 5 * b.height() >= 4 * charbox.height() && b.height() > wp.max() && 3 * wp[wp.pos(50)] < b.width() ) { if( hbars() == 1 && hbar(0).bottom() >= b.bottom() - 1 && hbar(0).top() > b.vpos( 75 ) && Ocrad::similar( lp[lp.pos(50)], rp[rp.pos(50)], 20, 2 ) ) return 'l'; if( hbars() == 2 && hbar(0).bottom() < b.vpos( 25 ) && hbar(1).top() > b.vpos( 75 ) && hbar(1).bottom() >= b.bottom() - 1 /*&& 3 * hbar(0).width() < 4 * hbar(1).width()*/ ) { if( hbar(0).right() <= hbar(1).hcenter() ) return 0; if( 3 * hbar(0).width() <= 2 * hbar(1).width() || b.height() >= 3 * wp.max() ) return 'l'; return 'I'; } } if( ( hbars() == 2 || hbars() == 3 ) && hbar(0).top() <= b.top() + 1 && hbar(1).includes_vcenter( b ) && 3 * hbar(0).width() > 4 * hbar(1).width() && ( hbars() == 2 || ( hbar(2).bottom() >= b.bottom() - 1 && 3 * hbar(0).width() > 4 * hbar(2).width() ) ) ) return 'F'; if( b.height() > 3 * wp.max() ) { if( rp.istip() && lp.ispit() ) { if( lp.istpit() ) return '{'; else return '('; } if( lp.istip() && rp.ispit() ) { if( rp.istpit() ) return '}'; else return ')'; } if( b.width() > 2 * wp.max() && rp.isconvex() ) return ')'; } if( b.height() > 2 * b.width() && 5 * b.height() >= 4 * charbox.height() && lp.max() + rp.max() < b.width() ) { if( 5 * rp[rp.pos(50)] > 2 * b.width() ) { const int row = b.seek_top( b.vpos( 75 ), b.hpos( 75 ) ); if( ( b.top() < charbox.top() || b.bottom() <= charbox.bottom() + ( b.height() / 5 ) ) && row <= b.top() ) return 'L'; if( row > b.top() && b.seek_bottom( b.vpos( 75 ), b.hpos( 75 ) ) < b.bottom() ) return '['; } return '|'; } } return 0; } ocrad-0.29/textline_r2.cc0000644000175000017500000007406214546517243015222 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "track.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "textline.h" // All the code in this file is provisional and will be rewritten someday. namespace { int find_space_or_hyphen( const std::vector< Character * > & cpv, unsigned i ) { while( i < cpv.size() && !cpv[i]->maybe(' ') && !cpv[i]->maybe('-') ) ++i; return i; } } // end namespace // transform some small letters to capitals void Textline::check_lower_ambiguous() { int begin = big_initials(); bool isolated = false; // isolated letters compare with all line for( int i = big_initials(); i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { if( i + 2 < characters() && character( i + 2 ).maybe(' ') ) { begin = big_initials(); isolated = true; } else { begin = i + 1; isolated = false; } continue; } if( c1.guesses() == 1 ) { const int code = c1.guess( 0 ).code; if( !UCS::islower_small_ambiguous( code ) ) continue; if( 5 * c1.height() < 4 * mean_height() ) continue; bool capital = ( 4 * c1.height() > 5 * mean_height() ); bool small = false; for( int j = begin; j < characters(); ++j ) if( j != i ) { const Character & c2 = character( j ); if( !c2.guesses() ) continue; if( c2.maybe(' ') ) { if( isolated ) continue; else break; } const int code2 = c2.guess( 0 ).code; if( code2 >= 128 || !std::isalpha( code2 ) ) continue; if( !capital ) { if( 4 * c1.height() > 5 * c2.height() ) capital = true; else if( std::isupper( code2 ) && code2 != 'B' && code2 != 'Q' && ( c1.height() >= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) capital = true; else if( code2 == 't' && c1.height() >= c2.height() ) capital = true; } if( !small && std::islower( code2 ) && code2 != 'l' && code2 != 'j' ) { if( 5 * c1.height() < 4 * c2.height() ) small = true; else if( UCS::islower_small( code2 ) && code2 != 'r' && !c2.maybe('Q') && ( j < i || !UCS::islower_small_ambiguous( code2 ) ) && Ocrad::similar( c1.height(), c2.height(), 10 ) ) small = true; } } if( capital && !small ) c1.insert_guess( 0, std::toupper( code ), 1 ); } } } void Textline::recognize2( const Charset & charset ) { if( big_initials() >= characters() ) return; // try to recognize separately the 3 overlapped blobs of an // unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 3 ) { const Blob & b1 = c.blob( 0 ); const Blob & b2 = c.blob( 1 ); const Blob & b3 = c.blob( 2 ); // lower blob if( Ocrad::similar( b2.height(), b3.height(), 20 ) && !b2.h_overlaps( b3 ) && b2.v_includes( b3.vcenter() ) && b3.v_includes( b2.vcenter() ) && b1.bottom() < b2.top() && b1.bottom() < b3.top() ) { if( b1.height() > b2.height() && b1.height() > b3.height() ) { Character c1( new Blob( b1 ) ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) c = c1; } else { Character c2( new Blob( b2 ) ); Character c3( new Blob( b3 ) ); if( b2.h_includes( b1.hcenter() ) ) c2.shift_blobp( new Blob( b1 ) ); else if( b3.h_includes( b1.hcenter() ) ) c3.shift_blobp( new Blob( b1 ) ); c2.recognize1( charset, charbox( c2 ) ); c3.recognize1( charset, charbox( c3 ) ); if( c2.guesses() && c3.guesses() ) { c = c2; shift_characterp( new Character( c3 ) ); ++i; } } } } } // try to recognize separately the 2 overlapped blobs of an // unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 2 && c.blob( 0 ).v_overlaps( c.blob( 1 ) ) ) { Character c1( new Blob( c.blob( 0 ) ) ); c1.recognize1( charset, charbox( c1 ) ); Character c2( new Blob( c.blob( 1 ) ) ); c2.recognize1( charset, charbox( c2 ) ); if( ( c1.guesses() && c2.guesses() ) || Ocrad::similar( c1.height(), c2.height(), 20 ) ) { if( c1.height() > c2.height() ) c = c1; else { c = c2; c2 = c1; } // discards spurious dots if( !c2.maybe('.') || c2.top() > c.vcenter() ) { shift_characterp( new Character( c2 ) ); ++i; } } } } // remove speckles under the charbox' bottom of an unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 2 && c.blob( 0 ).size() > 10 * c.blob( 1 ).size() && c.blob( 1 ).top() > charbox( c ).bottom() ) { Character c1( new Blob( c.blob( 0 ) ) ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) c = c1; } } // remove speckles above the charbox' top of an unrecognized character for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 2 && c.blob( 1 ).size() > 5 * c.blob( 0 ).size() && c.blob( 0 ).bottom() + 2 * c.blob( 0 ).height() < charbox( c ).top() ) { Character c1( new Blob( c.blob( 1 ) ) ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) c = c1; } } // try to separate lightly merged characters // FIXME try relative minima (small pixel count surrounded by larger counts) // FIXME try all possible separation points // FIXME try other separation paths (an irregular line, not a column) // FIXME sometimes leaves unconnected blobs for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.width() > 20 && 5 * c.width() >= 3 * c.height() && 5 * c.height() >= 3 * mean_height() ) { int ib = c.blobs() - 1; for( int k = ib - 1; k >= 0; --k ) if( c.blob( k ).width() > c.blob( ib ).width() ) ib = k; if( ib < 0 || 10 * c.blob( ib ).width() < 9 * c.width() || c.blob( ib ).bottom() < c.bottom() ) continue; const Blob & b = c.blob( ib ); // widest blob int colmin = 0, cmin = b.height() + 1; for( int col = b.hpos( 30 ); col <= b.hpos( 70 ); ++col ) { int c = 0; for( int row = b.top(); row <= b.bottom(); ++row ) if( b.id( row, col ) ) ++c; if( c < cmin || ( c == cmin && col <= b.hcenter() ) ) { cmin = c; colmin = col; } } if( 4 * cmin > b.height() || ( 5 * cmin > b.height() && ( colmin <= b.hpos( 40 ) || colmin >= b.hpos( 60 ) ) ) ) continue; if( colmin <= b.left() || colmin >= b.right() ) continue; Rectangle r1( b.left(), b.top(), colmin - 1, b.bottom() ); Rectangle r2( colmin + 1, b.top(), b.right(), b.bottom() ); Blob b1( b, r1 ); b1.adjust_height(); if( 2 * b1.height() < b.height() ) continue; Blob b2( b, r2 ); b2.adjust_height(); if( 2 * b2.height() < b.height() ) continue; b1.find_holes(); b2.find_holes(); Character c1( new Blob( b1 ) ); Character c2( new Blob( b2 ) ); for( int j = 0; j < c.blobs(); ++j ) if( j != ib ) { const Blob & bj = c.blob( j ); if( c1.includes_hcenter( bj ) ) c1.shift_blobp( new Blob( bj ) ); else if( c2.includes_hcenter( bj ) ) c2.shift_blobp( new Blob( bj ) ); } c1.recognize1( charset, charbox( c1 ) ); c2.recognize1( charset, charbox( c2 ) ); const bool good_c2 = ( c2.guesses() && c2.guess( 0 ).code != '\'' ); if( ( c1.guesses() && good_c2 ) || ( ( c1.guesses() || good_c2 ) && c.width() > c.height() ) ) { c = c1; shift_characterp( new Character( c2 ) ); if( !c1.guesses() ) --i; else if( c2.guesses() ) ++i; } } } // try to recognize 1 blob unrecognized characters with holes by // removing small holes (noise) for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() && c.blobs() == 1 && c.blob( 0 ).holes() ) { Character c1( c ); Blob & b = c1.blob( 0 ); for( int j = b.holes() - 1; j >= 0; --j ) if( 64 * b.hole( j ).size() <= b.size() || 16 * b.hole( j ).height() <= b.height() ) b.fill_hole( j ); if( b.holes() < c.blob( 0 ).holes() ) { c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) { c = c1; continue; } } /* if( b.holes() == 1 && 25 * b.hole( 0 ).size() < b.size() && Ocrad::similar( b.height(), b.width(), 40 ) ) { b.fill_hole( 0 ); c1.recognize1( charset, charbox( c1 ) ); if( c1.guesses() ) { c = c1; continue; } }*/ } } // separate merged characters recognized by recognize1 for( int i = big_initials(); i < characters(); ) { if( !cpv[i]->guesses() || cpv[i]->guess( 0 ).code >= 0 ) { ++i; continue; } const Character c( *cpv[i] ); const int blob_index = -(c.guess( 0 ).code + 1); delete_character( i ); if( c.guesses() >= 3 && c.blobs() >= 1 && blob_index < c.blobs() ) { int left = c.guess( 0 ).value; for( int g = 1; g < c.guesses(); ++g ) { Blob b( c.blob( blob_index ) ); Rectangle re( left, b.top(), c.guess( g ).value, b.bottom() ); b.add_rectangle( re ); Blob b1( b, re ); b1.adjust_height(); b1.adjust_width(); b1.find_holes(); Character c1( new Blob( b1 ) ); for( int k = 0; k < c.blobs(); ++k ) if( k != blob_index && !c.blob( k ).includes( re ) && re.includes_hcenter( c.blob( k ) ) ) c1.shift_blobp( new Blob( c.blob( k ) ) ); c1.add_guess( c.guess( g ).code, 0 ); shift_characterp( new Character( c1 ) ); left = re.right() + 1; } } } // choose between 'B' and 'a' for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() ) { int code = c1.guess( 0 ).code; if( c1.guesses() != 2 || code != 'B' || c1.guess( 1 ).code != 'a' ) continue; if( 4 * c1.height() > 5 * mean_height() ) continue; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( c2.guesses() >= 1 ) { int code2 = c2.guess( 0 ).code; if( code2 >= 128 ) continue; if( ( std::isupper( code2 ) && code2 != 'B' && code2 != 'Q' && 5 * c1.height() < 4 * c2.height() ) || ( UCS::islower_small( code2 ) && code2 != 'r' && !UCS::islower_small_ambiguous( code2 ) && ( c1.height() <= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) ) { c1.swap_guesses( 0, 1 ); break; } } } } } // choose between '8' and 'a' or 'e' for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() == 2 && c1.guess( 1 ).code == '8' ) { int code = c1.guess( 0 ).code; if( ( code != 'a' && code != 'e' ) || 5 * c1.height() < 4 * mean_height() ) continue; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( c2.guesses() >= 1 ) { int code2 = c2.guess( 0 ).code; if( code2 >= 128 ) continue; if( ( ( std::isalpha( code2 ) || code2 == ':' ) && 4 * c1.height() > 5 * c2.height() ) || ( ( std::isdigit( code2 ) || std::isupper( code2 ) || code2 == 'l' ) && ( c1.height() >= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) ) { c1.swap_guesses( 0, 1 ); break; } } } } } check_lower_ambiguous(); // transform 'i' into 'j' for( int i = big_initials(); i < characters(); ++i ) { Character & c1 = character( i ); if( c1.guesses() == 1 && c1.guess( 0 ).code == 'i' ) { int j = i + 1; if( j >= characters() || !character( j ).guesses() ) { j = i - 1; if( j < big_initials() || !character( j ).guesses() ) continue; } Character & c2 = character( j ); if( UCS::isvowel( c2.guess( 0 ).code ) && c1.bottom() >= c2.bottom() + ( c2.height() / 4 ) ) c1.insert_guess( 0, 'j', 1 ); } } // transform small o or u with accent or diaeresis to capital // transform small s or z with caron to capital { int begin = big_initials(); bool isolated = false; // isolated letters compare with all line for( int i = big_initials(); i < characters(); ++i ) { Character & c1 = character( i ); if( c1.guesses() >= 1 ) { if( c1.maybe(' ') ) { if( i + 2 < characters() && character( i + 2 ).maybe(' ') ) { begin = big_initials(); isolated = true; } else { begin = i + 1; isolated = false; } continue; } int code = c1.guess( 0 ).code; if( code < 128 || c1.blobs() < 2 ) continue; int codeb = UCS::base_letter( code ); if( codeb != 'o' && codeb != 'u' && codeb != 's' && codeb != 'z' ) continue; const Blob & b1 = c1.blob( c1.blobs() - 1 ); // lower blob for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.guesses() >= 1 ) { if( c2.maybe(' ') ) { if( isolated ) continue; else break; } int code2 = c2.guess( 0 ).code; int code2b = UCS::base_letter( code2 ); if( !code2b && code2 >= 128 ) continue; if( ( std::isalpha( code2 ) && 4 * b1.height() > 5 * c2.height() ) || ( std::isupper( code2 ) && Ocrad::similar( b1.height(), c2.height(), 10 ) ) || ( std::isalpha( code2b ) && 4 * c1.height() > 5 * c2.height() ) || ( std::isupper( code2b ) && Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) { c1.insert_guess( 0, UCS::toupper( code ), 1 ); break; } } } } } } // transform 'O' or 'l' into '0' or '1' for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() >= 1 ) { int code = c1.guess( 0 ).code; if( code != 'o' && code != 'O' && code != 'l' ) continue; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( c2.guesses() >= 1 ) { int code2 = c2.guess( 0 ).code; if( UCS::isdigit( code2 ) ) { if( Ocrad::similar( c1.height(), c2.height(), 10 ) ) c1.insert_guess( 0, (code == 'l') ? '1' : '0', c1.guess( 0 ).value + 1 ); break; } if( UCS::isalpha( code2 ) && code2 != 'o' && code2 != 'O' && code2 != 'l' ) break; } } } } // transform a small 'p' to a capital 'P' for( int i = characters() - 1; i >= big_initials(); --i ) { Character & c1 = character( i ); if( c1.guesses() == 1 && c1.guess( 0 ).code == 'p' ) { const int noise = std::max( 2, c1.height() / 20 ); bool cap = false, valid_c2 = false; if( i < characters() - 1 && character(i+1).guesses() ) { Character & c2 = character( i + 1 ); int code = c2.guess( 0 ).code; if( UCS::isalnum( code ) || code == '.' || code == '|' ) { valid_c2 = true; switch( code ) { case 'g': case 'j': case 'p': case 'q': case 'y': cap = ( c1.bottom() + noise <= c2.bottom() ); break; case 'Q': cap = ( std::abs( c1.top() - c2.top() ) <= noise ); break; default: cap = ( std::abs( c1.bottom() - c2.bottom() ) <= noise ); } } } if( !valid_c2 && i > big_initials() && !character(i-1).maybe(' ') ) cap = ( std::abs( c1.bottom() - charbox(c1).bottom() ) <= noise ); if( cap ) c1.only_guess( 'P', 0 ); } } // transform a capital 'Y' to a small 'y' for( int i = characters() - 1; i > big_initials(); --i ) { Character & c1 = character( i - 1 ); if( c1.guesses() == 1 && c1.guess( 0 ).code == 'Y' ) { Character & c2 = character( i ); if( !c2.guesses() ) continue; int code = c2.guess( 0 ).code; if( UCS::isalnum( code ) || code == '.' || code == '|' ) { switch( code ) { case 'g': case 'j': case 'p': case 'q': case 'y': if( c1.bottom() < c2.bottom() - 2 ) continue; break; case 'Q': if( c1.top() < c2.top() + 2 ) continue; break; default: if( c1.bottom() < c2.bottom() + 2 ) continue; } c1.only_guess( 'y', 0 ); } } } // transform a SSCEDI to a CSCEDI if( charset.enabled( Charset::iso_8859_9 ) ) for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( c.guesses() == 1 && c.guess( 0 ).code == UCS::SSCEDI ) { if( i > big_initials() && character( i - 1 ).guesses() ) { Character & c1 = character( i - 1 ); int code = c1.guess( 0 ).code; if( ( UCS::islower( code ) && c.top() < c1.top() - 2 ) || ( UCS::base_letter( code ) && code != UCS::SINODOT && Ocrad::similar( c.top(), c1.top(), 10 ) ) ) { c.insert_guess( 0, UCS::CSCEDI, 1 ); continue; } } if( i < characters() - 1 && character( i + 1 ).guesses() ) { Character & c1 = character( i + 1 ); int code = c1.guess( 0 ).code; if( ( UCS::islower( code ) && c.top() < c1.top() - 2 ) || ( UCS::base_letter( code ) && code != UCS::SINODOT && Ocrad::similar( c.top(), c1.top(), 10 ) ) ) { c.insert_guess( 0, UCS::CSCEDI, 1 ); continue; } } } } // transform words like 'lO.OOO' into numbers like '10.000' for( int begin = big_initials(), end = begin; begin < characters(); begin = end + 1 ) { end = find_space_or_hyphen( cpv, begin ); if( end - begin < 2 ) continue; Character & c1 = character( begin ); if( !c1.guesses() ) continue; const int height = c1.height(); const int code1 = c1.guess( 0 ).code; if( UCS::isdigit( code1 ) || code1 == 'l' || code1 == 'O' || code1 == 'o' ) { int digits = 1; int i = begin + 1; for( ; i < end; ++i ) { Character & c = character( i ); if( !c.guesses() ) break; bool valid = false; int code = c.guess( 0 ).code; if( ( UCS::isdigit( code ) || code == 'l' || code == 'O' || code == 'o' ) && Ocrad::similar( c.height(), height, 10 ) ) { valid = true; ++digits; } if( code == '.' || code == ',' || code == ':' || code == '+' || code == '-' ) valid = true; if( !valid ) break; } if( i >= end && digits >= 2 ) for( i = begin; i < end; ++i ) { Character & c = character( i ); int code = c.guess( 0 ).code; if( code == 'l' ) code = '1'; else if( code == 'O' || code == 'o' ) code = '0'; else code = 0; if( code ) c.insert_guess( 0, code, c.guess( 0 ).value + 1 ); } } } // detects Roman numerals 'II', 'III', and 'IIII' for( int begin = big_initials(), end = begin; begin < characters(); begin = end + 1 ) { end = find_space_or_hyphen( cpv, begin ); if( end - begin < 2 || end - begin > 4 ) continue; const int height = character( begin ).height(); int i; for( i = begin; i < end; ++i ) { Character & c = character( i ); if( !c.maybe('|') || !Ocrad::similar( c.height(), height, 10 ) ) break; } if( i >= end ) for( i = begin; i < end; ++i ) { Character & c = character( i ); if( !character(i).maybe('I') ) c.insert_guess( 0, 'I', c.guess( 0 ).value + 1 ); } } // choose between 'a' and 'Q' for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( c.guesses() == 2 && c.guess( 0 ).code == 'a' && c.guess( 1 ).code == 'Q' ) { if( 4 * c.height() > 5 * mean_height() ) { c.swap_guesses( 0, 1 ); check_lower_ambiguous(); continue; } if( i < characters() - 1 && character( i + 1 ).guesses() ) { const int code = character( i + 1 ).guess( 0 ).code; if( ( UCS::ishigh( code ) ) && 10 * c.height() > 9 * character( i + 1 ).height() ) { c.swap_guesses( 0, 1 ); check_lower_ambiguous(); continue; } } if( i > big_initials() && character( i - 1 ).guesses() ) { const int code = character( i - 1 ).guess( 0 ).code; if( ( UCS::ishigh( code ) ) && 10 * c.height() > 9 * character( i - 1 ).height() ) { c.swap_guesses( 0, 1 ); check_lower_ambiguous(); } } } } // transform a vertical bar into 'l' or 'I' (or a 'l' into an 'I') for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( c.guesses() != 1 ) continue; int code = c.guess( 0 ).code; if( code == '|' || code == 'l' ) { int lcode = 0, rcode = 0; if( i > 0 && character( i - 1 ).guesses() ) lcode = character( i - 1 ).guess( 0 ).code; if( i < characters() - 1 && character( i + 1 ).guesses() ) rcode = character( i + 1 ).guess( 0 ).code; if( ( UCS::isupper( rcode ) || UCS::isdigit( rcode ) ) && ( !lcode || UCS::isupper( lcode ) || !UCS::isalnum( lcode ) ) ) { c.insert_guess( 0, 'I', 1 ); continue; } if( code == 'l' ) continue; if( UCS::isalpha( lcode ) || UCS::isalpha( rcode ) ) { c.insert_guess( 0, 'l', 1 ); continue; } if( rcode == '|' && ( !lcode || !UCS::isalnum( lcode ) ) ) { if( i < characters() - 2 && character( i + 2 ).guesses() && UCS::isalpha( character( i + 2 ).guess( 0 ).code ) ) { c.insert_guess( 0, 'l', 1 ); continue; } if( i >= 2 && character( i - 2 ).guesses() && UCS::isalpha( character( i - 2 ).guess( 0 ).code ) ) { c.insert_guess( 0, 'l', 1 ); continue; } } } } // transform a vertical bar into 'I' at end of word for( int begin = big_initials(), end = begin; begin < characters(); begin = end + 1 ) { end = find_space_or_hyphen( cpv, begin ); if( end - begin < 3 ) continue; Character & ce = character( end - 1 ); if( !ce.maybe('|') || ce.maybe('I') ) continue; const int height = ce.height(); int i; for( i = begin; i < end - 1; ++i ) { const Character & c = character( i ); if( !c.guesses() ) break; const int code = c.guess( 0 ).code; if( ( !UCS::isupper( code ) && !UCS::isdigit( code ) ) || !Ocrad::similar( c.height(), height, 10 ) ) break; } if( i >= end - 1 ) ce.insert_guess( 0, 'I', ce.guess( 0 ).value + 1 ); } // transform 'l' or '|' into UCS::SINODOT if( charset.enabled( Charset::iso_8859_9 ) ) for( int i = big_initials(), begin = i; i < characters(); ++i ) { Character & c1 = character( i ); if( c1.maybe(' ') ) { begin = i + 1 ; continue; } if( c1.guesses() ) { int code = c1.guess( 0 ).code; if( code != 'l' && code != '|' ) continue; if( 4 * c1.height() > 5 * mean_height() ) continue; if( 5 * c1.height() < 4 * mean_height() ) { c1.only_guess( UCS::SINODOT, 0 ); continue; } bool capital = false, small = false; for( int j = begin; j < characters(); ++j ) if( j != i ) { Character & c2 = character( j ); if( c2.maybe(' ') ) break; if( !c2.guesses() ) continue; int code2 = c2.guess( 0 ).code; if( code2 >= 128 || !std::isalpha( code2 ) ) continue; if( !capital ) { if( 4 * c1.height() > 5 * c2.height() ) capital = true; else if( std::isupper( code2 ) && code2 != 'B' && code2 != 'Q' && ( c1.height() >= c2.height() || Ocrad::similar( c1.height(), c2.height(), 10 ) ) ) capital = true; } if( !small && std::islower( code2 ) && code2 != 'l' ) { if( 5 * c1.height() < 4 * c2.height() ) small = true; else if( UCS::islower_small( code2 ) && ( j < i || !UCS::islower_small_ambiguous( code2 ) ) && Ocrad::similar( c1.height(), c2.height(), 10 ) ) small = true; } } if( !capital && small ) c1.insert_guess( 0, UCS::SINODOT, 1 ); } } // join two adjacent single quotes into a double quote for( int i = big_initials(); i < characters() - 1; ++i ) { Character & c1 = character( i ); Character & c2 = character( i + 1 ); if( c1.guesses() == 1 && c2.guesses() == 1 ) { int code1 = c1.guess( 0 ).code; int code2 = c2.guess( 0 ).code; if( ( code1 == '\'' || code1 == '`' ) && code1 == code2 && 2 * ( c2.left() - c1.right() ) < 3 * c1.width() ) { c1.join( c2 ); c1.only_guess( '"', 0 ); delete_character( i + 1 ); } } } // join a comma followed by a period into a semicolon for( int i = big_initials(); i < characters() - 1; ++i ) { Character & c1 = character( i ); Character & c2 = character( i + 1 ); if( c1.guesses() == 1 && c2.guesses() == 1 ) { int code1 = c1.guess( 0 ).code; int code2 = c2.guess( 0 ).code; if( code1 == ',' && code2 == '.' && c1.top() > c2.bottom() && c2.left() - c1.right() < c2.width() ) { c1.join( c2 ); c1.only_guess( ';', 0 ); delete_character( i + 1 ); } } } // choose between '.' and '-' if( characters() >= 2 ) { Character & c = character( characters() - 1 ); if( c.guesses() >= 2 && c.guess( 0 ).code == '.' && c.guess( 1 ).code == '-' ) { const Character & lc = character( characters() - 2 ); if( lc.guesses() && UCS::isalpha( lc.guess( 0 ).code ) ) c.swap_guesses( 0, 1 ); } } // join a 'n' followed by a 'I' into a 'm' for( int i = big_initials(); i < characters() - 1; ++i ) { Character & c1 = character( i ); Character & c2 = character( i + 1 ); if( c1.guesses() == 1 && c2.guesses() == 1 ) { int code1 = c1.guess( 0 ).code; int code2 = c2.guess( 0 ).code; if( code1 == 'n' && ( code2 == 'I' || code2 == 'l' ) && Ocrad::similar( c1.height(), c2.height(), 10 ) && c2.left() - c1.right() < c2.width() ) { c1.join( c2 ); c1.only_guess( 'm', 0 ); delete_character( i + 1 ); } } } // separate merged 'VV' { int mean_upper_width = 0; for( int i = big_initials(); i < characters(); ++i ) { Character & c = character( i ); if( !c.guesses() || c.guess( 0 ).code != 'W' || c.width() <= c.height() || c.blobs() != 1 || c.blob( 0 ).holes() ) continue; if( mean_upper_width == 0 ) { int count = 0; for( int j = big_initials(); j < characters(); ++j ) { const Character & cj = character( j ); if( cj.guesses() && UCS::isupper_normal_width( cj.guess( 0 ).code ) ) { mean_upper_width += cj.width(); ++count; } } if( count <= 0 ) break; // no characters to compare mean_upper_width /= count; } if( c.width() < 2 * mean_upper_width ) continue; const Blob & b = c.blob( 0 ); int row = b.bottom(); while( row >= b.top() && b.id( row, b.hcenter() ) == 0 ) --row; if( row >= b.vpos( 20 ) ) continue; Rectangle r1( b.left(), b.top(), b.hcenter() - 1, b.bottom() ); Rectangle r2( b.hcenter() + 1, b.top(), b.right(), b.bottom() ); Blob b1( b, r1 ); Blob b2( b, r2 ); b1.adjust_height(); b2.adjust_height(); if( 2 * b1.height() < b.height() || 2 * b2.height() < b.height() || !Ocrad::similar( b1.height(), b2.height(), 10, 2 ) ) continue; Character c1( new Blob( b1 ) ); Character c2( new Blob( b2 ) ); c1.only_guess( 'V', 0 ); c2.only_guess( 'V', 0 ); c = c1; ++i; cpv.insert( cpv.begin() + i, new Character( c2 ) ); } } // join the secuence '°', '/', 'o', ' ' into a '%' for( int i = big_initials(); i + 2 < characters(); ++i ) { Character & c1 = character( i ); if( c1.guesses() == 1 && c1.guess( 0 ).code == UCS::DEG ) { if( character( i + 1 ).maybe('/') && character( i + 2 ).maybe('o') && ( i + 3 >= characters() || character( i + 3 ).maybe(' ') ) ) { c1.join( character( i + 1 ) ); c1.join( character( i + 2 ) ); delete_character( i + 2 ); delete_character( i + 1 ); c1.only_guess( '%', 0 ); } } } } ocrad-0.29/INSTALL0000644000175000017500000000421614545576754013512 0ustar andriusandriusRequirements ------------ You will need a C++98 compiler with support for 'long long'. (gcc 3.3.6 or newer is recommended). I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards compliant compiler. Gcc is available at http://gcc.gnu.org. The PNG Reference Library ("libpng") version 1.2.9 or newer must be installed. Libpng is available at http://www.libpng.org. Procedure --------- 1. Unpack the archive if you have not done so already: tar -xf ocrad[version].tar.lz or lzip -cd ocrad[version].tar.lz | tar -xf - This creates the directory ./ocrad[version] containing the source code extracted from the archive. 2. Change to ocrad directory and run configure. (Try 'configure --help' for usage instructions). cd ocrad[version] ./configure 3. Run make. make 4. Optionally, type 'make check' to run the tests that come with ocrad. 5. Type 'make install' to install the program, the library, and any data files and documentation. You need root privileges to install into a prefix owned by root. Or type 'make install-compress', which additionally compresses the info manual and the man page after installation. (Installing compressed docs may become the default in the future). You can install only the program, the info manual, or the man page by typing 'make install-bin', 'make install-info', or 'make install-man' respectively. Another way ----------- You can also compile ocrad into a separate directory. To do this, you must use a version of 'make' that supports the variable 'VPATH', such as GNU 'make'. 'cd' to the directory where you want the object files and executables to go and run the 'configure' script. 'configure' automatically checks for the source code in '.', in '..', and in the directory that 'configure' is in. 'configure' recognizes the option '--srcdir=DIR' to control where to look for the source code. Usually 'configure' can determine that directory automatically. After running 'configure', you can run 'make' and 'make install' as explained above. Copyright (C) 2003-2024 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. ocrad-0.29/rational.h0000644000175000017500000001524514544652010014422 0ustar andriusandrius/* Rational - Rational number class with overflow detection Copyright (C) 2005-2024 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ /* Rationals are kept normalized at all times. Invariant = ( gcd( num, den ) == 1 && den > 0 ). Range extends from INT_MAX to -INT_MAX. Maximum resolution is 1 / INT_MAX. In case of domain error or overflow, den is set to 0 and num is set to >0, <0, or 0, meaning +INF, -INF, and NAN respectively. This error condition can be tested with the function 'error', and can only be cleared by assigning a new value to the Rational. While in error state, arithmetic operators become no-ops and relational operators return false, except !=, which returns true. */ class Rational { int num, den; void normalize( long long n, long long d ); void normalize(); public: Rational( const int n, const int d ) : num( n ), den( d ) // n / d { normalize(); } explicit Rational( const int n ) : num( n ), den( 1 ) // n / 1 { if( num < -INT_MAX ) { num = -INT_MAX; den = 0; } } Rational() : num( 0 ), den( 1 ) {} // zero Rational & assign( const int n, const int d ) { num = n; den = d; normalize(); return *this; } Rational & operator=( const int n ) { num = n; den = 1; if( num < -INT_MAX ) { num = -INT_MAX; den = 0; } return *this; } int numerator() const { return num; } int denominator() const { return den; } int sign() const { return ( num > 0 ) - ( num < 0 ); } bool error() const { return ( den <= 0 ); } // true if in error state const Rational & operator+() const { return *this; } // unary plus const Rational & operator+() { return *this; } // unary plus Rational operator-() const // unary minus { Rational tmp( *this ); tmp.num = -tmp.num; return tmp; } Rational abs() const { if( num >= 0 ) return *this; else return -*this; } Rational inverse() const; Rational & operator+=( const Rational & r ); Rational & operator-=( const Rational & r ) { return operator+=( -r ); } Rational & operator*=( const Rational & r ); Rational & operator/=( const Rational & r ) { return operator*=( r.inverse() ); } Rational & operator+=( const int n ) { return operator+=( Rational( n ) ); } Rational & operator-=( const int n ) { return operator-=( Rational( n ) ); } Rational & operator*=( const int n ) { return operator*=( Rational( n ) ); } Rational & operator/=( const int n ) { return operator/=( Rational( n ) ); } Rational operator+( const Rational & r ) const { Rational tmp( *this ); return tmp += r; } Rational operator-( const Rational & r ) const { Rational tmp( *this ); return tmp -= r; } Rational operator*( const Rational & r ) const { Rational tmp( *this ); return tmp *= r; } Rational operator/( const Rational & r ) const { Rational tmp( *this ); return tmp /= r; } Rational operator+( const int n ) const { Rational tmp( *this ); return tmp += n; } Rational operator-( const int n ) const { Rational tmp( *this ); return tmp -= n; } Rational operator*( const int n ) const { Rational tmp( *this ); return tmp *= n; } Rational operator/( const int n ) const { Rational tmp( *this ); return tmp /= n; } Rational & operator++() { return operator+=( 1 ); } // prefix Rational operator++( int ) // suffix { Rational tmp( *this ); operator+=( 1 ); return tmp; } Rational & operator--() { return operator-=( 1 ); } // prefix Rational operator--( int ) // suffix { Rational tmp( *this ); operator-=( 1 ); return tmp; } bool operator==( const Rational & r ) const { return ( den > 0 && num == r.num && den == r.den ); } bool operator==( const int n ) const { return ( num == n && den == 1 ); } bool operator!=( const Rational & r ) const { return ( den <= 0 || r.den <= 0 || num != r.num || den != r.den ); } bool operator!=( const int n ) const { return ( num != n || den != 1 ); } bool operator< ( const Rational & r ) const { return ( den > 0 && r.den > 0 && (long long)num * r.den < (long long)r.num * den ); } bool operator<=( const Rational & r ) const { return ( den > 0 && r.den > 0 && (long long)num * r.den <= (long long)r.num * den ); } bool operator> ( const Rational & r ) const { return ( den > 0 && r.den > 0 && (long long)num * r.den > (long long)r.num * den ); } bool operator>=( const Rational & r ) const { return ( den > 0 && r.den > 0 && (long long)num * r.den >= (long long)r.num * den ); } bool operator< ( const int n ) const { return operator< ( Rational( n ) ); } bool operator<=( const int n ) const { return operator<=( Rational( n ) ); } bool operator> ( const int n ) const { return operator> ( Rational( n ) ); } bool operator>=( const int n ) const { return operator>=( Rational( n ) ); } int round() const; // nearest integer; -1.5 ==> -2, 1.5 ==> 2 int trunc() const // integer part; -x.y ==> -x, x.y ==> x { if( den > 0 ) return ( num / den ); else return num; } int parse( const char * const s ); // returns parsed size std::string to_decimal( const unsigned iwidth = 1, int prec = -2, const bool rounding = false ) const; std::string to_fraction( const unsigned width = 1 ) const; }; inline Rational operator+( const int n, const Rational & r ) { return r + n; } inline Rational operator-( const int n, const Rational & r ) { return -r + n; } inline Rational operator*( const int n, const Rational & r ) { return r * n; } inline Rational operator/( const int n, const Rational & r ) { return Rational( n ) / r; } inline bool operator==( const int n, const Rational & r ) { return r == n; } inline bool operator!=( const int n, const Rational & r ) { return r != n; } inline bool operator< ( const int n, const Rational & r ) { return r > n; } inline bool operator<=( const int n, const Rational & r ) { return r >= n; } inline bool operator> ( const int n, const Rational & r ) { return r < n; } inline bool operator>=( const int n, const Rational & r ) { return r <= n; } ocrad-0.29/main.cc0000644000175000017500000005504314546632740013705 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid command-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused ocrad to panic. */ #include #include #include #include #include #include #include #include #include #include #include #include #if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ #include #include #endif #include "arg_parser.h" #include "common.h" #include "rational.h" #include "user_filter.h" #include "page_image.h" #include "textpage.h" namespace { const char * const program_name = "ocrad"; const char * const program_year = "2024"; const char * invocation_name = program_name; // default value void show_help() { std::printf( "GNU Ocrad is an OCR (Optical Character Recognition) program based on a\n" "feature extraction method. It reads images in png or pnm formats and\n" "produces text in byte (8-bit) or UTF-8 formats. The formats pbm (bitmap),\n" "pgm (greyscale), and ppm (color) are collectively known as pnm.\n" "\nOcrad includes a layout analyser able to separate the columns or blocks\n" "of text normally found on printed pages.\n" "\nFor best results the characters should be at least 20 pixels high. If\n" "they are smaller, try the option --scale. Scanning the image at 300 dpi\n" "usually produces a character size good enough for ocrad.\n" "Merged, very bold, or very light (broken) characters are normally not\n" "recognized correctly. Try to avoid them.\n" "\nUsage: %s [options] [files]\n", invocation_name ); std::printf( "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" " -a, --append append text to output file\n" " -c, --charset= try '--charset=help' for a list of names\n" " -e, --filter= try '--filter=help' for a list of names\n" " -E, --user-filter= user-defined filter, see manual for format\n" " -f, --force force overwrite of output file\n" " -F, --format= output format (byte, utf8)\n" " -i, --invert invert image levels (white on black)\n" " -l, --layout perform layout analysis\n" " -o, --output= place the output into \n" " -q, --quiet suppress all messages\n" " -s, --scale=[-] scale input image by [1/]\n" " -t, --transform= try '--transform=help' for a list of names\n" " -T, --threshold= threshold for binarization (0.0-1.0)\n" " -u, --cut= cut the input image by the rectangle given\n" " -v, --verbose be verbose\n" " -x, --export= export results in ORF format to \n" ); if( verbosity >= 1 ) { std::printf( " -1..8 pnm or png output file type (debug)\n" " -C, --copy copy input to output (debug)\n" " -D, --debug= (0-100) output intermediate data (debug)\n" " -I, --info show png image information\n" ); } std::printf( "\nIf no files are specified, or if a file is '-', ocrad reads the image\n" "from standard input.\n" "If the option -o is not specified, ocrad sends text to standard output.\n" "\nExit status: 0 for a normal exit, 1 for environmental problems\n" "(file not found, invalid command-line options, I/O errors, etc), 2 to\n" "indicate a corrupt or invalid input file, 3 for an internal consistency\n" "error (e.g., bug) which caused ocrad to panic.\n" "\nReport bugs to bug-ocrad@gnu.org\n" "Ocrad home page: http://www.gnu.org/software/ocrad/ocrad.html\n" "General help using GNU software: http://www.gnu.org/gethelp\n" ); } void show_version() { std::printf( "GNU %s %s\n", program_name, PROGVERSION ); std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); std::printf( "License GPLv2+: GNU GPL version 2 or later \n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" ); } struct Input_control { Transformation transformation; int scale; Rational threshold, ltwh[4]; bool copy, cut, invert, layout; Input_control() : scale( 1 ), threshold( -1 ), copy( false ), cut( false ), invert( false ), layout( false ) {} void parse_cut_rectangle( const char * const arg, const char * const pn ); void parse_scale( const char * const arg, const char * const pn ); void parse_threshold( const char * const arg, const char * const pn ); }; void show_error( const char * const msg, const int errcode = 0, const bool help = false ) { if( verbosity < 0 ) return; if( msg && msg[0] ) std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? std::strerror( errcode ) : "" ); if( help ) std::fprintf( stderr, "Try '%s --help' for more information.\n", invocation_name ); } void show_file_error( const char * const filename, const char * const msg, const int errcode = 0 ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: %s: %s%s%s\n", program_name, filename, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? std::strerror( errcode ) : "" ); } void internal_error( const char * const msg ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: internal error: %s\n", program_name, msg ); std::exit( 3 ); } void show_option_error( const char * const arg, const char * const msg, const char * const option_name ) { if( verbosity >= 0 ) std::fprintf( stderr, "%s: '%s': %s option '%s'.\n", program_name, arg, msg, option_name ); } void Input_control::parse_cut_rectangle( const char * const arg, const char * const pn ) { int c = ltwh[0].parse( arg ); // left if( c && arg[c] == ',' && ltwh[0] >= -1 ) { int i = c + 1; c = ltwh[1].parse( &arg[i] ); // top if( c && arg[i+c] == ',' && ltwh[1] >= -1 ) { i += c + 1; c = ltwh[2].parse( &arg[i] ); // width if( c && arg[i+c] == ',' && ltwh[2] > 0 ) { i += c + 1; c = ltwh[3].parse( &arg[i] ); // height if( c && ltwh[3] > 0 ) { cut = true; return; } } } } show_option_error( arg, "Invalid cut rectangle in", pn ); std::exit( 1 ); } void Input_control::parse_scale( const char * const arg, const char * const pn ) { errno = 0; const long n = std::strtol( arg, 0, 0 ); if( errno || n == 0 || n > INT_MAX || n < -INT_MAX ) { show_option_error( arg, "Invalid scale factor in", pn ); std::exit( 1 ); } scale = n; } void Input_control::parse_threshold( const char * const arg, const char * const pn ) { Rational th; if( th.parse( arg ) && th >= 0 && th <= 1 ) { threshold = th; return; } show_option_error( arg, "Threshold out of limits [0.0, 1.0] in", pn ); std::exit( 1 ); } bool make_dirs( const char * const name, const int len ) { int i = len; while( i > 0 && name[i-1] != '/' ) --i; // remove last component while( i > 0 && name[i-1] == '/' ) --i; // remove slash(es) const int dirsize = i; // size of dirname without trailing slash(es) for( i = 0; i < dirsize; ) // if dirsize == 0, dirname is '/' or empty { while( i < dirsize && name[i] == '/' ) ++i; const int first = i; while( i < dirsize && name[i] != '/' ) ++i; if( first < i ) { const std::string partial( name, 0, i ); const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; struct stat st; if( stat( partial.c_str(), &st ) == 0 ) { if( !S_ISDIR( st.st_mode ) ) { errno = ENOTDIR; return false; } } else if( mkdir( partial.c_str(), mode ) != 0 && errno != EEXIST ) return false; // if EEXIST, another process created the dir } } return true; } FILE * open_outstream( const char * const outfile_name, const bool append, const bool force ) { const int len = strlen( outfile_name ); if( len > 0 && outfile_name[len-1] == '/' ) errno = EISDIR; else { if( !make_dirs( outfile_name, len ) ) { show_file_error( outfile_name, "Error creating intermediate directory", errno ); return 0; } const char * const mode = append ? "a" : force ? "w" : "wx"; FILE * outfile = std::fopen( outfile_name, mode ); if( outfile ) return outfile; if( errno == EEXIST ) { show_file_error( outfile_name, "Output file already exists, skipping." ); return 0; } } show_file_error( outfile_name, "Can't create output file", errno ); return 0; } const char * my_basename( const char * filename ) { const char * c = filename; while( *c ) { if( *c == '/' ) { filename = c + 1; } ++c; } return filename; } int process_file( FILE * const infile, const char * const infile_name, const Input_control & input_control, const Control & control ) { Page_image page_image( infile, input_control.invert ); if( input_control.cut ) { if( !page_image.cut( input_control.ltwh ) ) { show_file_error( infile_name, "File totally cut away." ); return 1; } if( verbosity >= 1 ) std::fprintf( stderr, "%s: File cut to %dw x %dh\n", infile_name, page_image.width(), page_image.height() ); } page_image.transform( input_control.transformation ); if( !page_image.change_scale( input_control.scale ) ) { show_file_error( infile_name, "Scale factor too small for file." ); return 1; } page_image.threshold( input_control.threshold ); if( verbosity >= 1 ) { const Rational th( page_image.threshold(), page_image.maxval() ); std::fprintf( stderr, "maxval = %d, threshold = %d (%s)\n", page_image.maxval(), page_image.threshold(), th.to_decimal( 1, -3 ).c_str() ); } if( input_control.copy ) { if( control.outfile ) page_image.save( control.outfile, control.filetype ); return 0; } Textpage textpage( page_image, my_basename( infile_name ), control, input_control.layout ); if( control.debug_level == 0 ) { if( control.outfile ) textpage.print( control ); if( control.exportfile ) textpage.xprint( control ); } if( verbosity >= 1 ) std::fputc( '\n', stderr ); return 0; } int process_full_file( FILE * const infile, const char * const infile_name, const Input_control & input_control, const Control & control ) { if( verbosity >= 1 ) std::fprintf( stderr, "Processing file '%s'\n", infile_name ); int retval = 0; while( true ) { const int tmp = process_file( infile, infile_name, input_control, control ); if( retval < tmp ) retval = tmp; if( control.outfile ) std::fflush( control.outfile ); if( control.exportfile ) std::fflush( control.exportfile ); if( infile != stdin ) break; if( tmp <= 1 ) // detect EOF { int ch; do ch = std::fgetc( infile ); while( ch == 0 || std::isspace( ch ) ); std::ungetc( ch, infile ); } if( tmp > 1 || std::feof( infile ) || std::ferror( infile ) ) break; } return retval; } } // end namespace void Charset::enable( const char * const arg, const char * const pn ) { const Value charset_value[charsets] = { ascii, iso_8859_9, iso_8859_15 }; const char * const charset_name[charsets] = { "ascii", "iso-8859-9", "iso-8859-15" }; for( int i = 0; i < charsets; ++i ) if( std::strcmp( arg, charset_name[i] ) == 0 ) { charset_ |= charset_value[i]; return; } if( verbosity < 0 ) std::exit( 1 ); if( arg && std::strcmp( arg, "help" ) != 0 ) show_option_error( arg, "Unknown charset name in", pn ); std::fputs( " Valid charset names:", stderr ); for( int i = 0; i < charsets; ++i ) std::fprintf( stderr, " %s", charset_name[i] ); std::fputc( '\n', stderr ); std::exit( 1 ); } void Transformation::set( const char * const arg, const char * const pn ) { struct T_entry { const char * const name; Transformation::Type type; }; const T_entry T_table[] = { { "none", Transformation::none }, { "rotate90", Transformation::rotate90 }, { "rotate180", Transformation::rotate180 }, { "rotate270", Transformation::rotate270 }, { "mirror_lr", Transformation::mirror_lr }, { "mirror_tb", Transformation::mirror_tb }, { "mirror_d1", Transformation::mirror_d1 }, { "mirror_d2", Transformation::mirror_d2 }, { 0, Transformation::none } }; for( int i = 0; T_table[i].name != 0; ++i ) if( std::strcmp( arg, T_table[i].name ) == 0 ) { type_ = T_table[i].type; return; } if( verbosity < 0 ) std::exit( 1 ); if( arg && std::strcmp( arg, "help" ) != 0 ) show_option_error( arg, "Unknown bitmap trasformation name in", pn ); std::fputs( " Valid transformation names:", stderr ); for( int i = 0; T_table[i].name != 0; ++i ) std::fprintf( stderr, " %s", T_table[i].name ); std::fputs( "\n Rotations are made counter-clockwise.\n", stderr ); std::exit( 1 ); } void Control::add_filter( const char * const arg, const char * const pn ) { struct F_entry { const char * const name; Filter::Type type; }; const F_entry F_table[] = { { "letters", Filter::letters }, { "letters_only", Filter::letters_only }, { "numbers", Filter::numbers }, { "numbers_only", Filter::numbers_only }, { "same_height", Filter::same_height }, { "text_block", Filter::text_block }, { "upper_num", Filter::upper_num }, { "upper_num_mark", Filter::upper_num_mark }, { "upper_num_only", Filter::upper_num_only }, { 0, Filter::user } }; for( int i = 0; F_table[i].name != 0; ++i ) if( std::strcmp( arg, F_table[i].name ) == 0 ) { filters.push_back( Filter( F_table[i].type ) ); return; } if( verbosity < 0 ) std::exit( 1 ); if( arg && std::strcmp( arg, "help" ) != 0 ) show_option_error( arg, "Unknown filter name in", pn ); std::fputs( " Valid filter names:", stderr ); for( int i = 0; F_table[i].name != 0; ++i ) std::fprintf( stderr, " %s", F_table[i].name ); std::fputc( '\n', stderr ); std::exit( 1 ); } void Control::add_user_filter( const char * const file_name ) { const User_filter * const user_filterp = new User_filter( file_name ); const int retval = user_filterp->retval(); if( retval == 0 ) { filters.push_back( Filter( user_filterp ) ); return; } if( user_filterp->error().empty() ) show_file_error( file_name, "Can't open filter file", errno ); else show_file_error( file_name, user_filterp->error().c_str() ); delete user_filterp; std::exit( retval ); } void Control::set_format( const char * const arg, const char * const pn ) { if( std::strcmp( arg, "byte" ) == 0 ) { utf8 = false; return; } if( std::strcmp( arg, "utf8" ) == 0 ) { utf8 = true; return; } show_option_error( arg, "Unknown format name in", pn ); std::exit( 1 ); } /* 'infile' contains the scanned image to be converted to text. 'outfile' is the destination for the text version of the scanned image (or for an image file if debugging). 'exportfile' is the Ocr Results File. */ int main( const int argc, const char * const argv[] ) { Input_control input_control; Control control; const char * outfile_name = 0, * exportfile_name = 0; bool append = false; bool force = false; bool info_mode = false; if( argc > 0 ) invocation_name = argv[0]; const Arg_parser::Option options[] = { { '1', 0, Arg_parser::no }, // P1 to P6 { '2', 0, Arg_parser::no }, { '3', 0, Arg_parser::no }, { '4', 0, Arg_parser::no }, { '5', 0, Arg_parser::no }, { '6', 0, Arg_parser::no }, { '7', 0, Arg_parser::no }, // png mono { '8', 0, Arg_parser::no }, // png greyscale { 'a', "append", Arg_parser::no }, { 'c', "charset", Arg_parser::yes }, { 'C', "copy", Arg_parser::no }, { 'D', "debug", Arg_parser::yes }, { 'e', "filter", Arg_parser::yes }, { 'E', "user-filter", Arg_parser::yes }, { 'f', "force", Arg_parser::no }, { 'F', "format", Arg_parser::yes }, { 'h', "help", Arg_parser::no }, { 'i', "invert", Arg_parser::no }, { 'I', "info", Arg_parser::no }, { 'l', "layout", Arg_parser::no }, { 'o', "output", Arg_parser::yes }, { 'q', "quiet", Arg_parser::no }, { 's', "scale", Arg_parser::yes }, { 't', "transform", Arg_parser::yes }, { 'T', "threshold", Arg_parser::yes }, { 'u', "cut", Arg_parser::yes }, { 'v', "verbose", Arg_parser::no }, { 'V', "version", Arg_parser::no }, { 'x', "export", Arg_parser::yes }, { 0 , 0, Arg_parser::no } }; const Arg_parser parser( argc, argv, options ); if( parser.error().size() ) // bad option { show_error( parser.error().c_str(), 0, true ); return 1; } int retval = 0; int argind = 0; for( ; argind < parser.arguments(); ++argind ) { const int code = parser.code( argind ); if( !code ) break; // no more options const char * const pn = parser.parsed_name( argind ).c_str(); const char * const arg = parser.argument( argind ).c_str(); switch( code ) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': control.filetype = code; break; case 'a': append = true; break; case 'c': control.charset.enable( arg, pn ); break; case 'C': input_control.copy = true; break; case 'D': control.debug_level = std::strtol( arg, 0, 0 ); break; case 'e': control.add_filter( arg, pn ); break; case 'E': control.add_user_filter( arg ); break; case 'f': force = true; break; case 'F': control.set_format( arg, pn ); break; case 'h': show_help(); return 0; case 'i': input_control.invert = true; break; case 'I': info_mode = true; break; case 'l': input_control.layout = true; break; case 'o': outfile_name = arg; break; case 'q': verbosity = -1; break; case 's': input_control.parse_scale( arg, pn ); break; case 't': input_control.transformation.set( arg, pn ); break; case 'T': input_control.parse_threshold( arg, pn ); break; case 'u': input_control.parse_cut_rectangle( arg, pn ); break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; case 'x': exportfile_name = arg; break; default: internal_error( "uncaught option." ); } } // end process options #if defined __MSVCRT__ || defined __OS2__ || defined __DJGPP__ setmode( STDIN_FILENO, O_BINARY ); setmode( STDOUT_FILENO, O_BINARY ); #endif if( !info_mode && outfile_name && std::strcmp( outfile_name, "-" ) != 0 ) { control.outfile = open_outstream( outfile_name, append, force ); if( !control.outfile ) return 1; } if( !info_mode && exportfile_name && control.debug_level == 0 && !input_control.copy ) { if( std::strcmp( exportfile_name, "-" ) == 0 ) { control.exportfile = stdout; if( !outfile_name ) control.outfile = 0; } else { control.exportfile = open_outstream( exportfile_name, false, true ); if( !control.exportfile ) return 1; } std::fprintf( control.exportfile, "# Ocr Results File. Created by GNU Ocrad version %s\n", PROGVERSION ); } // process any remaining command-line arguments (input files) bool stdin_used = false; for( bool first = true; first || argind < parser.arguments(); first = false ) { const char * infile_name = ( argind < parser.arguments() ) ? parser.argument( argind++ ).c_str() : "-"; FILE * infile; if( std::strcmp( infile_name, "-" ) == 0 ) { if( stdin_used ) continue; else stdin_used = true; infile = stdin; } else { infile = std::fopen( infile_name, "rb" ); if( !infile ) { show_file_error( infile_name, "Can't open input file", errno ); if( retval < 1 ) retval = 1; continue; } } int tmp; try { if( info_mode ) { if( !read_check_png_sig8( infile ) ) { tmp = 2; show_file_error( infile_name, "Not a PNG file." ); } else { tmp = 0; show_png_info( infile, infile_name, 8 ); } } else tmp = process_full_file( infile, infile_name, input_control, control ); } catch( std::bad_alloc & ) { show_file_error( infile_name, "Not enough memory." ); tmp = 1; } catch( Page_image::Error & e ) { show_file_error( infile_name, e.msg ); tmp = 2; } catch( Ocrad::Internal & e ) { show_file_error( infile_name, e.msg ); std::exit( 3 ); } if( std::fclose( infile ) != 0 ) { show_file_error( infile_name, "Error closing input file", errno ); if( tmp < 1 ) tmp = 1; } if( retval < tmp ) retval = tmp; } if( control.outfile ) std::fclose( control.outfile ); if( control.exportfile ) std::fclose( control.exportfile ); return retval; } ocrad-0.29/blob.h0000644000175000017500000000361514545576754013552 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Blob : public Bitmap { std::vector< Bitmap * > holepv; // vector of holes public: Blob( const int l, const int t, const int r, const int b ) : Bitmap( l, t, r, b ) {} Blob( const Bitmap & source, const Rectangle & re ) : Bitmap( source, re ) {} Blob( const Blob & b ); Blob & operator=( const Blob & b ); ~Blob(); using Bitmap::left; using Bitmap::top; using Bitmap::right; using Bitmap::bottom; using Bitmap::height; using Bitmap::width; void left ( const int l ); void top ( const int t ); void right ( const int r ); void bottom( const int b ); void height( const int h ) { bottom( top() + h - 1 ); } void width ( const int w ) { right( left() + w - 1 ); } const Bitmap & hole( const int i ) const; int holes() const { return holepv.size(); } // id = 1 for blob dots, negative for hole dots, 0 otherwise int id( const int row, const int col ) const; bool is_abnormal() const { return height() < 10 || height() >= 5 * width() || width() >= 3 * height(); } bool test_BD() const; bool test_Q() const; void print( FILE * const outfile ) const; void fill_hole( const int i ); void find_holes(); }; ocrad-0.29/bitmap.cc0000644000175000017500000003152714545576754014251 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "bitmap.h" // create a blank Bitmap Bitmap::Bitmap( const int l, const int t, const int r, const int b ) : Rectangle( l, t, r, b ), data( height() ) { for( int row = 0; row < height(); ++row ) data[row].resize( width(), false ); } // create a Bitmap from part of another Bitmap Bitmap::Bitmap( const Bitmap & source, const Rectangle & re ) : Rectangle( re ), data( re.height() ) { if( !source.includes( re ) ) throw Ocrad::Internal( "bad parameter building a Bitmap from part of another Bitmap." ); const int ldiff = left()-source.left(); const int tdiff = top()-source.top(); for( int row = 0; row < height(); ++row ) { data[row].resize( width() ); std::vector< uint8_t > & datarow = data[row]; const std::vector< uint8_t > & datarow2 = source.data[row+tdiff]; for( int col = 0; col < width(); ++col ) datarow[col] = datarow2[col+ldiff]; } } void Bitmap::left( const int l ) { if( l == left() ) return; if( l < left() ) for( int row = height() - 1; row >= 0 ; --row ) data[row].insert( data[row].begin(), left() - l, false ); else for( int row = height() - 1; row >= 0 ; --row ) data[row].erase( data[row].begin(), data[row].begin() + ( l - left() ) ); Rectangle::left( l ); } void Bitmap::top( const int t ) { if( t == top() ) return; if( t < top() ) data.insert( data.begin(), top() - t, std::vector< uint8_t >( width(), false ) ); else data.erase( data.begin(), data.begin() + ( t - top() ) ); Rectangle::top( t ); } void Bitmap::right( const int r ) { if( r == right() ) return; Rectangle::right( r ); for( int row = height() - 1; row >= 0 ; --row ) data[row].resize( width(), false ); } void Bitmap::bottom( const int b ) { if( b == bottom() ) return; int old_height = height(); Rectangle::bottom( b ); data.resize( height() ); for( int row = old_height; row < height(); ++row ) data[row].resize( width(), false ); } void Bitmap::add_bitmap( const Bitmap & bm ) { add_rectangle( bm ); for( int row = bm.top(); row <= bm.bottom(); ++row ) for( int col = bm.left(); col <= bm.right(); ++col ) if( bm.get_bit( row, col ) ) set_bit( row, col, true ); } void Bitmap::add_point( const int row, const int col ) { if( col > right() ) right( col ); else if( col < left() ) left( col ); if( row > bottom() ) bottom( row ); else if( row < top() ) top( row ); set_bit( row, col, true ); } void Bitmap::add_rectangle( const Rectangle & re ) { if( re.left() < left() ) left( re.left() ); if( re.top() < top() ) top( re.top() ); if( re.right() > right() ) right( re.right() ); if( re.bottom() > bottom() ) bottom( re.bottom() ); } // return false if bitmap is empty // bool Bitmap::adjust_height() { int row1, row2; for( row1 = top(); row1 <= bottom(); ++row1 ) for( int col = left(); col <= right(); ++col ) if( get_bit( row1, col ) ) goto L1; L1: for( row2 = bottom(); row2 >= row1; --row2 ) for( int col = left(); col <= right(); ++col ) if( get_bit( row2, col ) ) goto L2; L2: if( row1 > row2 ) return false; if( row1 > top() ) top( row1 ); if( row2 < bottom() ) bottom( row2 ); return true; } // return false if bitmap is empty // bool Bitmap::adjust_width() { int col1, col2; for( col1 = left(); col1 <= right(); ++col1 ) for( int row = top(); row <= bottom(); ++row ) if( get_bit( row , col1 ) ) goto L1; L1: for( col2 = right(); col2 >= col1; --col2 ) for( int row = top(); row <= bottom(); ++row ) if( get_bit( row , col2) ) goto L2; L2: if( col1 >= col2 ) return false; if( col1 > left() ) left( col1 ); if( col2 < right() ) right( col2 ); return true; } // return the total filled area of this Bitmap // int Bitmap::area() const { int a = 0; for( int row = top(); row <= bottom(); ++row ) for( int col = left(); col <= right(); ++col ) if( get_bit( row, col ) ) ++a; return a; } // return the central octagon filled area of this Bitmap // int Bitmap::area_octagon() const { int a = 0; int bevel = ( 29 * std::min( height(), width() ) ) / 100; int l = left() + bevel; int r = right() - bevel; for( int i = 0; i < bevel; ++i ) for( int row = top() + i, col = l - i; col <= r + i; ++col ) if( get_bit( row, col ) ) ++a; for( int row = top() + bevel; row <= bottom() - bevel; ++row ) for( int col = left(); col <= right(); ++col ) if( get_bit( row, col ) ) ++a; for( int i = bevel - 1; i >= 0; --i ) for( int row = bottom() - i, col = l - i; col <= r + i; ++col ) if( get_bit( row, col ) ) ++a; return a; } // return the size of the central octagon of this blob // int Bitmap::size_octagon() const { int bevel = ( 29 * std::min( height(), width() ) ) / 100; return size() - ( 2 * bevel * ( bevel + 1 ) ); } int Bitmap::seek_left( const int row, const int col, const bool black ) const { int c = col; while( c > left() && get_bit( row, c - 1 ) != black ) --c; return c; } int Bitmap::seek_top( const int row, const int col, const bool black ) const { int r = row; while( r > top() && get_bit( r - 1, col ) != black ) --r; return r; } int Bitmap::seek_right( const int row, const int col, const bool black ) const { int c = col; while( c < right() && get_bit( row, c + 1 ) != black ) ++c; return c; } int Bitmap::seek_bottom( const int row, const int col, const bool black ) const { int r = row; while( r < bottom() && get_bit( r + 1, col ) != black ) ++r; return r; } bool Bitmap::escape_left( int row, int col ) const { if( get_bit( row, col ) ) return false; int u, d; for( u = row; u > top() + 1; --u ) if( get_bit( u - 1, col ) ) break; for( d = row; d < bottom() - 1; ++d ) if( get_bit( d + 1, col ) ) break; while( u <= d && --col >= left() ) { if( u > top() + 1 && !get_bit( u, col ) ) --u; if( d < bottom() - 1 && !get_bit( d, col ) ) ++d; while( u <= d && get_bit( u, col ) ) ++u; while( u <= d && get_bit( d, col ) ) --d; } return ( col < left() ); } bool Bitmap::escape_top( int row, int col ) const { if( get_bit( row, col ) ) return false; int l, r; for( l = col; l > left() + 1; --l ) if( get_bit( row, l - 1 ) ) break; for( r = col; r < right() - 1; ++r ) if( get_bit( row, r + 1 ) ) break; while( l <= r && --row >= top() ) { if( l > left() + 1 && !get_bit( row, l ) ) --l; if( r < right() - 1 && !get_bit( row, r ) ) ++r; while( l <= r && get_bit( row, l ) ) ++l; while( l <= r && get_bit( row, r ) ) --r; } return ( row < top() ); } bool Bitmap::escape_right( int row, int col ) const { if( get_bit( row, col ) ) return false; int u, d; for( u = row; u > top() + 1; --u ) if( get_bit( u - 1, col ) ) break; for( d = row; d < bottom() - 1; ++d ) if( get_bit( d + 1, col ) ) break; while( u <= d && ++col <= right() ) { while( u > top() + 1 && !get_bit( u, col ) ) --u; while( d < bottom() - 1 && !get_bit( d, col ) ) ++d; while( u <= d && get_bit( u, col ) ) ++u; while( u <= d && get_bit( d, col ) ) --d; } return ( col > right() ); } bool Bitmap::escape_bottom( int row, int col ) const { if( get_bit( row, col ) ) return false; int l, r; for( l = col; l > left() + 1; --l ) if( get_bit( row, l - 1 ) ) break; for( r = col; r < right() - 1; ++r ) if( get_bit( row, r + 1 ) ) break; while( l <= r && ++row <= bottom() ) { if( l > left() + 1 && !get_bit( row, l ) ) --l; if( r < right() - 1 && !get_bit( row, r ) ) ++r; while( l <= r && get_bit( row, l ) ) ++l; while( l <= r && get_bit( row, r ) ) --r; } return ( row > bottom() ); } int Bitmap::follow_top( int row, int col ) const { if( !get_bit( row, col ) ) return row; std::vector< uint8_t > array; array.reserve( width() ); int c; for( c = col; c > left() && get_bit( row, c - 1 ); --c ) ; if( c > left() ) array.resize( c - left(), false ); for( c = col; c < right() && get_bit( row, c + 1 ); ++c ) ; array.resize( c - left() + 1, true ); if( c < right() ) array.resize( width(), false ); while( --row >= top() ) { bool alive = false; for( int i = 0; i < width(); ++i ) if( array[i] ) { if( !get_bit( row, left() + i ) ) array[i] = false; else alive = true; } if( !alive ) break; for( int i = 1; i < width(); ++i ) if( array[i-1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; for( int i = width() - 2; i >= 0; --i ) if( array[i+1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; } return row + 1; } int Bitmap::follow_bottom( int row, int col ) const { if( !get_bit( row, col ) ) return row; std::vector< uint8_t > array; array.reserve( width() ); int c; for( c = col; c > left() && get_bit( row, c - 1 ); --c ) ; if( c > left() ) array.resize( c - left(), false ); for( c = col; c < right() && get_bit( row, c + 1 ); ++c ) ; array.resize( c - left() + 1, true ); if( c < right() ) array.resize( width(), false ); while( ++row <= bottom() ) { bool alive = false; for( int i = 0; i < width(); ++i ) if( array[i] ) { if( !get_bit( row, left() + i ) ) array[i] = false; else alive = true; } if( !alive ) break; for( int i = 1; i < width(); ++i ) if( array[i-1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; for( int i = width() - 2; i >= 0; --i ) if( array[i+1] && !array[i] && get_bit( row, left() + i ) ) array[i] = true; } return row - 1; } /* Look for an inverted-U-shaped curve near the top, then test which of the vertical bars goes deeper. */ bool Bitmap::top_hook( int *hdiff ) const { int row, lcol = 0, rcol = 0, black_section = 0, wmax = 0; for( row = top() + 1; row < vcenter(); ++row ) { int l = -1, r = -2; bool prev_black = false; black_section = 0; for( int col = left(); col <= right(); ++col ) { bool black = get_bit( row, col ); if( black ) { if( !prev_black && ++black_section == 2 ) rcol = col; r = col; if( l < 0 ) l = col; } else if( prev_black && black_section == 1 ) lcol = col - 1; prev_black = black; } r = r - l + 1; if( 10 * r <= 9 * wmax ) return false; if( r > wmax ) wmax = r ; if( black_section >= 2 ) break; } if( black_section != 2 ) return false; if( escape_top( row, lcol + 1 ) ) return false; int lrow = follow_bottom( row, lcol ), rrow = follow_bottom( row, rcol ); if( lrow <= row || rrow <= row ) return false; if( hdiff ) *hdiff = lrow - rrow; return true; } /* Look for an U-shaped curve near the bottom, then test which of the vertical bars is taller. */ bool Bitmap::bottom_hook( int *hdiff ) const { int row, lcol = 0, rcol = 0, black_section = 0, wmax = 0; for( row = bottom(); row > vpos( 80 ); --row ) { int l, r; for( l = left(); l <= right(); ++l ) if( get_bit( row, l ) ) break; for( r = right(); r > l; --r ) if( get_bit( row, r ) ) break; const int w = r - l + 1; if( w > wmax ) wmax = w; if( 4 * w >= width() ) { int i; for( i = l + 1; i < r; ++i ) if( !get_bit( row, i ) ) break; if( i >= r ) break; } } if( row > vpos( 80 ) ) while( --row > vcenter() ) { int l = -1, r = -2; bool prev_black = false; black_section = 0; for( int col = left(); col <= right(); ++col ) { bool black = get_bit( row, col ); if( black ) { if( !prev_black && ++black_section == 2 ) rcol = col; r = col; if( l < 0 ) l = col; } else if( prev_black && black_section == 1 ) lcol = col - 1; prev_black = black; } const int w = r - l + 1; if( black_section > 2 || 10 * w <= 8 * wmax ) break; if( w > wmax ) wmax = w; if( black_section == 2 && rcol - lcol >= 2 ) { if( escape_bottom( row, lcol + 1 ) ) break; if( hdiff ) *hdiff = follow_top( row, lcol ) - follow_top( row, rcol ); return true; } } return false; } ocrad-0.29/feats.h0000644000175000017500000000537614545576754013744 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Features { const Blob & b; // Blob to witch these features belong mutable bool hbar_initialized, vbar_initialized; mutable std::vector< Rectangle > hbar_, vbar_; mutable std::vector< std::vector< Csegment > > row_scan, col_scan; Features( const Features & ); // declared as private void operator=( const Features & ); // declared as private void row_scan_init() const; void col_scan_init() const; public: mutable Profile lp, tp, rp, bp, hp, wp; explicit Features( const Blob & b_ ); // const Blob & blob() const { return b; } const Rectangle & hbar( const int i ) const { return hbar_[i]; } const Rectangle & vbar( const int i ) const { return vbar_[i]; } int hbars() const; int vbars() const; // number of vertical traces crossing every row int segments_in_row( const int row ) const { if( row_scan.empty() ) row_scan_init(); return row_scan[row-b.top()].size(); } // number of horizontal traces crossing every column int segments_in_col( const int col ) const { if( col_scan.empty() ) col_scan_init(); return col_scan[col-b.left()].size(); } // vertical segment containing the point (row,col), if any Csegment v_segment( const int row, const int col ) const; int test_235Esz( const Charset & charset ) const; int test_49ARegpq( const Rectangle & charbox ) const; int test_4ADQao( const Charset & charset, const Rectangle & charbox ) const; int test_6abd( const Charset & charset ) const; int test_EFIJLlT( const Charset & charset, const Rectangle & charbox ) const; int test_c() const; int test_frst( const Rectangle & charbox ) const; int test_G() const; int test_HKMNUuvwYy( const Rectangle & charbox ) const; int test_hknwx( const Rectangle & charbox ) const; int test_s_cedilla() const; bool test_comma() const; int test_easy( const Rectangle & charbox ) const; int test_line( const Rectangle & charbox ) const; int test_solid( const Rectangle & charbox ) const; int test_misc( const Rectangle & charbox ) const; }; ocrad-0.29/NEWS0000644000175000017500000000065614551027564013147 0ustar andriusandriusChanges in version 0.29: Recognition of 'L' with right uptick has been improved. File diagnostics have been reformatted as 'PROGRAM: FILE: MESSAGE'. Diagnostics caused by invalid arguments to command-line options now show the argument and the name of the option. The option '-o, --output' now creates missing intermediate directories when writing to a file. The variable MAKEINFO has been added to configure and Makefile.in. ocrad-0.29/rectangle.cc0000644000175000017500000001054514545576754014736 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" namespace { void error( const char * const msg ) { throw Ocrad::Internal( msg ); } } // end namespace Rectangle::Rectangle( const int l, const int t, const int r, const int b ) { if( r < l || b < t ) { if( verbosity >= 0 ) std::fprintf( stderr, "l = %d, t = %d, r = %d, b = %d\n", l, t, r, b ); error( "bad parameter building a Rectangle." ); } left_ = l; top_ = t; right_ = r; bottom_ = b; } Rectangle::Rectangle( const int h, const int w ) { if( h <= 0 || w <= 0 ) { if( verbosity >= 0 ) std::fprintf( stderr, "h = %d, w = %d\n", h, w ); error( "bad parameter building a Rectangle." ); } left_ = 0; top_ = 0; right_ = w - 1; bottom_ = h - 1; } void Rectangle::left( const int l ) { if( l <= right_ ) left_ = l; else error( "left: bad parameter resizing a Rectangle." ); } void Rectangle::top( const int t ) { if( t <= bottom_ ) top_ = t; else error( "top: bad parameter resizing a Rectangle." ); } void Rectangle::right( const int r ) { if( r >= left_ ) right_ = r; else error( "right: bad parameter resizing a Rectangle." ); } void Rectangle::bottom( const int b ) { if( b >= top_ ) bottom_ = b; else error( "bottom: bad parameter resizing a Rectangle." ); } void Rectangle::height( const int h ) { if( h > 0 ) bottom_ = top_ + h - 1; else error( "height: bad parameter resizing a Rectangle." ); } void Rectangle::width( const int w ) { if( w > 0 ) right_ = left_ + w - 1; else error( "width: bad parameter resizing a Rectangle." ); } int Rectangle::v_overlap_percent( const Rectangle & re ) const { int ov = std::min( bottom_, re.bottom_ ) - std::max( top_, re.top_ ) + 1; if( ov > 0 ) ov = std::max( 1, ( ov * 100 ) / std::min( height(), re.height() ) ); else ov = 0; return ov; } bool Rectangle::is_hcentred_in( const Rectangle & re ) const { if( this->h_includes( re.hcenter() ) ) return true; int w = std::min( re.height(), re.width() ) / 2; if( width() < w ) { int d = ( w + 1 ) / 2; if( hcenter() - d <= re.hcenter() && hcenter() + d >= re.hcenter() ) return true; } return false; } bool Rectangle::is_vcentred_in( const Rectangle & re ) const { if( this->v_includes( re.vcenter() ) ) return true; int h = std::min( re.height(), re.width() ) / 2; if( height() < h ) { int d = ( h + 1 ) / 2; if( vcenter() - d <= re.vcenter() && vcenter() + d >= re.vcenter() ) return true; } return false; } bool Rectangle::precedes( const Rectangle & re ) const { if( right_ < re.left_ ) return true; if( this->h_overlaps( re ) && ( ( top_ < re.top_ ) || ( top_ == re.top_ && left_ < re.left_ ) ) ) return true; return false; } bool Rectangle::v_precedes( const Rectangle & re ) const { if( bottom_ < re.vcenter() || vcenter() < re.top_ ) return true; if( this->includes_vcenter( re ) && re.includes_vcenter( *this ) ) return this->h_precedes( re ); return false; } int Rectangle::hypoti( const int c1, const int c2 ) { long long temp = c1; temp *= temp; long long target = c2; target *= target; target += temp; int lower = std::max( std::abs( c1 ), std::abs( c2 ) ); int upper = std::abs( c1 ) + std::abs( c2 ); while( upper - lower > 1 ) { int m = ( lower + upper ) / 2; temp = m; temp *= temp; if( temp < target ) lower = m; else upper = m; } temp = lower; temp *= temp; target *= 2; target -= temp; temp = upper; temp *= temp; if( target < temp ) return lower; else return upper; } ocrad-0.29/segment.h0000644000175000017500000000312014545576754014265 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ struct Csegment // cartesian (one-dimensional) segment { int left, right; // l > r means no segment // in vertical segments, left is top explicit Csegment( const int l = 1, const int r = 0 ) : left( l ), right( r ) {} void add_point( const int col ); void add_csegment( const Csegment & seg ); bool valid() const { return ( left <= right ); } int size() const { return ( left <= right ) ? right - left + 1 : 0; } bool includes( const Csegment & seg ) const { return ( seg.valid() && left <= seg.left && seg.right <= right ); } bool includes( const int col ) const { return ( left <= col && col <= right ); } bool overlaps( const Csegment & seg ) const { return ( valid() && seg.valid() && left <= seg.right && right >= seg.left ); } int distance( const Csegment & seg ) const; int distance( const int col ) const; }; ocrad-0.29/common.cc0000644000175000017500000000260614546351562014246 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "user_filter.h" int verbosity = 0; bool Ocrad::similar( const int a, const int b, const int percent_dif, const int abs_dif ) { int difference = std::abs( a - b ); if( percent_dif > 0 && difference <= abs_dif ) return true; int max_abs = std::max( std::abs( a ), std::abs( b ) ); return ( difference * 100 <= max_abs * percent_dif ); } Control::~Control() { for( unsigned f = filters.size(); f > 0; --f ) if( filters[f-1].user_filterp ) delete filters[f-1].user_filterp; } ocrad-0.29/track.cc0000644000175000017500000002300114545576754014065 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "common.h" #include "rectangle.h" #include "track.h" namespace { void error( const char * const msg ) { throw Ocrad::Internal( msg ); } int good_reference( const Rectangle & r1, const Rectangle & r2, int & val, const int mean_height, const int mean_width ) { if( 4 * r1.height() >= 3 * mean_height && 4 * r2.height() >= 3 * mean_height && ( r1.width() >= mean_width || r2.width() >= mean_width ) && val > 0 ) { if( 4 * r1.height() <= 5 * mean_height && 4 * r2.height() <= 5 * mean_height ) { if( 9 * r1.height() <= 10 * mean_height && 9 * r2.height() <= 10 * mean_height && 10 * std::abs( r1.bottom() - r2.bottom() ) <= mean_height ) { val = 0; return ( r1.height() <= r2.height() ) ? 0 : 1; } if( val > 1 && 10 * std::abs( r1.vcenter() - r2.vcenter() ) <= mean_height ) { val = 1; return ( r1.bottom() <= r2.bottom() ) ? 0 : 1; } } if( val > 2 && 10 * std::abs( r1.vcenter() - r2.vcenter() ) <= mean_height ) { val = 2; return ( r1.bottom() <= r2.bottom() ) ? 0 : 1; } } return -1; } int set_l( const std::vector< Rectangle > & rectangle_vector, const int mean_height, const int mean_width ) { const int rectangles = rectangle_vector.size(); const int imax = rectangles / 4; int ibest = -1, val = 3; for( int i1 = 0; i1 < imax && val > 0; ++i1 ) for( int i2 = i1 + 1; i2 <= imax && i2 <= i1 + 2; ++i2 ) { int i = good_reference( rectangle_vector[i1], rectangle_vector[i2], val, mean_height, mean_width ); if( i >= 0 ) { ibest = (i == 0) ? i1 : i2; if( val == 0 ) break; } } return ibest; } int set_r( const std::vector< Rectangle > & rectangle_vector, const int mean_height, const int mean_width ) { const int rectangles = rectangle_vector.size(); const int imin = rectangles - 1 - ( rectangles / 4 ); int ibest = -1, val = 3; for( int i1 = rectangles - 1; i1 > imin && val > 0; --i1 ) for( int i2 = i1 - 1; i2 >= imin && i2 >= i1 - 2; --i2 ) { int i = good_reference( rectangle_vector[i1], rectangle_vector[i2], val, mean_height, mean_width ); if( i >= 0 ) { ibest = (i == 0) ? i1 : i2; if( val == 0 ) break; } } return ibest; } Vrhomboid set_partial_track( const std::vector< Rectangle > & rectangle_vector ) { const int rectangles = rectangle_vector.size(); int mean_vcenter = 0, mean_height = 0, mean_width = 0; for( int i = 0; i < rectangles; ++i ) { mean_vcenter += rectangle_vector[i].vcenter(); mean_height += rectangle_vector[i].height(); mean_width += rectangle_vector[i].width(); } if( rectangles ) { mean_vcenter /= rectangles; mean_height /= rectangles; mean_width /= rectangles; } // short line if( rectangles < 8 ) return Vrhomboid( rectangle_vector.front().left(), mean_vcenter, rectangle_vector.back().right(), mean_vcenter, mean_height ); // look for reference rectangles (characters) int l = set_l( rectangle_vector, mean_height, mean_width ); int r = set_r( rectangle_vector, mean_height, mean_width ); int lcol, lvc, rcol, rvc; if( l >= 0 ) { lcol = rectangle_vector[l].hcenter(); lvc = rectangle_vector[l].bottom() - ( mean_height / 2 ); } else { lcol = rectangle_vector.front().hcenter(); lvc = mean_vcenter; } if( r >= 0 ) { rcol = rectangle_vector[r].hcenter(); rvc = rectangle_vector[r].bottom() - ( mean_height / 2 ); } else { rcol = rectangle_vector.back().hcenter(); rvc = mean_vcenter; } Vrhomboid tmp( lcol, lvc, rcol, rvc, mean_height ); tmp.extend_left( rectangle_vector.front().left() ); tmp.extend_right( rectangle_vector.back().right() ); return tmp; } } // end namespace Vrhomboid::Vrhomboid( const int l, const int lc, const int r, const int rc, const int h ) { if( r < l || h <= 0 ) { if( verbosity >= 0 ) std::fprintf( stderr, "l = %d, lc = %d, r = %d, rc = %d, h = %d\n", l, lc, r, rc, h ); error( "bad parameter building a Vrhomboid." ); } left_ = l; lvcenter_ = lc; right_ = r; rvcenter_ = rc; height_ = h; } void Vrhomboid::left( const int l ) { if( l > right_ ) error( "left: bad parameter resizing a Vrhomboid." ); left_ = l; } void Vrhomboid::right( const int r ) { if( r < left_ ) error( "right: bad parameter resizing a Vrhomboid." ); right_ = r; } void Vrhomboid::height( const int h ) { if( h <= 0 ) error( "height: bad parameter resizing a Vrhomboid." ); height_ = h; } void Vrhomboid::extend_left( const int l ) { if( l > right_ ) error( "extend_left: bad parameter resizing a Vrhomboid." ); lvcenter_ = vcenter( l ); left_ = l; } void Vrhomboid::extend_right( const int r ) { if( r < left_ ) error( "extend_right: bad parameter resizing a Vrhomboid." ); rvcenter_ = vcenter( r ); right_ = r; } int Vrhomboid::vcenter( const int col ) const { const int dx = right_ - left_, dy = rvcenter_ - lvcenter_; int vc = lvcenter_; if( dx && dy ) vc += ( dy * ( col - left_ ) ) / dx; return vc; } bool Vrhomboid::includes( const Rectangle & r ) const { if( r.left() < left_ || r.right() > right_ ) return false; const int tl = top( r.left() ), bl = bottom( r.left() ); const int tr = top( r.right() ), br = bottom( r.left() ); const int t = std::max( tl, tr ), b = std::min( bl, br ); return ( t <= r.top() && b >= r.bottom() ); } bool Vrhomboid::includes( const int row, const int col ) const { if( col < left_ || col > right_ ) return false; const int t = top( col ), b = bottom( col ); return ( t <= row && b >= row ); } // rectangle_vector must be ordered by increasing hcenter(). // void Track::set_track( const std::vector< Rectangle > & rectangle_vector ) { data.clear(); if( rectangle_vector.empty() ) return; std::vector< Rectangle > tmp; int max_gap = 0; bool last = false; { int s1 = rectangle_vector[0].width(), s2 = 0; for( unsigned i = 1; i < rectangle_vector.size(); ++i ) { s1 += rectangle_vector[i].width(); s2 += ( rectangle_vector[i].left() - rectangle_vector[i-1].right() ); } max_gap = ( 5 * std::max( s1, s2 ) ) / rectangle_vector.size(); } for( unsigned i = 0; i < rectangle_vector.size(); ++i ) { const Rectangle & r1 = rectangle_vector[i]; tmp.push_back( r1 ); if( i + 1 >= rectangle_vector.size() ) last = true; else { const Rectangle & r2 = rectangle_vector[i+1]; if( r2.left() - r1.right() >= max_gap ) last = true; } if( last ) { last = false; data.push_back( set_partial_track( tmp ) ); tmp.clear(); } } for( unsigned i = 0; i + 1 < data.size(); ++i ) { const Vrhomboid & v1 = data[i]; const Vrhomboid & v2 = data[i+1]; if( v1.right() + 1 < v2.left() ) { Vrhomboid v( v1.right() + 1, v1.rvcenter(), v2.left() - 1, v2.lvcenter(), ( v1.height() + v2.height() ) / 2 ); ++i; data.insert( data.begin() + i, v ); } } } int Track::bottom( const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) { const Vrhomboid & vr = data[i]; if( col <= vr.right() || i + 1 >= data.size() ) return vr.bottom( col ); } return 0; } int Track::top( const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) { const Vrhomboid & vr = data[i]; if( col <= vr.right() || i + 1 >= data.size() ) return vr.top( col ); } return 0; } int Track::vcenter( const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) { const Vrhomboid & vr = data[i]; if( col <= vr.right() || i + 1 >= data.size() ) return vr.vcenter( col ); } return 0; } /* bool Track::includes( const Rectangle & r ) const { for( unsigned i = 0; i < data.size(); ++i ) if( data[i].includes( r ) ) return true; if( data.empty() ) return false; if( r.right() > data.back().right() ) { Vrhomboid tmp = data.back(); tmp.extend_right( r.right() ); return tmp.includes( r ); } if( r.left() < data.front().left() ) { Vrhomboid tmp = data.front(); tmp.extend_left( r.left() ); return tmp.includes( r ); } return false; } bool Track::includes( const int row, const int col ) const { for( unsigned i = 0; i < data.size(); ++i ) if( data[i].includes( row, col ) ) return true; if( data.empty() ) return false; if( col > data.back().right() ) { Vrhomboid tmp = data.back(); tmp.extend_right( col ); return tmp.includes( row, col ); } if( col < data.front().left() ) { Vrhomboid tmp = data.front(); tmp.extend_left( col ); return tmp.includes( row, col ); } return false; }*/ ocrad-0.29/textblock.cc0000644000175000017500000004352314545576754014773 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "rational.h" #include "rectangle.h" #include "track.h" #include "ucs.h" #include "user_filter.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" #include "textblock.h" namespace { void insert_line( std::vector< Textline * > & textlinep_vector, int i ) { textlinep_vector.insert( textlinep_vector.begin() + i, new Textline ); } void delete_line( std::vector< Textline * > & textlinep_vector, int i ) { delete textlinep_vector[i]; textlinep_vector.erase( textlinep_vector.begin() + i ); } // build the vertical composite characters // void join_characters( std::vector< Textline * > & tlpv ) { for( unsigned current_line = 0; current_line < tlpv.size(); ++current_line ) { Textline & line = *tlpv[current_line]; for( int i = 0 ; i < line.characters() - 1; ) { Character & c1 = line.character( i ); bool joined = false; for( int j = i + 1 ; j < line.characters(); ++j ) { Character & c2 = line.character( j ); if( !c1.h_overlaps( c2 ) ) continue; Character *cup, *cdn; if( c1.vcenter() < c2.vcenter() ) cup = &c1, cdn = &c2; else cup = &c2, cdn = &c1; if( cdn->includes_hcenter( *cup ) || cup->includes_hcenter( *cdn ) || ( cdn->top() > cup->bottom() && cdn->hcenter() < cup->hcenter() ) || ( cdn->blobs() == 2 && 2 * cdn->blob( 0 ).size() < cdn->blob( 1 ).size() && cdn->blob( 0 ).includes_vcenter( *cup ) ) ) { int k; if( 64 * c1.size() < c2.main_blob().size() ) k = i; else if( 64 * c2.size() < c1.main_blob().size() ) k = j; else if( cdn == &c2 ) { c2.join( c1 ); k = i; } else { c1.join( c2 ); k = j; } line.delete_character( k ); joined = true; break; } } if( !joined ) ++i; } } } } // end namespace void Textblock::apply_filters( const Control & control ) { if( textlines() <= 0 ) return; for( unsigned f = 0; f < control.filters.size(); ++f ) { if( control.filters[f].user_filterp ) { for( int i = 0; i < textlines(); ++i ) tlpv[i]->apply_user_filter( *control.filters[f].user_filterp ); continue; } const Filter::Type filter = control.filters[f].type; if( filter != Filter::text_block ) { for( int i = 0; i < textlines(); ++i ) tlpv[i]->apply_filter( filter ); continue; } int l = right(), t = bottom(), r = left(), b = top(); for( int i = 0; i < textlines(); ++i ) { const Textline & line = *tlpv[i]; int first = line.characters(), last = -1; for( int j = line.big_initials(); j < line.characters(); ++j ) if( line.is_key_character( j ) ) { l = std::min( l, line.character(j).left() ); first = j; break; } for( int j = line.characters() - 1; j >= first; --j ) if( line.is_key_character( j ) ) { r = std::max( r, line.character(j).right() ); last = j; break; } if( i == 0 ) { for( int j = first; j <= last; ++j ) if( line.is_key_character( j ) ) t = std::min( t, line.character(j).top() ); } else if( i == textlines() - 1 ) { for( int j = first; j <= last; ++j ) if( line.is_key_character( j ) ) b = std::max( b, line.character(j).bottom() ); } } if( r < l || b < t ) continue; // can't apply filter; no text Rectangle re( l, t, r, b ); for( int i = 0; i < textlines(); ++i ) { Textline & line = *tlpv[i]; bool modified = false; for( int j = line.characters() - 1; j >= 0; --j ) if( line.character(j).height() >= 2 * line.height() || !re.includes( line.character(j).vcenter(), line.character(j).hcenter() ) ) { line.delete_character( j ); modified = true; } if( modified ) line.remove_leadind_trailing_duplicate_spaces(); } } } Textblock::Textblock( const int page_height, const Rectangle & block, std::vector< Blob * > & blobp_vector ) : Rectangle( block ) { std::vector< Blob * > pending; std::vector< Blob * > pending_tall; std::vector< Blob * > pending_short; for( unsigned begin = 0, end = 0; end < blobp_vector.size(); begin = end ) { int botmax = blobp_vector[begin]->bottom(); // make cuts while( ++end < blobp_vector.size() ) { if( blobp_vector[end]->top() > botmax ) break; botmax = std::max( botmax, blobp_vector[end]->bottom() ); } // Classify blobs by height. unsigned samples = 0; std::vector< int > height_distrib; for( unsigned i = begin; i < end; ++i ) { if( blobp_vector[i]->is_abnormal() ) continue; unsigned h = blobp_vector[i]->height(); if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } if( height_distrib.empty() ) // all blobs are abnormal for( unsigned i = begin; i < end; ++i ) { unsigned h = blobp_vector[i]->height(); if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } int mean_height = 0; int valid_samples = 0; for( unsigned i = 0, count = 0; i < height_distrib.size(); ++i ) { int a = height_distrib[i]; if( 10 * ( count + a ) >= samples && 10 * count < 9 * samples ) { mean_height += a * i; valid_samples += a; } count += a; } if( valid_samples ) mean_height /= valid_samples; for( unsigned i = begin; i < end; ++i ) { Blob * const p = blobp_vector[i]; const bool a = p->is_abnormal(); if( p->height() >= 2 * mean_height || ( a && p->height() > mean_height ) ) pending_tall.push_back( p ); else if( 2 * p->height() <= mean_height || p->height() <= 5 || ( a && p->height() < mean_height ) ) pending_short.push_back( p ); else pending.push_back( p ); } } if( pending.empty() ) { for( unsigned i = 0; i < blobp_vector.size(); ++i ) delete blobp_vector[i]; blobp_vector.clear(); return; } blobp_vector.clear(); // Assign normal blobs to characters and create lines. { int min_line = 0; // first line of current cut tlpv.push_back( new Textline ); int current_line = min_line = textlines() - 1; tlpv[current_line]->shift_characterp( new Character( pending[0] ) ); for( unsigned i = 1; i < pending.size(); ++i ) { Blob & b = *pending[i]; current_line = std::max( min_line, current_line - 2 ); while( true ) { const Character *cl = 0, *cr = 0; for( int j = tlpv[current_line]->characters() - 1; j >= 0; --j ) { const Character & cj = tlpv[current_line]->character( j ); if( !b.includes_hcenter( cj ) && !cj.includes_hcenter( b ) ) { if( b.h_precedes( cj ) ) cr = &cj; else { cl = &cj; break; } } } if( ( cl && ( cl->includes_vcenter( b ) || b.includes_vcenter( *cl ) ) ) || ( cr && ( cr->includes_vcenter( b ) || b.includes_vcenter( *cr ) ) ) ) { tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } else if( ( cl && cl->top() > b.bottom() ) || ( cr && cr->top() > b.bottom() ) ) { insert_line( tlpv, current_line ); tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } else if( ( cl && cl->v_overlap_percent( b ) > 5 ) || ( cr && cr->v_overlap_percent( b ) > 5 ) ) { tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } else if( ++current_line >= textlines() ) { tlpv.push_back( new Textline ); current_line = textlines() - 1; tlpv[current_line]->shift_characterp( new Character( &b ) ); break; } } } for( int i = textlines() - 1; i >= 0; --i ) if( !tlpv[i]->characters() ) delete_line( tlpv, i ); join_characters( tlpv ); // Create tracks of lines. for( int i = 0; i < textlines(); ++i ) tlpv[i]->set_track(); // Insert tall blobs. // Seek up, then seek down, needed for slanted or curved lines. current_line = 0; for( unsigned i = 0; i < pending_tall.size(); ++i ) { Blob & b = *pending_tall[i]; while( current_line > 0 && b.bottom() < tlpv[current_line]->vcenter( b.hcenter() ) ) --current_line; while( current_line < textlines() && b.top() > tlpv[current_line]->vcenter( b.hcenter() ) ) ++current_line; if( current_line >= textlines() ) { --current_line; delete &b; continue; } Textline & l = *tlpv[current_line]; const int bi = l.big_initials(); const int mh = l.mean_height(); if( b.height() <= 3 * mh && ( b.height() <= 2 * mh || l.character( bi ).left() < b.left() ) ) l.shift_characterp( new Character( &b ) ); else if( !l.characters() || l.character( std::min( bi+1, l.characters() - 1 ) ).left() > b.hcenter() ) l.shift_characterp( new Character( &b ), true ); // big initial else delete &b; } // for( int i = 0; i < textlines(); ++i ) tlpv[i]->check_big_initials(); // Insert short blobs. // Seek up, then seek down, needed for slanted or curved lines. current_line = 0; for( unsigned i = 0; i < pending_short.size(); ++i ) { Blob & b = *pending_short[i]; while( current_line > 0 && b.bottom() < tlpv[current_line]->top( b.hcenter() ) ) --current_line; int temp = std::max( 0, current_line - 1 ); while( current_line < textlines() && b.top() > tlpv[current_line]->bottom( b.hcenter() ) ) ++current_line; if( current_line >= textlines() ) { const Textline & l = *tlpv[--current_line]; const Character *p = l.character_at( b.hcenter() ); if( b.top() > l.bottom( b.hcenter() ) + l.height() / 2 && ( !p || b.top() > p->bottom() + l.height() / 2 ) ) { delete &b; continue; } else temp = current_line; } if( current_line - temp > 1 ) temp = current_line - 1; if( current_line != temp && 2 * ( b.top() - tlpv[temp]->bottom( b.hcenter() ) ) < tlpv[current_line]->top( b.hcenter() ) - b.bottom() ) current_line = temp; tlpv[current_line]->shift_characterp( new Character( &b ) ); } // remove clipped lines at top or bottom of page if( textlines() > 2 ) { const Textline * lp = tlpv[textlines()-1]; for( int i = 0, c = 0; i < lp->characters(); ++i ) if( lp->character( i ).bottom() >= page_height - 1 && 2 * ++c >= lp->characters() ) { delete_line( tlpv, textlines() - 1 ); break; } lp = tlpv[0]; // page top == 0 for( int i = 0, c = 0; i < lp->characters(); ++i ) if( lp->character( i ).top() <= 1 && 2 * ++c >= lp->characters() ) { delete_line( tlpv, 0 ); break; } } } // Second pass. Join lines of i-dots and tildes. for( int current_line = 0; current_line < textlines() - 1; ) { bool joined = false; Textline & line1 = *tlpv[current_line]; Textline & line2 = *tlpv[current_line+1]; if( line1.characters() <= 2 * line2.characters() && 2 * line1.mean_height() < line2.mean_height() ) for( int i1 = 0; !joined && i1 < line1.characters(); ++i1 ) { Character & c1 = line1.character( i1 ); if( 2 * c1.height() >= line2.mean_height() ) continue; for( int i2 = 0; !joined && i2 < line2.characters(); ++i2 ) { Character & c2 = line2.character( i2 ); if( c2.right() < c1.left() ) continue; if( c2.left() > c1.right() ) break; if( ( c2.includes_hcenter( c1 ) || c1.includes_hcenter( c2 ) ) && c2.top() - c1.bottom() < line2.mean_height() ) { joined = true; line2.join( line1 ); delete_line( tlpv, current_line ); } } } if( !joined ) ++current_line; } join_characters( tlpv ); for( int i = 0; i < textlines(); ++i ) tlpv[i]->check_big_initials(); // Fourth pass. Remove noise lines. if( textlines() >= 3 ) { for( int i = 0; i + 2 < textlines(); ++i ) { Textline & line1 = *tlpv[i]; Textline & line2 = *tlpv[i+1]; Textline & line3 = *tlpv[i+2]; if( line2.characters() > 2 || line1.characters() < 4 || line3.characters() < 4 ) continue; if( !Ocrad::similar( line1.height(), line3.height(), 10 ) ) continue; if( 8 * line2.height() > line1.height() + line3.height() ) continue; delete_line( tlpv, i + 1 ); } } // Remove leading and trailing noise characters. for( int i = 0; i < textlines(); ++i ) { Textline & l = *tlpv[i]; if( !l.big_initials() && l.characters() > 2 ) { const Character & c0 = l.character( 0 ); const Character & c1 = l.character( 1 ); const Character & c2 = l.character( 2 ); if( c0.blobs() == 1 && 4 * c0.size() < c1.size() && c1.left() - c0.right() > 2 * l.height() && 4 * c0.size() < c2.size() && c2.left() - c1.right() < l.height() ) l.delete_character( 0 ); } if( l.characters() > 2 ) { const Character & c0 = l.character( l.characters() - 1 ); const Character & c1 = l.character( l.characters() - 2 ); const Character & c2 = l.character( l.characters() - 3 ); if( c0.blobs() == 1 && 4 * c0.size() < c1.size() && c0.left() - c1.right() > 2 * l.height() && 4 * c0.size() < c2.size() && c1.left() - c2.right() < l.height() ) l.delete_character( l.characters() - 1 ); } } for( int i = 0; i < textlines(); ++i ) tlpv[i]->insert_spaces(); } Textblock::~Textblock() { for( int i = textlines() - 1; i >= 0; --i ) delete tlpv[i]; } void Textblock::recognize( const Control & control ) { // Recognize characters. for( int i = 0; i < textlines(); ++i ) { // First pass. Recognize the easy characters. tlpv[i]->recognize1( control.charset ); // Second pass. Use context to clear up ambiguities. tlpv[i]->recognize2( control.charset ); } apply_filters( control ); // Remove unrecognized lines. for( int i = textlines() - 1; i >= 0; --i ) { Textline & line1 = *tlpv[i]; bool recognized = false; for( int j = 0 ; j < line1.characters(); ++j ) { if( line1.character( j ).guesses() ) { recognized = true; break; } } if( !recognized ) delete_line( tlpv, i ); } // Add blank lines. if( textlines() >= 3 ) { int min_vdistance = ( tlpv.back()->mean_vcenter() - tlpv.front()->mean_vcenter() ) / ( textlines() - 1 ); for( int i = 0; i + 1 < textlines(); ++i ) { const Textline & line1 = *tlpv[i]; const Textline & line2 = *tlpv[i+1]; if( !Ocrad::similar( line1.characters(), line2.characters(), 50 ) || !Ocrad::similar( line1.width(), line2.width(), 30 ) ) continue; const int vdistance = line2.mean_vcenter() - line1.mean_vcenter(); if( vdistance >= min_vdistance ) continue; const int mh1 = line1.mean_height(), mh2 = line2.mean_height(); if( mh1 < 10 || mh2 < 10 ) continue; if( Ocrad::similar( mh1, mh2, 20 ) && 2 * vdistance > mh1 + mh2 ) min_vdistance = vdistance; } if( min_vdistance > 0 ) for( int i = 0; i + 1 < textlines(); ++i ) { const Textline & line1 = *tlpv[i]; const Textline & line2 = *tlpv[i+1]; int vdistance = line2.mean_vcenter() - line1.mean_vcenter() - min_vdistance; while( 2 * vdistance > min_vdistance ) { insert_line( tlpv, ++i ); vdistance -= min_vdistance; } } } } const Textline & Textblock::textline( const int i ) const { if( i < 0 || i >= textlines() ) throw Ocrad::Internal( "textline: index out of bounds." ); return *tlpv[i]; } int Textblock::characters() const { int total = 0; for( int i = 0; i < textlines(); ++i ) total += tlpv[i]->characters(); return total; } void Textblock::print( const Control & control ) const { for( int i = 0; i < textlines(); ++i ) tlpv[i]->print( control ); std::fputc( '\n', control.outfile ); } void Textblock::dprint( const Control & control, bool graph, bool recursive ) const { std::fprintf( control.outfile, "%d lines\n\n", textlines() ); for( int i = 0; i < textlines(); ++i ) { std::fprintf( control.outfile, "%d characters in line %d\n", tlpv[i]->characters(), i + 1 ); tlpv[i]->dprint( control, graph, recursive ); } std::fputc( '\n', control.outfile ); } void Textblock::xprint( const Control & control ) const { std::fprintf( control.exportfile, "lines %d\n", textlines() ); for( int i = 0; i < textlines(); ++i ) { std::fprintf( control.exportfile, "line %d chars %d height %d\n", i + 1, tlpv[i]->characters(), tlpv[i]->mean_height() ); tlpv[i]->xprint( control ); } } void Textblock::cmark( Page_image & page_image ) const { for( int i = 0; i < textlines(); ++i ) tlpv[i]->cmark( page_image ); } void Textblock::lmark( Page_image & page_image ) const { for( int i = 0; i < textlines(); ++i ) page_image.draw_track( *tlpv[i] ); } ocrad-0.29/iso_8859.cc0000644000175000017500000000513414545576754014257 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "iso_8859.h" /* 'seq[i]' begins a escape sequence (the characters following a '\'). Return the corresponding code and, in '*lenp', the number of characters read. Return -1 if error. */ int ISO_8859::escape( const std::string & seq, const unsigned i, int *lenp ) { if( i >= seq.size() ) return -1; int len = 1; unsigned ch = seq[i]; switch( ch ) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'e': ch = 27; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': ch -= '0'; while( len < 3 && i + len < seq.size() && ISO_8859::isodigit( seq[i+len] ) ) { ch <<= 3; ch += seq[i+len] - '0'; ++len; } if( ch > 255 ) return -1; break; case 'x': case 'X': if( i + 2 >= seq.size() || ISO_8859::xvalue( seq[i+1] ) < 0 || ISO_8859::xvalue( seq[i+2] ) < 0 ) return -1; ch = ( ISO_8859::xvalue( seq[i+1] ) << 4 ) + ISO_8859::xvalue( seq[i+2] ); len = 3; break; } if( lenp ) *lenp = len; return ch; } int ISO_8859::xvalue( const unsigned char ch ) { switch( ch ) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 0x0A; case 'b': case 'B': return 0x0B; case 'c': case 'C': return 0x0C; case 'd': case 'D': return 0x0D; case 'e': case 'E': return 0x0E; case 'f': case 'F': return 0x0F; default: return -1; } } ocrad-0.29/textline.cc0000644000175000017500000002623714545576754014633 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "common.h" #include "histogram.h" #include "rational.h" #include "rectangle.h" #include "track.h" #include "ucs.h" #include "user_filter.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" namespace { // return the character position >= first preceding a big gap or eol // int find_big_gap( const Textline & line, const int first, const int space_width_limit ) { int i = first; while( i + 1 < line.characters() ) { const Character & c1 = line.character( i ); const Character & c2 = line.character( i + 1 ); const int gap = c2.left() - c1.right() - 1; if( gap > space_width_limit ) break; else ++i; } return i; } } // end namespace Textline::Textline( const Textline & tl ) : Track( tl ), big_initials_( tl.big_initials_ ) { cpv.reserve( tl.cpv.size() ); for( unsigned i = 0; i < tl.cpv.size(); ++i ) cpv.push_back( new Character( *tl.cpv[i] ) ); } Textline & Textline::operator=( const Textline & tl ) { if( this != &tl ) { Track::operator=( tl ); big_initials_ = tl.big_initials_; for( unsigned i = 0; i < cpv.size(); ++i ) delete cpv[i]; cpv.clear(); cpv.reserve( tl.cpv.size() ); for( unsigned i = 0; i < tl.cpv.size(); ++i ) cpv.push_back( new Character( *tl.cpv[i] ) ); } return *this; } Textline::~Textline() { for( unsigned i = 0; i < cpv.size(); ++i ) delete cpv[i]; } void Textline::set_track() { std::vector< Rectangle > rv; for( unsigned i = big_initials_; i < cpv.size(); ++i ) if( !cpv[i]->maybe(' ') ) rv.push_back( *cpv[i] ); Track::set_track( rv ); } void Textline::check_big_initials() { while( big_initials_ > 0 && cpv[big_initials_-1]->height() <= 2 * mean_height() ) --big_initials_; } Character & Textline::character( const int i ) const { if( i < 0 || i >= characters() ) throw Ocrad::Internal( "character: index out of bounds." ); return *cpv[i]; } Character * Textline::character_at( const int col ) const { for( int i = 0; i < characters(); ++i ) if( cpv[i]->h_includes( col ) ) return cpv[i]; return 0; } Rectangle Textline::charbox( const Character & c ) const { return Rectangle( c.left(), top( c.hcenter() ), c.right(), bottom( c.hcenter() ) ); } bool Textline::is_key_character( const int i ) const { if( i < big_initials_ || i >= characters() ) throw Ocrad::Internal( "is_key_character: index out of bounds." ); return ( cpv[i]->isalnum() && cpv[i]->guess( 0 ).code != 'J' && cpv[i]->height() < 2 * height() && 2 * cpv[i]->height() > height() ); } void Textline::delete_character( const int i ) { if( i < 0 || i >= characters() ) throw Ocrad::Internal( "delete_character: index out of bounds." ); if( i < big_initials_ ) --big_initials_; delete cpv[i]; cpv.erase( cpv.begin() + i ); } int Textline::shift_characterp( Character * const p, const bool big ) { int i = characters(); while( i > 0 && p->h_precedes( *cpv[i-1] ) ) --i; cpv.insert( cpv.begin() + i, p ); if( i < big_initials_ ) ++big_initials_; else if( big ) big_initials_ = i + 1; return i; } bool Textline::insert_space( const int i, const bool tab ) { if( i <= 0 || i >= characters() ) throw Ocrad::Internal( "insert_space: index out of bounds." ); if( !height() ) throw Ocrad::Internal( "insert_space: track not set yet." ); Character & c1 = *cpv[i-1]; Character & c2 = *cpv[i]; int l = c1.right() + 1; int r = c2.left() - 1; if( l > r ) return false; int t = top( ( l + r ) / 2 ); int b = bottom( ( l + r ) / 2 ); Rectangle re( l, t, r, b ); Character * const p = new Character( re, ' ', tab ? 1 : 0 ); if( tab ) p->add_guess( '\t', 0 ); cpv.insert( cpv.begin() + i, p ); return true; } // insert spaces between characters // void Textline::insert_spaces() { const Rational mw = mean_width(); if( mw < 2 ) return; const int mwt = mw.trunc(); const int space_width_limit = ( 3 * mw ).trunc(); int first = big_initials_; while( first + 1 < characters() ) { int last = find_big_gap( *this, first, space_width_limit ); const Rational mg = mean_gap_width( first, last ); if( first < last && mg >= 0 ) { int spaces = 0, nospaces = 0, spsum = 0, nospsum = 0; for( int i = first ; i < last; ++i ) { const Character & c1 = character( i ); const Character & c2 = character( i + 1 ); const int gap = c2.left() - c1.right() - 1; if( gap >= mwt || gap > 3 * mg || ( 5 * gap > 2 * mw && gap > 2 * mg ) || ( 3 * c1.width() > 2 * mw && 3 * c2.width() > 2 * mw && 2 * gap > mw && 5 * gap > 8 * mg ) ) { ++spaces; spsum += gap; if( insert_space( i + 1 ) ) { ++i; ++last; } } else { ++nospaces; nospsum += gap; } } if( spaces && nospaces ) { const Rational th = ( Rational( 3 * spsum, spaces ) + Rational( nospsum, nospaces ) ) / 4; for( int i = first ; i < last; ++i ) { const Character & c1 = character( i ); const Character & c2 = character( i + 1 ); const int gap = c2.left() - c1.right() - 1; if( gap > th && insert_space( i + 1 ) ) { ++i; ++last; } } } } if( ++last < characters() && insert_space( last, true ) ) ++last; first = last; } } void Textline::join( Textline & tl ) { for( int i = 0; i < tl.characters(); ++i ) shift_characterp( tl.cpv[i], i < tl.big_initials_ ); tl.big_initials_ = 0; tl.cpv.clear(); } int Textline::mean_height() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) if( !cpv[i]->maybe(' ') ) { ++c; sum += cpv[i]->height(); } if( c ) sum /= c; return sum; } Rational Textline::mean_width() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) if( !cpv[i]->maybe(' ') ) { ++c; sum += cpv[i]->width(); } if( c ) return Rational( sum, c ); return Rational( 0 ); } Rational Textline::mean_gap_width( const int first, int last ) const { if( last < 0 ) last = characters() - 1; int sum = 0; for( int i = first; i < last; ++i ) sum += std::max( 0, cpv[i+1]->left() - cpv[i]->right() - 1 ); if( last > first ) return Rational( sum, last - first ); return Rational( 0 ); } int Textline::mean_hcenter() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) { ++c; sum += cpv[i]->hcenter(); } if( c ) sum /= c; return sum; } int Textline::mean_vcenter() const { int c = 0, sum = 0; for( int i = big_initials_; i < characters(); ++i ) { ++c; sum += cpv[i]->vcenter(); } if( c ) sum /= c; return sum; } void Textline::print( const Control & control ) const { for( int i = 0; i < characters(); ++i ) character( i ).print( control ); std::fputc( '\n', control.outfile ); } void Textline::dprint( const Control & control, const bool graph, const bool recursive ) const { if( graph || recursive ) { Histogram hist; for( int i = 0; i < characters(); ++i ) if( !character(i).maybe(' ') ) hist.add_sample( character(i).height() ); std::fprintf( control.outfile, "mean height = %d, median height = %d, track segments = %d, big initials = %d\n", mean_height(), hist.median(), segments(), big_initials_ ); } for( int i = 0; i < characters(); ++i ) { const Character & c = character( i ); if( i < big_initials_ ) c.dprint( control, c, graph, recursive ); else c.dprint( control, charbox( c ), graph, recursive ); } std::fputc( '\n', control.outfile ); } void Textline::xprint( const Control & control ) const { for( int i = 0; i < characters(); ++i ) character( i ).xprint( control ); } void Textline::cmark( Page_image & page_image ) const { for( int i = 0; i < characters(); ++i ) page_image.draw_rectangle( character( i ) ); } void Textline::recognize1( const Charset & charset ) const { for( int i = 0; i < characters(); ++i ) { Character & c = character( i ); if( i < big_initials_ ) { c.recognize1( charset, c ); if( c.guesses() ) { const int code = c.guess( 0 ).code; if( UCS::islower_ambiguous( code ) ) c.only_guess( UCS::toupper( code ), 0 ); } } else c.recognize1( charset, charbox( c ) ); } } void Textline::apply_filter( const Filter::Type filter ) { bool modified = false; if( filter == Filter::same_height ) { Histogram hist; for( int i = 0; i < characters(); ++i ) if( !character(i).maybe(' ') ) hist.add_sample( character(i).height() ); const int median_height = hist.median(); for( int i = characters() - 1; i >= 0; --i ) if( !character(i).maybe(' ') && !Ocrad::similar( character(i).height(), median_height, 10, 2 ) ) { delete_character( i ); modified = true; } } else { for( int i = characters() - 1; i >= 0; --i ) { Character & c = character( i ); if( !c.guesses() ) continue; c.apply_filter( filter ); if( !c.guesses() && filter != Filter::upper_num_mark ) { delete_character( i ); modified = true; } } if( filter == Filter::upper_num_mark ) join_broken_unrecognized_characters(); } if( modified ) remove_leadind_trailing_duplicate_spaces(); } void Textline::apply_user_filter( const User_filter & user_filter ) { bool modified = false; for( int i = characters() - 1; i >= 0; --i ) { Character & c = character( i ); if( !c.guesses() ) continue; c.apply_user_filter( user_filter ); if( !c.guesses() && user_filter.discard() ) { delete_character( i ); modified = true; } } if( user_filter.mark() ) join_broken_unrecognized_characters(); if( modified ) remove_leadind_trailing_duplicate_spaces(); } void Textline::join_broken_unrecognized_characters() { for( int i = characters() - 1; i > 0; --i ) if( !character(i).guesses() && character(i).h_overlaps( character( i - 1 ) ) ) delete_character( i ); } void Textline::remove_leadind_trailing_duplicate_spaces() { for( int i = characters() - 1; i >= 0; --i ) if( character(i).maybe(' ') && ( i == 0 || i == characters() - 1 || character(i-1).maybe(' ') ) ) delete_character( i ); } ocrad-0.29/png_io.cc0000644000175000017500000003015114545576754014240 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2022-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include // uint8_t #include #include "common.h" #include "page_image.h" #if PNG_LIBPNG_VER < 10209 #error "Wrong libpng version. At least 1.2.9 is required." #endif /* Check whether a file is a PNG file by using png_sig_cmp(). * png_sig_cmp() returns zero if the image is a PNG, and nonzero otherwise. * * If this call is successful, and you are going to keep the file open, * you should call png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK); once * you have created the png_ptr, so that libpng knows your application * has read that many bytes from the start of the file. */ bool read_check_png_sig8( FILE * const f, const int first_byte ) { enum { sig_size = 8 }; png_byte buf[sig_size]; const unsigned i = first_byte >= 0 && first_byte <= 0xFF; if( i ) buf[0] = first_byte; // read the signature bytes if( std::fread( buf + i, 1, sig_size - i, f ) != sig_size - i ) return false; return !png_sig_cmp( buf, 0, sig_size ); } /* Create a Page_image with elements in range [0, maxval] from a PNG file. 'sig_read' indicates the number of magic bytes already read (maybe 0). */ void Page_image::read_png( FILE * const f, const int sig_read, const bool invert ) { /* Create and initialize the png_struct with the desired error handler * functions. If you want to use the default stderr and longjump method, * you can supply NULL for the last three parameters. We also supply the * compiler header file version, so that we know if the application * was compiled with a compatible version of the library. REQUIRED. */ png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); if( !png_ptr ) throw std::bad_alloc(); /* Allocate/initialize the memory for image information. REQUIRED. */ png_infop info_ptr = png_create_info_struct( png_ptr ); if( !info_ptr ) { png_destroy_read_struct( &png_ptr, NULL, NULL ); // avoid memory leak throw std::bad_alloc(); } if( setjmp( png_jmpbuf( png_ptr ) ) ) // Set error handling { /* Free all of the memory associated with the png_ptr and info_ptr. */ png_destroy_read_struct( &png_ptr, &info_ptr, NULL ); throw Error( "Error reading PNG image." ); } /* Set up the input control if you are using standard C streams. */ png_init_io( png_ptr, f ); /* If we have already read some of the signature */ png_set_sig_bytes( png_ptr, sig_read ); /* Read the entire image (including pixels) into the info structure with a call to png_read_png, equivalent to png_read_info(), followed by the set of transformations indicated by the transform mask, then png_read_image(), and finally png_read_end(): PNG_TRANSFORM_PACKING does not expand 1-bit samples to bytes. (It seems to do nothing). */ png_read_png( png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_EXPAND, NULL ); /* At this point you have read the entire image. Now let's extract the data. */ const int rows = png_get_image_height( png_ptr, info_ptr ); const int cols = png_get_image_width( png_ptr, info_ptr ); test_size( rows, cols ); const unsigned bit_depth = png_get_bit_depth( png_ptr, info_ptr ); const unsigned maxval = ( 1 << bit_depth ) - 1; const unsigned color_type = png_get_color_type( png_ptr, info_ptr ); const unsigned channels = png_get_channels( png_ptr, info_ptr ); /* bit_depth - holds the bit depth of one of the image channels. (valid values are 1, 2, 4, 8, 16 and depend also on color_type. color_type - describes which color/alpha channels are present. PNG_COLOR_TYPE_GRAY (bit depths 1, 2, 4, 8, 16) PNG_COLOR_TYPE_GRAY_ALPHA (bit depths 8, 16) PNG_COLOR_TYPE_PALETTE (bit depths 1, 2, 4, 8) PNG_COLOR_TYPE_RGB (bit_depths 8, 16) PNG_COLOR_TYPE_RGB_ALPHA (bit_depths 8, 16) PNG_COLOR_MASK_PALETTE PNG_COLOR_MASK_COLOR PNG_COLOR_MASK_ALPHA channels - number of channels of info for the color type (valid values are 1 (GRAY, PALETTE), 2 (GRAY_ALPHA), 3 (RGB), 4 (RGB_ALPHA or RGB + filler byte)) */ if( ( color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_RGB ) || ( channels != 1 && channels != 3 ) || bit_depth > 8 ) throw Error( "Unsupported type of PNG image." ); const png_bytepp row_pointers = png_get_rows( png_ptr, info_ptr ); data.resize( rows ); for( int row = 0; row < rows; ++row ) data[row].reserve( cols ); maxval_ = maxval; threshold_ = maxval_ / 2; if( channels == 1 ) for( int row = 0; row < rows; ++row ) { const png_byte * ptr = row_pointers[row]; for( int col = 0; col < cols; ++col ) { const uint8_t val = *ptr++; data[row].push_back( invert ? maxval - val : val ); } } else if( channels == 3 ) for( int row = 0; row < rows; ++row ) { const png_byte * ptr = row_pointers[row]; for( int col = 0; col < cols; ++col ) { const uint8_t r = *ptr++; const uint8_t g = *ptr++; const uint8_t b = *ptr++; uint8_t val; if( !invert ) val = std::min( r, std::min( g, b ) ); else val = maxval_ - std::max( r, std::max( g, b ) ); data[row].push_back( val ); } } /* Clean up after the read, and free any memory allocated. REQUIRED. */ png_destroy_read_struct( &png_ptr, &info_ptr, NULL ); if( verbosity >= 1 ) { std::fprintf( stderr, "file type is PNG %s\n", ( channels == 1 ) ? "greyscale" : "color" ); std::fprintf( stderr, "file size is %dw x %dh\n", width(), height() ); } } // write greyscale images with bit depths of 1 or 8 only // void Page_image::write_png( FILE * const f, const unsigned bit_depth ) const { if( bit_depth != 1 && bit_depth != 8 ) throw Error( "Invalid bit depth writing PNG image." ); /* row_bytes is the width x number of channels x ceil(bit_depth / 8) */ const unsigned row_bytes = width(); // width() * 1 * 1 png_byte * const png_pixels = (png_byte *)std::malloc( height() * row_bytes ); if( !png_pixels ) throw std::bad_alloc(); png_byte ** const row_pointers = (png_byte **)std::malloc( height() * sizeof row_pointers[0] ); if( !row_pointers ) { std::free( png_pixels ); throw std::bad_alloc(); } // fill png_pixels[] and set row_pointers int idx = 0; // index in png_pixels[] if( bit_depth == 1 ) for( int row = 0; row < height(); ++row ) for( int col = 0; col < width(); ++col ) png_pixels[idx++] = !get_bit( row, col ); else if( maxval_ == 1 ) // expand to bit_depth == 8 for( int row = 0; row < height(); ++row ) for( int col = 0; col < width(); ++col ) png_pixels[idx++] = data[row][col] ? 255 : 0; else // bit_depth == 8 for( int row = 0; row < height(); ++row ) for( int col = 0; col < width(); ++col ) png_pixels[idx++] = data[row][col]; for( int i = 0; i < height(); ++i ) row_pointers[i] = png_pixels + ( i * row_bytes ); /* Create and initialize the png_struct with the desired error handler * functions. If you want to use the default stderr and longjump method, * you can supply NULL for the last three parameters. We also check that * the library version is compatible with the one used at compile time, * in case we are using dynamically linked libraries. REQUIRED. */ png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); if( !png_ptr ) { std::free( row_pointers ); std::free( png_pixels ); throw std::bad_alloc(); } /* Allocate/initialize the image information data. REQUIRED. */ png_infop info_ptr = png_create_info_struct( png_ptr ); if( !info_ptr ) { png_destroy_write_struct( &png_ptr, NULL ); std::free( row_pointers ); std::free( png_pixels ); throw std::bad_alloc(); } /* Set up error handling. */ if( setjmp( png_jmpbuf( png_ptr ) ) ) { /* If we get here, we had a problem writing the file. */ png_destroy_write_struct( &png_ptr, &info_ptr ); std::free( row_pointers ); std::free( png_pixels ); throw std::bad_alloc(); } /* Set up the output control if you are using standard C streams. */ png_init_io( png_ptr, f ); png_set_IHDR( png_ptr, info_ptr, width(), height(), bit_depth, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE ); png_set_rows( png_ptr, info_ptr, row_pointers ); // write the PNG image png_write_png( png_ptr, info_ptr, PNG_TRANSFORM_PACKING, NULL ); /* Clean up after the write, and free any allocated memory. */ png_destroy_write_struct( &png_ptr, &info_ptr ); std::free( row_pointers ); std::free( png_pixels ); } void show_png_info( FILE * const f, const char * const input_filename, const int sig_read ) { if( verbosity >= 0 ) std::printf( "%s\n", input_filename ); png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); if( !png_ptr ) throw std::bad_alloc(); png_infop info_ptr = png_create_info_struct( png_ptr ); if( !info_ptr ) { png_destroy_read_struct( &png_ptr, NULL, NULL ); // avoid memory leak throw std::bad_alloc(); } if( setjmp( png_jmpbuf( png_ptr ) ) ) // Set error handling { /* Free all of the memory associated with the png_ptr and info_ptr. */ png_destroy_read_struct( &png_ptr, &info_ptr, NULL ); throw Page_image::Error( "Error reading PNG image." ); } /* Set up the input control if you are using standard C streams. */ png_init_io( png_ptr, f ); /* If we have already read some of the signature */ png_set_sig_bytes( png_ptr, sig_read ); png_read_info( png_ptr, info_ptr ); // read image info /* Now let's print the data. */ const unsigned height = png_get_image_height( png_ptr, info_ptr ); const unsigned width = png_get_image_width( png_ptr, info_ptr ); const long size = height * width; const unsigned bit_depth = png_get_bit_depth( png_ptr, info_ptr ); const unsigned color_type = png_get_color_type( png_ptr, info_ptr ); const unsigned channels = png_get_channels( png_ptr, info_ptr ); const unsigned interlace_type = png_get_interlace_type( png_ptr, info_ptr ); const char * ct; if( color_type == PNG_COLOR_TYPE_GRAY_ALPHA ) ct = "greyscale with alpha channel"; else if( color_type == PNG_COLOR_TYPE_GRAY ) ct = "greyscale"; else if( color_type == PNG_COLOR_TYPE_PALETTE ) ct = "colormap"; else if( color_type == PNG_COLOR_TYPE_RGB ) ct = "RGB"; else if( color_type == PNG_COLOR_TYPE_RGB_ALPHA ) ct = "RGB with alpha channel"; else if( color_type == PNG_COLOR_MASK_PALETTE ) ct = "mask colormap"; else if( color_type == PNG_COLOR_MASK_COLOR ) ct = "mask color"; else if( color_type == PNG_COLOR_MASK_ALPHA ) ct = "mask alpha"; else ct = "unknown color_type"; if( verbosity >= 0 ) std::printf( " PNG image %4u x %4u (%5.2f megapixels), " "%2u-bit %s, %u channel(s), %sinterlaced\n", width, height, size / 1000000.0, bit_depth, ct, channels, ( interlace_type == PNG_INTERLACE_NONE ) ? "non-" : "" ); png_destroy_read_struct( &png_ptr, &info_ptr, NULL ); } ocrad-0.29/textline.h0000644000175000017500000000467414545576754014476 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Page_image; class Rational; class Textline : public Track { int big_initials_; mutable std::vector< Character * > cpv; void check_lower_ambiguous(); public: Textline() : big_initials_( 0 ) {} Textline( const Textline & tl ); Textline & operator=( const Textline & tl ); ~Textline(); void set_track(); void check_big_initials(); int big_initials() const { return big_initials_; } Character & character( const int i ) const; Character * character_at( const int col ) const; int characters() const { return cpv.size(); } Rectangle charbox( const Character & c ) const; int width() const { return cpv.empty() ? 0 : cpv.back()->right() - cpv.front()->left(); } bool is_key_character( const int i ) const; // isalnum of normal size void delete_character( const int i ); int shift_characterp( Character * const p, const bool big = false ); bool insert_space( const int i, const bool tab = false ); void insert_spaces(); void join( Textline & tl ); int mean_height() const; Rational mean_width() const; Rational mean_gap_width( const int first = 0, int last = -1 ) const; int mean_hcenter() const; int mean_vcenter() const; void print( const Control & control ) const; void dprint( const Control & control, const bool graph, const bool recursive ) const; void xprint( const Control & control ) const; void cmark( Page_image & page_image ) const; void recognize1( const Charset & charset ) const; void recognize2( const Charset & charset ); void apply_filter( const Filter::Type filter ); void apply_user_filter( const User_filter & user_filter ); void join_broken_unrecognized_characters(); void remove_leadind_trailing_duplicate_spaces(); }; ocrad-0.29/AUTHORS0000644000175000017500000000032112413300201013456 0ustar andriusandriusGNU Ocrad was written by Antonio Diaz Diaz. Thanks to Klaas Freitag for his ideas about the ORF file feature. Thanks also to the many people who have contributed useful ideas, sample images, testing, etc... ocrad-0.29/textpage.cc0000644000175000017500000004105214545576754014610 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "mask.h" #include "track.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" #include "textblock.h" #include "textpage.h" namespace { struct Zone { Mask mask; std::vector< Blob * > blobp_vector; Zone( const Rectangle & re ) : mask( re ) {} void join( Zone & z ); }; void Zone::join( Zone & z ) { mask.add_mask( z.mask ); blobp_vector.insert( blobp_vector.end(), z.blobp_vector.begin(), z.blobp_vector.end() ); z.blobp_vector.clear(); } int blobs_in_page( const std::vector< Zone > & zone_vector ) { int sum = 0; for( unsigned i = 0; i < zone_vector.size(); ++i ) sum += zone_vector[i].blobp_vector.size(); return sum; } void bprint( const std::vector< Zone > & zone_vector, FILE * const outfile ) { // std::fprintf( outfile, "page size %dw x %dh\n", width(), height() ); std::fprintf( outfile, "total zones in page %d\n", (int)zone_vector.size() ); std::fprintf( outfile, "total blobs in page %d\n\n", blobs_in_page( zone_vector ) ); for( unsigned zindex = 0; zindex < zone_vector.size(); ++zindex ) { const Rectangle & r = zone_vector[zindex].mask; const std::vector< Blob * > & blobp_vector = zone_vector[zindex].blobp_vector; std::fprintf( outfile, "zone %d of %d\n", zindex + 1, (int)zone_vector.size() ); std::fprintf( outfile, "zone size %dw x %dh\n", r.width(), r.height() ); std::fprintf( outfile, "total blobs in zone %u\n\n", (unsigned)zone_vector[zindex].blobp_vector.size() ); for( unsigned i = 0; i < blobp_vector.size(); ++i ) blobp_vector[i]->print( outfile ); } } inline void join_blobs( std::vector< Blob * > & blobp_vector, std::vector< Blob * > & v1, std::vector< Blob * > & v2, Blob * p1, Blob * p2, int i ) { if( p1->top() > p2->top() ) { Blob * const temp = p1; p1 = p2; p2 = temp; std::replace( v2.begin(), v2.begin() + ( i + 1 ), p2, p1 ); } else std::replace( v1.begin() + i, v1.end(), p2, p1 ); i = blobp_vector.size(); while( --i >= 0 && blobp_vector[i] != p2 ) ; if( i < 0 ) throw Ocrad::Internal( "join_blobs: lost blob." ); blobp_vector.erase( blobp_vector.begin() + i ); p1->add_bitmap( *p2 ); delete p2; } void ignore_abnormal_blobs( std::vector< Blob * > & blobp_vector ) { for( unsigned i = blobp_vector.size(); i > 0; ) { Blob & b = *blobp_vector[--i]; if( b.height() > 35 * b.width() || b.width() > 25 * b.height() ) { delete blobp_vector[i]; blobp_vector.erase( blobp_vector.begin() + i ); } } } void ignore_small_blobs( std::vector< Blob * > & blobp_vector ) { int to = 0, blobs = blobp_vector.size(); for( int from = 0; from < blobs; ++from ) { Blob * const p = blobp_vector[from]; if( p->height() > 4 || p->width() > 4 || ( ( p->height() > 2 || p->width() > 2 ) && p->area() > 5 ) ) { blobp_vector[from] = blobp_vector[to]; blobp_vector[to] = p; ++to; } } if( to < blobs ) { for( int i = to; i < blobs; ++i ) delete blobp_vector[i]; blobp_vector.erase( blobp_vector.begin() + to, blobp_vector.end() ); } } void remove_top_bottom_noise( std::vector< Blob * > & blobp_vector ) { int blobs = blobp_vector.size(); for( int i = 0; i < blobs; ++i ) { Blob & b = *blobp_vector[i]; if( b.height() < 11 ) continue; int c = 0; for( int col = b.left(); col <= b.right(); ++col ) if( b.get_bit( b.top(), col ) && ++c > 1 ) break; if( c <= 1 ) b.top( b.top() + 1 ); c = 0; for( int col = b.left(); col <= b.right(); ++col ) if( b.get_bit( b.bottom(), col ) && ++c > 1 ) break; if( c <= 1 ) b.bottom( b.bottom() - 1 ); } } void remove_left_right_noise( std::vector< Blob * > & blobp_vector ) { int blobs = blobp_vector.size(); for( int i = 0; i < blobs; ++i ) { Blob & b = *blobp_vector[i]; if( b.width() < 6 ) continue; int c = 0; for( int row = b.top(); row <= b.bottom(); ++row ) if( b.get_bit( row, b.left() ) && ++c > 1 ) break; if( c <= 1 ) b.left( b.left() + 1 ); c = 0; for( int row = b.top(); row <= b.bottom(); ++row ) if( b.get_bit( row, b.right() ) && ++c > 1 ) break; if( c <= 1 ) b.right( b.right() - 1 ); } } void find_holes( std::vector< Zone > & zone_vector ) { for( unsigned zi = 0; zi < zone_vector.size(); ++zi ) { std::vector< Blob * > & blobp_vector = zone_vector[zi].blobp_vector; for( unsigned bvi = 0; bvi < blobp_vector.size(); ++bvi ) blobp_vector[bvi]->find_holes(); } } void ignore_wide_blobs( const Rectangle & re, std::vector< Blob * > & blobp_vector ) { for( unsigned i = 0; i < blobp_vector.size(); ) { Blob & b = *blobp_vector[i]; if( 2 * b.width() < re.width() ) { ++i; continue; } blobp_vector.erase( blobp_vector.begin() + i ); if( 4 * b.area() <= 3 * b.size() ) { int blobs = 0; for( unsigned j = i; j < blobp_vector.size(); ++j ) { if( blobp_vector[j]->top() > b.bottom() ) break; if( blobp_vector[j]->size() >= 16 ) ++blobs; } if( blobs <= b.size() / 400 ) { if( 4 * b.area() <= b.size() ) // thin grid or frame { delete &b; continue; } b.find_holes(); bool frame = false; if( b.holes() < std::min( b.height(), b.width() ) ) for( int j = 0; j < b.holes(); ++j ) { if( 4 * b.hole( j ).size() >= b.size() && 4 * b.hole( j ).area() >= b.size() ) { frame = true; break; } } if( frame ) { delete &b; continue; } } } // picture, not frame if( 5 * b.width() > 4 * re.width() && 5 * b.height() > 4 * re.height() ) { for( unsigned j = 0; j < blobp_vector.size(); ++j ) delete blobp_vector[j]; blobp_vector.clear(); delete &b; break; } for( unsigned j = blobp_vector.size(); j > i; ) { const Blob & b2 = *blobp_vector[--j]; if( b.includes( b2 ) ) { delete &b2; blobp_vector.erase( blobp_vector.begin() + j ); } } delete &b; } } int mean_blob_height( const std::vector< Blob * > & blobp_vector ) { int mean_height = 0; unsigned samples = 0; std::vector< int > height_distrib; for( unsigned i = 0; i < blobp_vector.size(); ++i ) { const unsigned h = blobp_vector[i]->height(); const unsigned w = blobp_vector[i]->width(); if( h < 10 || w >= 3 * h ) continue; if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } if( height_distrib.empty() ) for( unsigned i = 0; i < blobp_vector.size(); ++i ) { const unsigned h = blobp_vector[i]->height(); if( h >= height_distrib.size() ) height_distrib.resize( h + 1 ); ++height_distrib[h]; ++samples; } int valid_samples = 0; for( unsigned i = 0, count = 0; i < height_distrib.size(); ++i ) { const int a = height_distrib[i]; if( 10 * ( count + a ) >= samples && 10 * count < 9 * samples ) { mean_height += a * i; valid_samples += a; } count += a; } if( valid_samples ) mean_height /= valid_samples; return mean_height; } int analyse_layout( std::vector< Blob * > & blobp_vector, std::vector< Zone > & zone_vector ) { if( blobp_vector.empty() ) return 0; const int mean_height = mean_blob_height( blobp_vector ); zone_vector.push_back( Zone( *blobp_vector[0] ) ); zone_vector.back().blobp_vector.push_back( blobp_vector[0] ); for( unsigned i = 1; i < blobp_vector.size(); ++i ) { Blob & b = *blobp_vector[i]; if( b.height() > 10 * mean_height ) { delete &b; continue; } int first = -1; for( unsigned j = 0; j < zone_vector.size(); ++j ) { if( zone_vector[j].mask.distance( b ) < 2 * mean_height ) { if( first < 0 ) first = j; else { zone_vector[first].join( zone_vector[j] ); zone_vector.erase( zone_vector.begin() + j ); --j; } } } if( first >= 0 ) { zone_vector[first].mask.add_rectangle( b ); zone_vector[first].blobp_vector.push_back( &b ); } else { zone_vector.push_back( Zone( b ) ); zone_vector.back().blobp_vector.push_back( &b ); } } blobp_vector.clear(); // sort zone_vector int botmax = zone_vector.empty() ? 0 : zone_vector[0].mask.bottom(); std::vector< int > cut_index_vector; for( unsigned i = 1; i < zone_vector.size(); ++i ) { if( zone_vector[i].mask.top() > botmax ) cut_index_vector.push_back( i ); botmax = std::max( botmax, zone_vector[i].mask.bottom() ); } cut_index_vector.push_back( zone_vector.size() ); for( unsigned begin = 0, cut = 0; cut < cut_index_vector.size(); ++cut ) { const unsigned end = cut_index_vector[cut]; for( unsigned i = begin; i + 1 < end; ++i ) { unsigned first = i; for( unsigned j = i + 1; j < end; ++j ) if( zone_vector[j].mask.precedes( zone_vector[first].mask ) ) first = j; if( first != i ) std::swap( zone_vector[i], zone_vector[first] ); } bool join = ( end - begin > 1 ); for( unsigned i = begin; join && i < end; ++i ) if( zone_vector[i].blobp_vector.size() > 80 || zone_vector[i].mask.v_distance( zone_vector[begin].mask ) > zone_vector[i].mask.height() + zone_vector[begin].mask.height() ) join = false; for( unsigned i = begin; join && i < end; ++i ) if( zone_vector[i].mask.height() > 4 * mean_blob_height( zone_vector[i].blobp_vector ) ) join = false; if( join ) { for( unsigned i = begin + 1; i < end; ++i ) zone_vector[begin].join( zone_vector[i] ); zone_vector.erase( zone_vector.begin() + ( begin + 1 ), zone_vector.begin() + end ); for( unsigned i = cut; i < cut_index_vector.size(); ++i ) cut_index_vector[i] -= ( end - begin - 1 ); ++begin; } else begin = end; } return zone_vector.size(); } void scan_page( const Page_image & page_image, std::vector< Zone > & zone_vector, const int debug_level, const bool layout ) { const Rectangle re( page_image.height(), page_image.width() ); const int zthreshold = page_image.threshold(); std::vector< Blob * > blobp_vector; std::vector< Blob * > old_data( re.width(), (Blob *) 0 ); std::vector< Blob * > new_data( re.width(), (Blob *) 0 ); for( int row = re.top(); row <= re.bottom(); ++row ) { old_data.swap( new_data ); for( int col = re.left(); col <= re.right(); ++col ) { const int dcol = col - re.left(); if( !page_image.get_bit( row, col, zthreshold ) ) new_data[dcol] = 0; // white pixel else // black pixel { Blob *p; Blob *lp = ( (dcol > 0) ? new_data[dcol-1] : 0 ); Blob *ltp = ( (dcol > 0) ? old_data[dcol-1] : 0 ); Blob *tp = old_data[dcol]; Blob *rtp = ( (col < re.right()) ? old_data[dcol+1] : 0 ); if( lp ) { p = lp; p->add_point( row, col ); } else if( ltp ) { p = ltp; p->add_point( row, col ); } else if( tp ) { p = tp; p->add_point( row, col ); } else if( rtp ) { p = rtp; p->add_point( row, col ); } else { p = new Blob( col, row, col, row ); p->set_bit( row, col, true ); blobp_vector.push_back( p ); } new_data[dcol] = p; if( rtp && p != rtp ) join_blobs( blobp_vector, old_data, new_data, p, rtp, dcol ); } } } if( debug_level <= 99 && blobp_vector.size() > 3 ) { ignore_wide_blobs( re, blobp_vector ); ignore_small_blobs( blobp_vector ); ignore_abnormal_blobs( blobp_vector ); remove_top_bottom_noise( blobp_vector ); remove_left_right_noise( blobp_vector ); } if( layout && re.width() > 200 && re.height() > 200 && blobp_vector.size() > 3 ) { analyse_layout( blobp_vector, zone_vector ); if( debug_level <= 99 && zone_vector.size() > 1 ) for( unsigned i = 0; i < zone_vector.size(); ++i ) ignore_wide_blobs( zone_vector[i].mask, zone_vector[i].blobp_vector ); } else { zone_vector.push_back( Zone( re ) ); zone_vector.back().blobp_vector.swap( blobp_vector ); } find_holes( zone_vector ); } } // end namespace Textpage::Textpage( const Page_image & page_image, const char * const filename, const Control & control, const bool layout ) : name( filename ) { const int debug_level = control.debug_level; if( debug_level < 0 || debug_level > 100 ) return; std::vector< Zone > zone_vector; // layout zones scan_page( page_image, zone_vector, debug_level, layout ); if( verbosity >= 1 ) std::fprintf( stderr, "number of text blocks = %d\n", (int)zone_vector.size() ); if( debug_level >= 98 ) { if( control.outfile ) bprint( zone_vector, control.outfile ); return; } if( debug_level > 95 || ( debug_level > 89 && debug_level < 94 ) ) return; // build a Textblock for every zone with text for( unsigned i = 0; i < zone_vector.size(); ++i ) { Textblock * const tbp = new Textblock( page_image.height(), zone_vector[i].mask, zone_vector[i].blobp_vector ); if( tbp->textlines() && debug_level < 90 ) tbp->recognize( control ); if( tbp->textlines() ) tbpv.push_back( tbp ); else delete tbp; } if( debug_level == 0 ) return; if( !control.outfile ) return; if( debug_level >= 86 ) { bool graph = ( debug_level >= 88 ); bool recursive = ( debug_level & 1 ); for( int i = 0; i < textblocks(); ++i ) tbpv[i]->dprint( control, graph, recursive ); return; } if( debug_level > 77 ) return; if( debug_level >= 70 ) { Page_image tmp( page_image ); if( ( debug_level - 70 ) & 1 ) // mark zones for( unsigned i = 0; i < zone_vector.size(); ++i ) { if( debug_level == 71 ) tmp.draw_mask( zone_vector[i].mask ); else tmp.draw_rectangle( zone_vector[i].mask ); } if( ( debug_level - 70 ) & 2 ) // mark lines { for( int i = 0; i < textblocks(); ++i ) tbpv[i]->lmark( tmp ); } if( ( debug_level - 70 ) & 4 ) // mark characters { for( int i = 0; i < textblocks(); ++i ) tbpv[i]->cmark( tmp ); } tmp.save( control.outfile, control.filetype ); return; } } Textpage::~Textpage() { for( int i = textblocks() - 1; i >= 0; --i ) delete tbpv[i]; } const Textblock & Textpage::textblock( const int i ) const { if( i < 0 || i >= textblocks() ) throw Ocrad::Internal( "textblock: index out of bounds." ); return *(tbpv[i]); } int Textpage::textlines() const { int total = 0; for( int i = 0; i < textblocks(); ++i ) total += tbpv[i]->textlines(); return total; } int Textpage::characters() const { int total = 0; for( int i = 0; i < textblocks(); ++i ) total += tbpv[i]->characters(); return total; } void Textpage::print( const Control & control ) const { if( control.outfile ) for( int i = 0; i < textblocks(); ++i ) tbpv[i]->print( control ); } void Textpage::xprint( const Control & control ) const { if( !control.exportfile ) return; std::fprintf( control.exportfile, "source file %s\n", name.c_str() ); std::fprintf( control.exportfile, "total text blocks %d\n", textblocks() ); for( int i = 0; i < textblocks(); ++i ) { const Textblock & tb = *(tbpv[i]); std::fprintf( control.exportfile, "text block %d %d %d %d %d\n", i + 1, tb.left(), tb.top(), tb.width(), tb.height() ); tb.xprint( control ); } } ocrad-0.29/user_filter.h0000644000175000017500000000322714546345521015141 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2014-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class User_filter { public: // default action: discard, leave unmodified, mark as unrecognized enum Default { d_discard = 0, d_leave, d_mark }; private: struct Entry { int code; int new_code; Entry( const int c, const int nc ) : code( c ), new_code( nc ) {} }; std::vector< int > table1; // -1 or new_code of first 256 UCS chars std::vector< Entry > table2; // codes of UCS chars >= 256 std::string error_; int retval_; Default default_; bool enable_char( const int code, int new_code ); int parse_char( const std::string & line, unsigned &i ) const; public: explicit User_filter( const char * const file_name ); const std::string & error() const { return error_; } int retval() const { return retval_; } int get_new_code( const int code ) const; // -1 means disabled bool discard() const { return default_ == d_discard; } bool mark() const { return default_ == d_mark; } }; ocrad-0.29/common.h0000644000175000017500000000515714546351562014114 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ extern int verbosity; namespace Ocrad { struct Internal { const char * const msg; explicit Internal( const char * const s ) : msg( s ) {} }; bool similar( const int a, const int b, const int percent_dif, const int abs_dif = 1 ); } // end namespace Ocrad class Charset { enum { charsets = 3 }; int charset_; public: enum Value { ascii = 1, iso_8859_9 = 2, iso_8859_15 = 4 }; Charset() : charset_( 0 ) {} void enable( const char * const arg, const char * const pn ); bool enabled( const Value cset ) const { return charset_ ? charset_ & cset : cset == iso_8859_15; } bool only( const Value cset ) const { return charset_ ? charset_ == cset : cset == iso_8859_15; } }; class Transformation { public: enum Type { none, rotate90, rotate180, rotate270, mirror_lr, mirror_tb, mirror_d1, mirror_d2 }; private: Type type_; public: Transformation() : type_( none ) {} void set( const char * const arg, const char * const pn ); Type type() const { return type_; } }; class User_filter; struct Filter { enum Type { letters, letters_only, numbers, numbers_only, same_height, text_block, upper_num, upper_num_mark, upper_num_only, user }; const User_filter * user_filterp; Type type; explicit Filter( const User_filter * p ) : user_filterp( p ), type( user ) {} explicit Filter( const Type t ) : user_filterp( 0 ), type( t ) {} }; struct Control { Charset charset; std::vector< Filter > filters; FILE * outfile, * exportfile; int debug_level; char filetype; bool utf8; Control() : outfile( stdout ), exportfile( 0 ), debug_level( 0 ), filetype( '4' ), utf8( false ) {} ~Control(); void add_filter( const char * const arg, const char * const pn ); void add_user_filter( const char * const file_name ); void set_format( const char * const arg, const char * const pn ); }; ocrad-0.29/page_image_io.cc0000644000175000017500000002356514545576754015545 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "common.h" #include "page_image.h" namespace { uint8_t pnm_getrawbyte( FILE * const f ) { int ch = std::fgetc( f ); if( ch == EOF ) throw Page_image::Error( "End-of-file reading pnm file." ); return ch; } uint8_t pnm_getc( FILE * const f ) { uint8_t ch = pnm_getrawbyte( f ); if( ch == '#' ) // comment { do ch = pnm_getrawbyte( f ); while( ch != '\n' ); } return ch; } int pnm_getint( FILE * const f ) { uint8_t ch; int i = 0; do ch = pnm_getc( f ); while( std::isspace( ch ) ); if( !std::isdigit( ch ) ) throw Page_image::Error( "Junk in pnm file where an integer should be." ); do { if( ( INT_MAX - (ch - '0') ) / 10 < i ) throw Page_image::Error( "Number too big in pnm file." ); i = (i * 10) + (ch - '0'); ch = pnm_getc( f ); } while( std::isdigit( ch ) ); return i; } uint8_t pbm_getbit( FILE * const f ) { uint8_t ch; do ch = pnm_getc( f ); while( std::isspace( ch ) ); if( ch == '0' ) return 0; if( ch == '1' ) return 1; throw Page_image::Error( "Junk in pbm file where bits should be." ); } } // end namespace void Page_image::test_size( const int rows, const int cols ) { if( rows < 3 || cols < 3 ) throw Error( "Image too small. Minimum size is 3x3." ); if( INT_MAX / rows < cols ) throw Error( "Image too big. 'int' will overflow." ); } void Page_image::read_p1( FILE * const f, const int cols, const bool invert ) { maxval_ = 1; threshold_ = 0; const int rows = height(); if( !invert ) for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) data[row].push_back( 1 - pbm_getbit( f ) ); else for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) data[row].push_back( pbm_getbit( f ) ); } void Page_image::read_p4( FILE * const f, const int cols, const bool invert ) { maxval_ = 1; threshold_ = 0; const int rows = height(); if( !invert ) for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ) { uint8_t byte = pnm_getrawbyte( f ); for( uint8_t mask = 0x80; mask > 0 && col < cols; mask >>= 1, ++col ) data[row].push_back( ( byte & mask ) ? 0 : 1 ); } else for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ) { uint8_t byte = pnm_getrawbyte( f ); for( uint8_t mask = 0x80; mask > 0 && col < cols; mask >>= 1, ++col ) data[row].push_back( ( byte & mask ) ? 1 : 0 ); } } void Page_image::read_p2( FILE * const f, const int cols, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Error( "Zero maxval in pgm file." ); maxval_ = std::min( maxval, 255 ); threshold_ = maxval_ / 2; const int rows = height(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { int val = pnm_getint( f ); if( val > maxval ) throw Error( "Pixel value > maxval in pgm file." ); if( invert ) val = maxval - val; if( maxval > 255 ) { val *= 255; val /= maxval; } data[row].push_back( val ); } } void Page_image::read_p5( FILE * const f, const int cols, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Error( "Zero maxval in pgm file." ); if( maxval > 255 ) throw Error( "maxval > 255 in pgm \"P5\" file." ); maxval_ = maxval; threshold_ = maxval_ / 2; const int rows = height(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { uint8_t val = pnm_getrawbyte( f ); if( val > maxval_ ) throw Error( "Pixel value > maxval in pgm file." ); if( invert ) val = maxval_ - val; data[row].push_back( val ); } } void Page_image::read_p3( FILE * const f, const int cols, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Error( "Zero maxval in ppm file." ); maxval_ = std::min( maxval, 255 ); threshold_ = maxval_ / 2; const int rows = height(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { const int r = pnm_getint( f ); // Red value const int g = pnm_getint( f ); // Green value const int b = pnm_getint( f ); // Blue value if( r > maxval || g > maxval || b > maxval ) throw Error( "Pixel value > maxval in ppm file." ); int val; if( !invert ) val = std::min( r, std::min( g, b ) ); else val = maxval - std::max( r, std::max( g, b ) ); if( maxval > 255 ) { val *= 255; val /= maxval; } data[row].push_back( val ); } } void Page_image::read_p6( FILE * const f, const int cols, const bool invert ) { const int maxval = pnm_getint( f ); if( maxval == 0 ) throw Error( "Zero maxval in ppm file." ); if( maxval > 255 ) throw Error( "maxval > 255 in ppm \"P6\" file." ); maxval_ = maxval; threshold_ = maxval_ / 2; const int rows = height(); for( int row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col ) { const uint8_t r = pnm_getrawbyte( f ); // Red value const uint8_t g = pnm_getrawbyte( f ); // Green value const uint8_t b = pnm_getrawbyte( f ); // Blue value if( r > maxval_ || g > maxval_ || b > maxval_ ) throw Error( "Pixel value > maxval in ppm file." ); uint8_t val; if( !invert ) val = std::min( r, std::min( g, b ) ); // darkest color else val = maxval_ - std::max( r, std::max( g, b ) ); // lightest color data[row].push_back( val ); } } /* Create a Page_image from a png, pbm, pgm, or ppm file. "P1" (pbm), "P4" (pbm RAWBITS), "P2" (pgm), "P5" (pgm RAWBITS), "P3" (ppm), "P6" (ppm RAWBITS) file formats are recognized. */ Page_image::Page_image( FILE * const f, const bool invert ) { unsigned char filetype = 0; const unsigned char first_byte = pnm_getrawbyte( f ); if( first_byte == 'P' ) { unsigned char ch = pnm_getrawbyte( f ); if( ch >= '1' && ch <= '6' ) filetype = ch; } else if( read_check_png_sig8( f, first_byte ) ) { read_png( f, 8, invert ); return; } if( filetype == 0 ) throw Error( "Bad magic number - not a png, pbm, pgm, or ppm file." ); const int cols = pnm_getint( f ); if( cols == 0 ) throw Error( "Zero width in pnm file." ); const int rows = pnm_getint( f ); if( rows == 0 ) throw Error( "Zero height in pnm file." ); test_size( rows, cols ); data.resize( rows ); for( int row = 0; row < rows; ++row ) data[row].reserve( cols ); switch( filetype ) { case '1': read_p1( f, cols, invert ); break; case '2': read_p2( f, cols, invert ); break; case '3': read_p3( f, cols, invert ); break; case '4': read_p4( f, cols, invert ); break; case '5': read_p5( f, cols, invert ); break; case '6': read_p6( f, cols, invert ); break; } if( verbosity >= 1 ) { std::fprintf( stderr, "file type is P%c\n", filetype ); std::fprintf( stderr, "file size is %dw x %dh\n", width(), height() ); } } bool Page_image::save( FILE * const f, const char filetype ) const { if( filetype == '7' ) { write_png( f, 1 ); return true; } if( filetype == '8' ) { write_png( f, 8 ); return true; } if( filetype < '1' || filetype > '6' ) return false; std::fprintf( f, "P%c\n%d %d\n", filetype, width(), height() ); if( filetype != '1' && filetype != '4' ) std::fprintf( f, "%d\n", maxval_ ); if( filetype == '1' ) // pbm for( int row = 0; row < height(); ++row ) { for( int col = 0; col < width(); ++col ) std::putc( get_bit( row, col ) ? '1' : '0', f ); std::putc( '\n', f ); } else if( filetype == '4' ) // pbm RAWBITS for( int row = 0; row < height(); ++row ) { uint8_t byte = 0, mask = 0x80; for( int col = 0; col < width(); ++col ) { if( get_bit( row, col ) ) byte |= mask; mask >>= 1; if( mask == 0 ) { std::putc( byte, f ); byte = 0; mask = 0x80; } } if( mask != 0x80 ) std::putc( byte, f ); // incomplete byte at end of row } else if( filetype == '2' ) // pgm for( int row = 0; row < height(); ++row ) { for( int col = 0; col < width() - 1; ++col ) std::fprintf( f, "%d ", data[row][col] ); std::fprintf( f, "%d\n", data[row][width()-1] ); } else if( filetype == '5' ) // pgm RAWBITS for( int row = 0; row < height(); ++row ) for( int col = 0; col < width(); ++col ) std::fprintf( f, "%c", data[row][col] ); else if( filetype == '3' ) // ppm for( int row = 0; row < height(); ++row ) { for( int col = 0; col < width() - 1; ++col ) { const uint8_t d = data[row][col]; std::fprintf( f, "%d %d %d ", d, d, d ); } const uint8_t d = data[row][width()-1]; std::fprintf( f, "%d %d %d\n", d, d, d ); } else if( filetype == '6' ) // ppm RAWBITS for( int row = 0; row < height(); ++row ) for( int col = 0; col < width(); ++col ) { const uint8_t d = data[row][col]; std::fprintf( f, "%c%c%c", d, d, d ); } return true; } ocrad-0.29/mask.cc0000644000175000017500000000753214545576754013727 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "rectangle.h" #include "segment.h" #include "mask.h" int Mask::left( const int row ) const { if( top() <= row && row <= bottom() && data[row-top()].valid() ) return data[row-top()].left; return -1; } int Mask::right( const int row ) const { if( top() <= row && row <= bottom() && data[row-top()].valid() ) return data[row-top()].right; return -1; } void Mask::top( const int t ) { if( t == top() ) return; if( t < top() ) data.insert( data.begin(), top() - t, Csegment() ); else data.erase( data.begin(), data.begin() + ( t - top() ) ); Rectangle::top( t ); } void Mask::bottom( const int b ) { if( b != bottom() ) { Rectangle::bottom( b ); data.resize( height() ); } } void Mask::add_mask( const Mask & m ) { if( m.top() < top() ) top( m.top() ); if( m.bottom() > bottom() ) bottom( m.bottom() ); for( int i = m.top(); i <= m.bottom(); ++i ) { Csegment & seg = data[i-top()]; seg.add_csegment( m.data[i-m.top()] ); if( seg.left < left() ) left( seg.left ); if( seg.right > right() ) right( seg.right ); } } void Mask::add_point( const int row, const int col ) { if( row < top() ) top( row ); else if( row > bottom() ) bottom( row ); data[row-top()].add_point( col ); if( col < left() ) left( col ); else if( col > right() ) right( col ); } void Mask::add_rectangle( const Rectangle & re ) { if( re.top() < top() ) top( re.top() ); if( re.bottom() > bottom() ) bottom( re.bottom() ); const Csegment rseg( re.left(), re.right() ); for( int i = re.top(); i <= re.bottom(); ++i ) { Csegment & seg = data[i-top()]; seg.add_csegment( rseg ); if( seg.left < left() ) left( seg.left ); if( seg.right > right() ) right( seg.right ); } } bool Mask::includes( const Rectangle & re ) const { if( re.top() < top() || re.bottom() > bottom() ) return false; const Csegment seg( re.left(), re.right() ); for( int i = re.top(); i <= re.bottom(); ++i ) if( !data[i-top()].includes( seg ) ) return false; return true; } bool Mask::includes( const int row, const int col ) const { return ( row >= top() && row <= bottom() && data[row-top()].includes( col ) ); } int Mask::distance( const Rectangle & re ) const { const Csegment seg( re.left(), re.right() ); int mindist = INT_MAX; for( int i = bottom(); i >= top(); --i ) { const int vd = re.v_distance( i ); if( vd >= mindist ) { if( i < re.top() ) break; else continue; } const int hd = data[i-top()].distance( seg ); if( hd >= mindist ) continue; const int d = Rectangle::hypoti( hd, vd ); if( d < mindist ) mindist = d; } return mindist; } int Mask::distance( const int row, const int col ) const { int mindist = INT_MAX; for( int i = bottom(); i >= top(); --i ) { const int vd = std::abs( i - row ); if( vd >= mindist ) { if( i < row ) break; else continue; } const int hd = data[i-top()].distance( col ); if( hd >= mindist ) continue; const int d = Rectangle::hypoti( hd, vd ); if( d < mindist ) mindist = d; } return mindist; } ocrad-0.29/textblock.h0000644000175000017500000000305314545576754014627 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Textblock : public Rectangle { mutable std::vector< Textline * > tlpv; Textblock( const Textblock & ); // declared as private void operator=( const Textblock & ); // declared as private void apply_filters( const Control & control ); public: Textblock( const int page_height, const Rectangle & block, std::vector< Blob * > & blobp_vector ); ~Textblock(); void recognize( const Control & control ); const Textline & textline( const int i ) const; int textlines() const { return tlpv.size(); } int characters() const; void print( const Control & control ) const; void dprint( const Control & control, bool graph, bool recursive ) const; void xprint( const Control & control ) const; void cmark( Page_image & page_image ) const; void lmark( Page_image & page_image ) const; }; ocrad-0.29/ucs.h0000644000175000017500000001521714545576754013427 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ namespace UCS { enum { IEXCLAM = 0x00A1, // inverted exclamation mark CENT = 0x00A2, // cent sign POUND = 0x00A3, // pound sign YEN = 0x00A5, // yen sign SECTION = 0x00A7, // section sign COPY = 0x00A9, // copyright sign FEMIORD = 0x00AA, // feminine ordinal indicator LDANGLE = 0x00AB, // left-pointing double angle quotation mark NOT = 0x00AC, // not sign REG = 0x00AE, // registered sign MACRON = 0x00AF, // macron DEG = 0x00B0, // degree sign PLUSMIN = 0x00B1, // plus-minus sign POW2 = 0x00B2, // superscript two POW3 = 0x00B3, // superscript three MICRO = 0x00B5, // micro sign PILCROW = 0x00B6, // pilcrow sign MIDDOT = 0x00B7, // middle dot POW1 = 0x00B9, // superscript one MASCORD = 0x00BA, // masculine ordinal indicator RDANGLE = 0x00BB, // right-pointing double angle quotation mark IQUEST = 0x00BF, // inverted question mark CAGRAVE = 0x00C0, // latin capital letter a with grave CAACUTE = 0x00C1, // latin capital letter a with acute CACIRCU = 0x00C2, // latin capital letter a with circumflex CATILDE = 0x00C3, // latin capital letter a with tilde CADIAER = 0x00C4, // latin capital letter a with diaeresis CARING = 0x00C5, // latin capital letter a with ring above CCCEDI = 0x00C7, // latin capital letter c with cedilla CEGRAVE = 0x00C8, // latin capital letter e with grave CEACUTE = 0x00C9, // latin capital letter e with acute CECIRCU = 0x00CA, // latin capital letter e with circumflex CEDIAER = 0x00CB, // latin capital letter e with diaeresis CIGRAVE = 0x00CC, // latin capital letter i with grave CIACUTE = 0x00CD, // latin capital letter i with acute CICIRCU = 0x00CE, // latin capital letter i with circumflex CIDIAER = 0x00CF, // latin capital letter i with diaeresis CNTILDE = 0x00D1, // latin capital letter n with tilde COGRAVE = 0x00D2, // latin capital letter o with grave COACUTE = 0x00D3, // latin capital letter o with acute COCIRCU = 0x00D4, // latin capital letter o with circumflex COTILDE = 0x00D5, // latin capital letter o with tilde CODIAER = 0x00D6, // latin capital letter o with diaeresis CUGRAVE = 0x00D9, // latin capital letter u with grave CUACUTE = 0x00DA, // latin capital letter u with acute CUCIRCU = 0x00DB, // latin capital letter u with circumflex CUDIAER = 0x00DC, // latin capital letter u with diaeresis CYACUTE = 0x00DD, // latin capital letter y with acute SSSHARP = 0x00DF, // latin small letter sharp s (german) SAGRAVE = 0x00E0, // latin small letter a with grave SAACUTE = 0x00E1, // latin small letter a with acute SACIRCU = 0x00E2, // latin small letter a with circumflex SATILDE = 0x00E3, // latin small letter a with tilde SADIAER = 0x00E4, // latin small letter a with diaeresis SARING = 0x00E5, // latin small letter a with ring above SCCEDI = 0x00E7, // latin small letter c with cedilla SEGRAVE = 0x00E8, // latin small letter e with grave SEACUTE = 0x00E9, // latin small letter e with acute SECIRCU = 0x00EA, // latin small letter e with circumflex SEDIAER = 0x00EB, // latin small letter e with diaeresis SIGRAVE = 0x00EC, // latin small letter i with grave SIACUTE = 0x00ED, // latin small letter i with acute SICIRCU = 0x00EE, // latin small letter i with circumflex SIDIAER = 0x00EF, // latin small letter i with diaeresis SNTILDE = 0x00F1, // latin small letter n with tilde SOGRAVE = 0x00F2, // latin small letter o with grave SOACUTE = 0x00F3, // latin small letter o with acute SOCIRCU = 0x00F4, // latin small letter o with circumflex SOTILDE = 0x00F5, // latin small letter o with tilde SODIAER = 0x00F6, // latin small letter o with diaeresis DIV = 0x00F7, // division sign SUGRAVE = 0x00F9, // latin small letter u with grave SUACUTE = 0x00FA, // latin small letter u with acute SUCIRCU = 0x00FB, // latin small letter u with circumflex SUDIAER = 0x00FC, // latin small letter u with diaeresis SYACUTE = 0x00FD, // latin small letter y with acute SYDIAER = 0x00FF, // latin small letter y with diaeresis CGBREVE = 0X011E, // latin capital letter g with breve SGBREVE = 0x011F, // latin small letter g with breve CIDOT = 0x0130, // latin capital letter i with dot above SINODOT = 0x0131, // latin small letter i dotless CLIGOE = 0x0152, // latin capital ligature oe SLIGOE = 0x0153, // latin small ligature oe CSCEDI = 0x015E, // latin capital letter s with cedilla SSCEDI = 0x015F, // latin small letter s with cedilla CSCARON = 0x0160, // latin capital letter s with caron SSCARON = 0x0161, // latin small letter s with caron CYDIAER = 0x0178, // latin capital letter y with diaeresis CZCARON = 0x017D, // latin capital letter z with caron SZCARON = 0x017E, // latin small letter z with caron EURO = 0x20AC // symbole euro }; int base_letter( const int code ); int compose( const int letter, const int accent ); bool isalnum( const int code ); bool isalpha( const int code ); inline bool isdigit( const int code ) { return ( code <= '9' && code >= '0' ); } bool ishigh( const int code ); // high chars like "A1bp|" bool islower( const int code ); bool islower_ambiguous( const int code ); bool islower_small( const int code ); bool islower_small_ambiguous( const int code ); bool isspace( const int code ); bool isupper( const int code ); bool isupper_normal_width( const int code ); bool isvowel( int code ); unsigned char map_to_byte( const int code ); int map_to_ucs( const unsigned char ch ); // ISO-8859-15 to UCS const char * ucs_to_utf8( const int code ); int to_nearest_digit( const int code ); int to_nearest_letter( const int code ); int to_nearest_upper_num( const int code ); int toupper( const int code ); } // end namespace UCS ocrad-0.29/character_r11.cc0000644000175000017500000003435314551070110015360 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" // first attempt at recognition without relying on context // void Character::recognize1( const Charset & charset, const Rectangle & charbox ) { if( blobs() == 1 ) { const Blob & b = blob( 0 ); if( b.holes() == 0 ) recognize110( charset, charbox ); else if( b.holes() == 1 ) recognize111( charset, charbox ); else if( b.holes() == 2 ) recognize112( charbox ); } else if( blobs() == 2 ) recognize12( charset, charbox ); else if( blobs() == 3 ) recognize13( charset, charbox ); } /* Recognize 1 blob characters without holes. 12357CEFGHIJKLMNSTUVWXYZcfhklmnrstuvwxyz '()*+,-./<>@[\]^_`{|}~¬ */ void Character::recognize110( const Charset & charset, const Rectangle & charbox ) { const Blob & b = blob( 0 ); Features f( b ); int code = f.test_easy( charbox ); if( code ) { if( code == '.' && b.width() > b.height() && b.v_includes( charbox.vcenter() ) ) { add_guess( code, 1 ); add_guess( '-', 0 ); return; } add_guess( code, 0 ); return; } if( b.height() < 5 || ( b.height() < 8 && b.width() < 6 ) || b.height() > 10 * b.width() || 5 * b.height() < b.width() ) return; code = f.test_EFIJLlT( charset, charbox ); if( code ) { add_guess( code, 0 ); return; } code = f.test_frst( charbox ); if( code ) { add_guess( code, 0 ); return; } code = f.test_G(); if( code ) { add_guess( code, 0 ); return; } code = f.test_c(); if( code ) { add_guess( code, 0 ); return; } if( charset.enabled( Charset::iso_8859_9 ) ) { code = f.test_s_cedilla(); if( code ) { add_guess( code, 0 ); return; } } code = f.test_235Esz( charset ); if( code ) { add_guess( code, 0 ); return; } code = f.test_HKMNUuvwYy( charbox ); if( code == 'u' && f.lp.istpit() ) // Look for merged 'tr' { int col = b.seek_left( b.vcenter(), b.right() ); if( col < b.hpos( 90 ) && !b.escape_top( b.vcenter(), col ) ) { col = b.seek_left( b.vcenter(), col - 1, false ); while( --col > b.hpos( 40 ) && ( b.seek_top( b.vcenter(), col ) > b.top() || f.hp[col-b.left()] > b.height() / 10 ) ) ; if( col > b.hpos( 40 ) && col < b.right() && set_merged_guess( 't', col, 'r', 0 ) ) return; } } if( code == 'N' && b.width() > b.height() && b.top() >= charbox.top() && 4 * f.tp[f.tp.pos(50)] < b.height() ) { // Look for merged 'rv' const int col = f.hp.iminimum(); if( col >= f.hp.pos( 40 ) && col < f.hp.pos( 50 ) && set_merged_guess( 'r', b.left() + col, 'v', 0 ) ) return; } if( code ) { add_guess( code, 0 ); return; } const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; if( f.bp.minima() <= 2 && ( f.bp.minima( b.height() / 8 + noise ) == 2 || ( b.height() >= 16 && f.bp.minima( b.height() / 8 ) == 2 ) ) ) { code = f.test_hknwx( charbox ); if( code == 'n' ) // Look for '"' or merged 'rt' or 'fl' { if( b.bottom() <= charbox.vcenter() ) { add_guess( '"', 0 ); return; } if( b.width() > b.height() && 10 * f.lp[f.lp.pos(10)] < b.width() && !f.rp.increasing( f.rp.pos( 75 ) ) ) { const int rgap = f.rp[f.rp.pos(50)]; if( 10 * rgap > b.width() && !b.escape_top( b.vcenter(), b.right() ) ) return; // leave 'rr', 'TT', 'rz', 'FT' etc, for next pass } if( 2 * f.lp[f.lp.pos(10)] > b.width() && !f.rp.increasing( f.rp.pos( 75 ) ) ) { const int col = b.seek_left( b.vcenter(), b.right() ); if( col <= b.hpos( 95 ) && !b.escape_top( b.vcenter(), col ) && set_merged_guess( 'r', b.hcenter(), 't', 0 ) ) return; } if( f.rp.minima() == 1 && !f.rp.increasing( f.rp.pos( 75 ) ) ) { int dmax = 0; bool bar = false; for( int row = b.vpos( 60 ); row > b.vpos( 25 ); --row ) { int d = b.hcenter() - b.seek_left( row, b.hcenter() ); if( d > dmax ) dmax = d; else if( 2 * d < dmax && dmax > 2 ) bar = true; if( bar && Ocrad::similar( d, dmax, 25 ) ) { int col, limit = b.seek_right( b.vcenter(), b.hcenter() ); for( col = b.hcenter(); col <= limit; ++col ) if( b.seek_bottom( b.vcenter(), col ) < b.bottom() ) break; if( col > b.left() && col < b.right() && set_merged_guess( 'f', col - 1, 'l', 0 ) ) return; } } } } else if( code == 'h' ) // Look for merged 'rf' or 'fi' { if( 2 * f.lp[f.lp.pos(10)] > b.width() ) { if( f.rp[f.rp.pos(70)] >= 2 && b.seek_top( b.vpos( 70 ), b.right() ) > b.top() ) { int col = 0, hmin = f.hp.range() + 1; for( int i = b.hpos( 40 ); i <= b.hpos( 60 ); ++i ) if( f.hp[i-b.left()] < hmin ) { hmin = f.hp[i-b.left()]; col = i; } if( col > b.left() && col < b.right() ) set_merged_guess( 'r', col - 1, 'f', 0 ); } return; } if( f.rp.isctip( 30 ) ) { set_merged_guess( 'f', b.hcenter(), 'i', 0 ); return; } } else if( code == 'k' ) // Look for merged 'rt' { if( 2 * f.lp[f.lp.pos(10)] > b.width() && !f.rp.increasing( f.rp.pos( 75 ) ) && set_merged_guess( 'r', b.hcenter(), 't', 0 ) ) return; } if( code ) { add_guess( code, 0 ); return; } } if( f.bp.minima() == 3 ) { if( f.bp.minima( b.height() / 2 ) == 1 && f.tp.minima() == 3 && f.lp.minima() == 2 && f.rp.minima() == 2 ) { add_guess( '*', 0 ); return; } if( b.id( b.vcenter(), b.hcenter() ) == 0 && b.id( b.vcenter() - 1, b.hcenter() ) == 0 && b.id( b.vcenter() + 1, b.hcenter() ) == 0 && b.seek_left( b.vcenter(), b.hcenter() ) <= b.hpos( 25 ) ) { // Found merged 'rn' int row = b.vpos( 95 ); int col = b.seek_right( row, b.left() ); col = b.seek_right( row, col + 1, false ); col = b.seek_right( row, col + 1 ); if( col > b.left() && col < b.right() && set_merged_guess( 'r', col, 'n', 0 ) ) return; } if( f.tp.minima( b.height() / 3 ) == 1 ) add_guess( 'm', 0 ); return; } if( f.bp.minima() == 4 && f.tp.minima( b.height() / 3 ) == 1 ) { // Found merged 'rm' int row = b.vpos( 95 ); int col = b.seek_right( row, b.left() ); col = b.seek_right( row, col + 1, false ); col = b.seek_right( row, col + 1 ); if( col > b.left() && col < b.right() && set_merged_guess( 'r', col, 'm', 0 ) ) return; } if( f.tp.minima( b.height() / 4 ) == 3 ) { if( f.segments_in_row( b.vcenter() ) == 2 && f.segments_in_row( b.vpos( 80 ) ) == 3 ) return; // merged 'IX' int hdiff; if( !b.bottom_hook( &hdiff ) && ( f.segments_in_row( b.vcenter() ) < 4 || !b.escape_top( b.vcenter(), b.hcenter() ) ) ) add_guess( 'w', 0 ); return; } code = f.test_line( charbox ); if( code ) { add_guess( code, 0 ); return; } code = f.test_misc( charbox ); if( code ) { add_guess( code, 0 ); return; } } /* Recognize 1 blob characters with 1 hole. 0469ADOPQRabdegopq# */ void Character::recognize111( const Charset & charset, const Rectangle & charbox ) { const Blob & b = blob( 0 ); const Bitmap & h = b.hole( 0 ); if( !h.is_hcentred_in( b ) ) return; Features f( b ); int top_delta = h.top() - b.top(), bottom_delta = b.bottom() - h.bottom(); if( std::abs( top_delta - bottom_delta ) <= std::max( 2, h.height() / 4 ) || Ocrad::similar( top_delta, bottom_delta, 40, 2 ) ) { // hole is vertically centred int code = f.test_4ADQao( charset, charbox ); if( code ) { if( code == 'Q' && Ocrad::similar( top_delta, bottom_delta, 40, 2 ) ) add_guess( 'a', 1 ); add_guess( code, 0 ); } return; } if( top_delta < bottom_delta ) // hole is high { int code = f.test_49ARegpq( charbox ); if( code ) add_guess( code, 0 ); return; } if( top_delta > bottom_delta ) // hole is low { int code = f.test_6abd( charset ); if( code ) { add_guess( code, 0 ); if( code == UCS::SOACUTE ) { int row = h.top() - ( b.bottom() - h.bottom() ) - 1; if( row <= b.top() || row + 1 >= h.top() ) return; Blob & b1 = const_cast< Blob & >( b ); Blob b2( b ); b1.bottom( row ); b2.top( row + 1 ); blobpv.push_back( new Blob( b2 ) ); } } } } /* Recognize 1 blob characters with 2 holes. 8BQg$& */ void Character::recognize112( const Rectangle & charbox ) { const Blob & b = blob( 0 ); const Bitmap & h1 = b.hole( 0 ); // upper hole const Bitmap & h2 = b.hole( 1 ); // lower hole Profile lp( b, Profile::left ); Profile tp( b, Profile::top ); Profile rp( b, Profile::right ); Profile bp( b, Profile::bottom ); // Check for 'm' or 'w' with merged serifs if( 10 * std::abs( h2.vcenter() - h1.vcenter() ) <= b.height() && h1.is_vcentred_in( b ) && h2.is_vcentred_in( b ) ) { if( ( b.bottom() - h1.bottom() <= h1.top() - b.top() ) && ( b.bottom() - h2.bottom() <= h2.top() - b.top() ) && bp.isflats() ) { add_guess( 'm', 0 ); return; } if( 5 * std::abs( h1.bottom() - b.vcenter() ) <= b.height() && 5 * std::abs( h2.bottom() - b.vcenter() ) <= b.height() && tp.isflats() && bp.minima() == 2 ) { add_guess( 'w', 0 ); return; } return; } if( !h1.is_hcentred_in( b ) ) return; if( !h2.is_hcentred_in( b ) ) return; if( h1.left() > b.hcenter() && h2.left() > b.hcenter() ) return; if( h1.right() < b.hpos( 40 ) && h2.right() < b.hpos( 40 ) ) return; if( h1.top() > b.vcenter() || h2.bottom() < b.vcenter() ) return; const int a1 = h1.area(); const int a2 = h2.area(); { const int w = b.right() - std::min( b.hcenter(), std::min( h1.hcenter(), h2.hcenter() ) ); for( int i = h1.bottom() - b.top() + 1; i < h2.top() - b.top(); ++i ) if( rp[i] > w ) { add_guess( 'g', 2 ); return; } } if( Ocrad::similar( a1, a2, 50 ) ) // I don't like this { if( h1.bottom() > b.vcenter() && h2.top() < b.vcenter() && h1.h_overlaps( h2 ) && !h1.h_includes( h2 ) ) { add_guess( '0', 0 ); return; } if( h1.bottom() <= h2.top() ) { int hdiff; if( b.bottom_hook( &hdiff ) && hdiff > b.height() / 2 ) if( b.top_hook( &hdiff ) && hdiff > b.height() / 2 ) { add_guess( 's', 0 ); return; } if( lp.isflats() && ( lp.istip() || ( lp.isflat() && b.test_BD() ) ) ) { add_guess( 'B', 0 ); return; } int col1 = h1.seek_left( h1.bottom(), h1.right() + 1 ) - 1; int col2 = h2.seek_right( h2.top(), h2.left() - 1 ) + 1; if( col1 <= col2 ) { if( lp.isconvex() || lp.ispit() ) add_guess( 'e', 1 ); else if( !rp.isctip() && tp.minima() == 1 ) add_guess( 'a', 1 ); if( bp.istpit() ) { add_guess( '$', 0 ); return; } } if( b.hcenter() > h1.hcenter() && b.hcenter() > h2.hcenter() && ( b.hcenter() >= h1.right() || b.hcenter() >= h2.right() ) ) { add_guess( '&', 0 ); return; } for( int row = h1.bottom() + 1; row < h2.top(); ++row ) if( !b.get_bit( row, hcenter() ) ) { add_guess( 'g', 0 ); return; } if( charbox.bottom() > h2.vcenter() && ( bp.isconvex() || ( bp.ispit() && tp.ispit() ) ) ) { if( b.top() >= charbox.top() && b.height() <= charbox.height() ) { if( ( lp.ispit() || lp.isconvex() ) && ( !rp.ispit() || h2.right() > h1.right() ) ) add_guess( 'e', 1 ); else if( b.right() - rp[rp.pos(50)] > h1.right() && !rp.isctip() ) add_guess( 'a', 1 ); } if( h1.bottom() > b.vcenter() && h1.top() > b.vpos( 30 ) ) add_guess( UCS::SEACUTE, 0 ); else add_guess( '8', 0 ); return; } if( lp.minima() == 2 && rp.minima() == 1 ) { if( charbox.vcenter() < h1.bottom() && charbox.bottom() < h2.bottom() ) add_guess( 'g', 0 ); else add_guess( 'a', 0 ); return; } if( charbox.vcenter() > h1.top() && ( charbox.vcenter() < h1.bottom() || charbox.bottom() < h2.vcenter() ) ) add_guess( 'g', 2 ); add_guess( 'B', 1 ); add_guess( 'a', 0 ); return; } } if( a1 > a2 && h1.h_overlaps( h2 ) ) { if( !h1.v_overlaps( h2 ) ) { if( h2.left() > b.hcenter() && h2.bottom() < b.bottom() - h1.height() ) add_guess( '9', 0 ); else add_guess( 'g', 0 ); return; } if( h1.h_includes( h2 ) ) { add_guess( 'Q', 0 ); return; } return; } if( a1 < a2 && tp.minima() == 1 ) { if( h1.h_overlaps( h2 ) ) { if( rp.minima() == 1 ) { if( 2 * h1.height() > h2.height() && 2 * h1.width() > h2.width() && 3 * h2.width() >= b.width() && !lp.isctip() ) { if( lp.ispit() && lp.isconvex() ) add_guess( '6', 0 ); else add_guess( 'B', 0 ); } else if( h2.right() < b.hcenter() ) add_guess( '&', 0 ); else add_guess( 'a', 0 ); return; } if( !h1.v_overlaps( h2 ) && h1.right() < b.hcenter() && h1.top() > b.top() + h1.height() ) { add_guess( '6', 0 ); return; } } if( h1.bottom() < h2.top() ) { add_guess( '&', 0 ); return; } } } ocrad-0.29/histogram.h0000644000175000017500000000317114545576754014626 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Histogram { unsigned samples_; std::vector< int > distrib; public: Histogram() : samples_( 0 ) {} int samples() const { return samples_; } int size() const { return distrib.size(); } bool empty() const { return distrib.empty(); } int operator[]( const int i ) const { return distrib[i]; } void reset() { samples_ = 0; distrib.clear(); } void add_sample( const unsigned sample ) { if( sample < INT_MAX && samples_ < INT_MAX ) { if( sample >= distrib.size() ) distrib.resize( sample + 1 ); ++distrib[sample]; ++samples_; } } int median() const { unsigned l = 0, cum = 0; while( l < distrib.size() ) { cum += distrib[l]; if( 2 * cum >= samples_ ) break; else ++l; } unsigned r = l; while( true ) { if( 2 * cum > samples_ || r >= distrib.size() ) break; cum += distrib[r]; ++r; } return ( l + r ) / 2; } }; ocrad-0.29/page_image.h0000644000175000017500000000624014545576754014707 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ struct OCRAD_Pixmap; class Mask; class Rational; class Rectangle; class Track; class Transformation; class Page_image { public: struct Error { const char * const msg; explicit Error( const char * const s ) : msg( s ) {} }; private: std::vector< std::vector< uint8_t > > data; // 256 level greymap uint8_t maxval_, threshold_; // x > threshold == white static void test_size( const int rows, const int cols ); void read_p1( FILE * const f, const int cols, const bool invert ); void read_p4( FILE * const f, const int cols, const bool invert ); void read_p2( FILE * const f, const int cols, const bool invert ); void read_p5( FILE * const f, const int cols, const bool invert ); void read_p3( FILE * const f, const int cols, const bool invert ); void read_p6( FILE * const f, const int cols, const bool invert ); void read_png( FILE * const f, const int sig_read, const bool invert ); void write_png( FILE * const f, const unsigned bit_depth ) const; public: // Create a Page_image from a png, pbm, pgm, or ppm file Page_image( FILE * const f, const bool invert ); // Create a Page_image from a OCRAD_Pixmap Page_image( const OCRAD_Pixmap & image, const bool invert ); // Create a reduced Page_image Page_image( const Page_image & source, const int scale ); bool get_bit( const int row, const int col ) const { return data[row][col] <= threshold_; } bool get_bit( const int row, const int col, const uint8_t th ) const { return data[row][col] <= th; } void set_bit( const int row, const int col, const bool bit ) { data[row][col] = ( bit ? 0 : maxval_ ); } int height() const { return data.size(); } int width() const { return data.empty() ? 0 : data[0].size(); } uint8_t maxval() const { return maxval_; } uint8_t threshold() const { return threshold_; } void threshold( const Rational & th ); // 0 <= th <= 1, else auto void threshold( const int th ); // 0 <= th <= 255, else auto bool cut( const Rational ltwh[4] ); void draw_mask( const Mask & m ); void draw_rectangle( const Rectangle & re ); void draw_track( const Track & tr ); bool save( FILE * const f, const char filetype ) const; bool change_scale( int n ); void transform( const Transformation & t ); }; // defined in png_io.cc bool read_check_png_sig8( FILE * const f, const int first_byte = -1 ); void show_png_info( FILE * const f, const char * const input_filename, const int sig_read ); ocrad-0.29/blob.cc0000644000175000017500000002052214545576754013704 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "common.h" #include "rectangle.h" #include "bitmap.h" #include "blob.h" namespace { void delete_hole( std::vector< Bitmap * > & holep_vector, std::vector< Bitmap * > & v1, std::vector< Bitmap * > & v2, Bitmap * const p, int i ) { std::replace( v1.begin() + i, v1.end(), p, (Bitmap *) 0 ); std::replace( v2.begin(), v2.begin() + i, p, (Bitmap *) 0 ); i = holep_vector.size(); while( --i >= 0 && holep_vector[i] != p ) ; if( i < 0 ) throw Ocrad::Internal( "delete_hole: lost hole." ); holep_vector.erase( holep_vector.begin() + i ); delete p; } inline void join_holes( std::vector< Bitmap * > & holep_vector, std::vector< Bitmap * > & v1, std::vector< Bitmap * > & v2, Bitmap * p1, Bitmap * p2, int i ) { if( p1->top() > p2->top() ) { Bitmap * const temp = p1; p1 = p2; p2 = temp; std::replace( v2.begin(), v2.begin() + ( i + 1 ), p2, p1 ); } else std::replace( v1.begin() + i, v1.end(), p2, p1 ); i = holep_vector.size(); while( --i >= 0 && holep_vector[i] != p2 ) ; if( i < 0 ) throw Ocrad::Internal( "join_holes: lost hole." ); holep_vector.erase( holep_vector.begin() + i ); p1->add_bitmap( *p2 ); delete p2; } void delete_outer_holes( const Rectangle & re, std::vector< Bitmap * > & holepv ) { for( unsigned i = holepv.size(); i > 0; ) { Bitmap & h = *holepv[--i]; if( !re.strictly_includes( h ) ) { delete &h; holepv.erase( holepv.begin() + i ); } } } } // end namespace Blob::Blob( const Blob & b ) : Bitmap( b ), holepv( b.holepv ) { for( unsigned i = 0; i < holepv.size(); ++i ) holepv[i] = new Bitmap( *b.holepv[i] ); } Blob & Blob::operator=( const Blob & b ) { if( this != &b ) { Bitmap::operator=( b ); for( unsigned i = 0; i < holepv.size(); ++i ) delete holepv[i]; holepv = b.holepv; for( unsigned i = 0; i < holepv.size(); ++i ) holepv[i] = new Bitmap( *b.holepv[i] ); } return *this; } Blob::~Blob() { for( unsigned i = 0; i < holepv.size(); ++i ) delete holepv[i]; } void Blob::left( const int l ) { const int d = l - left(); if( d ) { Bitmap::left( l ); if( d > 0 ) delete_outer_holes( *this, holepv ); } } void Blob::top( const int t ) { const int d = t - top(); if( d ) { Bitmap::top( t ); if( d > 0 ) delete_outer_holes( *this, holepv ); } } void Blob::right( const int r ) { const int d = r - right(); if( d ) { Bitmap::right( r ); if( d < 0 ) delete_outer_holes( *this, holepv ); } } void Blob::bottom( const int b ) { const int d = b - bottom(); if( d ) { Bitmap::bottom( b ); if( d < 0 ) delete_outer_holes( *this, holepv ); } } const Bitmap & Blob::hole( const int i ) const { if( i < 0 || i >= holes() ) throw Ocrad::Internal( "hole: index out of bounds." ); return *holepv[i]; } int Blob::id( const int row, const int col ) const { if( this->includes( row, col ) ) { if( get_bit( row, col ) ) return 1; for( int i = 0; i < holes(); ++i ) if( holepv[i]->includes( row, col ) && holepv[i]->get_bit( row, col ) ) return -( i + 1 ); } return 0; } bool Blob::test_BD() const { const int wlimit = std::min( height(), width() ) / 2; int lb = wlimit, rt = wlimit; // index of first dot found for( int i = 0; i < wlimit; ++i ) if( id( bottom() - i, left() + i ) != 0 || id( bottom() - i, left() + i + 1 ) != 0 ) { lb = i; break; } for( int i = 0; i < wlimit; ++i ) if( id( top() + i, right() - i ) != 0 ) { rt = i; break; } return ( rt >= 2 && 3 * lb <= rt ); } bool Blob::test_Q() const { const int wlimit = std::min( height(), width() ) / 2; int ltwmax = 0, rbwmax = 0; int ltimin = wlimit, rbimin = wlimit; // index of first dot found for( int disp = 0; disp < width() / 4; ++disp ) { int ltw = 0, rbw = 0; for( int i = 0; i < wlimit; ++i ) { if( id( top() + i, left() + disp + i ) == 1 ) { ++ltw; if( ltimin > i ) ltimin = i; } if( id( bottom() - i, right() - disp - i ) == 1 ) { ++rbw; if( rbimin > i ) rbimin = i; } } if( ltwmax < ltw ) ltwmax = ltw; if( rbwmax < rbw ) rbwmax = rbw; } return ( ( ltimin > rbimin || rbimin == 0 ) && ( 2 * ltwmax < rbwmax || ( 2 * ltwmax == rbwmax && rbwmax >= 4 ) ) ); } void Blob::print( FILE * const outfile ) const { for( int row = top(); row <= bottom(); ++row ) { for( int col = left(); col <= right(); ++col ) std::fputs( get_bit( row, col ) ? " O" : " .", outfile ); std::fputc( '\n', outfile ); } std::fputc( '\n', outfile ); } void Blob::fill_hole( const int i ) { if( i < 0 || i >= holes() ) throw Ocrad::Internal( "fill_hole: index out of bounds." ); add_bitmap( *holepv[i] ); delete holepv[i]; holepv.erase( holepv.begin() + i ); } void Blob::find_holes() { for( unsigned i = 0; i < holepv.size(); ++i ) delete holepv[i]; holepv.clear(); if( height() < 3 || width() < 3 ) return; std::vector< Bitmap * > old_data( width(), (Bitmap *) 0 ); std::vector< Bitmap * > new_data( width(), (Bitmap *) 0 ); for( int row = top(); row <= bottom(); ++row ) { old_data.swap( new_data ); new_data[0] = get_bit( row, left() ) ? this : 0; for( int col = left() + 1; col < right(); ++col ) { const int dcol = col - left(); if( get_bit( row, col ) ) new_data[dcol] = this; // black pixel else // white pixel { Bitmap *p; Bitmap *lp = new_data[dcol-1]; Bitmap *tp = old_data[dcol]; if( lp == 0 || tp == 0 ) { p = 0; if( lp && lp != this ) delete_hole( holepv, old_data, new_data, lp, dcol ); else if( tp && tp != this ) delete_hole( holepv, old_data, new_data, tp, dcol ); } else if( lp != this ) { p = lp; p->add_point( row, col ); } else if( tp != this ) { p = tp; p->add_point( row, col ); } else { p = new Bitmap( col, row, col, row ); p->set_bit( row, col, true ); holepv.push_back( p ); } new_data[dcol] = p; if( p && lp != tp && lp != this && tp != this ) join_holes( holepv, old_data, new_data, lp, tp, dcol ); } } if( !get_bit( row, right() ) ) { Bitmap *lp = new_data[width()-2]; if( lp && lp != this ) delete_hole( holepv, old_data, new_data, lp, width() - 1 ); } } for( unsigned i = holepv.size(); i > 0; ) // FIXME noise holes removal { Bitmap & h = *holepv[--i]; if( this->strictly_includes( h ) && ( h.height() > 4 || h.width() > 4 || ( ( h.height() > 2 || h.width() > 2 ) && h.area() > 3 ) ) ) continue; delete &h; holepv.erase( holepv.begin() + i ); } /* while( holepv.size() > 3 ) { int smin = holepv[0]->size(); for( unsigned i = 1; i < holepv.size(); ++i ) if( holepv[i]->size() < smin ) smin = holepv[i]->size(); for( int i = holepv.size() - 1; i >= 0; --i ) if( holepv[i]->size() == smin ) holepv.erase( holepv.begin() + i ); } for( unsigned i = holepv.size(); i > 0; ) { Bitmap & h = *holepv[--i]; if( !this->strictly_includes( h ) ) { delete &h; holepv.erase( holepv.begin() + i ); } if( 20 * h.height() < height() && 16 * h.width() < width() ) fill_hole( i ); // else if( h.height() < 2 && h.width() < 2 && h.area() < 2 ) // { delete &h; holepv.erase( holepv.begin() + i ); } } */ } ocrad-0.29/bitmap.h0000644000175000017500000000512414545576754014105 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Bitmap : public Rectangle { std::vector< std::vector< uint8_t > > data; // faster than bool public: // Create a blank Bitmap Bitmap( const int l, const int t, const int r, const int b ); // Create a Bitmap from part of another Bitmap Bitmap( const Bitmap & source, const Rectangle & re ); using Rectangle::left; using Rectangle::top; using Rectangle::right; using Rectangle::bottom; using Rectangle::height; using Rectangle::width; void left ( const int l ); void top ( const int t ); void right ( const int r ); void bottom( const int b ); void height( const int h ) { bottom( top() + h - 1 ); } void width ( const int w ) { right( left() + w - 1 ); } void add_bitmap( const Bitmap & bm ); void add_point( const int row, const int col ); void add_rectangle( const Rectangle & re ); bool adjust_height(); bool adjust_width(); bool get_bit( const int row, const int col ) const { return data[row-top()][col-left()]; } void set_bit( const int row, const int col, const bool bit ) { data[row-top()][col-left()] = bit; } int area() const; // 'area' means filled area int area_octagon() const; int size_octagon() const; int seek_left ( const int row, const int col, const bool black = true ) const; int seek_top ( const int row, const int col, const bool black = true ) const; int seek_right ( const int row, const int col, const bool black = true ) const; int seek_bottom( const int row, const int col, const bool black = true ) const; bool escape_left ( int row, int col ) const; bool escape_top ( int row, int col ) const; bool escape_right ( int row, int col ) const; bool escape_bottom( int row, int col ) const; int follow_top ( int row, int col ) const; int follow_bottom( int row, int col ) const; bool top_hook ( int *hdiff ) const; bool bottom_hook( int *hdiff ) const; }; ocrad-0.29/character.h0000644000175000017500000000642514545576754014572 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Character : public Rectangle { public: struct Guess { int code; int value; Guess( const int c, const int v ) : code( c ), value( v ) {} }; private: std::vector< Blob * > blobpv; // the blobs forming this Character std::vector< Guess > gv; // vector of possible char codes // and their associated values. // gv[0].code < 0 means further // processing is needed (merged chars) void recognize110( const Charset & charset, const Rectangle & charbox ); void recognize111( const Charset & charset, const Rectangle & charbox ); void recognize112( const Rectangle & charbox ); void recognize12( const Charset & charset, const Rectangle & charbox ); void recognize13( const Charset & charset, const Rectangle & charbox ); public: explicit Character( Blob * const p ) : Rectangle( *p ), blobpv( 1, p ) {} Character( const Rectangle & re, int code, int value ) : Rectangle( re ), gv( 1, Guess( code, value ) ) {} Character( const Character & c ); Character & operator=( const Character & c ); ~Character(); int area() const; const Blob & blob( const int i ) const; Blob & blob( const int i ); int blobs() const { return blobpv.size(); } Blob & main_blob(); void shift_blobp( Blob * const p ); void add_guess( const int code, const int value ) { gv.push_back( Guess( code, value ) ); } void clear_guesses() { gv.clear(); } void insert_guess( const int i, const int code, const int value ); void delete_guess( const int i ); void only_guess( const int code, const int value ) { gv.clear(); gv.push_back( Guess( code, value ) ); } bool set_merged_guess( const int code1, const int right1, const int code2, const int blob_index ); void swap_guesses( const int i, const int j ); const Guess & guess( const int i ) const; int guesses() const { return gv.size(); } bool maybe( const int code ) const; bool isalnum() const { return ( gv.size() > 0 && UCS::isalnum( gv[0].code ) ); } // bool maybe_digit() const; // bool maybe_letter() const; void join( Character & c ); unsigned char byte_result() const; const char * utf8_result() const; void print( const Control & control ) const; void dprint( const Control & control, const Rectangle & charbox, const bool graph, const bool recursive ) const; void xprint( const Control & control ) const; void recognize1( const Charset & charset, const Rectangle & charbox ); void apply_filter( const Filter::Type filter ); void apply_user_filter( const User_filter & user_filter ); }; ocrad-0.29/page_image.cc0000644000175000017500000004661414550754471015044 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "ocradlib.h" #include "common.h" #include "rational.h" #include "rectangle.h" #include "segment.h" #include "mask.h" #include "track.h" #include "page_image.h" namespace { // binarization by Otsu's method based on maximization of inter-class variance // int otsu_th( const std::vector< std::vector< uint8_t > > & data, const Rectangle & re, const int maxval ) { if( maxval == 1 ) return 0; std::vector< int > hist( maxval + 1, 0 ); // histogram of image data for( int row = re.top(); row <= re.bottom(); ++row ) for( int col = re.left(); col <= re.right(); ++col ) ++hist[data[row][col]]; std::vector< int > chist; // cumulative histogram chist.reserve( maxval + 1 ); chist.push_back( hist[0] ); std::vector< long long > cmom; // cumulative moment cmom.reserve( maxval + 1 ); cmom.push_back( 0 ); // 0 times hist[0] equals zero for( int i = 1; i <= maxval; ++i ) { chist.push_back( chist[i-1] + hist[i] ); cmom.push_back( cmom[i-1] + ( i * hist[i] ) ); } const double cmom_max = cmom[maxval]; double bvar_max = 0; int threshold = 0; // threshold for binarization for( int i = 0; i < maxval; ++i ) if( chist[i] > 0 && chist[i] < re.size() ) { double bvar = (double)cmom[i] / chist[i]; bvar -= ( cmom_max - cmom[i] ) / ( re.size() - chist[i] ); bvar *= bvar; bvar *= chist[i]; bvar *= ( re.size() - chist[i] ); if( bvar > bvar_max ) { bvar_max = bvar; threshold = i; } } return threshold; } int absolute_pos( Rational pos, const int left, const int right ) { int a; if( pos >= 0 ) { if( pos <= 1 ) a = left + ( pos * ( right - left ) ).trunc(); else a = left + pos.round(); } else { pos = -pos; if( pos <= 1 ) a = right - ( pos * ( right - left ) ).trunc(); else a = right - pos.round(); } return a; } void convol_23( std::vector< std::vector< uint8_t > > & data, const int scale ) { const int height = data.size(); const int width = data[0].size(); if( height < 3 || width < 3 ) return; std::vector< std::vector< uint8_t > > new_data( height ); new_data[0] = data[0]; // copy first row for( int row = 1; row < height - 1; ++row ) new_data[row].reserve( width ); new_data[height-1] = data[height-1]; // copy last row for( int row = 1; row < height - 1; ++row ) { const std::vector< uint8_t > & datarow1 = data[row-1]; const std::vector< uint8_t > & datarow2 = data[row]; const std::vector< uint8_t > & datarow3 = data[row+1]; std::vector< uint8_t > & new_datarow = new_data[row]; new_datarow.push_back( datarow2[0] ); // copy first col if( scale < 3 ) for( int col = 1; col < width - 1; ++col ) { int sum = datarow1[col-1] + datarow1[col] + datarow1[col+1] + datarow2[col-1] + 2 * datarow2[col] + datarow2[col+1] + datarow3[col-1] + datarow3[col] + datarow3[col+1]; new_datarow.push_back( ( sum + 5 ) / 10 ); } else for( int col = 1; col < width - 1; ++col ) { int sum = datarow1[col-1] + datarow1[col] + datarow1[col+1] + datarow2[col-1] + datarow2[col] + datarow2[col+1] + datarow3[col-1] + datarow3[col] + datarow3[col+1]; new_datarow.push_back( ( 2 * sum + 9 ) / 18 ); } new_datarow.push_back( datarow2[width-1] ); // copy last col } data.swap( new_data ); } void convol_n( std::vector< std::vector< uint8_t > > & data, const int scale ) { const int radius = scale / 2; // this is really radius - 0.5 const int min_size = 2 * radius + 1; const int area = min_size * min_size; const int height = data.size(); const int width = data[0].size(); if( radius < 1 || height < min_size || width < min_size ) return; std::vector< std::vector< uint8_t > > new_data( height ); for( int row = 0; row < radius; ++row ) new_data[row] = data[row]; // copy first rows for( int row = radius; row < height - radius; ++row ) new_data[row].reserve( width ); for( int row = height - radius; row < height; ++row ) new_data[row] = data[row]; // copy last rows for( int row = radius; row < height - radius; ++row ) { const std::vector< uint8_t > & datarow = data[row]; std::vector< uint8_t > & new_datarow = new_data[row]; for( int col = 0; col < radius; ++col ) new_datarow.push_back( datarow[col] ); // copy first cols for( int col = radius; col < width - radius; ++col ) { int sum = 0; for( int r = -radius; r < radius; ++r ) for( int c = -radius; c < radius; ++c ) sum += data[row+r][col+c]; new_datarow.push_back( ( 2 * sum + area ) / ( 2 * area ) ); } for( int col = width - radius; col < width; ++col ) new_datarow.push_back( datarow[col] ); // copy last cols } data.swap( new_data ); } void enlarge_2b( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); const int width = data[0].size(); std::vector< std::vector< uint8_t > > new_data( 2 * height ); for( unsigned row = 0; row < new_data.size(); ++row ) new_data[row].resize( 2 * width, 1 ); for( int row = 0; row < height; ++row ) { const std::vector< uint8_t > & datarow = data[row]; std::vector< uint8_t > & new_datarow0 = new_data[2*row]; std::vector< uint8_t > & new_datarow1 = new_data[2*row+1]; for( int col = 0; col < width; ++col ) { if( datarow[col] == 0 ) { const bool l = col > 0 && datarow[col-1] == 0; const bool t = row > 0 && data[row-1][col] == 0; const bool r = col < width - 1 && datarow[col+1] == 0; const bool b = row < height - 1 && data[row+1][col] == 0; const bool lt = row > 0 && col > 0 && data[row-1][col-1] == 0; const bool rt = row > 0 && col < width - 1 && data[row-1][col+1] == 0; const bool lb = row < height - 1 && col > 0 && data[row+1][col-1] == 0; const bool rb = row < height - 1 && col < width - 1 && data[row+1][col+1] == 0; if( l || t || lt || ( !rt && !lb ) ) new_datarow0[2*col] = 0; if( r || t || rt || ( !lt && !rb ) ) new_datarow0[2*col+1] = 0; if( l || b || lb || ( !lt && !rb ) ) new_datarow1[2*col] = 0; if( r || b || rb || ( !rt && !lb ) ) new_datarow1[2*col+1] = 0; } } } data.swap( new_data ); } void enlarge_3b( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); const int width = data[0].size(); std::vector< std::vector< uint8_t > > new_data( 3 * height ); for( unsigned row = 0; row < new_data.size(); ++row ) new_data[row].resize( 3 * width, 1 ); for( int row = 0; row < height; ++row ) { const int row3 = 3 * row; const std::vector< uint8_t > & datarow = data[row]; std::vector< uint8_t > & new_datarow0 = new_data[row3]; std::vector< uint8_t > & new_datarow1 = new_data[row3+1]; std::vector< uint8_t > & new_datarow2 = new_data[row3+2]; for( int col = 0; col < width; ++col ) { const int col3 = 3 * col; const bool l = col > 0 && datarow[col-1] == 0; const bool t = row > 0 && data[row-1][col] == 0; const bool r = col < width - 1 && datarow[col+1] == 0; const bool b = row < height - 1 && data[row+1][col] == 0; const bool lt = row > 0 && col > 0 && data[row-1][col-1] == 0; const bool rt = row > 0 && col < width - 1 && data[row-1][col+1] == 0; const bool lb = row < height - 1 && col > 0 && data[row+1][col-1] == 0; const bool rb = row < height - 1 && col < width - 1 && data[row+1][col+1] == 0; if( datarow[col] == 0 ) { if( l || t || lt || ( !rt && !lb ) ) new_datarow0[col3] = 0; new_datarow0[col3+1] = 0; if( r || t || rt || ( !lt && !rb ) ) new_datarow0[col3+2] = 0; new_datarow1[col3] = new_datarow1[col3+1] = new_datarow1[col3+2] = 0; if( l || b || lb || ( !lt && !rb ) ) new_datarow2[col3] = 0; new_datarow2[col3+1] = 0; if( r || b || rb || ( !rt && !lb ) ) new_datarow2[col3+2] = 0; } else { if( l && t && lt && ( !rt || !lb ) ) new_datarow0[col3] = 0; if( r && t && rt && ( !lt || !rb ) ) new_datarow0[col3+2] = 0; if( l && b && lb && ( !lt || !rb ) ) new_datarow2[col3] = 0; if( r && b && rb && ( !rt || !lb ) ) new_datarow2[col3+2] = 0; } } } data.swap( new_data ); } void enlarge_n( std::vector< std::vector< uint8_t > > & data, const int n ) { if( n < 2 ) return; const int height = data.size(); const int width = data[0].size(); std::vector< std::vector< uint8_t > > new_data; new_data.reserve( n * height ); for( int row = 0; row < height; ++row ) { const std::vector< uint8_t > & datarow = data[row]; new_data.push_back( std::vector< uint8_t >() ); new_data.back().reserve( n * width ); for( int col = 0; col < width; ++col ) { const uint8_t d = datarow[col]; for( int i = 0; i < n; ++i ) new_data.back().push_back( d ); } for( int i = 1; i < n; ++i ) new_data.push_back( new_data.back() ); } data.swap( new_data ); } void mirror_left_right( std::vector< std::vector< uint8_t > > & data ) { const int height = data.size(); for( int row = 0; row < height; ++row ) std::reverse( data[row].begin(), data[row].end() ); } void mirror_top_bottom( std::vector< std::vector< uint8_t > > & data ) { std::reverse( data.begin(), data.end() ); } void mirror_diagonal( std::vector< std::vector< uint8_t > > & data ) { const int rows = data.size(); const int cols = ( data.empty() ? 0 : data[0].size() ); const int size = std::max( rows, cols ); if( rows <= 0 || cols <= 0 ) return; if( rows < size ) // add rows { data.resize( size ); for( int row = rows; row < size; ++row ) data[row].resize( size ); } else if( cols < size ) // add cols for( int row = 0; row < rows; ++row ) data[row].resize( size ); for( int row = 1; row < size; ++row ) { std::vector< uint8_t > & datarow = data[row]; for( int col = 0; col < row; ++col ) { uint8_t tmp = datarow[col]; datarow[col] = data[col][row]; data[col][row] = tmp; } } // swap the number of rows and cols if( cols < size ) data.resize( cols ); else if( rows < size ) for( int row = 0; row < cols; ++row ) data[row].resize( rows ); } } // end namespace // create a Page_image from an OCRAD_Pixmap // Page_image::Page_image( const OCRAD_Pixmap & image, const bool invert ) { const int rows = image.height, cols = image.width; data.resize( rows ); for( int row = 0; row < rows; ++row ) data[row].reserve( cols ); switch( image.mode ) { case OCRAD_bitmap: { maxval_ = 1; threshold_ = 0; if( !invert ) for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( image.data[i] ? 0 : 1 ); else for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( image.data[i] ? 1 : 0 ); } break; case OCRAD_greymap: { maxval_ = 255; threshold_ = 127; if( !invert ) for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( image.data[i] ); else for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, ++i ) data[row].push_back( maxval_ - image.data[i] ); } break; case OCRAD_colormap: { maxval_ = 255; threshold_ = 127; for( int i = 0, row = 0; row < rows; ++row ) for( int col = 0; col < cols; ++col, i += 3 ) { const uint8_t r = image.data[i]; // Red value const uint8_t g = image.data[i+1]; // Green value const uint8_t b = image.data[i+2]; // Blue value uint8_t val; if( !invert ) val = std::min( r, std::min( g, b ) ); else val = maxval_ - std::max( r, std::max( g, b ) ); data[row].push_back( val ); } } break; } } // create a reduced Page_image // Page_image::Page_image( const Page_image & source, const int scale ) : maxval_( source.maxval_ ), threshold_( source.threshold_ ) { if( scale < 2 || scale > source.width() || scale > source.height() ) throw Ocrad::Internal( "bad parameter building a reduced Page_image." ); const int scale2 = scale * scale; const int rows = source.height() / scale; const int cols = source.width() / scale; data.resize( rows ); for( int row = 0; row < rows; ++row ) { const int srow = ( row * scale ) + scale; data[row].reserve( cols ); std::vector< uint8_t > & datarow = data[row]; for( int col = 0; col < cols; ++col ) { const int scol = ( col * scale ) + scale; int sum = 0; for( int i = srow - scale; i < srow; ++i ) { const std::vector< uint8_t > & sdatarow = source.data[i]; for( int j = scol - scale; j < scol; ++j ) sum += sdatarow[j]; } datarow.push_back( sum / scale2 ); } } } void Page_image::threshold( const Rational & th ) { if( th >= 0 && th <= 1 ) threshold_ = ( th * maxval_ ).trunc(); else threshold_ = otsu_th( data, Rectangle( height(), width() ), maxval_ ); } void Page_image::threshold( const int th ) { if( th >= 0 && th <= 255 ) threshold_ = ( th * maxval_ ) / 255; else threshold_ = otsu_th( data, Rectangle( height(), width() ), maxval_ ); } bool Page_image::cut( const Rational ltwh[4] ) { Rectangle re( height(), width() ); const int l = absolute_pos( ltwh[0], 0, width() - 1 ); if( l > re.left() ) { if( l < re.right() ) re.left( l ); else return false; } const int t = absolute_pos( ltwh[1], 0, height() - 1 ); if( t > re.top() ) { if( t < re.bottom() ) re.top( t ); else return false; } const int r = l + absolute_pos( ltwh[2], 0, width() ) - 1; if( r < re.right() ) { if( r > re.left() ) re.right( r ); else return false; } const int b = t + absolute_pos( ltwh[3], 0, height() ) - 1; if( b < re.bottom() ) { if( b > re.top() ) re.bottom( b ); else return false; } if( re.width() < 3 || re.height() < 3 ) return false; // cutting is performed here if( re.bottom() < height() - 1 ) data.resize( re.bottom() + 1 ); if( re.right() < width() - 1 ) for( int row = height() - 1; row >= 0 ; --row ) data[row].resize( re.right() + 1 ); if( re.top() > 0 ) data.erase( data.begin(), data.begin() + re.top() ); if( re.left() > 0 ) for( int row = height() - 1; row >= 0 ; --row ) data[row].erase( data[row].begin(), data[row].begin() + re.left() ); return true; } void Page_image::draw_mask( const Mask & m ) { const int t = std::max( 0, m.top() ); const int b = std::min( height() - 1, m.bottom() ); if( t == m.top() && m.left( t ) >= 0 && m.right( t ) >= 0 ) for( int col = m.left( t ); col <= m.right( t ); ++col ) set_bit( t, col, true ); if( b == m.bottom() && m.left( b ) >= 0 && m.right( b ) >= 0 ) for( int col = m.left( b ); col <= m.right( b ); ++col ) set_bit( b, col, true ); int lprev = m.left( t ); int rprev = m.right( t ); for( int row = t + 1; row <= b; ++row ) { int lnew = m.left( row ), rnew = m.right( row ); if( lnew < 0 ) lnew = lprev; if( rnew < 0 ) rnew = rprev; if( lprev >= 0 && lnew >= 0 ) { int c1 = std::min( lprev, lnew ); int c2 = std::min( width() - 1, std::max( lprev, lnew ) ); for( int col = c1; col <= c2; ++col ) set_bit( row, col, true ); } if( rprev >= 0 && rnew >= 0 ) { int c1 = std::min( rprev, rnew ); int c2 = std::min( width() - 1, std::max( rprev, rnew ) ); for( int col = c1; col <= c2; ++col ) set_bit( row, col, true ); } lprev = lnew; rprev = rnew; } } void Page_image::draw_rectangle( const Rectangle & re ) { const int l = std::max( 0, re.left() ); const int t = std::max( 0, re.top() ); const int r = std::min( width() - 1, re.right() ); const int b = std::min( height() - 1, re.bottom() ); if( l == re.left() ) for( int row = t; row <= b; ++row ) set_bit( row, l, true ); if( t == re.top() ) for( int col = l; col <= r; ++col ) set_bit( t, col, true ); if( r == re.right() ) for( int row = t; row <= b; ++row ) set_bit( row, r, true ); if( b == re.bottom() ) for( int col = l; col <= r; ++col ) set_bit( b, col, true ); } void Page_image::draw_track( const Track & tr ) { int l = std::max( 0, tr.left() ); int r = std::min( width() - 1, tr.right() ); if( l == tr.left() ) for( int row = tr.top( l ); row <= tr.bottom( l ); ++row ) if( row >= 0 && row < width() ) set_bit( row, l, true ); if( r == tr.right() ) for( int row = tr.top( r ); row <= tr.bottom( r ); ++row ) if( row >= 0 && row < height() ) set_bit( row, r, true ); for( int col = l; col <= r; ++col ) { int row = tr.top( col ); if( row >= 0 && row < height() ) set_bit( row, col, true ); row = tr.bottom( col ); if( row >= 0 && row < height() ) set_bit( row, col, true ); } } bool Page_image::change_scale( int n ) // no change if n == ±1 { if( n == 0 || n < -width() || n < -height() ) return false; if( n <= -2 ) { Page_image reduced( *this, -n ); *this = reduced; } else if( n >= 2 ) { if( INT_MAX / n < width() * height() ) throw Error( "Scale factor too big; 'int' will overflow." ); if( maxval_ == 1 ) { if( n && ( n % 2 ) == 0 ) { enlarge_2b( data ); n /= 2; } else if( n && ( n % 3 ) == 0 ) { enlarge_3b( data ); n /= 3; } } if( n >= 2 ) // scale 8-bit greyscale images keeping borders smooth { enlarge_n( data, n ); if( maxval_ > 1 ) { if( n <= 3 ) convol_23( data, n ); else convol_n( data, n ); } } } return true; } void Page_image::transform( const Transformation & t ) { switch( t.type() ) { case Transformation::none: break; case Transformation::rotate90: mirror_diagonal( data ); mirror_top_bottom( data ); break; case Transformation::rotate180: mirror_left_right( data ); mirror_top_bottom( data ); break; case Transformation::rotate270: mirror_diagonal( data ); mirror_left_right( data ); break; case Transformation::mirror_lr: mirror_left_right( data ); break; case Transformation::mirror_tb: mirror_top_bottom( data ); break; case Transformation::mirror_d1: mirror_diagonal( data ); break; case Transformation::mirror_d2: mirror_diagonal( data ); mirror_left_right( data ); mirror_top_bottom( data ); break; } } ocrad-0.29/iso_8859.h0000644000175000017500000000232414545576754014117 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ namespace ISO_8859 { /* 'seq[i]' begins a escape sequence (the characters following a '\'). Return the corresponding code and, in '*lenp', the number of characters read. Return -1 if error. */ int escape( const std::string & seq, const unsigned i, int *lenp = 0 ); inline bool isodigit( const unsigned char ch ) // is 'ch' an octal digit? { return ( ch <= '7' && ch >= '0' ); } int xvalue( const unsigned char ch ); // value of hex digit 'ch' or -1 } // end namespace ISO_8859 ocrad-0.29/arg_parser.h0000644000175000017500000000765214544516472014754 0ustar andriusandrius/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version) Copyright (C) 2006-2024 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ /* Arg_parser reads the arguments in 'argv' and creates a number of option codes, option arguments, and non-option arguments. In case of error, 'error' returns a non-empty error message. 'options' is an array of 'struct Option' terminated by an element containing a code which is zero. A null long_name means a short-only option. A code value outside the unsigned char range means a long-only option. Arg_parser normally makes it appear as if all the option arguments were specified before all the non-option arguments for the purposes of parsing, even if the user of your program intermixed option and non-option arguments. If you want the arguments in the exact order the user typed them, call 'Arg_parser' with 'in_order' = true. The argument '--' terminates all options; any following arguments are treated as non-option arguments, even if they begin with a hyphen. The syntax for optional option arguments is '-' (without whitespace), or '--='. */ class Arg_parser { public: enum Has_arg { no, yes, maybe }; struct Option { int code; // Short option letter or code ( code != 0 ) const char * long_name; // Long option name (maybe null) Has_arg has_arg; }; private: struct Record { int code; std::string parsed_name; std::string argument; explicit Record( const unsigned char c ) : code( c ), parsed_name( "-" ) { parsed_name += c; } Record( const int c, const char * const long_name ) : code( c ), parsed_name( "--" ) { parsed_name += long_name; } explicit Record( const char * const arg ) : code( 0 ), argument( arg ) {} }; const std::string empty_arg; std::string error_; std::vector< Record > data; bool parse_long_option( const char * const opt, const char * const arg, const Option options[], int & argind ); bool parse_short_option( const char * const opt, const char * const arg, const Option options[], int & argind ); public: Arg_parser( const int argc, const char * const argv[], const Option options[], const bool in_order = false ); // Restricted constructor. Parses a single token and argument (if any). Arg_parser( const char * const opt, const char * const arg, const Option options[] ); const std::string & error() const { return error_; } // The number of arguments parsed. May be different from argc. int arguments() const { return data.size(); } /* If code( i ) is 0, argument( i ) is a non-option. Else argument( i ) is the option's argument (or empty). */ int code( const int i ) const { if( i >= 0 && i < arguments() ) return data[i].code; else return 0; } // Full name of the option parsed (short or long). const std::string & parsed_name( const int i ) const { if( i >= 0 && i < arguments() ) return data[i].parsed_name; else return empty_arg; } const std::string & argument( const int i ) const { if( i >= 0 && i < arguments() ) return data[i].argument; else return empty_arg; } }; ocrad-0.29/README0000644000175000017500000000427614545576754013347 0ustar andriusandriusDescription GNU Ocrad is an OCR (Optical Character Recognition) program and library based on a feature extraction method. It reads images in png or pnm formats and produces text in byte (8-bit) or UTF-8 formats. The formats pbm (bitmap), pgm (greyscale), and ppm (color) are collectively known as pnm. Ocrad includes a layout analyser able to separate the columns and blocks of text normally found on printed pages. For best results the characters should be at least 20 pixels high. If they are smaller, try the option --scale. Scanning the image at 300 dpi usually produces a character size good enough for ocrad. See the file INSTALL for compilation and installation instructions. Try "ocrad --help" for usage instructions. Caveats. Merged characters are always a problem. Try to avoid them. Very bold or very light (broken) characters are also a problem. Always see with your own eyes the image contained in the input file before blaming ocrad for the results. Remember the saying, "garbage in, garbage out". Ideas, comments, patches, donations (hardware, money, etc), etc, are welcome. --------------------------- Debug levels ( option -D ) 100 - Show raw block list. 99 - Show recursive block list. 98 - Show main block list. 96..97 - reserved. 95 - Show all blocks from every character before recognition. 94 - Show main black blocks from every character before recognition. 90..93 - reserved. 89 - Show all blocks from every character. 88 - Show main black blocks from every character. 87 - Show guess list for every character. 86 - Show best guess for every character. 80..85 - reserved. 78..79 - reserved. 7X - X = 0 Show page as bitmap. X = 1 Show page as bitmap with marked zones. X = 2 Show page as bitmap with marked lines. X = 4 Show page as bitmap with marked characters. Output file types 1..6 - pnm files P1 to P6. 7 - png mono (greyscale 1 bit). 8 - png greyscale 8 bit. Copyright (C) 2003-2024 Antonio Diaz Diaz. This file is free documentation: you have unlimited permission to copy, distribute, and modify it. The file Makefile.in is a data file used by configure to produce the Makefile. It has the same copyright owner and permissions that configure itself. ocrad-0.29/textpage.h0000644000175000017500000000253414545576754014454 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Textblock; class Textpage { const std::string name; std::vector< Textblock * > tbpv; Textpage( const Textpage & ); // declared as private void operator=( const Textpage & ); // declared as private public: Textpage( const Page_image & page_image, const char * const filename, const Control & control, const bool layout ); ~Textpage(); const Textblock & textblock( const int i ) const; int textblocks() const { return tbpv.size(); } int textlines() const; int characters() const; void print( const Control & control ) const; void xprint( const Control & control ) const; }; ocrad-0.29/ocradlib.h0000644000175000017500000000722114545576754014410 0ustar andriusandrius/* Ocradlib - Optical Character Recognition library Copyright (C) 2009-2024 Antonio Diaz Diaz. This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see . */ #ifdef __cplusplus extern "C" { #endif /* OCRAD_API_VERSION is defined as (major * 1000 + minor). */ #define OCRAD_API_VERSION 29 static const char * const OCRAD_version_string = "0.29"; enum OCRAD_Errno { OCRAD_ok = 0, OCRAD_bad_argument, OCRAD_mem_error, OCRAD_sequence_error, OCRAD_library_error }; int OCRAD_api_version( void ); const char * OCRAD_version( void ); /* OCRAD_Pixmap.data is a pointer to image data formed by "height" rows of "width" pixels each. The format for each pixel depends on mode like this: OCRAD_bitmap --> 1 byte per pixel; 0 = white, 1 = black OCRAD_greymap --> 1 byte per pixel; 256 level greymap (0 = black) OCRAD_colormap --> 3 bytes per pixel; 16777216 colors RGB (0,0,0 = black) */ enum OCRAD_Pixmap_Mode { OCRAD_bitmap, OCRAD_greymap, OCRAD_colormap }; struct OCRAD_Pixmap { const unsigned char * data; int height; int width; enum OCRAD_Pixmap_Mode mode; }; /* --------------------------- Functions --------------------------- */ struct OCRAD_Descriptor; struct OCRAD_Descriptor * OCRAD_open( void ); int OCRAD_close( struct OCRAD_Descriptor * const ocrdes ); enum OCRAD_Errno OCRAD_get_errno( struct OCRAD_Descriptor * const ocrdes ); int OCRAD_set_image( struct OCRAD_Descriptor * const ocrdes, const struct OCRAD_Pixmap * const image, const bool invert ); int OCRAD_set_image_from_file( struct OCRAD_Descriptor * const ocrdes, const char * const filename, const bool invert ); int OCRAD_set_utf8_format( struct OCRAD_Descriptor * const ocrdes, const bool utf8 ); // 0 = byte, 1 = utf8 int OCRAD_set_threshold( struct OCRAD_Descriptor * const ocrdes, const int threshold ); // 0..255, -1 = auto int OCRAD_scale( struct OCRAD_Descriptor * const ocrdes, const int value ); int OCRAD_recognize( struct OCRAD_Descriptor * const ocrdes, const bool layout ); int OCRAD_result_blocks( struct OCRAD_Descriptor * const ocrdes ); int OCRAD_result_lines( struct OCRAD_Descriptor * const ocrdes, const int blocknum ); // 0..blocks-1 int OCRAD_result_chars_total( struct OCRAD_Descriptor * const ocrdes ); int OCRAD_result_chars_block( struct OCRAD_Descriptor * const ocrdes, const int blocknum ); // 0..blocks-1 int OCRAD_result_chars_line( struct OCRAD_Descriptor * const ocrdes, const int blocknum, // 0..blocks-1 const int linenum ); // 0..lines(block)-1 const char * OCRAD_result_line( struct OCRAD_Descriptor * const ocrdes, const int blocknum, // 0..blocks-1 const int linenum ); // 0..lines(block)-1 int OCRAD_result_first_character( struct OCRAD_Descriptor * const ocrdes ); #ifdef __cplusplus } #endif ocrad-0.29/rectangle.h0000644000175000017500000001212514545576754014574 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Rectangle { int left_, top_, right_, bottom_; // inclusive coordinates public: Rectangle( const int l, const int t, const int r, const int b ); Rectangle( const int h, const int w ); // left_ = 0, top_ = 0 void left ( const int l ); void top ( const int t ); void right ( const int r ); void bottom( const int b ); void height( const int h ); void width ( const int w ); void add_point( const int row, const int col ) { if( row > bottom_ ) bottom_ = row; else if( row < top_ ) top_ = row; if( col > right_ ) right_ = col; else if( col < left_ ) left_ = col; } void add_rectangle( const Rectangle & re ) { if( re.left_ < left_ ) left_ = re.left_; if( re.top_ < top_ ) top_ = re.top_; if( re.right_ > right_ ) right_ = re.right_; if( re.bottom_ > bottom_ ) bottom_ = re.bottom_; } int left() const { return left_; } int top() const { return top_; } int right() const { return right_; } int bottom() const { return bottom_; } int height() const { return bottom_ - top_ + 1; } int width() const { return right_ - left_ + 1; } int size() const { return height() * width(); } int hcenter() const { return ( left_ + right_ ) / 2; } int vcenter() const { return ( top_ + bottom_ ) / 2; } int hpos( const int p ) const { return left_ + ( ( ( right_ - left_ ) * p ) / 100 ); } int vpos( const int p ) const { return top_ + ( ( ( bottom_ - top_ ) * p ) / 100 ); } bool operator==( const Rectangle & re ) const { return ( left_ == re.left_ && top_ == re.top_ && right_ == re.right_ && bottom_ == re.bottom_ ); } bool operator!=( const Rectangle & re ) const { return !( *this == re ); } bool includes( const Rectangle & re ) const { return left_ <= re.left_ && top_ <= re.top_ && right_ >= re.right_ && bottom_ >= re.bottom_; } bool includes( const int row, const int col ) const { return left_ <= col && right_ >= col && top_ <= row && bottom_ >= row; } bool strictly_includes( const Rectangle & re ) const { return left_ < re.left_ && top_ < re.top_ && right_ > re.right_ && bottom_ > re.bottom_; } bool strictly_includes( const int row, const int col ) const { return left_ < col && right_ > col && top_ < row && bottom_ > row; } bool includes_hcenter( const Rectangle & re ) const { const int hc = re.hcenter(); return left_ <= hc && right_ >= hc; } bool includes_vcenter( const Rectangle & re ) const { const int vc = re.vcenter(); return top_ <= vc && bottom_ >= vc; } bool h_includes( const Rectangle & re ) const { return left_ <= re.left_ && right_ >= re.right_; } bool h_includes( const int col ) const { return left_ <= col && right_ >= col; } bool v_includes( const Rectangle & re ) const { return top_ <= re.top_ && bottom_ >= re.bottom_; } bool v_includes( const int row ) const { return top_ <= row && bottom_ >= row; } bool h_overlaps( const Rectangle & re ) const { return left_ <= re.right_ && right_ >= re.left_; } bool v_overlaps( const Rectangle & re ) const { return top_ <= re.bottom_ && bottom_ >= re.top_; } int v_overlap_percent( const Rectangle & re ) const; bool is_hcentred_in( const Rectangle & re ) const; bool is_vcentred_in( const Rectangle & re ) const; bool precedes( const Rectangle & re ) const; bool h_precedes( const Rectangle & re ) const { return hcenter() < re.hcenter(); } bool v_precedes( const Rectangle & re ) const; int distance( const Rectangle & re ) const { return hypoti( h_distance( re ), v_distance( re ) ); } int distance( const int row, const int col ) const { return hypoti( h_distance( col ), v_distance( row ) ); } int h_distance( const Rectangle & re ) const { if( re.right_ <= left_ ) return left_ - re.right_; if( re.left_ >= right_ ) return re.left_ - right_; return 0; } int h_distance( const int col ) const { if( col <= left_ ) return left_ - col; if( col >= right_ ) return col - right_; return 0; } int v_distance( const Rectangle & re ) const { if( re.bottom_ <= top_ ) return top_ - re.bottom_; if( re.top_ >= bottom_ ) return re.top_ - bottom_; return 0; } int v_distance( const int row ) const { if( row <= top_ ) return top_ - row; if( row >= bottom_ ) return row - bottom_; return 0; } static int hypoti( const int c1, const int c2 ); }; ocrad-0.29/segment.cc0000644000175000017500000000307614545576754014435 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "segment.h" void Csegment::add_point( const int col ) { if( !valid() ) left = right = col; else if( col < left ) left = col; else if( col > right ) right = col; } void Csegment::add_csegment( const Csegment & seg ) { if( seg.valid() ) { if( !valid() ) *this = seg; else { if( seg.left < left ) left = seg.left; if( seg.right > right ) right = seg.right; } } } int Csegment::distance( const Csegment & seg ) const { if( !valid() || !seg.valid() ) return INT_MAX; if( seg.right < left ) return left - seg.right; if( seg.left > right ) return seg.left - right; return 0; } int Csegment::distance( const int col ) const { if( !valid() ) return INT_MAX; if( col < left ) return left - col; if( col > right ) return col - right; return 0; } ocrad-0.29/character.cc0000644000175000017500000003017414545576754014726 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "user_filter.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" Character::Character( const Character & c ) : Rectangle( c ), blobpv( c.blobpv ), gv( c.gv ) { for( unsigned i = 0; i < blobpv.size(); ++i ) blobpv[i] = new Blob( *c.blobpv[i] ); } Character & Character::operator=( const Character & c ) { if( this != &c ) { Rectangle::operator=( c ); for( unsigned i = 0; i < blobpv.size(); ++i ) delete blobpv[i]; blobpv = c.blobpv; for( unsigned i = 0; i < blobpv.size(); ++i ) blobpv[i] = new Blob( *c.blobpv[i] ); gv = c.gv; } return *this; } Character::~Character() { for( unsigned i = 0; i < blobpv.size(); ++i ) delete blobpv[i]; } // return the filled area of the main blobs only (no recursive) // int Character::area() const { int a = 0; for( int i = 0; i < blobs(); ++i ) a += blobpv[i]->area(); return a; } const Blob & Character::blob( const int i ) const { if( i < 0 || i >= blobs() ) throw Ocrad::Internal( "const blob: index out of bounds" ); return *blobpv[i]; } Blob & Character::blob( const int i ) { if( i < 0 || i >= blobs() ) throw Ocrad::Internal( "blob: index out of bounds" ); return *blobpv[i]; } Blob & Character::main_blob() { int imax = 0; for( int i = 1; i < blobs(); ++i ) if( blobpv[i]->size() > blobpv[imax]->size() ) imax = i; return *blobpv[imax]; } void Character::shift_blobp( Blob * const p ) { add_rectangle( *p ); int i = blobs() - 1; for( ; i >= 0; --i ) { Blob & bi = *blobpv[i]; if( p->vcenter() > bi.vcenter() ) break; if( p->vcenter() == bi.vcenter() && p->hcenter() >= bi.hcenter() ) break; } blobpv.insert( blobpv.begin() + ( i + 1 ), p ); } void Character::insert_guess( const int i, const int code, const int value ) { if( i < 0 || i > guesses() ) throw Ocrad::Internal( "insert_guess: index out of bounds" ); gv.insert( gv.begin() + i, Guess( code, value ) ); } void Character::delete_guess( const int i ) { if( i < 0 || i >= guesses() ) throw Ocrad::Internal( "delete_guess: index out of bounds" ); gv.erase( gv.begin() + i ); } bool Character::set_merged_guess( const int code1, const int right1, const int code2, const int blob_index ) { if( blob_index < 0 || blob_index >= blobs() ) return false; const Blob & b = *blobpv[blob_index]; if( b.left() <= right1 && right1 < b.right() ) { only_guess( -(blob_index + 1), left() ); add_guess( code1, right1 ); add_guess( code2, right() ); return true; } return false; } void Character::swap_guesses( const int i, const int j ) { if( i < 0 || i >= guesses() || j < 0 || j >= guesses() ) throw Ocrad::Internal( "swap_guesses: index out of bounds" ); const int code = gv[i].code; gv[i].code = gv[j].code; gv[j].code = code; } const Character::Guess & Character::guess( const int i ) const { if( i < 0 || i >= guesses() ) throw Ocrad::Internal( "guess: index out of bounds" ); return gv[i]; } bool Character::maybe( const int code ) const { for( int i = 0; i < guesses(); ++i ) if( code == gv[i].code ) return true; return false; } /* bool Character::maybe_digit() const { for( int i = 0; i < guesses(); ++i ) if( UCS::isdigit( gv[i].code ) ) return true; return false; } bool Character::maybe_letter() const { for( int i = 0; i < guesses(); ++i ) if( UCS::isalpha( gv[i].code ) ) return true; return false; } */ void Character::join( Character & c ) { for( int i = 0; i < c.blobs(); ++i ) shift_blobp( c.blobpv[i] ); c.blobpv.clear(); } unsigned char Character::byte_result() const { if( guesses() ) { const unsigned char ch = UCS::map_to_byte( gv[0].code ); if( ch ) return ch; } return '_'; } const char * Character::utf8_result() const { if( guesses() ) { const char * s = UCS::ucs_to_utf8( gv[0].code ); if( *s ) return s; } return "_"; } void Character::print( const Control & control ) const { if( guesses() ) { if( !control.utf8 ) { unsigned char ch = UCS::map_to_byte( gv[0].code ); if( ch ) std::putc( ch, control.outfile ); } else if( gv[0].code ) std::fputs( UCS::ucs_to_utf8( gv[0].code ), control.outfile ); } else std::putc( '_', control.outfile ); } void Character::dprint( const Control & control, const Rectangle & charbox, const bool graph, const bool recursive ) const { if( graph || recursive ) std::fprintf( control.outfile, "%d guesses ", guesses() ); for( int i = 0; i < guesses(); ++i ) { if( gv[i].code == '\t' ) std::fprintf( control.outfile, "guess '\\t', confidence %d ", gv[i].value ); else if( !control.utf8 || !gv[i].code ) { unsigned char ch = UCS::map_to_byte( gv[i].code ); if( ch ) std::fprintf( control.outfile, "guess '%c', confidence %d ", ch, gv[i].value ); } else std::fprintf( control.outfile, "guess '%s', confidence %d ", UCS::ucs_to_utf8( gv[i].code ), gv[i].value ); if( !graph && !recursive ) break; } std::fputc( '\n', control.outfile ); if( graph ) { std::fprintf( control.outfile, "left = %d, top = %d, right = %d, bottom = %d\n", left(), top(), right(), bottom() ); std::fprintf( control.outfile, "width = %d, height = %d, hcenter = %d, vcenter = %d, black area = %d%%\n", width(), height(), hcenter(), vcenter(), ( area() * 100 ) / size() ); if( blobs() >= 1 && blobs() <= 3 ) { const Blob & b = blob( blobs() - 1 ); Features f( b ); std::fprintf( control.outfile, "hbars = %d, vbars = %d\n", f.hbars(), f.vbars() ); } std::fputc( '\n', control.outfile ); const int minrow = std::min( top(), charbox.top() ); const int maxrow = std::max( bottom(), charbox.bottom() ); for( int row = minrow; row <= maxrow; ++row ) { bool istop = ( row == top() ); bool isvc = ( row == vcenter() ); bool isbot = ( row == bottom() ); bool iscbtop = ( row == charbox.top() ); bool iscbvc = ( row == charbox.vcenter() ); bool iscbbot = ( row == charbox.bottom() ); bool ish1top = false, ish1bot = false, ish2top = false, ish2bot = false; if( blobs() == 1 && blobpv[0]->holes() ) { const Blob & b = *blobpv[0]; ish1top = ( row == b.hole(0).top() ); ish1bot = ( row == b.hole(0).bottom() ); if( b.holes() > 1 ) { ish2top = ( row == b.hole(1).top() ); ish2bot = ( row == b.hole(1).bottom() ); } } for( int col = left(); col <= right(); ++col ) { char ch = ( isvc && col == hcenter() ) ? '+' : '.'; for( int i = 0; i < blobs(); ++i ) { int id = blobpv[i]->id( row, col ); if( id != 0 ) { if( id > 0 ) ch = (ch == '+') ? 'C' : 'O'; else ch = (ch == '+') ? '=' : '-'; break; } } std::fprintf( control.outfile, " %c", ch ); } if( istop ) std::fprintf( control.outfile, " top(%d)", row ); if( isvc ) std::fprintf( control.outfile, " vcenter(%d)", row ); if( isbot ) std::fprintf( control.outfile, " bottom(%d)", row ); if( iscbtop ) std::fprintf( control.outfile, " box.top(%d)", row ); if( iscbvc ) std::fprintf( control.outfile, " box.vcenter(%d)", row ); if( iscbbot ) std::fprintf( control.outfile, " box.bottom(%d)", row ); if( ish1top ) std::fprintf( control.outfile, " h1.top(%d)", row ); if( ish1bot ) std::fprintf( control.outfile, " h1.bottom(%d)", row ); if( ish2top ) std::fprintf( control.outfile, " h2.top(%d)", row ); if( ish2bot ) std::fprintf( control.outfile, " h2.bottom(%d)", row ); std::fputc( '\n', control.outfile ); } std::fputs( "\n\n", control.outfile ); } } void Character::xprint( const Control & control ) const { std::fprintf( control.exportfile, "%3d %3d %2d %2d; %d", left(), top(), width(), height(), guesses() ); for( int i = 0; i < guesses(); ++i ) if( !control.utf8 || !gv[i].code ) { unsigned char ch = UCS::map_to_byte( gv[i].code ); if( !ch ) ch = '_'; std::fprintf( control.exportfile, ", '%c'%d", ch, gv[i].value ); } else std::fprintf( control.exportfile, ", '%s'%d", UCS::ucs_to_utf8( gv[i].code ), gv[i].value ); std::fputc( '\n', control.exportfile ); } void Character::apply_filter( const Filter::Type filter ) { if( !guesses() ) return; const int code = gv[0].code; bool remove = false; switch( filter ) { case Filter::letters_only: remove = true; // fall through case Filter::letters: if( !UCS::isalpha( code ) && !UCS::isspace( code ) ) { for( int i = 1; i < guesses(); ++i ) if( UCS::isalpha( gv[i].code ) ) { swap_guesses( 0, i ); break; } if( gv[0].code == '+' && 2 * height() > 3 * width() ) { gv[0].code = 't'; break; } if( !UCS::isalpha( gv[0].code ) ) gv[0].code = UCS::to_nearest_letter( gv[0].code ); if( remove && !UCS::isalpha( gv[0].code ) ) clear_guesses(); } break; case Filter::numbers_only: remove = true; // fall through case Filter::numbers: if( !UCS::isdigit( code ) && !UCS::isspace( code ) ) { for( int i = 1; i < guesses(); ++i ) if( UCS::isdigit( gv[i].code ) ) { swap_guesses( 0, i ); break; } if( !UCS::isdigit( gv[0].code ) ) gv[0].code = UCS::to_nearest_digit( gv[0].code ); if( remove && !UCS::isdigit( gv[0].code ) ) clear_guesses(); } break; case Filter::same_height: break; // handled at line level case Filter::text_block: break; // handled at block level case Filter::upper_num_mark: // fall through case Filter::upper_num_only: remove = true; // fall through case Filter::upper_num: if( !UCS::isupper( code ) && !UCS::isdigit( code ) && !UCS::isspace( code ) ) { for( int i = 1; i < guesses(); ++i ) if( UCS::isupper( gv[i].code ) || UCS::isdigit( gv[i].code ) ) { swap_guesses( 0, i ); break; } if( !UCS::isupper( gv[0].code ) && !UCS::isdigit( gv[0].code ) ) gv[0].code = UCS::to_nearest_upper_num( gv[0].code ); if( remove && !UCS::isupper( gv[0].code ) && !UCS::isdigit( gv[0].code ) ) clear_guesses(); } break; case Filter::user: break; // handled by apply_user_filter } } void Character::apply_user_filter( const User_filter & user_filter ) { if( !guesses() || UCS::isspace( gv[0].code ) ) return; int new_code = user_filter.get_new_code( gv[0].code ); if( new_code >= 0 ) gv[0].code = new_code; else // disabled { for( int i = 1; i < guesses(); ++i ) { new_code = user_filter.get_new_code( gv[i].code ); if( new_code >= 0 ) { swap_guesses( 0, i ); gv[0].code = new_code; break; } } if( new_code < 0 ) clear_guesses(); } } ocrad-0.29/profile.h0000644000175000017500000000421414545576754014270 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Profile { public: enum Type { left, top, right, bottom, height, width }; private: const Bitmap & bm; // Bitmap to witch this profile belongs // may be a Blob or a hole Type type; int limit_, max_, min_, mean_; signed char isconcave_, isconvex_, isflat_, isflats_, ispit_, istpit_, isupit_, isvpit_, istip_; std::vector< int > data; void initialize(); int mean(); public: Profile( const Bitmap & bm_, const Type t ); // const Bitmap & bitmap() const { return bm; } int limit() { if( limit_ < 0 ) initialize(); return limit_; } int max(); int max( const int l, int r = -1 ); int min(); int min( const int l, int r = -1 ); int operator[]( int i ); int pos( const int p ) { return ( ( samples() - 1 ) * p ) / 100; } int range() { return max() - min(); } int samples() { if( limit_ < 0 ) initialize(); return data.size(); } int area( const int l = 0, int r = -1 ); bool increasing( int i = 1, const int min_delta = 2 ); bool decreasing( int i = 1, int end = -1 ); bool isconcave(); bool isconvex(); bool isflat(); bool isflats(); bool ispit(); bool iscpit( const int cpos = 50 ); bool istpit(); bool isupit(); bool isvpit(); bool istip(); bool isctip( const int cpos = 50 ); bool isltip(); bool isrtip(); int imaximum(); int iminimum( const int m = 0, int th = -1 ); int minima( int th = -1 ); bool straight( int * const dyp ); }; ocrad-0.29/mask.h0000644000175000017500000000337614545576754013573 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Mask : public Rectangle { std::vector< Csegment > data; // csegment in each line public: // Create a rectangular Mask explicit Mask( const Rectangle & re ) : Rectangle( re ), data( height(), Csegment( re.left(), re.right() ) ) {} Mask( const int l, const int t, const int r, const int b ) : Rectangle( l, t, r, b ), data( height(), Csegment( l, r ) ) {} using Rectangle::left; using Rectangle::top; using Rectangle::right; using Rectangle::bottom; using Rectangle::height; int left ( const int row ) const; int right( const int row ) const; void top ( const int t ); void bottom( const int b ); void height( const int h ) { bottom( top() + h - 1 ); } void add_mask( const Mask & m ); void add_point( const int row, const int col ); void add_rectangle( const Rectangle & re ); bool includes( const Rectangle & re ) const; bool includes( const int row, const int col ) const; int distance( const Rectangle & re ) const; int distance( const int row, const int col ) const; }; ocrad-0.29/track.h0000644000175000017500000000514014545576754013733 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ class Vrhomboid // Rhomboid with two vertical sides. { int left_, lvcenter_, right_, rvcenter_, height_; public: Vrhomboid( const int l, const int lc, const int r, const int rc, const int h ); int left() const { return left_; } int lvcenter() const { return lvcenter_; } int right() const { return right_; } int rvcenter() const { return rvcenter_; } int height() const { return height_; } int width() const { return right_ - left_ + 1; } int size() const { return height_ * width(); } void left( const int l ); void lvcenter( const int lc ) { lvcenter_ = lc; } void right( const int r ); void rvcenter( const int rc ) { rvcenter_ = rc; } void height( const int h ); void extend_left( const int l ); void extend_right( const int r ); int bottom( const int col ) const { return vcenter( col ) + ( height_ / 2 ); } int top( const int col ) const { return bottom( col ) - height_ + 1; } int vcenter( const int col ) const; bool includes( const Rectangle & r ) const; bool includes( const int row, const int col ) const; }; class Track // vector of Vrhomboids tracking a Textline. { std::vector< Vrhomboid > data; public: Track() {} Track( const Track & t ) : data( t.data ) {} Track & operator=( const Track & t ) { if( this != &t ) { data = t.data; } return *this; } void set_track( const std::vector< Rectangle > & rectangle_vector ); int segments() const { return data.size(); } int height() const { return data.empty() ? 0 : data[0].height(); } int left() const { return data.empty() ? 0 : data[0].left(); } int right() const { return data.empty() ? 0 : data.back().right(); } int bottom( const int col ) const; int top( const int col ) const; int vcenter( const int col ) const; // bool includes( const Rectangle & r ) const; // bool includes( const int row, const int col ) const; }; ocrad-0.29/ocrad.png0000644000175000017500000000033310363251477014235 0ustar andriusandrius‰PNG  IHDR0¼ý~Ü¢IDATX…í–Ý€ …¥õþ¯L7±~DE›‹oë";ëˆÇ°”‚àßÀó‘‘@>f­Ós{gÆ™Zëx0%<'XÕnZ¹àºìa £–¡TçK!uØm^ó] ‘À(ÔmzŸSwšPÀ»Ún·É ä“/õùÁsÀßܨDÙÉ[ÓèX¾ 1Ð*µVKª#|R($ 5±ß½8þN“F:†Öë2.¼BgIEND®B`‚ocrad-0.29/ocradlib.cc0000644000175000017500000002145414545576754014552 0ustar andriusandrius/* Ocradlib - Optical Character Recognition library Copyright (C) 2009-2024 Antonio Diaz Diaz. This library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see . */ #include #include #include #include #include #include #include #include "ocradlib.h" #include "common.h" #include "rectangle.h" #include "ucs.h" #include "track.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "page_image.h" #include "textline.h" #include "textblock.h" #include "textpage.h" struct OCRAD_Descriptor { Page_image * page_image; Textpage * textpage; OCRAD_Errno ocr_errno; Control control; std::string text; OCRAD_Descriptor() : page_image( 0 ), textpage( 0 ), ocr_errno( OCRAD_ok ) { control.outfile = 0; } }; bool check_descriptor( OCRAD_Descriptor * const ocrdes, const bool result = false ) { if( !ocrdes ) return false; if( !ocrdes->page_image || ( result && !ocrdes->textpage ) ) { ocrdes->ocr_errno = OCRAD_sequence_error; return false; } return true; } int OCRAD_api_version( void ) { return OCRAD_API_VERSION; } const char * OCRAD_version() { return OCRAD_version_string; } OCRAD_Descriptor * OCRAD_open() { verbosity = -1; // keep library silent return new( std::nothrow ) OCRAD_Descriptor; // may be null } int OCRAD_close( OCRAD_Descriptor * const ocrdes ) { if( !ocrdes ) return -1; if( ocrdes->textpage ) { delete ocrdes->textpage; ocrdes->textpage = 0; } if( ocrdes->page_image ) { delete ocrdes->page_image; ocrdes->page_image = 0; } delete ocrdes; return 0; } OCRAD_Errno OCRAD_get_errno( OCRAD_Descriptor * const ocrdes ) { if( !ocrdes ) return OCRAD_bad_argument; return ocrdes->ocr_errno; } int OCRAD_set_image( OCRAD_Descriptor * const ocrdes, const OCRAD_Pixmap * const image, const bool invert ) { if( !ocrdes ) return -1; if( !image || image->height < 3 || image->width < 3 || INT_MAX / image->width < image->height || ( image->mode != OCRAD_bitmap && image->mode != OCRAD_greymap && image->mode != OCRAD_colormap ) ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } try { Page_image * const page_image = new Page_image( *image, invert ); if( ocrdes->textpage ) { delete ocrdes->textpage; ocrdes->textpage = 0; } if( ocrdes->page_image ) delete ocrdes->page_image; ocrdes->page_image = page_image; } catch( std::bad_alloc & ) { ocrdes->ocr_errno = OCRAD_mem_error; return -1; } catch( ... ) { ocrdes->ocr_errno = OCRAD_library_error; return -1; } return 0; } int OCRAD_set_image_from_file( OCRAD_Descriptor * const ocrdes, const char * const filename, const bool invert ) { if( !ocrdes ) return -1; FILE * infile = 0; if( filename && filename[0] ) { if( std::strcmp( filename, "-" ) == 0 ) infile = stdin; else infile = std::fopen( filename, "rb" ); } if( !infile ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } int retval = 0; try { Page_image * const page_image = new Page_image( infile, invert ); if( ocrdes->textpage ) { delete ocrdes->textpage; ocrdes->textpage = 0; } if( ocrdes->page_image ) delete ocrdes->page_image; ocrdes->page_image = page_image; } catch( std::bad_alloc & ) { ocrdes->ocr_errno = OCRAD_mem_error; retval = -1; } catch( Page_image::Error & ) { ocrdes->ocr_errno = OCRAD_bad_argument; retval = -1; } catch( ... ) { ocrdes->ocr_errno = OCRAD_library_error; retval = -1; } std::fclose( infile ); return retval; } int OCRAD_set_utf8_format( OCRAD_Descriptor * const ocrdes, const bool utf8 ) { if( !check_descriptor( ocrdes ) ) return -1; ocrdes->control.utf8 = utf8; return 0; } int OCRAD_set_threshold( OCRAD_Descriptor * const ocrdes, const int threshold ) { if( !check_descriptor( ocrdes ) ) return -1; if( threshold < -1 || threshold > 255 ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } ocrdes->page_image->threshold( threshold ); return 0; } int OCRAD_scale( OCRAD_Descriptor * const ocrdes, const int value ) { if( !check_descriptor( ocrdes ) ) return -1; try { if( !ocrdes->page_image->change_scale( value ) ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } } catch( std::bad_alloc & ) { ocrdes->ocr_errno = OCRAD_mem_error; return -1; } catch( Page_image::Error & ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } catch( ... ) { ocrdes->ocr_errno = OCRAD_library_error; return -1; } return 0; } int OCRAD_recognize( OCRAD_Descriptor * const ocrdes, const bool layout ) { if( !check_descriptor( ocrdes ) ) return -1; try { Textpage * const textpage = new Textpage( *ocrdes->page_image, "", ocrdes->control, layout ); if( ocrdes->textpage ) delete ocrdes->textpage; ocrdes->textpage = textpage; } catch( std::bad_alloc & ) { ocrdes->ocr_errno = OCRAD_mem_error; return -1; } catch( ... ) { ocrdes->ocr_errno = OCRAD_library_error; return -1; } return 0; } int OCRAD_result_blocks( OCRAD_Descriptor * const ocrdes ) { if( !check_descriptor( ocrdes, true ) ) return -1; return ocrdes->textpage->textblocks(); } int OCRAD_result_lines( OCRAD_Descriptor * const ocrdes, const int blocknum ) { if( !check_descriptor( ocrdes, true ) ) return -1; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } return ocrdes->textpage->textblock( blocknum ).textlines(); } int OCRAD_result_chars_total( OCRAD_Descriptor * const ocrdes ) { if( !check_descriptor( ocrdes, true ) ) return -1; int c = 0; for( int b = 0; b < ocrdes->textpage->textblocks(); ++b ) for( int i = 0; i < ocrdes->textpage->textblock( b ).textlines(); ++i ) c += ocrdes->textpage->textblock( b ).textline( i ).characters(); return c; } int OCRAD_result_chars_block( OCRAD_Descriptor * const ocrdes, const int blocknum ) { if( !check_descriptor( ocrdes, true ) ) return -1; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } int c = 0; for( int i = 0; i < ocrdes->textpage->textblock( blocknum ).textlines(); ++i ) c += ocrdes->textpage->textblock( blocknum ).textline( i ).characters(); return c; } int OCRAD_result_chars_line( OCRAD_Descriptor * const ocrdes, const int blocknum, const int linenum ) { if( !check_descriptor( ocrdes, true ) ) return -1; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() || linenum < 0 || linenum >= ocrdes->textpage->textblock( blocknum ).textlines() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return -1; } return ocrdes->textpage->textblock( blocknum ).textline( linenum ).characters(); } const char * OCRAD_result_line( OCRAD_Descriptor * const ocrdes, const int blocknum, const int linenum ) { if( !check_descriptor( ocrdes, true ) ) return 0; if( blocknum < 0 || blocknum >= ocrdes->textpage->textblocks() || linenum < 0 || linenum >= ocrdes->textpage->textblock( blocknum ).textlines() ) { ocrdes->ocr_errno = OCRAD_bad_argument; return 0; } const Textline & textline = ocrdes->textpage->textblock( blocknum ).textline( linenum ); ocrdes->text.clear(); if( !ocrdes->control.utf8 ) for( int i = 0; i < textline.characters(); ++i ) ocrdes->text += textline.character( i ).byte_result(); else for( int i = 0; i < textline.characters(); ++i ) ocrdes->text += textline.character( i ).utf8_result(); ocrdes->text += '\n'; return ocrdes->text.c_str(); } int OCRAD_result_first_character( OCRAD_Descriptor * const ocrdes ) { if( !check_descriptor( ocrdes, true ) ) return -1; int ch = 0; if( ocrdes->textpage->textblocks() > 0 && ocrdes->textpage->textblock( 0 ).textlines() > 0 ) { const Character & character = ocrdes->textpage->textblock( 0 ).textline( 0 ).character( 0 ); if( character.guesses() ) { if( !ocrdes->control.utf8 ) ch = UCS::map_to_byte( character.guess( 0 ).code ); else ch = character.guess( 0 ).code; } } return ch; } ocrad-0.29/feats_test0.cc0000644000175000017500000010243014551077304015166 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "profile.h" #include "feats.h" /* Look for three black sections in column hcenter() ± n, then test whether upper and lower gaps are open to the right or to the left. */ int Features::test_235Esz( const Charset & charset ) const { const int csize = 3; const int ucoff[csize] = { 0, -1, +1 }; const int lcoff[3*csize] = { 0, -1, +1, -1, 0, +1, +1, 0, -1 }; if( b.width() < 9 || b.height() > 3 * b.width() || bp.minima( b.height() / 2 ) > 1 ) return 0; const int noise = ( std::min( b.height(), b.width() ) / 15 ) + 1; int lrow1 = 0, urow2 = 0, lrow2 = 0, urow3 = 0; int lcol1 = 0, ucol2 = 0, lcol2 = 0, ucol3 = 0; bool done = false; for( int i = 0; i < csize && !done; ++i ) { const int ucol = b.hcenter() + ( noise * ucoff[i] ); int row = b.top() + tp[ucol-b.left()]; while( ++row < b.bottom() && b.get_bit( row, ucol ) ) ; if( row <= b.vpos( 30 ) ) { lrow1 = row; lcol1 = ucol; } else continue; while( ++row < b.bottom() && !b.get_bit( row, ucol ) ) ; if( row < b.bottom() ) { urow2 = row - 1; ucol2 = ucol; for( int j = 0; j < csize && !done; ++j ) { row = urow2 + 1; const int lcol = b.hcenter() + ( noise * lcoff[(csize*i)+j] ); if( ucol != lcol ) { const int d = ( ucol > lcol ) ? +1 : -1; int c = lcol; while( c != ucol && b.get_bit( row, c ) ) c += d; if( c != ucol ) continue; } while( ++row < b.bottom() && b.get_bit( row, lcol ) ) ; if( row < b.bottom() ) { lrow2 = row; lcol2 = lcol; } else continue; while( ++row <= b.bottom() && !b.get_bit( row, lcol ) ) ; if( row <= b.bottom() && row > b.vpos( 70 ) ) { urow3 = row - 1; ucol3 = lcol; done = true; } } } } if( !done ) return 0; const bool bopen = b.escape_bottom( urow3, ucol3 ); const bool topen = b.escape_top( lrow1, lcol1 ); const bool tbopen = bopen && topen; const int ascode = ( b.get_bit( b.vcenter(), b.hcenter() ) ) ? '*' : 0; if( b.escape_left( lrow2, lcol2 ) ) { if( b.escape_left( urow2, ucol2 ) ) { if( tbopen ) return ascode; if( !bopen && !topen && b.height() <= 3 * b.width() ) { const int lm = lp.minima(), rm = rp.minima(); if( ( lm == 3 || lm == 2 ) && ( rm == 2 || ( rm == 1 && rp.iminimum() < rp.pos( 80 ) ) ) ) return '3'; } } else if( b.escape_right( urow2, ucol2 ) ) { if( tbopen ) return ascode; if( rp[lrow1 + 1 - b.top()] >= lcol1 - b.left() && ( lp[lrow2 + 1 - b.top()] < lcol2 - b.left() || lp[urow3 - 1 - b.top()] < ucol3 - b.left() ) ) { for( int i = lp.pos( 40 ); i <= lp.pos( 70 ); ++i ) if( 5 * lp[i] < b.width() && 2 * lp[i+1] > b.width() ) return '5'; int c = 0, hdiff; if( !b.top_hook( &hdiff ) || 5 * hdiff >= 4 * b.height() ) ++c; if( 2 * lp[lrow2 - b.top()] < lcol2 - b.left() ) ++c; if( !tp.isconvex() || ( !tp.ispit() && bp.ispit() ) ) ++c; if( c >= 2 ) return '5'; } if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( urow2 > b.vpos( 55 ) && b.seek_right( urow2 - 1, ucol2 ) < b.right() ) { if( urow2 > b.vpos( 63 ) ) return UCS::CCCEDI; else return UCS::SCCEDI; } return 's'; } } else if( b.escape_right( lrow2, lcol2 ) ) { if( b.escape_right( urow2, ucol2 ) ) { if( tbopen ) return ascode; if( bp.minima( b.height() / 5 ) == 1 ) { if( 8 * lp[((lrow2+urow3)/2)-b.top()] >= b.width() && b.escape_top( ( lrow1 + urow2 ) / 2, b.left() ) && !b.escape_top( ( lrow2 + urow3 ) / 2, b.left() ) ) return 'f'; if( rp.minima( b.width() / 8 ) < 3 && b.escape_bottom( urow3, ucol3 ) ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( 2 * lp[lp.pos(95)] > rp[rp.pos(95)] ) { if( urow2 > b.vpos( 63 ) ) return UCS::CCCEDI; else return UCS::SCCEDI; } return 'F'; } else if( lrow1 < urow2 && urow2 + 2 < lrow2 && lrow2 < urow3 && urow2 <= b.vcenter() && lrow2 >= b.vcenter() ) return 'E'; } } else if( b.escape_left( urow2, ucol2 ) ) { if( !tbopen && ( 2 * lp[lp.pos(50)] ) + 2 >= b.width() && ( tp.isconvex() || ( (tp.ispit() || tp.isrtip()) && !bp.ispit() ) ) ) return '2'; if( 2 * b.height() <= 5 * wp.max() && bp[bp.pos(75)] <= b.height() / 10 && Ocrad::similar( wp.max( 0, wp.pos(30) ), wp.max( wp.pos(70) ), 20 ) ) return 'z'; } } return 0; } int Features::test_EFIJLlT( const Charset & charset, const Rectangle & charbox ) const { if( tp.minima( b.height() / 4 ) != 1 || bp.minima( b.height() / 4 ) != 1 ) return 0; const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; { const bool maybe_j = ( 2 * ( lp[lp.pos(50)] + noise ) >= b.width() ); const int col = b.hpos( maybe_j ? 25 : 75 ); int row = b.seek_top( b.vcenter(), col ); if( row <= b.top() || ( row < b.vpos( 25 ) && b.escape_top( row, col ) ) ) { int hdiff; if( b.bottom_hook( &hdiff ) ) { if( maybe_j && hdiff > b.height() / 2 && rp.increasing( rp.pos( 80 ), 1 ) && !rp.decreasing() ) return 'J'; if( !maybe_j && -hdiff > b.height() / 2 ) { if( 5 * lp[lp.pos(80)] >= 2 * b.width() ) return 'v'; // broken 'v' if( col > b.hcenter() ) return 'L'; } } } } const int vnoise = ( b.height() / 30 ) + 1; const int topmax = b.top() + vnoise; const int botmin = b.bottom() - vnoise; if( vbars() == 1 && vbar(0).width() >= 2 && 2 * vbar(0).width() <= b.width() ) { if( std::abs( vbar(0).hcenter() - b.hcenter() ) <= noise && std::abs( (vbar(0).left() - b.left()) - (b.right() - vbar(0).right()) ) <= 2 * noise ) { if( hbars() == 1 && 4 * hbar(0).height() <= b.height() ) { if( ( hbar(0).top() <= topmax || hbar(0).bottom() < b.vpos( 15 ) ) && hbar(0).width() >= wp[wp.pos(75)] + wp[wp.pos(80)] && 4 * lp[lp.pos(50)] >= b.width() ) return 'T'; if( std::abs( hbar(0).vcenter() - b.vcenter() ) <= vnoise && hbar(0).width() >= b.width() && Ocrad::similar( b.height(), b.width(), 50 ) ) return '+'; } if( hbars() == 2 && hbar(0).top() <= topmax && 4 * hbar(0).height() <= b.height() && hbar(1).bottom() >= botmin && 4 * hbar(1).height() <= b.height() && 3 * hbar(0).width() > 4 * hbar(1).width() ) return 'T'; } } if( vbars() == 1 && vbar(0).width() >= 2 ) { if( 2 * vbar(0).width() <= b.width() && vbar(0).right() <= b.hcenter() ) { if( ( hbars() == 2 || hbars() == 3 ) && hbar(0).top() <= topmax && hbar(0).width() + 1 >= hbar(1).width() && 2 * hbar(1).width() >= 3 * vbar(0).width() && vbar(0).h_overlaps( hbar(1) ) ) { if( hbars() == 3 && Ocrad::similar( hbar(0).width(), hbar(2).width(), 10, 2 ) && 10 * hbar(2).width() >= 9 * hbar(1).width() && hbar(0).left() <= hbar(1).left() + 1 ) return 'E'; if( ( hbars() == 2 || hbar(0).width() > hbar(2).width() ) && ( hbar(1).includes_vcenter( b ) || ( 3 * hbar(1).width() > 2 * hbar(0).width() && 10 * lp[vnoise] < b.width() && hbar(1).top() > b.vpos( 30 ) && hbar(1).bottom() < b.vpos( 60 ) ) ) ) return 'F'; } if( hbars() == 2 && hbar(1).bottom() >= botmin && b.height() > b.width() && hbar(1).width() > hbar(0).width() && std::abs( vbar(0).hcenter() - hbar(0).hcenter() ) <= 1 && rp.iminimum() > rp.pos( 70 ) ) return 'L'; if( hbars() == 1 && Ocrad::similar( hbar(0).width(), b.width(), 10 ) && vbar(0).left() <= b.hpos( 30 ) ) { if( hbar(0).bottom() >= botmin && b.escape_top( b.vcenter(), b.hpos( 75 ) ) && b.escape_right( b.vcenter(), b.hpos( 75 ) ) ) return 'L'; if( hbar(0).top() <= topmax && 2 * wp[wp.pos(50)] >= b.width() && 4 * wp[wp.pos(75)] < b.width() && b.escape_right( b.vpos( 25 ), b.hcenter() ) ) return 'F'; } } if( 3 * vbar(0).width() < 2 * b.width() && vbar(0).left() > b.hpos( 33 ) && hbars() == 1 ) { if( vbar(0).right() >= b.hpos( 90 ) && hbar(0).bottom() >= botmin && hbar(0).left() == b.left() && b.bottom() > charbox.vpos( 90 ) && b.escape_top( b.vcenter(), b.hpos( 25 ) ) ) { if( b.height() > b.width() ) return 'J'; else return 0; } if( hbar(0).top() <= topmax && hbar(0).width() + 1 >= b.width() && b.width() > b.height() ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) return UCS::NOT; return 0; } } } if( vbars() == 1 && vbar(0).width() >= 2 && tp.minima() == 1 && bp.minima() == 1 ) { if( 3 * b.height() > 4 * b.width() && Ocrad::similar( vbar(0).left() - b.left(), b.right() - vbar(0).right(), 30, 2 * noise ) ) { if( b.height() <= 3 * wp.max() && rp.istip() && lp.istip() ) { if( b.height() <= 3 * b.width() && lp[lp.pos(40)] > lp[lp.pos(60)] + noise && rp[rp.pos(60)] > rp[rp.pos(40)] + noise ) return 'z'; return 'I'; } if( rp.isflats() && ( lp.istip() || lp.isflats() || ( lp.isctip() && lp.minima() == 2 && lp.iminimum() < lp.pos( 30 ) && lp.iminimum(1) > lp.pos( 80 ) ) ) ) return 'l'; if( b.height() > 3 * wp.max() ) { if( rp.istip() && lp.ispit() && Ocrad::similar( lp.iminimum(), lp.pos( 50 ), 10 ) ) { if( lp.istpit() ) return '{'; else return '('; } if( lp.istip() && rp.ispit() && Ocrad::similar( rp.iminimum(), rp.pos( 50 ), 10 ) ) { if( rp.istpit() ) return '}'; else return ')'; } if( rp.isflats() && 2 * vbar(0).size() >= b.area() ) return 'l'; } if( 2 * b.height() > 3 * b.width() && lp.minima() <= 2 ) if( rp.isflats() || rp.minima() == 1 ) if( vbar(0).right() >= b.hpos( 70 ) || b.escape_top( b.vpos( 75 ), std::min( b.right(), vbar(0).right() + 1 ) ) ) for( int i = vbar(0).left() - 1; i > b.left(); --i ) if( b.seek_bottom( b.vpos( 75 ), i ) < b.bottom() && bp[i-b.left()] <= noise ) return 'l'; } if( vbar(0).right() >= b.right() - 1 ) { if( lp.istip() && b.height() > 2 * b.width() ) { if( 2 * vbar(0).width() <= wp.max() && lp[lp.pos(50)] >= b.width() / 2 ) return ']'; if( b.height() >= 3 * b.width() ) return 'l'; } if( 2 * b.height() >= 3 * b.width() && vbar(0).height() >= 3 * vbar(0).width() && lp.istpit() && lp.minima() == 1 ) { const int i = lp.iminimum(); if( i > lp.pos( 10 ) && i < lp.pos( 40 ) ) return '1'; } } } if( hbars() == 1 && hbar(0).width() >= b.width() && std::abs( hbar(0).vcenter() - b.vcenter() ) <= vnoise && Ocrad::similar( b.height(), b.width(), 50 ) && tp.isupit() && bp.isupit() ) return '+'; return 0; } int Features::test_c() const { if( lp.isconvex() || lp.ispit() ) { int urow = b.seek_top( b.vcenter(), b.hcenter() ); int lrow = b.seek_bottom( b.vcenter(), b.hcenter() ); if( b.height() > 2 * b.width() && 3 * wp.max() <= 2 * b.width() ) { if( lp.isconvex() ) return '('; else return 0; } if( urow > b.top() && lrow < b.bottom() && rp.isctip() && ( bp.ispit() || tp.ispit() || ( bp.isltip() && tp.isltip() ) ) && b.escape_right( b.vcenter(), b.hcenter() ) ) return 'c'; } if( b.height() > 2 * b.width() && rp.isconvex() ) { int urow = b.seek_top( b.vcenter(), b.hcenter() ); int lrow = b.seek_bottom( b.vcenter(), b.hcenter() ); if( 3 * wp.max() <= 2 * b.width() || ( 2 * lp[urow-b.top()] >= b.width() && 2 * lp[lrow-b.top()] >= b.width() ) ) return ')'; } return 0; } int Features::test_frst( const Rectangle & charbox ) const { if( bp.minima( b.height() / 4 ) != 1 || tp.minima( b.height() / 2 ) != 1 || bp.minima( b.height() / 2 ) != 1 ) return 0; const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; const bool maybe_slanted_r = ( tp.minima( b.height() / 4 ) != 1 ); bool maybe_t = true; if( !maybe_slanted_r ) { int b_hdiff = 0, t_hdiff = 0; if( b.bottom_hook( &b_hdiff ) ) { if( -2 * b_hdiff > b.height() ) { if( b.height() >= 3 * wp.max() && !lp.ispit() && ( hbars() == 0 || hbar(0).bottom() < b.vpos( 20 ) ) ) return 'l'; if( 2 * wp[wp.pos(6)] < b.width() && hbars() >= 1 && hbars() <= 2 && hbar(0).top() >= b.vpos( 15 ) && hbar(0).bottom() < b.vcenter() && Ocrad::similar( hbar(0).width(), wp.max(), 10 ) ) return 't'; } } if( b.top_hook( &t_hdiff ) ) { if( 3 * t_hdiff > 2 * b.height() && b.height() > 2 * wp.max() && tp.iminimum() > tp.pos( 50 ) && bp.iminimum() <= bp.pos( 50 ) && ( !b_hdiff || rp.increasing( rp.pos( 50 ) ) ) ) return 'f'; if( 2 * b_hdiff > b.height() && 2 * t_hdiff > b.height() ) return 0; // recognized 's' or SCCEDI maybe_t = false; } } if( 2 * rp[rp.pos(50)] > b.width() && 2 * bp[bp.pos(50)] > b.height() && tp.isctip() ) return 'r'; if( maybe_slanted_r || vbars() != 1 || vbar(0).width() < 2 ) return 0; if( vbar(0).hcenter() <= b.hcenter() ) { const int col = b.right() - rp[rp.pos(50)] + 2; if( col < b.right() ) { const int row = b.seek_bottom( b.vcenter(), col ); if( row >= b.bottom() || b.escape_bottom( row - 1, col ) ) { if( rp.minima() == 3 ) { if( rp.minima( b.width() / 8 ) < 3 ) return 'f'; else return 0; } if( Ocrad::similar( b.height(), b.width(), 40 ) ) { if( tp.minima( b.height() / 8 ) == 2 && bp.minima( b.height() / 8 ) == 2 ) return 'x'; int row2 = b.vpos( 75 ); int col2 = b.seek_right( row2, b.hcenter(), false ) + 1; if( b.seek_right( row2, col2 ) >= b.right() ) { if( lp.isconvex() && ( col > b.hpos( 60 ) || row < b.bottom() ) ) return 0; if( ( hbars() == 1 || ( hbars() == 2 && hbar(1).bottom() >= b.bottom() - 1 && 2 * hbar(0).width() > 3 * hbar(1).width() ) ) && hbar(0).top() <= b.top() + 1 && 4 * hbar(0).height() <= b.height() && 4 * lp[lp.pos(50)] >= b.width() ) return 'T'; if( 3 * rp[rp.pos(50)] > b.width() ) return 'r'; return 0; } } } if( Ocrad::similar( b.height(), b.width(), 40 ) && segments_in_row( b.vpos( 15 ) ) == 3 && segments_in_row( b.vpos( 85 ) ) == 3 && b.seek_right( row - 1, col ) < b.right() && lp.isctip() ) return 'x'; } if( 3 * b.height() > 4 * b.width() && vbar(0).left() > b.left() && rp.minima() <= 2 ) { const int col = b.right() - std::max( 0, rp[rp.pos(50)] - 1 ); if( !b.escape_bottom( b.vcenter(), col ) ) { if( 3 * wp[wp.pos(6)] < 2 * b.width() && tp.ispit() && lp.iminimum() < lp.pos( 40 ) ) return 't'; else return 0; } else if( 2 * wp.max() > b.width() ) { if( rp.iminimum() < rp.pos( 20 ) ) { if( rp.increasing( rp.pos( 20 ) ) || bp.increasing() || tp.minima( noise ) == 2 || ( rp.minima() == 1 && ( b.height() < charbox.height() || tp.iminimum() > tp.pos( 50 ) ) ) ) { if( b.height() <= 3 * wp.max() ) return 'r'; else return 0; } else if( 3 * b.height() >= 5 * b.width() && !rp.istip() ) return 'f'; } else { if( maybe_t && !rp.isconvex() && bp.minima( b.height() / 3 ) == 1 ) return 't'; else return 0; } } } if( b.seek_bottom( b.vcenter(), b.hpos( 60 ) + 1 ) >= b.bottom() ) { if( rp.minima() == 2 ) return 'f'; else return 'r'; } if( vbar(0).right() <= b.hcenter() && hbars() == 1 && hbar(0).bottom() >= b.bottom() - 1 && lp.istip() && rp.istip() && !b.escape_top( b.vcenter(), b.hpos( 75 ) ) ) return 'r'; } return 0; } int Features::test_G() const { if( lp.isconvex() || lp.ispit() ) { int col = 0, row = 0; for( int i = rp.pos( 60 ); i >= rp.pos( 30 ); --i ) if( rp[i] > col ) { col = rp[i]; row = i; } if( col == 0 ) return 0; row += b.top(); col = b.right() - col + 1; if( col <= b.left() || col >= b.hcenter() ) return 0; col = ( col + b.hcenter() ) / 2; row = b.seek_bottom( row, col ); if( row < b.bottom() && b.escape_right( row, col ) && !b.escape_bottom( row, b.hcenter() ) ) { const int noise = std::max( 2, b.height() / 20 ); int lrow, urow; for( lrow = row - 1 ; lrow > b.top(); --lrow ) if( b.seek_right( lrow, b.hcenter() ) >= b.right() ) break; for( urow = lrow - 1 ; urow > b.top(); --urow ) if( b.seek_right( urow, b.hcenter() ) < b.right() ) break; lrow += noise; if( lrow < row && urow > b.top() ) { urow -= std::min( noise, ( urow - b.top() ) / 2 ); int uwidth = b.seek_left( urow, b.right() ) - b.seek_right( urow, b.hcenter() ); int lwidth = b.seek_left( lrow, b.right() ) - b.seek_right( lrow, b.hcenter() ); if( lrow - noise <= b.vcenter() || lwidth > uwidth + noise ) return 'G'; } } } return 0; } // common feature: U-shaped top of character // int Features::test_HKMNUuvwYy( const Rectangle & charbox ) const { if( tp.minima( b.height() / 5 ) == 2 && tp.minima( b.height() / 4 ) == 2 && tp.minima( b.height() / 2 ) <= 3 && tp.isctip() ) { const int noise = ( std::min( b.height(), b.width() ) / 30 ) + 1; const int m5 = bp.minima( b.height() / 5 ); if( 2 * b.height() >= b.width() && b.height() >= 10 && ( m5 == 1 || ( m5 == 2 && Ocrad::similar( bp.iminimum(), bp.pos( 50 ), 10 ) ) ) ) { const int stem = std::min( tp.range() + ( b.height() / 10 ), wp.pos(90) ); const bool maybe_Y = ( 5 * tp.range() <= 3 * b.height() || ( stem <= wp.pos(75) && 5 * wp[stem] <= b.width() ) ); const int lg = lp.min( lp.pos( 90 ) ); if( lg > 1 && bp.isvpit() && tp.minima( b.height() / 2 ) == 2 && lp[lp.pos(75)] <= lg && ( !maybe_Y || 3 * wp[stem] > b.width() || wp[stem] > wp[wp.pos(90)] + 1 ) ) return 'v'; int hdiff; if( b.bottom_hook( &hdiff ) ) { if( std::abs( hdiff ) <= b.height() / 8 ) { if( segments_in_row( b.vpos( 30 ) ) >= 3 ) return 'v'; if( bp.isconvex() ) { if( 9 * wp[wp.pos(30)] > 10 * wp[wp.pos(50)] && 9 * wp[wp.pos(50)] > 10 * wp[wp.pos(70)] ) return 'v'; else return 'u'; } } if( hdiff > b.height() / 2 ) { if( bp.minima( b.height() / 2 ) == 1 ) return 'y'; else return 0; } } const int rg = rp.min( rp.pos( 90 ) ); const int lg2 = lp.max( lp.pos( 70 ), lp.pos( 90 ) ); const int rg2 = rp.max( rp.pos( 70 ), rp.pos( 90 ) ); const int lc = ( lg + ( 2 * ( lp.limit() - rg ) ) ) / 3; const int lc2 = ( lg2 + lp.limit() - rg2 ) / 2; if( bp.ispit() && maybe_Y ) { int row2 = b.top(); while( row2 < b.bottom() && segments_in_row( row2 ) != 2 ) ++row2; int row1 = row2 + 1; while( row1 < b.bottom() && segments_in_row( row1 ) != 1 ) ++row1; if( row1 < b.bottom() ) row1 += wp[row1-b.top()] / 4; if( row1 < b.bottom() && wp[row1-b.top()] < b.width() ) { const int w1 = wp[row1-b.top()]; int row0 = w1 * ( row1 - row2 ) / ( b.width() - w1 ) + row1; if( row0 < b.bottom() && 2 * wp[wp.pos(70)] < b.width() && ( Ocrad::similar( lg, rg, 20 ) || ( lg > 1 && lg < rg && lc >= lc2 && !rp.increasing() ) ) ) return 'Y'; } } if( b.escape_top( b.vpos( 60 ), b.hcenter() ) && !lp.istip() && ( 4 * b.height() >= 3 * b.width() || segments_in_col( b.hpos( 75 ) ) <= 2 ) ) return 'u'; if( lg < rg + 1 && !lp.increasing( lp.pos( 50 ) ) && ( 2 * lg < rg || b.vpos( 90 ) >= charbox.bottom() ) && ( tp.minima( b.height()/2 ) == 1 || lp.imaximum() > b.height()/2 ) ) return 'y'; if( lg > 1 && bp.ispit() && tp.minima( b.height() / 3 ) == 2 ) return 'v'; if( lg <= 1 && 2 * ( b.width() - rg - lg ) < b.width() && rp.increasing() && tp.minima( b.height() / 2 ) == 2 ) return 'v'; return 0; } if( 2 * b.height() >= b.width() && b.height() >= 9 && bp.minima() == 2 && bp.isctip() ) { const int th = std::max( b.height() / 4, bp[bp.pos(50)] + noise ); if( bp.minima( th ) == 3 ) return 'M'; const int lg = lp[lp.pos(50)]; const int rg = rp[rp.pos(50)]; if( Ocrad::similar( lg, rg, 80, 2 ) && 4 * lg < b.width() && 4 * rg < b.width() ) { if( lg > 1 && rg > 1 && lp.increasing() && rp.increasing() && 5 * tp[tp.pos(50)] > b.height() ) return 'w'; if( hbars() == 1 && 5 * ( hbar(0).height() - 1 ) < b.height() && hbar(0).top() >= b.vpos( 30 ) && hbar(0).bottom() <= b.vpos( 60 ) && 10 * hbar(0).width() > 9 * wp[hbar(0).vcenter()-b.top()] && Ocrad::similar( v_segment( hbar(0).vcenter(), hbar(0).hcenter() ).size(), hbar(0).height(), 30, 2 ) ) { if( 9 * hbar(0).width() <= 10 * wp[wp.pos(50)] ) return 'H'; return 0; } if( segments_in_row( b.vpos( 60 ) ) == 4 || segments_in_row( b.vpos( 70 ) ) == 4 ) { if( 2 * tp[tp.pos(50)] > b.height() ) return 'M'; return 'w'; } if( ( vbars() <= 2 || ( vbars() == 3 && b.height() >= b.width() ) ) && tp.minima( b.height() / 2 ) <= 2 && tp.minima( ( 2 * b.height() ) / 5 ) <= 2 && !lp.istpit() && 4 * std::abs( rp[rp.pos(20)] - rp[rp.pos(80)] ) <= b.width() ) { const int row = b.top() + tp[tp.pos(50)]; if( row > b.vcenter() ) { Rectangle r( b.left(), b.top(), b.hcenter(), b.bottom() ); Bitmap bm( b, r ); int hdiff; if( bm.bottom_hook( &hdiff ) && -2 * hdiff > bm.height() ) return 'u'; } if( row > b.vpos( 10 ) || vbars() >= 2 ) return 'N'; } return 0; } if( 3 * lg < 2 * rg && lg < b.width() / 4 && rg > b.width() / 4 && rp.isctip() && tp.minima( b.height() / 8 ) == 2 ) return 'K'; return 0; } if( bp.minima() <= 2 && 2 * b.width() > 5 * b.height() ) return '~'; if( bp.minima() == 3 && ( hbars() == 0 || ( hbars() == 1 && hbar(0).top() >= b.vpos( 20 ) ) ) ) return 'M'; } return 0; } /* Look for the nearest frontier in column hcenter(), then test whether gap is open downwards (except for 'x'). */ int Features::test_hknwx( const Rectangle & charbox ) const { const int m8 = tp.minima( b.height() / 8 ); if( m8 == 2 && bp.minima( b.height() / 2 ) == 1 && ( ( lp.isctip() && rp.isctip() ) || ( lp.isconcave() && rp.isconcave() ) ) ) return 'x'; if( b.width() >= b.height() && tp.ispit() && ( b.bottom() < charbox.vcenter() || ( lp.decreasing() && rp.decreasing() ) ) ) return '^'; int col = 0, row = 0; for( int i = bp.pos( 40 ); i <= bp.pos( 60 ); ++i ) if( bp[i] > row ) { row = bp[i]; col = i; } row = b.bottom() - row + 1; col += b.left(); if( row > b.vpos( 90 ) || row <= b.top() ) return 0; // FIXME follow gap up { int c = col; col = b.seek_right( row, col ); if( col > c ) --col; row = b.seek_top( row, col ); } const int urow = b.seek_top( row - 1, col, false ); if( urow > b.vpos( 20 ) || 3 * tp[tp.pos(60)] > b.height() ) { const int m5 = tp.minima( b.height() / 5 ); if( m5 == 3 && segments_in_row( b.vcenter() ) == 2 && segments_in_row( b.vpos( 80 ) ) == 3 ) return 0; // merged 'IX' if( ( m5 == 2 || m5 == 3 ) && tp.minima() >= 2 && rp[rp.pos(25)] <= b.width() / 4 && ( !lp.istpit() || rp.minima() == 1 ) ) return 'w'; if( m5 == 1 && m8 == 1 && 4 * tp.max( tp.pos(40), tp.pos(60) ) < 3 * b.height() ) { if( rp.isctip( 66 ) ) return 'k'; else return 'h'; } return 0; } if( Ocrad::similar( b.height(), b.width(), 40 ) && row > b.vcenter() && urow < b.vcenter() && tp.minima( b.height() / 5 ) == 2 && bp.minima( urow + 1 ) == 3 ) return 'w'; if( urow <= b.vpos( 20 ) && tp.minima( b.height() / 4 ) == 1 && Ocrad::similar( b.height(), b.width(), 40 ) && ( 8 * ( rp[rp.pos(50)] - 1 ) <= b.width() || tp[tp.pos(99)] > b.height() / 2 ) ) return 'n'; return 0; } /* Look for four black sections in column hcenter() ± 1, then test whether upper gap is open to the right and lower gaps are open to the left. */ int Features::test_s_cedilla() const { int urow2 = 0, urow3 = 0, urow4 = 0, col, black_section = 0; for( col = b.hcenter() - 1; col <= b.hcenter() + 1; ++col ) { bool prev_black = false; for( int row = b.top(); row <= b.bottom(); ++row ) { bool black = b.get_bit( row, col ); if( black && !prev_black ) { if( ++black_section == 2 ) urow2 = row - 1; else if( black_section == 3 ) urow3 = row - 1; else if( black_section == 4 ) urow4 = row - 1; } prev_black = black; } if( black_section == 4 && urow2 < b.vpos( 50 ) && urow4 >= b.vpos( 70 ) ) break; black_section = 0; } if( black_section == 4 && b.escape_right( urow2, col ) && b.escape_left( urow3, col ) && b.escape_left( urow4, col ) ) return UCS::SSCEDI; return 0; } bool Features::test_comma() const { if( b.holes() || b.height() <= b.width() || b.height() > 3 * b.width() ) return false; if( b.width() >= 3 && b.height() >= 3 ) { int upper_area = 0; for( int row = b.top(); row < b.top() + b.width(); ++row ) for( int col = b.left(); col <= b.right(); ++col ) if( b.get_bit( row, col ) ) ++upper_area; if( upper_area < (b.width() - 2) * (b.width() - 2) ) return false; int count1 = 0, count2 = 0; for( int col = b.left(); col <= b.right(); ++col ) { if( b.get_bit( b.top() + 1, col ) ) ++count1; if( b.get_bit( b.bottom() - 1, col ) ) ++count2; } if( count1 <= count2 ) return false; } return true; } int Features::test_easy( const Rectangle & charbox ) const { int code = test_solid( charbox ); if( code ) return code; if( b.top() >= charbox.vcenter() && test_comma() ) return ','; if( b.bottom() <= charbox.vcenter() && b.height() > b.width() && bp.minima() == 1 ) { if( tp.iminimum() < tp.pos( 50 ) && bp.iminimum() > bp.pos( 50 ) ) return '`'; else return '\''; } if( 2 * b.height() > 3 * wp.max() && b.top() >= charbox.vcenter() && bp.minima() == 1 ) return ','; return 0; } /* Recognize single line, non-rectangular characters without holes. '/<>C[\^`c */ int Features::test_line( const Rectangle & charbox ) const { const int vnoise = ( b.height() / 30 ) + 1; const int topmax = b.top() + vnoise; const int botmin = b.bottom() - vnoise; const bool vbar_left = ( vbars() == 1 && vbar(0).width() >= 2 && vbar(0).left() <= b.hpos( 10 ) + 1 ); if( tp.minima() == 1 && bp.minima() == 1 && rp.istip() ) { if( vbar_left && b.height() > 2 * b.width() && 2 * rp[rp.pos(50)] > b.width() ) { int row = b.seek_top( b.vcenter(), b.hcenter() ); int col = b.seek_right( row, b.hcenter() ); if( col < b.right() ) { row = b.seek_bottom( b.vcenter(), b.hcenter() ); col = b.seek_right( row, b.hcenter() ); if( col < b.right() ) return 'C'; } } if( hbars() == 2 && hbar(0).top() <= topmax && 4 * hbar(0).height() <= b.height() && hbar(1).bottom() >= botmin && 4 * hbar(1).height() <= b.height() ) { if( vbar_left && b.height() > 2 * b.width() ) return '['; if( vbar_left || lp.ispit() ) return 'c'; } } int slope1, slope2; if( tp.minima() != 1 ) return 0; if( lp.minima() == 1 && rp.minima() == 1 && 2 * b.height() >= b.width() && lp.straight( &slope1 ) && rp.straight( &slope2 ) ) { if( slope1 < 0 && slope2 < 0 && bp.minima() == 2 ) return '^'; if( bp.minima() != 1 ) return 0; if( slope1 < 0 && slope2 > 0 ) { if( b.v_includes( charbox.vcenter() ) ) { if( 10 * b.area() < 3 * b.size() ) return '/'; if( b.height() > 2 * b.width() ) return 'l'; return 0; } if( b.top() >= charbox.vcenter() ) return ','; return '\''; } if( slope1 > 0 && slope2 < 0 ) { if( b.bottom() > charbox.vcenter() ) { if( ( 3 * b.width() > b.height() && b.height() > charbox.height() ) || 2 * b.width() >= b.height() ) return '\\'; else return 0; } return '`'; } return 0; } if( bp.minima() == 1 && 2 * b.width() >= b.height() && tp.straight( &slope1 ) && bp.straight( &slope2 ) ) { if( lp.minima() == 1 && rp.minima() == 1 ) { if( slope1 < 0 && slope2 > 0 ) { if( b.v_includes( charbox.vcenter() ) ) return '/'; if( b.top() >= charbox.vcenter() ) return ','; return '\''; } if( slope1 > 0 && slope2 < 0 ) { if( b.bottom() > charbox.vcenter() ) return '\\'; return '`'; } } else if( 2 * b.width() >= b.height() ) { if( slope1 < 0 && slope2 < 0 && lp.minima() == 1 && rp.minima() == 2 ) return '<'; if( slope1 > 0 && slope2 > 0 && lp.minima() == 2 && rp.minima() == 1 ) return '>'; } } return 0; } int Features::test_solid( const Rectangle & charbox ) const { if( b.holes() ) return 0; if( b.height() >= 5 && b.width() >= 5 ) { if( 2 * b.height() > b.width() && ( tp.minima() != 1 || bp.minima() != 1 ) ) return 0; if( b.height() < 2 * b.width() && ( lp.minima() != 1 || rp.minima() != 1 ) ) return 0; } int inner_area, inner_size, porosity = 0; if( b.width() >= 3 && b.height() >= 3 ) { const int vnoise = ( b.height() / 100 ) + 1; inner_size = ( b.width() - 2 ) * ( b.height() - 2 ); inner_area = 0; for( int row = b.top() + vnoise; row <= b.bottom() - vnoise; ++row ) { int holes = 0; // FIXME for( int col = b.left() + 1; col < b.right(); ++col ) { if( b.get_bit( row, col ) ) ++inner_area; else ++holes; } if( 5 * holes >= b.width() ) porosity += ( 5 * holes ) / b.width(); } if( inner_area * 100 < inner_size * 70 ) return 0; } else { inner_size = 0; inner_area = b.area(); } if( Ocrad::similar( b.height(), wp.max(), 20, 2 ) ) { const int n = std::min( b.height(), b.width() ); if( n >= 6 ) { int d = 0; for( int i = 0; i < n; ++i ) { if( b.get_bit( b.top() + i, b.left() + i ) ) ++d; if( b.get_bit( b.top() + i, b.right() - i ) ) --d; } if( 2 * std::abs( d ) >= n - 1 ) return 0; } if( ( !porosity && inner_area * 100 >= inner_size * 75 ) || ( b.width() >= 7 && b.height() >= 7 && ( 100 * b.area_octagon() >= 95 * b.size_octagon() || 100 * b.area_octagon() >= 95 * b.area() ) ) ) return '.'; return 0; } if( porosity > 1 || inner_area * 100 < inner_size * 85 || ( porosity && inner_area * 100 < inner_size * 95 ) ) return 0; if( b.width() > b.height() ) { if( b.top() > charbox.vpos( 90 ) || ( charbox.bottom() - b.bottom() < b.top() - charbox.vcenter() && b.width() >= 5 * b.height() ) ) return '_'; return '-'; } if( b.height() > b.width() ) { if( b.top() > charbox.vcenter() ) return ','; if( b.bottom() <= charbox.vcenter() ) return '\''; return '|'; } return 0; } ocrad-0.29/user_filter.cc0000644000175000017500000001667014546345521015305 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2014-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "common.h" #include "iso_8859.h" #include "ucs.h" #include "user_filter.h" namespace { class Set { std::vector< unsigned char > data; // faster than bool int parsed_len_; bool in; public: explicit Set( const std::string & regex, const unsigned i0 = 0 ); bool includes( const unsigned char ch ) const { return ( parsed_len_ > 0 && in == data[ch] ); } int parsed_len() const { return parsed_len_; } }; Set::Set( const std::string & regex, const unsigned i0 ) : data( 256, false ), parsed_len_( 0 ), in( true ) { if( i0 + 2 >= regex.size() || regex[i0] != '[' ) return; unsigned i = i0 + 1; bool fail = true; if( regex[i] == '^' ) { ++i; in = false; } if( regex[i] == ']' ) data[regex[i++]] = true; for( ; i < regex.size(); ++i ) { unsigned char ch1 = regex[i]; if( ch1 == ']' ) { ++i; fail = false; break; } if( ch1 == '\\' ) { int len, cht = ISO_8859::escape( regex, i + 1, &len ); if( cht < 0 ) break; ch1 = cht; i += len; } if( i + 2 >= regex.size() || regex[i+1] != '-' || regex[i+2] == ']' ) data[ch1] = true; else // range { i += 2; unsigned char ch2 = regex[i]; if( ch2 == '\\' ) { int len, cht = ISO_8859::escape( regex, i + 1, &len ); if( cht < 0 ) break; ch2 = cht; i += len; } for( int c = ch1; c <= ch2; ++c ) data[c] = true; } } if( !fail ) parsed_len_ = i - i0; } int my_fgetc( FILE * const f, const bool allow_comment = true ) { int ch = std::fgetc( f ); if( ch == '#' && allow_comment ) // comment { do ch = std::fgetc( f ); while( ch != '\n' && ch != EOF ); } return ch; } /* Read a line discarding comments, leading and trailing whitespace, and blank lines. Return the size of 'line', or 0 if at EOF. */ int my_fgets( FILE * const f, std::string & line, int & linenum ) { line.clear(); int ch; do { ch = my_fgetc( f ); if( ch == '\n' ) ++linenum; } while( UCS::isspace( ch ) ); // discard leading whitespace and blank lines while( ch != EOF ) { line += ch; ch = my_fgetc( f, UCS::isspace( ch ) ); if( ch == '\n' ) { ++linenum; break; } } if( ch == EOF && !line.empty() ) ++linenum; unsigned i = line.size(); // remove trailing whitespace while( i > 0 && UCS::isspace( (unsigned char)line[i-1] ) ) --i; if( i < line.size() ) line.resize( i ); return line.size(); } } // end namespace bool User_filter::enable_char( const int code, int new_code ) { if( code < 0 || code > 0xFFFF ) return false; if( new_code < 0 || new_code > 0xFFFF ) new_code = code; if( code < 256 ) table1[code] = new_code; else { unsigned i = table2.size(); while( i > 0 && table2[i-1].code > code ) --i; if( i == 0 || table2[i-1].code < code ) table2.insert( table2.begin() + i, Entry( code, new_code ) ); else table2[i-1].new_code = new_code; } return true; } int User_filter::parse_char( const std::string & line, unsigned &i ) const { int code = -1; const unsigned len = line.size(); while( i < len && std::isspace( line[i] ) ) ++i; // strip spaces if( i + 2 < len && line[i] == '\'' ) { if( line[i+1] == '\\' ) // '\X' { int l, cht = ISO_8859::escape( line, i + 2, &l ); if( cht >= 0 && i + 2 + l < len && line[i+2+l] == '\'' ) { code = UCS::map_to_ucs( cht ); i += 3 + l; } } else if( line[i+2] == '\'' ) // 'X' { code = UCS::map_to_ucs( line[i+1] ); i += 3; } } else if( i + 4 < len && line[i] == 'U' ) // UXXXX { code = 0; for( int j = 1; j <= 4; ++j ) { const int d = ISO_8859::xvalue( line[i+j] ); if( d < 0 ) { code = -1; ; break; } code = ( code << 4 ) + d; } if( code >= 0 ) i += 5; } if( code >= 0 ) { while( i < len && std::isspace( line[i] ) ) ++i; // strip spaces if( i < len && line[i] != ',' && line[i] != '-' ) code = -1; } return code; } User_filter::User_filter( const char * const file_name ) : table1( 256, -1 ), retval_( 0 ), default_( d_discard ) { FILE * const f = std::fopen( file_name, "r" ); if( !f ) { retval_ = 1; return; } std::string line; int linenum = 0; while( retval_ == 0 ) { if( my_fgets( f, line, linenum ) <= 0 ) break; if( line == "leave" ) { if( !default_ ) default_ = d_leave; else retval_ = 2; continue; } else if( line == "mark" ) { if( !default_ ) default_ = d_mark; else retval_ = 2; continue; } int new_code = -1; // parse new_code first (if any) for( unsigned equ = line.size(), j = 0; j < 2; ++j ) { equ = line.rfind( '=', equ - 1 ); if( equ >= line.size() ) break; unsigned i = equ + 1; const int tmp = parse_char( line, i ); if( tmp >= 0 && i == line.size() && enable_char( tmp, tmp ) ) { new_code = tmp; line.resize( equ ); break; } } unsigned i = 0; // index in line while( retval_ == 0 ) // parse { while( i < line.size() && std::isspace( line[i] ) ) ++i; // strip spaces if( i >= line.size() ) break; // no more chars in line const unsigned char ch = line[i]; if( ch == '[' ) // parse set { Set set( line, i ); // Set of characters to recognize if( !set.parsed_len() ) { retval_ = 2; break; } i += set.parsed_len(); for( int c = 0; c < 256; ++c ) if( set.includes( c ) && !enable_char( c, new_code ) ) { retval_ = 2; break; } } else if( ch == '\'' || ch == 'U' ) // parse char or range { const int code = parse_char( line, i ); if( code < 0 || !enable_char( code, new_code ) ) { retval_ = 2; break; } if( i < line.size() && line[i] == '-' ) { ++i; const int code2 = parse_char( line, i ); if( code2 <= code ) { retval_ = 2; break; } for( int j = code + 1; j <= code2; ++j ) if( !enable_char( j, new_code ) ) { retval_ = 2; break; } } } else { retval_ = 2; break; } // no set, char, or range if( i < line.size() && line[i] == ',' ) ++i; } } if( std::fclose( f ) != 0 && retval_ == 0 ) retval_ = 1; if( retval_ != 0 ) { char buf[80]; snprintf( buf, sizeof buf, "error in line %d.", linenum ); error_ = buf; } } int User_filter::get_new_code( const int code ) const { int result = -1; if( code >= 0 ) { if( code < 256 ) result = table1[code]; else { for( unsigned i = 0; i < table2.size(); ++i ) if( code == table2[i].code ) { result = table2[i].new_code; break; } } } if( result < 0 && default_ == d_leave ) result = code; return result; } ocrad-0.29/ChangeLog0000644000175000017500000002254314552216306014214 0ustar andriusandrius2024-01-18 Antonio Diaz Diaz * Version 0.29 released. * Improve recognition of 'L' with right uptick. * main.cc: Reformat file diagnostics as 'PROGRAM: FILE: MESSAGE'. (show_option_error): New function showing argument and option name. (open_outstream): Create missing intermediate directories. * Rename verify_* to check_*. * configure, Makefile.in: New variable 'MAKEINFO'. 2022-01-17 Antonio Diaz Diaz * Version 0.28 released. * Add support for PNG images using libpng. * configure: Set variables AR and ARFLAGS. * main.cc: Set a valid invocation_name even if argc == 0. * Don't derive Page_image and Textpage from Rectangle. * New exception Ocrad::Internal. * ocradlib.h: Define OCRAD_API_VERSION as 1000 * major + minor. (OCRAD_api_version): New function. * ocradcheck.cc: Use Arg_parser. * check.sh: Quote all file name variables to allow names with spaces. 2019-01-10 Antonio Diaz Diaz * Version 0.27 released. * Fix a GCC warning about catching std::bad_alloc by value. * main.cc (process_full_file): Check return value of fclose( infile ). * configure: Accept appending to CXXFLAGS; 'CXXFLAGS+=OPTIONS'. 2017-03-24 Antonio Diaz Diaz * Version 0.26 released. * main.cc (main): Don't use stdin more than once. * configure: Avoid warning on some shells when testing for g++. * Makefile.in: Detect the existence of install-info. 2015-03-31 Antonio Diaz Diaz * Version 0.25 released. * New option '-E, --user-filter'. * Improvements in character recognition. * Recognize uppercase 'Y' with acute and 'Y' with diaeresis. * textline_r2.cc: Recognize uppercase 'S' and 'Z' with caron. * New filters 'text_block' and 'upper_num_mark'. * ocrad.texi: New chapters 'Introduction' and 'Filters'. * test.pbm: Add 11 new characters. * Makefile.in: New targets 'install*-compress'. 2014-10-03 Antonio Diaz Diaz * Version 0.24 released. * Improvements in character recognition. * Allow more than one option '-e, --filter'. * New filters 'same_height', 'upper_num', and 'upper_num_only'. * New file histogram.h. * ocrcheck.cc: Rename to ocradcheck.cc. * ocrad.texi: Fix description of OCRAD_result_blocks. * Change license to GPL version 2 or later. 2014-03-10 Antonio Diaz Diaz * Version 0.23 released. * Improvements in character recognition. * Filters of type '*_only' now remove leading whitespace. * ocradlib.h: Change 'uint8_t' to 'unsigned char'. * Add some missing inclusions of 'cstdlib'. * ocrad.texinfo: Rename to ocrad.texi. 2013-07-09 Antonio Diaz Diaz * Version 0.22 released. * Scaling and smoothing are now made before thresholding. * Improvements in character recognition. * ocradlib.h: New function OCRAD_set_utf8_format. * Small improvements have been made in manual and man page. * Change quote characters in messages as advised by GNU Standards. * configure: Options now accept a separate argument. * configure: Rename 'datadir' to 'datarootdir'. * Makefile.in: New target 'install-bin'. 2011-01-10 Antonio Diaz Diaz * Version 0.21 released. * Fix some internal errors triggered by noisy input. * ocrad.texinfo: New chapter 'OCR Results File'. * main.cc: Set stdin/stdout in binary mode on MSVC and OS2. 2010-07-16 Antonio Diaz Diaz * Version 0.20 released. * ocradlib.h: New functions OCRAD_scale, OCRAD_result_chars_line, OCRAD_result_chars_block, and OCRAD_result_chars_total. 2010-01-27 Antonio Diaz Diaz * Version 0.19 released. * ocradlib.h: New library interface. * ocradlib.cc, ocrcheck.cc: New files. * Replace option '-p, --crop' with similar but different option '-u, --cut', which can accept coordinates taken from the ORF file. * Fix recognition of files with a single character and without white space at the edges. * check.sh: New tests for the library interface and for single character images. * Makefile.in: Add option '--name' to help2man invocation. 2009-05-08 Antonio Diaz Diaz * Version 0.18 released. * Add a layout analyser able to process arbitrary pages. * New option '-q, --quiet'. * The option '--layout' no longer accepts an argument. * The option '--crop' now accepts negative coordinates. * New recognized letter; 'a' with ring above. * Fix recognition on files with a single big character. * Fix bug that didn't write maxval when saving pgm or ppm. * Fix some includes that prevented compilation with GCC 4.3.0. * 'make install-info' should now work on Debian and OS X. * Makefile.in: Man page is now installed by default. * New file testsuite/check.sh. * Arg_parser updated to 1.2. * Verbosity control of messages has been modified. 2007-06-29 Antonio Diaz Diaz * Version 0.17 released. * Update license to GPL version 3 or later. * '--scale' no longer suppresses ORF output. * Improve removal of thick frames. * Change 'Textline' to accept more than one big initial. * Class Rename 'Block' to 'Blob'. * configure, Makefile.in: Make more GNU-standards compliant. 2006-10-20 Antonio Diaz Diaz * Version 0.16 released. * New option '-e, --filter'. * Better algorithm for vertical space detection (blank lines). * configure: Some fixes. * Add two new debug levels. * Improvements in character recognition. 2006-04-03 Antonio Diaz Diaz * Version 0.15 released. * New argument parser that replaces 'getopt_long'. * Fix a bug that prevented compilation with GCC 4.1. 2006-02-15 Antonio Diaz Diaz * Version 0.14 released. * Ocrad is now able to read ppm files. * New class 'Page_image' (256-level greymap). * Add automatic and adaptive binarization by Otsu's method. * New option '-p, --crop'. * ocrad.texinfo: New chapters 'Image Format Conversion' and 'Algorithm'. * Target 'check' added to Makefile. * Change 'ocrad.png' icon to color, one line. 2005-10-10 Antonio Diaz Diaz * Version 0.13 released. * Ocrad is now able to read pgm files. * New rational number class. * Use rationals instead of integers in space detection algorithm. * Better algorithm for space detection in tables. * 'vector' replaced by 'vector' in bitmap (faster). * block.cc: Variable-size arrays replaced by vectors. * main.cc, textpage.cc: Fix sizeof(size_t) != sizeof(int) on some 64 bit systems. * Improve number recognition (mainly in textline_r2.cc). * Overflow detection when loading or scaling file. * Fix a miscompilation with GCC 3.3.1. * Class 'Vrhomboid' merged into files 'track.h' and 'track.cc'. 2005-06-07 Antonio Diaz Diaz * Version 0.12 released. * Change in internal representation; Blockmap has been eliminated. * Text inside tables of solid lines is now recognized. * Improvements in character recognition. * Fix possible integer overflow when loading pbm file. 2005-02-12 Antonio Diaz Diaz * Version 0.11 released. * New option '-s, --scale'. * Improvements in character recognition. * Fix bug in '--transform' (introduced in 0.10). 2004-12-09 Antonio Diaz Diaz * Version 0.10 released. * New suboption '-D7X'. * Change in internal representation; only 1 Blockmap per Textpage. * Use of absolute coordinates in ORF file. * Improve space detection algorithm. * Improvements in character recognition and separation. 2004-10-23 Antonio Diaz Diaz * Version 0.9 released. * New option '-t, --transform'. * 'DESTDIR' now works as expected. * New class 'Textpage' is top of internal representation. 2004-05-23 Antonio Diaz Diaz * Version 0.8 released. * Better algorithm for line detection. * New feature '-x -' (export ORF file to stdout). * Small improvements in picture elimination. 2004-02-09 Antonio Diaz Diaz * Version 0.7 released. * Internal change to UCS instead of ISO 8859-1. * Default charset is now ISO 8859-15 (latin9). * Ocrad now recognizes Turkish characters (ISO 8859-9). * New output format (UTF-8). * New options '-c, --charset' and '-F, --format'. * ocrad.1: New man page. 2003-12-18 Antonio Diaz Diaz * Version 0.6 released. * configure: Fix compatibility with 'sh'. * Better algorithm for lowercase-uppercase decision. * Small changes to line detector. * Fix bug (output of char 0 when separating some merged chars). 2003-10-18 Antonio Diaz Diaz * Version 0.5 released. * Fix bug when creating ORF file from stdin. * Add the ability to read multiple files from stdin. * Use 'vector' instead of 'list' due to problem with GCC 3.3.1. * Faster 'processing' of pictures. 2003-09-03 Antonio Diaz Diaz * Version 0.4 released. * More standard configure and Makefile. * ocrad.texinfo: New file. * Small changes to layout detector. * Character codes > 127 now in ISO_8859_1:: format. * New option '-i, --invert'. 2003-07-19 Antonio Diaz Diaz * Version 0.3 released. * ORF file feature added. * Recursive 'layout detector' added. Copyright (C) 2003-2024 Antonio Diaz Diaz. This file is a collection of facts, and thus it is not copyrightable, but just in case, you have unlimited permission to copy, distribute, and modify it. ocrad-0.29/COPYING0000644000175000017500000004315112347427454013503 0ustar andriusandrius GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ocrad-0.29/testsuite/0000775000175000017500000000000014726507103014470 5ustar andriusandriusocrad-0.29/testsuite/test.txt0000644000175000017500000000026012443027266016205 0ustar andriusandrius00123456789 ABCDEFGHIJKLMN ÑOPQRSTUVWXYZ abcdefghijklmn ñopqrstuvwxyz ÁÉÍÓÚ ÀÈÌÒÙ ÄËÏÖÜ ÂÊÎÔÛ Å¦Ý¾´ å¨ýÿ¸ áéíóú àèìòù äëïöü âêîôû !"#$%&'()*+,-./:;<=>? @[\]^_`{|}~¡ª¬°±º¿çÇ÷ ocrad-0.29/testsuite/test2.png0000644000175000017500000000033310363251477016237 0ustar andriusandrius‰PNG  IHDR0¼ý~Ü¢IDATX…í–Ý€ …¥õþ¯L7±~DE›‹oë";ëˆÇ°”‚àßÀó‘‘@>f­Ós{gÆ™Zëx0%<'XÕnZ¹àºìa £–¡TçK!uØm^ó] ‘À(ÔmzŸSwšPÀ»Ún·É ä“/õùÁsÀßܨDÙÉ[ÓèX¾ 1Ð*µVKª#|R($ 5±ß½8þN“F:†Öë2.¼BgIEND®B`‚ocrad-0.29/testsuite/test_utf8.txt0000644000175000017500000000035612443027266017161 0ustar andriusandrius00123456789 ABCDEFGHIJKLMN ÑOPQRSTUVWXYZ abcdefghijklmn ñopqrstuvwxyz ÃÉÃÓÚ ÀÈÌÒÙ ÄËÃÖÜ ÂÊÎÔÛ Ã…Å ÃŸŽ åšýÿž áéíóú àèìòù äëïöü âêîôû !"#$%&'()*+,-./:;<=>? @[\]^_`{|}~¡ª¬°±º¿çÇ÷ ocrad-0.29/testsuite/test2.txt0000644000175000017500000000000610363251477016267 0ustar andriusandriusOCR+ ocrad-0.29/testsuite/check.sh0000755000175000017500000002103714545765372016122 0ustar andriusandrius#! /bin/sh # check script for GNU Ocrad - Optical Character Recognition program # Copyright (C) 2009-2024 Antonio Diaz Diaz. # # This script is free software: you have unlimited permission # to copy, distribute, and modify it. LC_ALL=C export LC_ALL objdir=`pwd` testdir=`cd "$1" ; pwd` OCRAD="${objdir}"/ocrad OCRADCHECK="${objdir}"/ocradcheck framework_failure() { echo "failure in testing framework" ; exit 1 ; } if [ ! -f "${OCRAD}" ] || [ ! -x "${OCRAD}" ] ; then echo "${OCRAD}: cannot execute" exit 1 fi if [ -d tmp ] ; then rm -rf tmp ; fi mkdir tmp cd "${objdir}"/tmp || framework_failure in="${testdir}"/test.pbm ouf="${testdir}"/test.ouf txt="${testdir}"/test.txt utxt="${testdir}"/test_utf8.txt t2_png="${testdir}"/test2.png t2_txt="${testdir}"/test2.txt fail=0 test_failed() { fail=1 ; printf " $1" ; [ -z "$2" ] || printf "($2)" ; } printf "testing ocrad-%s..." "$2" "${OCRAD}" -q -T-0.1 "${in}" > /dev/null [ $? = 1 ] || test_failed $LINENO "${OCRAD}" -q -T 1.1 "${in}" > /dev/null [ $? = 1 ] || test_failed $LINENO "${OCRAD}" -q -u -2,-1,1,1 "${in}" > /dev/null [ $? = 1 ] || test_failed $LINENO "${OCRAD}" -q -u 1,1,1,1 "${in}" > /dev/null [ $? = 1 ] || test_failed $LINENO "${OCRAD}" "${in}" -o out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" "${in}" > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" < "${in}" > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -F utf8 "${in}" > out || test_failed $LINENO cmp "${utxt}" out || test_failed $LINENO "${OCRAD}" -F utf8 < "${in}" > out || test_failed $LINENO cmp "${utxt}" out || test_failed $LINENO "${OCRAD}" "${in}" -o a/b/c/out || test_failed $LINENO cmp "${txt}" a/b/c/out || test_failed $LINENO rm -rf a || framework_failure "${OCRAD}" -q "${in}" -o a/b/c/ [ $? = 1 ] || test_failed $LINENO [ ! -e a ] || test_failed $LINENO "${OCRAD}" -E "${ouf}" "${in}" > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -E "${ouf}" -F utf8 "${in}" > out || test_failed $LINENO cmp "${utxt}" out || test_failed $LINENO "${OCRAD}" -u 0,0,1,1 "${in}" > out cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -u 0,0,1,1 - < "${in}" > out cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -C -u 0,0,1,1 "${in}" > out cmp "${in}" out || test_failed $LINENO "${OCRAD}" -u -1,-1,1,1 "${in}" > out cmp "${txt}" out || test_failed $LINENO "${OCRAD}" - -u -1,-1,1,1 < "${in}" > out cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -C -u -1,-1,1,1 "${in}" > out cmp "${in}" out || test_failed $LINENO cat "${in}" "${in}" > in2 || framework_failure cat "${txt}" "${txt}" > txt2 || framework_failure "${OCRAD}" < in2 > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO "${OCRAD}" "${in}" "${in}" > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO "${OCRAD}" "${in}" - < "${in}" > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO "${OCRAD}" - "${in}" < "${in}" > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO "${OCRAD}" - "${in}" - < "${in}" > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO "${OCRAD}" - - "${in}" < "${in}" > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO "${OCRAD}" "${in}" - - < "${in}" > out || test_failed $LINENO cmp txt2 out || test_failed $LINENO cat "${utxt}" "${utxt}" > utxt2 || framework_failure "${OCRAD}" -F utf8 < in2 > out || test_failed $LINENO cmp utxt2 out || test_failed $LINENO "${OCRAD}" -F utf8 "${in}" "${in}" > out || test_failed $LINENO cmp utxt2 out || test_failed $LINENO rm -f in2 txt2 utxt2 || framework_failure test_chars() { for coord in ${coords} ; do produced_chars="${produced_chars}`"${OCRAD}" -u${coord} "${in}"`" || test_failed $LINENO ${coord} done if [ "${produced_chars}" != "${expected_chars}" ] ; then echo echo "expected \"${expected_chars}\"" echo "produced \"${produced_chars}\"" fail=1 fi } # lines 1, 2, 3 coords=' 71,109,17,26 92,109,17,26 114,109,15,26 132,109,17,26 152,109,18,26 172,109,19,26 193,109,17,26 214,109,17,26 234,108,17,27 253,109,18,26 274,109,17,26 68,153,29,27 97,153,24,27 126,153,23,27 153,153,27,27 183,153,24,27 210,153,23,27 237,153,27,27 266,153,30,27 298,153,13,27 313,153,20,27 335,153,29,27 365,153,23,27 391,153,34,27 426,153,30,27 69,189,30,35 102,197,26,27 132,197,24,27 159,197,26,34 188,197,26,27 217,197,20,27 241,197,24,27 266,197,30,27 297,197,28,27 326,197,37,27 364,197,27,27 390,197,28,27 420,197,21,27' expected_chars="0ol23456789ABcDEFGHIJKLMNÑopQRsTuvwxYz" produced_chars= test_chars # lines 4, 5 coords=' 71,250,18,18 90,240,20,28 112,250,15,18 131,240,19,28 152,250,17,18 170,241,16,27 183,249,20,27 204,240,23,28 227,241,11,27 236,241,11,35 251,240,22,28 274,240,11,28 287,250,32,18 321,250,22,18 70,288,22,25 92,295,17,18 111,295,19,26 132,295,20,26 152,295,16,18 169,295,14,18 185,288,13,25 200,295,22,18 221,295,20,18 242,295,27,18 270,295,20,18 289,295,20,26 310,295,16,18' expected_chars="abcdefghijklmnñopqrstuvwxyz" produced_chars= test_chars # line 7 coords=' 68,366,29,36 97,366,24,36 124,366,13,36 140,366,26,36 168,366,30,36 208,366,29,36 237,366,24,36 265,366,13,36 281,366,26,36 308,366,30,36 349,368,29,34 378,368,24,34 405,368,13,34 421,368,26,34 449,368,30,34' expected_chars="ÁÉÍóúÀÈÌòùÄËÏöü" produced_chars= test_chars # line 8 coords=' 68,410,29,36 97,410,24,36 124,410,13,36 140,410,26,36 167,410,30,36 208,410,29,36 238,410,20,36 259,410,28,36 288,412,28,34 317,410,21,36 355,419,18,27 377,419,14,27 392,419,20,35 414,421,20,33 435,419,16,27' expected_chars="ÂÊÎôûŨݾ¸å¨ýÿ¸" produced_chars= test_chars # line 9 coords=' 71,463,18,27 91,463,17,27 109,463,11,27 123,463,17,27 142,463,22,27 177,463,18,27 198,463,17,27 216,463,11,27 229,463,17,27 249,463,22,27 284,466,18,24 305,466,17,24 323,466,12,24 336,466,17,24 356,466,22,24 391,463,18,27 411,463,17,27 431,463,10,27 443,463,17,27 462,463,22,27' expected_chars="áéíóúàèìòùäëïöüâêîôû" produced_chars= test_chars # line 10 coords=' 71,508,5,27 97,509,19,26 120,505,17,35 174,508,27,27 216,508,10,31 230,508,9,31 244,508,15,15 264,516,19,19 333,508,11,27 367,516,19,19 413,516,19,19 438,508,14,27' expected_chars="!#$&()*+/<>?" produced_chars= test_chars # line 11 coords=' 70,552,25,27 99,552,9,31 113,552,15,27 133,552,9,31 148,552,17,15 205,552,8,31 223,552,3,27 236,552,9,31 250,552,18,6 272,558,5,29 285,554,12,15 301,566,19,10 325,554,11,11 341,557,19,22 365,554,11,15 381,558,15,29 400,561,15,26 417,552,23,35' expected_chars="@[\\]^{|}~¡ª¬o±º¿çÇ" produced_chars= test_chars "${OCRAD}" -C -t rotate90 "${in}" | "${OCRAD}" -t rotate270 > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -C -t rotate180 "${in}" | "${OCRAD}" -t rotate180 > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -C -t mirror_lr "${in}" | "${OCRAD}" -t mirror_lr > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRAD}" -C -t mirror_tb "${in}" | "${OCRAD}" -t mirror_tb > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO # test conversion to/from png "${OCRAD}" -C7 "${in}" | "${OCRAD}" -C4 - > out || test_failed $LINENO cmp "${in}" out || test_failed $LINENO "${OCRAD}" -C8 "${in}" | "${OCRAD}" -C4 - > out || test_failed $LINENO cmp "${in}" out || test_failed $LINENO "${OCRAD}" -C7 "${in}" | "${OCRAD}" -C5 - > out7 || test_failed $LINENO "${OCRAD}" -C8 "${in}" | "${OCRAD}" -C5 - > out8 || test_failed $LINENO cmp out7 out8 || test_failed $LINENO rm -f out7 out8 || framework_failure "${OCRAD}" -i "${t2_png}" > out || test_failed $LINENO cmp "${t2_txt}" out || test_failed $LINENO for i in 1 2 3 4 5 6 7 8 ; do "${OCRAD}" -i -C$i "${t2_png}" | "${OCRAD}" - > out || test_failed $LINENO $i cmp "${t2_txt}" out || test_failed $LINENO $i done "${OCRADCHECK}" "${in}" > out || test_failed $LINENO cmp "${txt}" out || test_failed $LINENO "${OCRADCHECK}" --utf8 "${in}" > out || test_failed $LINENO cmp "${utxt}" out || test_failed $LINENO "${OCRADCHECK}" -i "${t2_png}" > out || test_failed $LINENO cmp "${t2_txt}" out || test_failed $LINENO "${OCRADCHECK}" -i -l "${t2_png}" > out || test_failed $LINENO cmp "${t2_txt}" out || test_failed $LINENO "${OCRADCHECK}" -i --utf8 "${t2_png}" > out || test_failed $LINENO cmp "${t2_txt}" out || test_failed $LINENO echo if [ ${fail} = 0 ] ; then echo "tests completed successfully." cd "${objdir}" && rm -r tmp else echo "tests failed." fi exit ${fail} ocrad-0.29/testsuite/test.ouf0000644000175000017500000000063512443027266016165 0ustar andriusandrius# Ocrad User-defined Filter file (.ouf) [0123456789] [A-Za-z], 'Ñ', 'ñ' U00C0 - U00C5 # A U00C8-U00CB # E U00CC-U00CF # I U00D2-U00D6 # O U00D9-U00DC # U '\xE3',U00E5 # a tilde, a ring U0160, U0161 # s caron U00DD,U00FD # y acute U0178,'\377' # y diaeresis U017D , U017E # z caron [áéíóúàèìòùäëïöüâêîôû] [-!"#$%&'()*+,./:;<=>?] []@[\\^_`{|}~¡ª¬°±º¿çÇ÷] '=' = '=' # this line is just to test the parser ocrad-0.29/testsuite/test.pbm0000644000175000017500000015423312443027266016156 0ustar andriusandriusP4 560 792 @€ À>pøà€ðÿàÀøðÿ€àÿÀþàÿüÿà?ðþÀà<€p<€ààø<€Àøð>ÁÀÀð>€ðàx<€Àà>8À<Àà~pàx>€Àà8À<Àà~pà€x€Àà0à<ààïxà€ø€3ÀÀð8<ààïxà€ø3ÀøÀü8<àáÏxàðcÀ?þÇàÿà<àáÏxààüÃÀàãxààüÃÀ8ƒøx?ðàãxàÀƒÀ0ƒð<?øàçxà€À Ãà>8óüýàçxà†ÀÃà8À~ñàîxàÆÀÃÀ8Ààîxà88Ïÿþ8ÃÀx€àüxà`|Ïÿþ|ÃÀx€ÀüpàÀüÀÀ|ÃÀø€ Àø€ðà€øÀÀ|Áàø€Àx€àà8øÀÀxà>ø€>€p€ààÿøà€Àp€ð<øÀ>?ÿŸÿøøüðàðàðpÿðþ0ÿðÿÿü?ÿÿ€þ ?þÿ?þùÿñÿçÿÀÿ€ÿ¿àÿpÿøÿðÿüÿÿü?ÿÿ€ÿð?þÿ?þùÿñÿçÿÀÿÀÿ¿ðÿð€~ð€þüÀ€~ðÀàà€?|Àðððø€8ð€<À€øðÀàà€<xÀðøðø€xx€€À€ððÀàà€pxàðü`ü€ðx€ÀÀððÀàà€`xàðþ`¼€ðx€à ÀƒàpÀàà€Àxàð¾`¾€ð8€à ÀáƒàpÀàà€€xð ðŸ`>€ð8€à Àá‡à0Ààà€xð ð€`€à8€ðÀà‡à0Ààà€xððÀ`€à€ðÁàÀÀàà€xøð‡À`€øà€ðÃàÀÀàà€?xøðƒà`€ÿàà€ðÿÿàÀÿÿàà€xx8ðð` €ÿøà€ðÿÿàÀÿÿàà€ï€x|0ð€ð` €€üà€ðÃàÀÿÀàà€ÇÀx|0ð€ø`À€à€ðÀàÀÿÀàà€‡Àx>pð€|`ÿÀ€à€ðÀàÀÿÀàà€àx>`ð€>`?ÿà€ƒà€ðÀàÀðÀàà€ðx8>`ð€`0à€ð€àÀàÀðÀàà?€øx0àð€`0ð€ð€àÀàðÀàà€øx0Àð€àpð€ð8€ÀÀàðÀàà€|x0Àð€à`ð€€ø0€ÀÀàðÀàà~€~xpÀð€ààø€€xp€€ÀððÀàà`€>xp€ðÀààø€<à€ÀøðÀààpxð<€ðÀàð|À~À€~þÀ~ðÀàà8?€|ð>€ðààþÿ¿ÿüÿ€ÿüÿÿþ?þ?þ0?þÿ?þþÿñÿ÷ÿÿñÿÇÿŸü`þÿ¿ÿðþÿàÿÿü?þø0?þÿ?þøÿñÿ÷ÿÿñÿÃÿŸü`@ø`ÿÀ€üÿàþÿþÿÿüþ0ÿÿ?þÿüûÿŸüïÿûÿáÿÇÿÿ€þÿàÿ€ÿÿ€ÿÀÿÿÿðÿÿ?þÿüûÿŸüïÿûÿáÿÇÿÿ~>>àðÀðð€ð|?àøÀà|àðà~>à?xððà<xðÀðxàxÀÀ|àø€>€>?€ øxðð<<ðà8ðpàpÀ€<ð|<?À ð|ððx>ðà8ppà0à€>ð|€8|7À ð<ððøðà8p`à0à€>ð>€pø3à ð>ððøðà<0`à0ðð À`ð1ð à>ððøðà<0`à0ðø ààð1ø àðñð€ðÀ?0`à0ðø¸àÀà0ø àðáð€ðÀðÀà0øxðñÀÀ0| àðÁð€ð€þà0øƒ|àñ€À0> àðð€ÿþÿÀà0| ‡<8àû€€0 àÿÿð€ÿðÿàà0| †<0ð€0 àÿüð€ð|ÿðà0<†>0ð?0Œàðð€ððà0>Îpø>>0Ìàðð€ð@øà0>8Ìp|>0Ìàðð|ð`xà00Ì`~|€0ìà>ðùþð`8à00ü` >ø€0üð>ðùÇð`8à0pøàø€0üð<ð{ƒžð€p8àpàøÀ8€ð€0|ð|ð{üðŒp8ð`àøÀp€à€8|xxð?üðŒx8ðàÀðÀ`Ààx<|ððøðÌ|pøÀÀð€ààÀü?àðððø€à>þ€Àð€ðð>€?ÿ€ ÿ€ÿ€ÿàÿ‡øwÿÀÿà?ÿ€p€þ?þÿàÿÿÿ€ þÿ€ÿàÿƒðaÿÿàü€`þ?þÿàÿÿðñ€ñ€ù€ÿ€>€àðàÀ?€à?€ðÀ à?À?€àÿÀðàà?À€àãÀðààÀ€àÃÀðÀ àÀ€àÁÀðàÀ€àÀðàÀ€àÀðàÀ€àÀðàÀ€àÀÀðàÀàŸ€ðùàð?üóàñðàçþÀ|€ø¿àüýàø?üþðóüáþçþÁýþ?À?Àð|ñðà<À>>àþ<áþàøÁÿqàãÀà<Àø<>à<À<@øàààÀ>Áðàð<Àxx>àxÀxøàáÀÀ>Àððð<€|x>àxÀxðàã€À>Àððà<€|ðàxÀxðàçÀ<€ððü€<ðàøƒÀxðàïÀ<€ððü€<ðàÿÿƒÀ|ðàÿ€À<€ðð><€<ðàÿÿƒÀ<ðà÷€À<€ððp<€<ðàøÀ|ðàóÀÀ<€ððð<€<ðàøÀððàãàÀ<€ððà<€<øàxÀ<ðàáðÀ<€ððà<ÀxxàxÀ`ðààðÀ<€ððà|Àp| à|À`ðààxÀ<€ðððü‡ñà>‡à>Àþðàà|À<€ððÿ߯ÀøýüüøÿþïüûÿŸùÿ¿÷þçþ ?€àùüðø?ÿ‡þïüûÿŸùÿ¿÷þçþ8?€ð€à€à€à€<ø8þðøàÆü0øøø|ü~ <Ìø>Çü¿Ïóûÿ¿çùÿøùþÿýþÿ˜þ~üÿøþÇü¿Ïóûÿ¿çùÿøÿãÁÿãøþß0|þÁðÀpüáàø|à>ƒ€øŸ`Àð À`|ÁÀð|‡€ð>‡€ø` ÀðÀ`>€Áàx‡€ð>Ç€x p ÀxƒàÀ0ƒàxx<Ïx|ÀxƒàÀ`ÁƒÀxx<ÏxàÀ<0†ðÀÀÀ€xx<Ïx?øÀ<0Æñ€ÀÃxx<ÏxüÀ0Æq€àãxx<ÏxþÀ`ì{€àæxx<Ïx`> À`ì{ðö<x€ø<Ï€ø` ÀÀø? øþxx‡€ð>‡€øp ÀÀø>x|xx‡€à>‡Àøx À€ø0<|ðxƒãÀ?áø~ À€pp?xàxÿ?ñÿ€=þÿxÿÀøðûü€pþÿÀ8ÿøÿ?ð<ü~xÿÀgðàãüp þÿÀ8ÿø<x0<x0<x`<xx`<xxÀ<xy€ÿ€ÿÿ€ÿ>` `0à€à<ð8ðÀà<xx€<xàƒ0`ƒ €pàÀ<ðÇ€pðÇ€88àÀ`8Ç€xðÇ€` `€0€ ƒ0`ƒ €€€@pÿÿÿ€øÿøüÿÿðüÀÿÿÀ8?ÿÿ‡ÿÀüüþpÿÿÿ€?þÿøüÿÿðüÿðÿÿÀ8?ÿÿ‡ÿÀÿüþð€?xø€€àxðÀÀüx>xÀ€<|ÀÀðø€xàÀ€à€xðÀx|À€<ðàÀðø€xàà€À€xpÀx|À€<ððÀàü€xÀð€ÀÀxpÀ€x þÀ€<àøÀ`¼€xÀð€ÀÀx0À>€x ÞÀ€<àxÀ`¾ÃxÀø€À;àx0À>Àx ßÀá€<à|À`>Ãx€ø€À3àx0À|Àx ŸÀá€<À|À`Áx€|€À1àxÀ|àx Àà€<À>À`ƒÀx€|€Àqðx<À|àx €Áà<À>À`‡Àx€|€À`ðx|À|àx €Ãà<À>À`€ÿÀx€|€ÀàøüÀ|àx Àÿà<À>À` €ÿÀx€|€ÀÀøüÀ|àx Àÿà<À>À` €‡Àx€|€ÀÀxx|À|àx ÀÃà<À>À`ÀÀx€|€ÀÀ|xÀ|àx àÀà<À>À`ÿÀÀx€|€ÀÿüxÀ|àx ÿàÀà<À>À`?ÿàÁ€x€|€ÀÿþxÀ|àx ÿðÀàÀ<À>À`0àÁ€x€ø€À>xÀ<Àx ðÀàÀ<À|À`0ð€€xÀø€Àx8À>Àx øÀÀ<à|À`pð€€xÀð€Àx8À>€x8øÀÀ<àxÀà`ð€€xÀðÀ€x8À€|0xÀÀ<àøàÀàø€€xààÀ€€xxÀ|8p|ÀÀ<ððàÀàø€€xðÀà€xxÀ€>pp|ÀÀ<øàð€ð|À?€øü€øÀ|øÀà|?àø>àÀ|~Àüþÿ¿ÿÿÿ€?þÿüÿàûÿÿøüÿðÿÀÿÿßÿÿÇÿÀÿþþÿ¿ÿÿÿ€ø?ðÿàûÿÿðüÀÿÿÿßÿÿ‡ÿÀüøppÀ øøà€ € 0ü€üðÀÀ 0`0Œ9€Ì0à@08xð ``À 8`ÀppxðàÀ` 0 €àÀ0`À€ @À€pÿÿÿ€øÿð?øñŸÿþÿøóÿÿÀ8pÿÿÿ€?þÿð?ø?ÿŸÿþÿøóÿÿ€l ð€?xø€Àpƒðð€ƒð€Æ  ø€xàÀÀ€àðà€À‚0<Œø€xàà€€À€øÀÀ€Æp8<Üü€xÀð€ÀÀ€|Àà€>là` ø¼€xÀð€ÀÀ€|€à€|8À€p¾ÃxÀø€;àà€>ðx>Ãx€ø€3àà€ø8øÁx€|€1àø€ø0ðþÌÏñÿ?ÏÿÀƒÀx€|€qðÿ€Ž|pàÿ€üÏñÿ?ÏÿÀ‡Àx€|€`ðÿðŒ<`àÀ0|>ÀøÀ€ÿÀx€|€àøþÜ>àÀÀ`€x€ €ÿÀx€|€ÀøÿøÀÀÀ` €<  €‡Àx€|€Àxÿ€øÀ€Àp < ÀÀx€|€À|€ð€À|ƒ> ÿÀÀx€|€ÿüÀð€€Àà‡<?ÿàÁ€x€|€ÿþÀð€>ÀÿÀ?ø†x0àÁ€x€ø€>Àð€|ÀãÀüÆø0ð€€xÀø€Àð€|ÀÀþÌ0ðpð€€xÀð€€Àð€øÀÀ`>ì°à@`ð€€xÀð€€Àð€ðÀÀ`üðÀààø€€xàà€ƒÀÀð€ð€ÀpøàÀààø€€xðÀÀƒà€ð€à€Àxøà€Àð|À?€øü€ð<Ãüð€À€È~ ðÀÀþÿ¿ÿÿÿ€?þÿøÿàû¿þÿÿøÿÿ€ýüøpÀÿÀþÿ¿ÿÿÿ€øàÿàûøÿÿøÿÿ€ððgðpÀÿÀ`€`€ ÀƒðÀÃñ€ÆóÌþø|ð€€ @À0€8À€Àpàà€€<<8Àx€€<xððÀÀ|~|àðx€<8xàÃÀ ppàînîpÀ<àÀðãÀ><øð`ÇÃÇ0€8p8Àà8ÃÀpð00ƒ`À`0€ €  ` ƒÂ€ à~xþøøÀ?€àÿøàðàà~<þøøÿøÿøþ?àÿÀàÿÀüàøáüøÿüÿøð|LJøǃø<xðÿñààƒàà<áüð|Çüǃøà<ÀxÀxxð8Àðààà8à<à<À<Àxð<àxàx|à<Àxàà<àxà<ð<à<àxð<àxàx|à<Àxàà<àxà<ð<à<àxà<àxðx8à<€<àà<àðà<à<à<ðxüðxðxà>€<àà|Áàðà<üð<ðxüÿðxðxÿÿþ€<ààÿÁàðà<üÿð<ðx><ÿðxðxÿþ€<àñàÿÁàðà<><ÿð<ðxp<xðxà€<àà|àðà<p<<ðxð<xðx<à€<àà|àðà<ð<<ðxà<xðxxàÀ|àà<àøà<à<<ðxà< xàxxàÀxàà<àxà<à< <àxà|€`xÀxxð Àpàà>àxà|à|€`<Àxðü‡ÁÀxÇ€|?|? ø8ñà‡à‡äà><ðüðü‡ÁÀ<Ç€|?ÿßÃÿƒÿÿ?ïð?÷ððàÿÀýþþþþüøÿ¿ÀÿßÃÿÿƒÿ?ïðþÿþðÃÀÀà?€ñþøxøüð~?Àþÿ€þð€€€àp€0øÃ8€ðà€?Àÿÿ€øùƒ€ƒà?€x<à€ðÿÿ€ààñƒ€ð€x<àüÿÿ€ðá‡øÀ8<àppü>ðqŽ Œü?ÿ<8àøø€Àð>ð=ü øÿóþpàøøÀÀÀ>àøð?Áü pàøðÀÀÀàXÀ@€ÀÀ€€8€€Ã0€€`À €þðÀü<`xÀ€@Æÿ€ð`ü>pðÀ€àpÿþ?à`<~xàÀÀ?øðüøøÀþxp0<<ÀÀÀxÿàŒŒ>ð88<÷ÀÀÀ ?À†ŽŽà<ç€ÀÀÀ€ÀóŒ<ãÀÀÀÀp~>ÿŽ <ÁÀÀÀÀøæ>ƒÏŽ<ÁàÀÀÀø~€‡<€àÀÀÀøð|€<€ðÀÀÀpŽŽü|€<pÀÀÀ€ŒŒ|€<xÀÀÀó€øø<>|>À<8ÀÀàÿÿx>|< À<<€Àø0ÿÿÿÿx>|< à<Àx0ÿÿÿÿÿÿð|¼`<€Àð0ÿÿÿÿð|ÿÿ¼>8p<ÀÀà0 ð|ÿÿž|p0<ÀÀÀ0 ð~ÿÿÏÜà<ÀÀÀ0ð>ÇŸŒ<ÀÀÀp8ð>à <ÀÀÀppøp8<ÀÀÀpðx8p<ÀÀÀpà| €€à<ÀÀÀpÿÿÀ>àx€ÿ€<ÀÀÀpÿÿÀøÿð€þ€<ÀÀÀpÿÿ€àðÿÀ<ÀÀøðÀ8<àÀøð0ðüð€øðà>ðü?ÿøxøÀðð?ÿøø€ÀàøÃÀàxÿ€Àüpþ€øocrad-0.29/feats_test1.cc0000644000175000017500000002137414551072416015175 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "profile.h" #include "feats.h" /* Test whether the lower half of character is open to the left, to the right, and/or to the bottom. */ int Features::test_49ARegpq( const Rectangle & charbox ) const { const Bitmap & h = b.hole( 0 ); if( bp.minima( b.height() / 10 + 1 ) == 2 && bp.isctip() && tp.minima() == 1 ) { if( tp.isvpit() || rp.decreasing() || ( rp.decreasing( 1, rp.pos( 20 ) ) && lp.decreasing( 1, lp.pos( 20 ) ) ) ) return 'A'; if( hbars() == 2 && hbar(1).width() >= b.width() ) { const int i = hbar(1).top() - b.top(); const int j = hbar(1).bottom() - b.top(); if( rp.area( i, j ) <= lp.area( i, j ) ) return 'A'; } return 'R'; } int col = h.hcenter(); int row = b.seek_bottom( h.bottom(), col, false ) + 1; if( row >= b.vpos( 90 ) ) { col = h.left(); row = b.seek_bottom( h.bottom(), col, false ) + 1; } if( row >= b.bottom() ) return 0; if( b.escape_right( row, col ) ) { if( ( lp.ispit() && b.seek_bottom( row, h.right() ) < b.bottom() ) || ( lp.isconvex() && b.seek_bottom( row, h.hcenter() ) < b.bottom() ) ) return 'e'; if( bp.ispit() ) { int row2 = b.seek_bottom( row, h.right() ); if( row2 < b.vpos( 75 ) ) return 'g'; if( row2 < b.bottom() ) return 'e'; } return 'p'; } else if( b.escape_left( row, col ) ) { Profile hlp( h, Profile::left ); Profile htp( h, Profile::top ); Profile hwp( h, Profile::width ); if( vbars() == 1 && vbar(0).hcenter() > b.hcenter() && hlp.decreasing() && htp.decreasing() && hwp[hwp.pos(30)] < hwp[hwp.pos(70)] ) return '4'; if( rp.ispit() && rp.minima() == 1 && rp.iminimum() < rp.pos( 70 ) && tp.ispit() && charbox.bottom() > b.vpos( rp.isconvex() ? 80 : 90 ) ) return '9'; int hdiff; if( b.bottom_hook( &hdiff ) && hdiff > 0 ) { if( h.bottom() < b.vcenter() && h.right() + 2 <= b.right() && ( !b.get_bit( h.bottom() + 1, h.right() + 1 ) || !b.get_bit( h.bottom() + 1, h.right() + 2 ) || rp.isctip() ) ) return 's'; else return 'g'; } if( row > b.vpos( 85 ) && tp.ispit() ) return 'Q'; int row2 = b.seek_bottom( row, col ); if( row2 < b.bottom() && rp.increasing( ( ( row + ( 2 * row2 ) ) / 3 ) - b.top() ) ) return 'g'; if( bp.minima() == 1 ) { if( h.height() >= charbox.height() ) return 'Q'; if( h.right() < b.hcenter() && h.bottom() < b.vcenter() ) return '2'; return 'q'; } } return 0; } int Features::test_4ADQao( const Charset & charset, const Rectangle & charbox ) const { const Bitmap & h = b.hole( 0 ); int left_delta = h.left() - b.left(), right_delta = b.right() - h.right(); if( !lp.ispit() && lp.isflats() && rp.ispit() ) return 'D'; if( Ocrad::similar( left_delta, right_delta, 40 ) && tp.minima() == 2 && bp.minima() == 2 && !rp.isconvex() ) return '#'; if( tp.minima() == 1 && bp.minima() == 1 ) { int row = b.seek_bottom( h.bottom(), h.hcenter(), false ); if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( !lp.isconvex() && bp.isconvex() && !rp.isconvex() && b.seek_bottom( row, h.hcenter() ) < b.bottom() ) return UCS::SEACUTE; row = ( row + b.seek_bottom( row, h.hcenter() ) ) / 2; if( row < b.bottom() - 1 && !lp.isflats() && b.seek_left( row, h.hcenter() ) <= b.left() ) { if( ( 2 * h.height() <= b.height() || 2 * h.width() <= b.width() ) && wp[h.top()-b.top()] < wp[h.bottom()-b.top()] ) return '4'; if( !rp.ispit() && !rp.isconvex() ) return 'Q'; } } if( 2 * b.width() > 5 * h.width() && !rp.isconvex() ) { const int c = segments_in_row( h.vcenter() ); const int m = bp.minima(); if( c == 3 && h.top() < b.vcenter() && h.bottom() > b.vcenter() && 3 * h.height() >= b.height() && ( m == 3 || m == 2 ) && !lp.ispit() ) return 'm'; if( c == 3 && left_delta > right_delta && lp.ispit() && segments_in_col( h.hcenter() ) == 4 ) return '@'; if( c == 4 && Ocrad::similar( left_delta, right_delta, 40 ) && lp.ispit() ) return '@'; } if( tp.minima() == 1 && bp.istip() && !rp.isctip( 66 ) ) return 'A'; if( Ocrad::similar( left_delta, right_delta, 50 ) ) { if( bp.minima() == 1 && rp.isconvex() && b.test_BD() ) return 'D'; if( bp.minima() > 1 || rp.minima() > 1 || b.test_Q() ) { if( 4 * h.size() >= b.size() || tp.ispit() || lp.ispit() ) return 'Q'; else return 0; } if( 3 * bp[bp.pos(100)] < b.height() && 5 * rp[rp.pos(55)] >= b.width() ) return 'a'; if( lp.istip() ) return 'n'; if( b.vpos( 80 ) < charbox.vcenter() ) return UCS::DEG; return 'o'; } if( left_delta > right_delta && rp.ispit() && tp.minima() == 1 && bp.minima() == 1 ) return 'D'; if( Ocrad::similar( left_delta, right_delta, 50 ) && ( bp.minima() > 1 || rp.minima() > 1 ) ) return 'a'; return 0; } /* Test whether the upper half of character is open to the left, to the right, and/or to the bottom. */ int Features::test_6abd( const Charset & charset ) const { const Bitmap & h = b.hole( 0 ); if( 3 * h.width() < b.width() && ( bp.minima( b.height() / 4 ) != 1 || tp.minima( h.vcenter() - b.top() ) != 1 ) ) return 0; int col = h.hcenter(); int row = b.seek_top( h.top(), col, false ) - 1; if( row <= b.top() ) { col = h.right(); if( b.right() - h.right() > h.width() ) ++col; row = b.seek_top( h.top(), col, false ) - 1; } if( row <= b.top() ) return 0; const int rcol = ( b.right() + h.right() ) / 2; const int urow = h.top() - ( b.bottom() - h.bottom() ); const bool oacute1 = ( ( b.seek_right( urow - 1, h.right() ) >= b.right() ) || ( b.seek_right( row, col ) >= b.right() ) ); if( b.escape_right( row, col ) ) { const int noise = ( b.width() / 30 ) + 1; const int c = lp[urow-b.top()]; const bool oacute2 = ( c > lp[h.top()-b.top()] + noise && urow <= b.top() + tp[std::min( c - 1, b.width() / 4 )] ); if( ( oacute1 && oacute2 ) && ( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) ) { const bool oacute3 = ( b.right() - rp[rp.pos(5)] >= h.right() || b.left() + lp[h.top()-b.top()] <= b.hpos( 5 ) ); if( oacute3 ) return UCS::SOACUTE; } if( !oacute2 && lp.ispit() && bp.ispit() ) { int row2 = b.seek_top( h.top(), h.right() + 1, false ) - 1; row2 = b.seek_top( row2, h.right() + 1 ); if( row2 > b.top() ) return '6'; } int row2 = b.seek_top( h.top(), rcol, false ) - 1; row2 = b.seek_top( row2, rcol ); if( row2 <= b.top() ) return 'b'; const int m = tp.minima( b.height() / 2 ); if( m == 1 && bp.minima() == 1 ) return 's'; if( m == 2 ) return 'k'; else return 0; } if( b.escape_left( row, col ) ) { const int col2 = std::max( h.left(), h.hpos( 10 ) ); int row2 = b.seek_top( h.top(), col2, false ) - 1; row2 = b.seek_top( row2, col2 ); if( row2 > b.top() ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) { int row3 = b.seek_top( row, col ); if( row > b.vcenter() && row3 > b.vpos( 20 ) ) return UCS::SAACUTE; if( oacute1 ) return UCS::SOGRAVE; } return 'a'; } if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( oacute1 ) return UCS::SOACUTE; return 'd'; } if( b.width() > 3 * h.width() && h.top() < b.vcenter() && segments_in_row( b.vcenter() ) == 3 && !lp.isconvex() ) return 'm'; int hdiff; if( b.top_hook( &hdiff ) && hdiff > 0 ) return 's'; return 0; } ocrad-0.29/character_r12.cc0000644000175000017500000002272314551103427015367 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" /* Recognize 2 blob characters. ijÑñ!%:;=?|¡ª±º¿ÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛáéíóúàèìòùâêîôûÅå */ void Character::recognize12( const Charset & charset, const Rectangle & charbox ) { const Blob & b1 = blob( 0 ); // upper blob const Blob & b2 = blob( 1 ); // lower blob int a1 = b1.area(); int a2 = b2.area(); Features f1( b1 ); Features f2( b2 ); if( Ocrad::similar( a1, a2, 10 ) ) { if( !b1.holes() && !b2.holes() && 2 * a1 > b1.size() && 2 * a2 > b2.size() ) { if( width() > height() || Ocrad::similar( width(), height(), 40 ) ) { add_guess( '=', 0 ); return; } if( Ocrad::similar( b1.width(), b1.height(), 20, 2 ) && Ocrad::similar( b2.width(), b2.height(), 20, 2 ) ) add_guess( ':', 0 ); return; } return; } if( Ocrad::similar( a1, a2, 60 ) ) { if( f1.test_solid( charbox ) == '.' ) { if( f2.test_solid( charbox ) == '.' ) { add_guess( ':', 0 ); return; } if( b2.height() > b1.height() && b2.top() > charbox.vcenter() ) { add_guess( ';', 0 ); return; } } if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) { int code = f2.test_solid( charbox ); if( code == '-' || code == '_' ) { add_guess( UCS::PLUSMIN, 0 ); return; } } if( b1.includes_hcenter( b2 ) && b2.includes_hcenter( b1 ) ) { if( b1.holes() && b2.holes() ) { add_guess( 'g', 0 ); return; } } if( b1.hcenter() < b2.hcenter() ) // Look for merged 'fi' { if( b2.height() > b2.width() && b1.hcenter() < b2.left() && b1.includes_hcenter( b2 ) && 4 * b1.height() > 5 * b2.height() && Ocrad::similar( b1.bottom()-b1.top(), b2.bottom()-b1.top(), 10 ) ) { Character c2( new Blob( b2 ) ); c2.recognize1( charset, charbox ); if( ( c2.maybe('l') || c2.maybe('|') ) && set_merged_guess( 'f', b2.left() - 1, 'i', 0 ) ) return; } } } if( a1 > a2 && b1.hcenter() < b2.hcenter() && 2 * b1.height() > 3 * b2.height() && b1.holes() == 1 && b2.holes() == 1 && Ocrad::similar( b2.width(), b2.height(), 50 ) ) { add_guess( '%', 0 ); return; } if( a1 < a2 ) { { int code = f1.test_solid( charbox ); //FIXME all this if( code == '-' && 2 * b1.height() > b1.width() ) code = '.'; else if( code == '\'' || code == '|' ) code = '.'; if( !code && !b1.holes() && 2 * b1.height() < b2.height() && b1.width() <= b2.width() ) { if( 10 * a1 >= 7 * b1.height() * b1.width() ) code = '.'; else code = '\''; } if( !b2.holes() && ( code == '.' || code == '\'' ) ) { // Look for merged 'ri' or 'rí' if( f2.bp.minima( b2.height() / 4 ) == 2 && b2.top() > b1.bottom() && b2.hcenter() < b1.left() ) { Character c2( new Blob( b2 ) ); c2.recognize1( charset, charbox ); if( c2.maybe('n') ) { if( code == '.' && ( b1.left() < b2.hcenter() || b1.right() > b2.right() ) ) { add_guess( 'n', 0 ); return; } // FIXME remove dot int col, limit = b2.seek_right( b2.vcenter(), b2.hcenter() ); for( col = b2.hcenter(); col <= limit; ++col ) if( b2.seek_bottom( b2.vcenter(), col ) < b2.bottom() ) break; if( b2.left() < col && col < b2.right() ) { if( charset.enabled( Charset::iso_8859_9 ) && f2.rp.istip() ) set_merged_guess( 'T', col - 1, UCS::CIDOT, 1 ); else { const int code2 = ( ( code == '.' ) ? 'i' : (int)UCS::SIACUTE ); set_merged_guess( 'r', col - 1, code2, 1 ); } return; } } } if( code == '.' && f2.bp.minima( b2.height() / 4 ) == 1 && b1.bottom() <= b2.top() && f2.rp.minima( b2.width() / 2 ) <= 2 ) { int hdiff; if( b2.bottom_hook( &hdiff ) && std::abs( hdiff ) >= b2.height() / 2 ) { if( hdiff > 0 && f2.rp.increasing( f2.rp.pos( 80 ) ) ) { add_guess( 'j', 0 ); return; } if( hdiff < 0 ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( -4 * hdiff <= 3 * b2.height() && f2.wp.max() > 2 * f1.wp.max() && f2.lp.minima() == 1 && 2 * f2.bp[0] < b2.height() ) { add_guess( UCS::IQUEST, 0 ); return; } add_guess( 'i', 0 ); return; } } if( f2.tp.minima() == 1 ) { const bool maybe_j = ( b2.height() > charbox.height() && b2.vpos( 80 ) > charbox.bottom() ); if( Ocrad::similar( f1.wp.max(), f2.wp.max(), 20 ) ) { if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) if( !f2.lp.isctip() && f2.wp.max() >= f1.wp.max() && ( 3 * f2.wp[f2.wp.pos(10)] < 2 * f1.wp.max() || ( b1.left() <= b2.left() && b2.vpos( 80 ) > charbox.bottom() ) ) ) { add_guess( UCS::IEXCLAM, 0 ); return; } if( maybe_j ) add_guess( 'j', 0 ); else add_guess( 'i', 0 ); return; } if( 3 * f2.wp.max() > 4 * f1.wp.max() && b2.seek_bottom( b2.vcenter(), b2.hpos( 10 ) ) < b2.bottom() && f2.rp.increasing( f2.rp.pos( 75 ) ) && ( b1.left() >= b2.hcenter() || b2.seek_top( b2.vcenter(), b2.hpos( 10 ) ) <= b2.top() ) ) { add_guess( 'j', 0 ); return; } if( charset.enabled( Charset::iso_8859_9 ) && f2.rp.istip() ) { add_guess( UCS::CIDOT, 0 ); return; } if( maybe_j ) add_guess( 'j', 0 ); else add_guess( 'i', 0 ); return; } } } } if( ( !b1.holes() && ( b1.bottom() < b2.vcenter() || 2 * a1 < a2 ) ) || ( b1.holes() == 1 && b1.bottom() < b2.top() && b2.top() - b1.bottom() < b1.height() ) ) { Character c( new Blob( b2 ) ); c.recognize1( charset, charbox ); if( c.guesses() ) { int code = c.guess( 0 ).code; if( b1.holes() == 1 ) { if( code == 'a' ) code = UCS::SARING; else if( code == 'A' ) code = UCS::CARING; else code = 0; } else if( code == 'u' && 5 * b1.width() <= b2.width() && 5 * b1.height() <= b2.width() ) return; else if( b1.bottom() < b2.vcenter() /*&& b2.top() - b1.bottom() < 2 * b1.height()*/ ) { int atype = '\''; if( UCS::isvowel( code ) && 2 * b1.width() > 3 * b1.height() && !f1.tp.iscpit() && f1.hp.iscpit() ) atype = ':'; else if( f1.bp.minima() == 2 || f1.bp.istip() ) atype = '^'; else if( std::min( b1.height(), b1.width() ) >= 5 && ( f1.rp.decreasing() || f1.tp.increasing() ) && ( f1.bp.decreasing() || f1.lp.increasing() ) ) atype = '`'; code = UCS::compose( code, atype ); } if( code != c.guess( 0 ).code && charset.only( Charset::ascii ) ) { if( UCS::base_letter( code ) == 'i' ) code = 'i'; else code = c.guess( 0 ).code; } if( code ) add_guess( code, 0 ); } } return; } if( b1.bottom() <= b2.top() ) { const int code = f2.test_solid( charbox ); if( !b1.holes() && ( code == '.' || ( code && Ocrad::similar( b2.height(), b2.width(), 50 ) ) ) ) { if( Ocrad::similar( b1.width(), b2.width(), 50 ) && !f1.lp.isctip() ) { add_guess( '!', 0 ); return; } if( f1.bp.minima() == 1 ) add_guess( '?', 0 ); return; } if( code == '-' || code == '_' ) if( charset.enabled( Charset::iso_8859_15 ) || charset.enabled( Charset::iso_8859_9 ) ) { if( b1.holes() == 1 ) { const Bitmap & h = b1.hole( 0 ); if( b2.width() >= h.width() && b2.top() - b1.bottom() < h.height() ) { if( Ocrad::similar( h.left() - b1.left(), b1.right() - h.right(), 40 ) ) { add_guess( UCS::MASCORD, 0 ); return; } add_guess( UCS::FEMIORD, 0 ); return; } } } } } ocrad-0.29/ocradcheck.cc0000644000175000017500000001501414547605133015036 0ustar andriusandrius/* Ocrcheck - A test program for the library ocradlib Copyright (C) 2009-2024 Antonio Diaz Diaz. This program is free software: you have unlimited permission to copy, distribute, and modify it. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include "arg_parser.h" #include "ocradlib.h" namespace { const char * const program_name = "ocradcheck"; const char * invocation_name = program_name; // default value void show_help() { std::printf( "Ocradcheck is a test program for the library ocradlib. It reads the image\n" "files specified, feeds them to the OCR engine, and sends the resulting text\n" "to stdout.\n" "\nUsage: %s [options] [files]\n", invocation_name ); std::printf( "\nOptions:\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" " -i, --invert invert image levels (white on black)\n" " -l, --layout perform layout analysis\n" " -u, --utf8 output text in UTF-8 format [default byte]\n" "\nIf no files are specified, or if a file is '-', ocradcheck reads the image\n" "from standard input.\n" ); } void show_version() { std::printf( "%s %s\n", program_name, PROGVERSION ); std::printf( "Using ocradlib %s\n", OCRAD_version() ); } void show_error( const char * const msg, const int errcode = 0, const bool help = false ) { if( msg && msg[0] ) std::fprintf( stderr, "%s: %s%s%s\n", program_name, msg, ( errcode > 0 ) ? ": " : "", ( errcode > 0 ) ? std::strerror( errcode ) : "" ); if( help ) std::fprintf( stderr, "Try '%s --help' for more information.\n", invocation_name ); } } // end namespace int main( const int argc, const char * const argv[] ) { bool invert = false; bool layout = false; bool utf8 = false; if( argc > 0 ) invocation_name = argv[0]; const Arg_parser::Option options[] = { { 'h', "help", Arg_parser::no }, { 'i', "invert", Arg_parser::no }, { 'l', "layout", Arg_parser::no }, { 'u', "utf8", Arg_parser::no }, { 'V', "version", Arg_parser::no }, { 0 , 0, Arg_parser::no } }; const Arg_parser parser( argc, argv, options ); if( parser.error().size() ) // bad option { show_error( parser.error().c_str(), 0, true ); return 1; } int argind = 0; for( ; argind < parser.arguments(); ++argind ) { const int code = parser.code( argind ); if( !code ) break; // no more options switch( code ) { case 'h': show_help(); return 0; case 'i': invert = true; break; case 'l': layout = true; break; case 'u': utf8 = true; break; case 'V': show_version(); return 0; default: std::fprintf( stderr, "%s: internal error: uncaught option.\n", program_name ); return 3; } } // end process options if( OCRAD_version()[0] != OCRAD_version_string[0] ) { std::fputs( "wrong library version.\n", stderr ); return 3; } if( std::strcmp( PROGVERSION, OCRAD_version_string ) != 0 ) { std::fputs( "wrong library version_string.\n", stderr ); return 3; } // process any remaining command-line arguments (input files) bool stdin_used = false; for( bool first = true; first || argind < parser.arguments(); first = false ) { const char * infile_name; if( argind < parser.arguments() ) infile_name = parser.argument( argind++ ).c_str(); else { infile_name = "-"; if( stdin_used ) continue; else stdin_used = true; } OCRAD_Descriptor * const ocrdes = OCRAD_open(); if( !ocrdes || OCRAD_get_errno( ocrdes ) != OCRAD_ok ) { OCRAD_close( ocrdes ); std::fputs( "Not enough memory.\n", stderr ); return 1; } if( OCRAD_set_image_from_file( ocrdes, infile_name, invert ) < 0 ) { const OCRAD_Errno ocr_errno = OCRAD_get_errno( ocrdes ); OCRAD_close( ocrdes ); if( ocr_errno == OCRAD_mem_error ) std::fputs( "Not enough memory.\n", stderr ); else std::fprintf( stderr, "%s: Can't open file for reading.\n", infile_name ); return 1; } // std::fprintf( stderr, "ocradcheck: testing file '%s'\n", infile_name ); if( ( utf8 && OCRAD_set_utf8_format( ocrdes, true ) < 0 ) || OCRAD_set_threshold( ocrdes, -1 ) < 0 || // auto threshold OCRAD_recognize( ocrdes, layout ) < 0 ) { const OCRAD_Errno ocr_errno = OCRAD_get_errno( ocrdes ); OCRAD_close( ocrdes ); if( ocr_errno == OCRAD_mem_error ) { std::fputs( "Not enough memory.\n", stderr ); return 1; } std::fprintf( stderr, "%s: internal error: invalid argument.\n", program_name ); return 3; } const int blocks = OCRAD_result_blocks( ocrdes ); int chars_total_by_block = 0; int chars_total_by_line = 0; int chars_total_by_count = 0; for( int b = 0; b < blocks; ++b ) { const int lines = OCRAD_result_lines( ocrdes, b ); chars_total_by_block += OCRAD_result_chars_block( ocrdes, b ); for( int l = 0; l < lines; ++l ) { const char * const s = OCRAD_result_line( ocrdes, b, l ); chars_total_by_line += OCRAD_result_chars_line( ocrdes, b, l ); if( s && s[0] ) { std::fputs( s, stdout ); const int len = std::strlen( s ) - 1; if( !utf8 ) chars_total_by_count += len; else for( int i = 0; i < len; ++i ) if( (uint8_t)s[i] < 128 || (uint8_t)s[i] >= 0xC0 ) ++chars_total_by_count; } } std::fputc( '\n', stdout ); } const int chars_total = OCRAD_result_chars_total( ocrdes ); if( chars_total_by_block != chars_total || chars_total_by_line != chars_total || chars_total_by_count != chars_total ) { std::fprintf( stderr, "library_error: character counts differ.\n" "%d %d %d %d\n", chars_total, chars_total_by_block, chars_total_by_line, chars_total_by_count ); return 1; } OCRAD_close( ocrdes ); } return 0; } ocrad-0.29/Makefile.in0000644000175000017500000001523114546066756014522 0ustar andriusandrius DISTNAME = $(pkgname)-$(pkgversion) INSTALL = install INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_DATA = $(INSTALL) -m 644 INSTALL_DIR = $(INSTALL) -d -m 755 SHELL = /bin/sh CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1 lib_objs = ocradlib.o ocr_objs = common.o segment.o mask.o rational.o rectangle.o track.o \ iso_8859.o ucs.o user_filter.o page_image.o page_image_io.o \ png_io.o bitmap.o blob.o profile.o feats.o feats_test0.o \ feats_test1.o character.o character_r11.o character_r12.o \ character_r13.o textline.o textline_r2.o textblock.o textpage.o objs = arg_parser.o main.o .PHONY : all install install-bin install-info install-man \ install-strip install-compress install-strip-compress \ install-bin-strip install-info-compress install-man-compress \ uninstall uninstall-bin uninstall-info uninstall-man \ doc info man check dist clean distclean all : $(progname) lib$(libname).a lib$(libname).a: $(ocr_objs) $(lib_objs) $(AR) $(ARFLAGS) $@ $(ocr_objs) $(lib_objs) $(progname) : $(ocr_objs) $(objs) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(ocr_objs) $(objs) $(LIBS) ocradcheck : arg_parser.o ocradcheck.o lib$(libname).a $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ arg_parser.o ocradcheck.o lib$(libname).a $(LIBS) ocradcheck.o : ocradcheck.cc $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $< main.o : main.cc $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROGVERSION=\"$(pkgversion)\" -c -o $@ $< %.o : %.cc $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< # prevent 'make' from trying to remake source files $(VPATH)/configure $(VPATH)/Makefile.in $(VPATH)/doc/$(pkgname).texi : ; %.h %.cc : ; $(lib_objs) : Makefile ocradlib.h $(ocr_objs) : Makefile bitmap.h blob.h common.h rectangle.h ucs.h $(objs) : Makefile arg_parser.h character.o : segment.h user_filter.h character.h profile.h feats.h character_r11.o : segment.h character.h profile.h feats.h character_r12.o : segment.h character.h profile.h feats.h character_r13.o : segment.h character.h profile.h feats.h common.o : user_filter.h feats.o : segment.h profile.h feats.h feats_test0.o : segment.h profile.h feats.h feats_test1.o : segment.h profile.h feats.h iso_8859.o : iso_8859.h main.o : common.h rational.h user_filter.h page_image.h textpage.h mask.o : segment.h mask.h ocradcheck.o : Makefile arg_parser.h ocradlib.h ocradlib.o : common.h rectangle.h ucs.h track.h bitmap.h blob.h character.h page_image.h textline.h textblock.h textpage.h page_image.o : ocradlib.h rational.h segment.h mask.h track.h page_image.h page_image_io.o : page_image.h png_io.o : page_image.h profile.o : profile.h rational.o : rational.h segment.o : segment.h textblock.o : rational.h track.h user_filter.h character.h page_image.h textline.h textblock.h textline.o : histogram.h rational.h track.h user_filter.h character.h page_image.h textline.h textline_r2.o : track.h character.h textline.h textpage.o : segment.h mask.h track.h character.h page_image.h textline.h textblock.h textpage.h track.o : track.h user_filter.o : iso_8859.h user_filter.h doc : info man info : $(VPATH)/doc/$(pkgname).info $(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texi cd $(VPATH)/doc && $(MAKEINFO) $(pkgname).texi man : $(VPATH)/doc/$(progname).1 $(VPATH)/doc/$(progname).1 : $(progname) help2man -n 'optical text recognition tool' -o $@ ./$(progname) Makefile : $(VPATH)/configure $(VPATH)/Makefile.in ./config.status check : all ocradcheck @$(VPATH)/testsuite/check.sh $(VPATH)/testsuite $(pkgversion) install : install-bin install-info install-man install-strip : install-bin-strip install-info install-man install-compress : install-bin install-info-compress install-man-compress install-strip-compress : install-bin-strip install-info-compress install-man-compress install-bin : all if [ ! -d "$(DESTDIR)$(bindir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(bindir)" ; fi if [ ! -d "$(DESTDIR)$(includedir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(includedir)" ; fi if [ ! -d "$(DESTDIR)$(libdir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(libdir)" ; fi $(INSTALL_PROGRAM) ./$(progname) "$(DESTDIR)$(bindir)/$(progname)" $(INSTALL_DATA) $(VPATH)/$(libname)lib.h "$(DESTDIR)$(includedir)/$(libname)lib.h" $(INSTALL_DATA) ./lib$(libname).a "$(DESTDIR)$(libdir)/lib$(libname).a" install-bin-strip : all $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install-bin install-info : if [ ! -d "$(DESTDIR)$(infodir)" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(infodir)" ; fi -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"* $(INSTALL_DATA) $(VPATH)/doc/$(pkgname).info "$(DESTDIR)$(infodir)/$(pkgname).info" -if $(CAN_RUN_INSTALLINFO) ; then \ install-info --info-dir="$(DESTDIR)$(infodir)" "$(DESTDIR)$(infodir)/$(pkgname).info" ; \ fi install-info-compress : install-info lzip -v -9 "$(DESTDIR)$(infodir)/$(pkgname).info" install-man : if [ ! -d "$(DESTDIR)$(mandir)/man1" ] ; then $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1" ; fi -rm -f "$(DESTDIR)$(mandir)/man1/$(progname).1"* $(INSTALL_DATA) $(VPATH)/doc/$(progname).1 "$(DESTDIR)$(mandir)/man1/$(progname).1" install-man-compress : install-man lzip -v -9 "$(DESTDIR)$(mandir)/man1/$(progname).1" uninstall : uninstall-man uninstall-info uninstall-bin uninstall-bin : -rm -f "$(DESTDIR)$(bindir)/$(progname)" -rm -f "$(DESTDIR)$(includedir)/$(libname)lib.h" -rm -f "$(DESTDIR)$(libdir)/lib$(libname).a" uninstall-info : -if $(CAN_RUN_INSTALLINFO) ; then \ install-info --info-dir="$(DESTDIR)$(infodir)" --remove "$(DESTDIR)$(infodir)/$(pkgname).info" ; \ fi -rm -f "$(DESTDIR)$(infodir)/$(pkgname).info"* uninstall-man : -rm -f "$(DESTDIR)$(mandir)/man1/$(progname).1"* dist : doc ln -sf $(VPATH) $(DISTNAME) tar -Hustar --owner=root --group=root -cvf $(DISTNAME).tar \ $(DISTNAME)/AUTHORS \ $(DISTNAME)/COPYING \ $(DISTNAME)/ChangeLog \ $(DISTNAME)/INSTALL \ $(DISTNAME)/Makefile.in \ $(DISTNAME)/NEWS \ $(DISTNAME)/README \ $(DISTNAME)/configure \ $(DISTNAME)/doc/$(progname).1 \ $(DISTNAME)/doc/$(pkgname).info \ $(DISTNAME)/doc/$(pkgname).texi \ $(DISTNAME)/*.h \ $(DISTNAME)/*.cc \ $(DISTNAME)/testsuite/check.sh \ $(DISTNAME)/testsuite/test.ouf \ $(DISTNAME)/testsuite/test.txt \ $(DISTNAME)/testsuite/test_utf8.txt \ $(DISTNAME)/testsuite/test2.txt \ $(DISTNAME)/testsuite/test.pbm \ $(DISTNAME)/testsuite/test2.png \ $(DISTNAME)/ocrad.png rm -f $(DISTNAME) lzip -v -9 $(DISTNAME).tar clean : -rm -f $(progname) $(objs) -rm -f ocradcheck ocradcheck.o $(ocr_objs) $(lib_objs) *.a distclean : clean -rm -f Makefile config.status *.tar *.tar.lz ocrad-0.29/ucs.cc0000644000175000017500000002745214546514757013565 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "ucs.h" int UCS::base_letter( const int code ) { switch( code ) { case CAGRAVE: case CAACUTE: case CACIRCU: case CATILDE: case CADIAER: case CARING : return 'A'; case CCCEDI : return 'C'; case CEGRAVE: case CEACUTE: case CECIRCU: case CEDIAER: return 'E'; case CGBREVE: return 'G'; case CIGRAVE: case CIACUTE: case CICIRCU: case CIDIAER: case CIDOT : return 'I'; case CNTILDE: return 'N'; case COGRAVE: case COACUTE: case COCIRCU: case COTILDE: case CODIAER: return 'O'; case CSCEDI : case CSCARON: return 'S'; case CUGRAVE: case CUACUTE: case CUCIRCU: case CUDIAER: return 'U'; case CYACUTE: case CYDIAER: return 'Y'; case CZCARON: return 'Z'; case SAGRAVE: case SAACUTE: case SACIRCU: case SATILDE: case SADIAER: case SARING : return 'a'; case SCCEDI : return 'c'; case SEGRAVE: case SEACUTE: case SECIRCU: case SEDIAER: return 'e'; case SGBREVE: return 'g'; case SIGRAVE: case SIACUTE: case SICIRCU: case SIDIAER: case SINODOT: return 'i'; case SNTILDE: return 'n'; case SOGRAVE: case SOACUTE: case SOCIRCU: case SOTILDE: case SODIAER: return 'o'; case SSCEDI : case SSCARON: return 's'; case SUGRAVE: case SUACUTE: case SUCIRCU: case SUDIAER: return 'u'; case SYACUTE: case SYDIAER: return 'y'; case SZCARON: return 'z'; default: return 0; } } int UCS::compose( const int letter, const int accent ) { switch( letter ) { case 'A': if( accent == '\'') return CAACUTE; if( accent == '`' ) return CAGRAVE; if( accent == '^' ) return CACIRCU; if( accent == ':' ) { return CADIAER; } break; case 'E': if( accent == '\'') return CEACUTE; if( accent == '`' ) return CEGRAVE; if( accent == '^' ) return CECIRCU; if( accent == ':' ) { return CEDIAER; } break; case 'G': return CGBREVE; case '[': case 'I': if( accent == '\'') return CIACUTE; if( accent == '`' ) return CIGRAVE; if( accent == '^' ) return CICIRCU; if( accent == ':' ) { return CIDIAER; } break; case 'N': if( accent != ':' ) return CNTILDE; break; case 'O': if( accent == '\'') return COACUTE; if( accent == '`' ) return COGRAVE; if( accent == '^' ) return COCIRCU; if( accent == ':' ) { return CODIAER; } break; case 'S': return CSCARON; case 'U': case 'V': if( accent == '\'') return CUACUTE; if( accent == '`' ) return CUGRAVE; if( accent == '^' ) return CUCIRCU; if( accent == ':' ) { return CUDIAER; } break; case 'Y': if( accent == '\'') return CYACUTE; if( accent == ':' ) { return CYDIAER; } break; case 'Z': return CZCARON; case 'a': if( accent == '\'') return SAACUTE; if( accent == '`' ) return SAGRAVE; if( accent == '^' ) return SACIRCU; if( accent == ':' ) { return SADIAER; } break; case 'e': if( accent == '\'') return SEACUTE; if( accent == '`' ) return SEGRAVE; if( accent == '^' ) return SECIRCU; if( accent == ':' ) { return SEDIAER; } break; case '9': case 'g': return SGBREVE; case '|': case ']': case 'i': case 'l': if( accent == '\'') return SIACUTE; if( accent == '`' ) return SIGRAVE; if( accent == '^' ) return SICIRCU; if( accent == ':' ) { return SIDIAER; } break; case 'n': if( accent != ':' ) return SNTILDE; break; case 'o': if( accent == '\'') return SOACUTE; if( accent == '`' ) return SOGRAVE; if( accent == '^' ) return SOCIRCU; if( accent == ':' ) { return SODIAER; } break; case 's': return SSCARON; case 'u': case 'v': if( accent == '\'') return SUACUTE; if( accent == '`' ) return SUGRAVE; if( accent == '^' ) return SUCIRCU; if( accent == ':' ) { return SUDIAER; } break; case 'y': if( accent == '\'') return SYACUTE; if( accent == ':' ) { return SYDIAER; } break; case 'z': return SZCARON; } return 0; } bool UCS::isalnum( const int code ) { return ( UCS::isalpha( code ) || UCS::isdigit( code ) ); } bool UCS::isalpha( const int code ) { return ( ( code < 128 && std::isalpha( code ) ) || base_letter( code ) ); } bool UCS::ishigh( const int code ) { if( isupper( code ) || isdigit( code ) ) return true; switch( code ) { case 'b': case 'd': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'p': case 'q': case 't': case 'y': case '|': return true; default: return false; } } bool UCS::islower( const int code ) { if( code < 128 && std::islower( code ) ) return true; const int base = base_letter( code ); return ( base && std::islower( base ) ); } bool UCS::islower_ambiguous( const int code ) { if( islower_small_ambiguous( code ) ) return true; switch( code ) { case 'k': case 'p': case SCCEDI: case SIGRAVE: case SIACUTE: case SICIRCU: case SIDIAER: case SOGRAVE: case SOACUTE: case SOCIRCU: case SOTILDE: case SODIAER: case SUGRAVE: case SUACUTE: case SUCIRCU: case SUDIAER: case SSCEDI: case SSCARON: case SZCARON: return true; default: return false; } } bool UCS::islower_small( const int code ) { if( code >= 128 || !std::islower( code ) ) return false; switch( code ) { case 'a': case 'c': case 'e': case 'm': case 'n': case 'o': case 'r': case 's': case 'u': case 'v': case 'w': case 'x': case 'z': return true; default: return false; } } bool UCS::islower_small_ambiguous( const int code ) { if( code >= 128 || !std::islower( code ) ) return false; switch( code ) { case 'c': case 'o': case 's': case 'u': case 'v': case 'w': case 'x': case 'z': return true; default: return false; } } bool UCS::isspace( const int code ) { return ( code < 128 && std::isspace( code ) ) || code == 0xA0; } bool UCS::isupper( const int code ) { if( code < 128 && std::isupper( code ) ) return true; const int base = base_letter( code ); return ( base && std::isupper( base ) ); } bool UCS::isupper_normal_width( const int code ) { if( code >= 128 || !std::isupper( code ) ) return false; switch( code ) { case 'I': case 'J': case 'L': case 'M': case 'Q': case 'W': return false; default: return true; } } bool UCS::isvowel( int code ) { if( code >= 128 ) code = base_letter( code ); if( !code || !std::isalpha( code ) ) return false; code = std::tolower( code ); return ( code == 'a' || code == 'e' || code == 'i' || code == 'o' || code == 'u' ); } unsigned char UCS::map_to_byte( const int code ) { if( code < 0 ) return 0; if( code < 256 ) return code; switch( code ) { case CGBREVE: return 0xD0; case SGBREVE: return 0xF0; case CIDOT : return 0xDD; case SINODOT: return 0xFD; case CSCEDI : return 0xDE; case SSCEDI : return 0xFE; case CSCARON: return 0xA6; case SSCARON: return 0xA8; case CYDIAER: return 0xBE; case CZCARON: return 0xB4; case SZCARON: return 0xB8; case EURO : return 0xA4; default : return 0; } } int UCS::map_to_ucs( const unsigned char ch ) { switch( ch ) { case 0xA4: return EURO; case 0xA6: return CSCARON; case 0xA8: return SSCARON; case 0xB4: return CZCARON; case 0xB8: return SZCARON; case 0xBC: return CLIGOE; case 0xBD: return SLIGOE; case 0xBE: return CYDIAER; } return ch; } // does not work for 'code' == 0 const char * UCS::ucs_to_utf8( const int code ) { static char s[7]; if( code < 0 || code > 0x7FFFFFFF ) { s[0] = 0; return s; } // invalid code if( code < 128 ) { s[0] = code; s[1] = 0; return s; } // plain ascii int i, mask; if( code < 0x800 ) { i = 2; mask = 0xC0; } // 110X XXXX else if( code < 0x10000 ) { i = 3; mask = 0xE0; } // 1110 XXXX else if( code < 0x200000 ) { i = 4; mask = 0xF0; } // 1111 0XXX else if( code < 0x4000000 ) { i = 5; mask = 0xF8; } // 1111 10XX else { i = 6; mask = 0xFC; } // 1111 110X s[i] = 0; --i; int d = 0; for( ; i > 0; --i, d += 6 ) s[i] = 0x80 | ( ( code >> d ) & 0x3F ); // 10XX XXXX s[0] = mask | ( code >> d ); return s; } int UCS::to_nearest_digit( const int code ) { switch( code ) { case 'D': case 'O': case 'Q': case 'o': return '0'; case 'I': case 'L': case 'l': case '|': case SINODOT: return '1'; case 'Z': case 'z': return '2'; case 'A': case 'q': return '4'; case 'S': case 's': return '5'; case 'G': case 'b': case SOACUTE: return '6'; case 'J': case 'T': return '7'; case '&': case 'B': return '8'; case 'g': return '9'; default: return code; } } int UCS::to_nearest_letter( const int code ) { switch( code ) { case '0': return 'O'; case '1': return 'l'; case '2': return 'Z'; case '4': return 'q'; case '5': return 'S'; case '6': return SOACUTE; case '7': return 'I'; case '8': return 'B'; case '9': return 'g'; default: return code; } } int UCS::to_nearest_upper_num( const int code ) { switch( code ) { case '(': case '[': return 'C'; case 'l': case '|': return 'I'; case DEG: return 'O'; case MICRO: return 'U'; case POW1: case SINODOT: return '1'; case POW2: return '2'; case POW3: return '3'; case 'q': return '4'; case 'b': case SOACUTE: return '6'; case '&': return '8'; case 'g': case MASCORD: return '9'; } if( islower_ambiguous( code ) ) return toupper( code ); return code; } int UCS::toupper( const int code ) { if( code < 128 ) return std::toupper( code ); switch( code ) { case SAGRAVE: return CAGRAVE; case SAACUTE: return CAACUTE; case SACIRCU: return CACIRCU; case SATILDE: return CATILDE; case SADIAER: return CADIAER; case SARING : return CARING; case SCCEDI : return CCCEDI; case SEGRAVE: return CEGRAVE; case SEACUTE: return CEACUTE; case SECIRCU: return CECIRCU; case SEDIAER: return CEDIAER; case SGBREVE: return CGBREVE; case SIGRAVE: return CIGRAVE; case SIACUTE: return CIACUTE; case SICIRCU: return CICIRCU; case SIDIAER: return CIDIAER; case SNTILDE: return CNTILDE; case SOGRAVE: return COGRAVE; case SOACUTE: return COACUTE; case SOCIRCU: return COCIRCU; case SOTILDE: return COTILDE; case SODIAER: return CODIAER; case SSCEDI : return CSCEDI; case SSCARON: return CSCARON; case SUGRAVE: return CUGRAVE; case SUACUTE: return CUACUTE; case SUCIRCU: return CUCIRCU; case SUDIAER: return CUDIAER; case SYACUTE: return CYACUTE; case SYDIAER: return CYDIAER; case SZCARON: return CZCARON; default: return code; } } ocrad-0.29/arg_parser.cc0000644000175000017500000001335414544516472015106 0ustar andriusandrius/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version) Copyright (C) 2006-2024 Antonio Diaz Diaz. This library is free software. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include "arg_parser.h" bool Arg_parser::parse_long_option( const char * const opt, const char * const arg, const Option options[], int & argind ) { unsigned len; int index = -1; bool exact = false, ambig = false; for( len = 0; opt[len+2] && opt[len+2] != '='; ++len ) ; // Test all long options for either exact match or abbreviated matches. for( int i = 0; options[i].code != 0; ++i ) if( options[i].long_name && std::strncmp( options[i].long_name, &opt[2], len ) == 0 ) { if( std::strlen( options[i].long_name ) == len ) // Exact match found { index = i; exact = true; break; } else if( index < 0 ) index = i; // First nonexact match found else if( options[index].code != options[i].code || options[index].has_arg != options[i].has_arg ) ambig = true; // Second or later nonexact match found } if( ambig && !exact ) { error_ = "option '"; error_ += opt; error_ += "' is ambiguous"; return false; } if( index < 0 ) // nothing found { error_ = "unrecognized option '"; error_ += opt; error_ += '\''; return false; } ++argind; data.push_back( Record( options[index].code, options[index].long_name ) ); if( opt[len+2] ) // '--=' syntax { if( options[index].has_arg == no ) { error_ = "option '--"; error_ += options[index].long_name; error_ += "' doesn't allow an argument"; return false; } if( options[index].has_arg == yes && !opt[len+3] ) { error_ = "option '--"; error_ += options[index].long_name; error_ += "' requires an argument"; return false; } data.back().argument = &opt[len+3]; return true; } if( options[index].has_arg == yes ) { if( !arg || !arg[0] ) { error_ = "option '--"; error_ += options[index].long_name; error_ += "' requires an argument"; return false; } ++argind; data.back().argument = arg; return true; } return true; } bool Arg_parser::parse_short_option( const char * const opt, const char * const arg, const Option options[], int & argind ) { int cind = 1; // character index in opt while( cind > 0 ) { int index = -1; const unsigned char c = opt[cind]; if( c != 0 ) for( int i = 0; options[i].code; ++i ) if( c == options[i].code ) { index = i; break; } if( index < 0 ) { error_ = "invalid option -- '"; error_ += c; error_ += '\''; return false; } data.push_back( Record( c ) ); if( opt[++cind] == 0 ) { ++argind; cind = 0; } // opt finished if( options[index].has_arg != no && cind > 0 && opt[cind] ) { data.back().argument = &opt[cind]; ++argind; cind = 0; } else if( options[index].has_arg == yes ) { if( !arg || !arg[0] ) { error_ = "option requires an argument -- '"; error_ += c; error_ += '\''; return false; } data.back().argument = arg; ++argind; cind = 0; } } return true; } Arg_parser::Arg_parser( const int argc, const char * const argv[], const Option options[], const bool in_order ) { if( argc < 2 || !argv || !options ) return; std::vector< const char * > non_options; // skipped non-options int argind = 1; // index in argv while( argind < argc ) { const unsigned char ch1 = argv[argind][0]; const unsigned char ch2 = ch1 ? argv[argind][1] : 0; if( ch1 == '-' && ch2 ) // we found an option { const char * const opt = argv[argind]; const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0; if( ch2 == '-' ) { if( !argv[argind][2] ) { ++argind; break; } // we found "--" else if( !parse_long_option( opt, arg, options, argind ) ) break; } else if( !parse_short_option( opt, arg, options, argind ) ) break; } else { if( in_order ) data.push_back( Record( argv[argind++] ) ); else non_options.push_back( argv[argind++] ); } } if( !error_.empty() ) data.clear(); else { for( unsigned i = 0; i < non_options.size(); ++i ) data.push_back( Record( non_options[i] ) ); while( argind < argc ) data.push_back( Record( argv[argind++] ) ); } } Arg_parser::Arg_parser( const char * const opt, const char * const arg, const Option options[] ) { if( !opt || !opt[0] || !options ) return; if( opt[0] == '-' && opt[1] ) // we found an option { int argind = 1; // dummy if( opt[1] == '-' ) { if( opt[2] ) parse_long_option( opt, arg, options, argind ); } else parse_short_option( opt, arg, options, argind ); if( !error_.empty() ) data.clear(); } else data.push_back( Record( opt ) ); } ocrad-0.29/character_r13.cc0000644000175000017500000000450314545576754015410 0ustar andriusandrius/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003-2024 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "common.h" #include "rectangle.h" #include "segment.h" #include "ucs.h" #include "bitmap.h" #include "blob.h" #include "character.h" #include "profile.h" #include "feats.h" /* Recognize 3 blob characters. %ÄËÏÖÜäëïöüÿ÷ */ void Character::recognize13( const Charset & charset, const Rectangle & charbox ) { const Blob & b1 = blob( 0 ); const Blob & b2 = blob( 1 ); const Blob & b3 = blob( 2 ); // lower blob Character c( new Blob( b3 ) ); int code = 0; c.recognize1( charset, charbox ); if( c.guesses() ) { if( c.maybe('.') || ( c.height() < 2 * c.width() && c.maybe(',') && 2 * b3.area() >= b3.size() ) ) { if( b1.bottom() <= b2.top() && b2.bottom() <= b3.top() ) { if( b2.width() >= 2 * b2.height() ) code = UCS::DIV; } else if( b1.top() < b3.top() && b2.top() < b3.top() ) code = '%'; } else if( std::max( b1.width(), b2.width() ) < b3.width() && Ocrad::similar( b1.height(), b2.height(), 20, 2 ) && 2 * std::max( b1.height(), b2.height() ) < b3.height() ) code = UCS::compose( c.guess( 0 ).code, ':' ); else if( c.maybe('o') ) { if( ( b1.hcenter() < b2.hcenter() && b1.holes() == 1 && !b2.holes() ) || ( b2.hcenter() < b1.hcenter() && b2.holes() == 1 && !b1.holes() ) ) code = '%'; } } if( charset.only( Charset::ascii ) ) { if( code == UCS::DIV ) code = '%'; else code = UCS::base_letter( code ); } if( code ) add_guess( code, 0 ); } ocrad-0.29/configure0000755000175000017500000001623214546517501014353 0ustar andriusandrius#! /bin/sh # configure script for GNU Ocrad - Optical Character Recognition program # Copyright (C) 2003-2024 Antonio Diaz Diaz. # # This configure script is free software: you have unlimited permission # to copy, distribute, and modify it. pkgname=ocrad pkgversion=0.29 progname=ocrad libname=ocrad srctrigger=doc/${pkgname}.texi # clear some things potentially inherited from environment. LC_ALL=C export LC_ALL srcdir= prefix=/usr/local exec_prefix='$(prefix)' bindir='$(exec_prefix)/bin' datarootdir='$(prefix)/share' includedir='${prefix}/include' infodir='$(datarootdir)/info' libdir='${exec_prefix}/lib' mandir='$(datarootdir)/man' CXX=g++ AR=ar CPPFLAGS= CXXFLAGS='-Wall -W -O2' LDFLAGS= ARFLAGS=-rcs LIBS=-lpng MAKEINFO=makeinfo # checking whether we are using GNU C++. /bin/sh -c "${CXX} --version" > /dev/null 2>&1 || { CXX=c++ ; CXXFLAGS=-O2 ; } # Loop over all args args= no_create= while [ $# != 0 ] ; do # Get the first arg, and shuffle option=$1 ; arg2=no shift # Add the argument quoted to args if [ -z "${args}" ] ; then args="\"${option}\"" else args="${args} \"${option}\"" ; fi # Split out the argument for options that take them case ${option} in *=*) optarg=`echo "${option}" | sed -e 's,^[^=]*=,,;s,/$,,'` ;; esac # Process the options case ${option} in --help | -h) echo "Usage: $0 [OPTION]... [VAR=VALUE]..." echo echo "To assign makefile variables (e.g., CXX, CXXFLAGS...), specify them as" echo "arguments to configure in the form VAR=VALUE." echo echo "Options and variables: [defaults in brackets]" echo " -h, --help display this help and exit" echo " -V, --version output version information and exit" echo " --srcdir=DIR find the source code in DIR [. or ..]" echo " --prefix=DIR install into DIR [${prefix}]" echo " --exec-prefix=DIR base directory for arch-dependent files [${exec_prefix}]" echo " --bindir=DIR user executables directory [${bindir}]" echo " --datarootdir=DIR base directory for doc and data [${datarootdir}]" echo " --includedir=DIR C header files [${includedir}]" echo " --infodir=DIR info files directory [${infodir}]" echo " --libdir=DIR object code libraries [${libdir}]" echo " --mandir=DIR man pages directory [${mandir}]" echo " CXX=COMPILER C++ compiler to use [${CXX}]" echo " AR=ARCHIVER library archiver to use [${AR}]" echo " CPPFLAGS=OPTIONS command-line options for the preprocessor [${CPPFLAGS}]" echo " CXXFLAGS=OPTIONS command-line options for the C++ compiler [${CXXFLAGS}]" echo " CXXFLAGS+=OPTIONS append options to the current value of CXXFLAGS" echo " LDFLAGS=OPTIONS command-line options for the linker [${LDFLAGS}]" echo " ARFLAGS=OPTIONS command-line options for the library archiver [${ARFLAGS}]" echo " LIBS=OPTIONS libraries to pass to the linker [${LIBS}]" echo " MAKEINFO=NAME makeinfo program to use [${MAKEINFO}]" echo exit 0 ;; --version | -V) echo "Configure script for GNU ${pkgname} version ${pkgversion}" exit 0 ;; --srcdir) srcdir=$1 ; arg2=yes ;; --prefix) prefix=$1 ; arg2=yes ;; --exec-prefix) exec_prefix=$1 ; arg2=yes ;; --bindir) bindir=$1 ; arg2=yes ;; --datarootdir) datarootdir=$1 ; arg2=yes ;; --includedir) includedir=$1 ; arg2=yes ;; --infodir) infodir=$1 ; arg2=yes ;; --libdir) libdir=$1 ; arg2=yes ;; --mandir) mandir=$1 ; arg2=yes ;; --srcdir=*) srcdir=${optarg} ;; --prefix=*) prefix=${optarg} ;; --exec-prefix=*) exec_prefix=${optarg} ;; --bindir=*) bindir=${optarg} ;; --datarootdir=*) datarootdir=${optarg} ;; --includedir=*) includedir=${optarg} ;; --infodir=*) infodir=${optarg} ;; --libdir=*) libdir=${optarg} ;; --mandir=*) mandir=${optarg} ;; --no-create) no_create=yes ;; CXX=*) CXX=${optarg} ;; AR=*) AR=${optarg} ;; CPPFLAGS=*) CPPFLAGS=${optarg} ;; CXXFLAGS=*) CXXFLAGS=${optarg} ;; CXXFLAGS+=*) CXXFLAGS="${CXXFLAGS} ${optarg}" ;; LDFLAGS=*) LDFLAGS=${optarg} ;; ARFLAGS=*) ARFLAGS=${optarg} ;; LIBS=*) LIBS="${optarg} ${LIBS}" ;; MAKEINFO=*) MAKEINFO=${optarg} ;; --*) echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;; *=* | *-*-*) ;; *) echo "configure: unrecognized option: '${option}'" 1>&2 echo "Try 'configure --help' for more information." 1>&2 exit 1 ;; esac # Check if the option took a separate argument if [ "${arg2}" = yes ] ; then if [ $# != 0 ] ; then args="${args} \"$1\"" ; shift else echo "configure: Missing argument to '${option}'" 1>&2 exit 1 fi fi done # Find the source code, if location was not specified. srcdirtext= if [ -z "${srcdir}" ] ; then srcdirtext="or . or .." ; srcdir=. if [ ! -r "${srcdir}/${srctrigger}" ] ; then srcdir=.. ; fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then ## the sed command below emulates the dirname command srcdir=`echo "$0" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` fi fi if [ ! -r "${srcdir}/${srctrigger}" ] ; then echo "configure: Can't find source code in ${srcdir} ${srcdirtext}" 1>&2 echo "configure: (At least ${srctrigger} is missing)." 1>&2 exit 1 fi # Set srcdir to . if that's what it is. if [ "`pwd`" = "`cd "${srcdir}" ; pwd`" ] ; then srcdir=. ; fi echo if [ -z "${no_create}" ] ; then echo "creating config.status" rm -f config.status cat > config.status << EOF #! /bin/sh # This file was generated automatically by configure. Don't edit. # Run this file to recreate the current configuration. # # This script is free software: you have unlimited permission # to copy, distribute, and modify it. exec /bin/sh "$0" ${args} --no-create EOF chmod +x config.status fi echo "creating Makefile" echo "VPATH = ${srcdir}" echo "prefix = ${prefix}" echo "exec_prefix = ${exec_prefix}" echo "bindir = ${bindir}" echo "datarootdir = ${datarootdir}" echo "includedir = ${includedir}" echo "infodir = ${infodir}" echo "libdir = ${libdir}" echo "mandir = ${mandir}" echo "CXX = ${CXX}" echo "AR = ${AR}" echo "CPPFLAGS = ${CPPFLAGS}" echo "CXXFLAGS = ${CXXFLAGS}" echo "LDFLAGS = ${LDFLAGS}" echo "ARFLAGS = ${ARFLAGS}" echo "LIBS = ${LIBS}" echo "MAKEINFO = ${MAKEINFO}" rm -f Makefile cat > Makefile << EOF # Makefile for GNU Ocrad - Optical Character Recognition program # Copyright (C) 2003-2024 Antonio Diaz Diaz. # This file was generated automatically by configure. Don't edit. # # This Makefile is free software: you have unlimited permission # to copy, distribute, and modify it. pkgname = ${pkgname} pkgversion = ${pkgversion} progname = ${progname} libname = ${libname} VPATH = ${srcdir} prefix = ${prefix} exec_prefix = ${exec_prefix} bindir = ${bindir} datarootdir = ${datarootdir} includedir = ${includedir} infodir = ${infodir} libdir = ${libdir} mandir = ${mandir} CXX = ${CXX} AR = ${AR} CPPFLAGS = ${CPPFLAGS} CXXFLAGS = ${CXXFLAGS} LDFLAGS = ${LDFLAGS} ARFLAGS = ${ARFLAGS} LIBS = ${LIBS} MAKEINFO = ${MAKEINFO} EOF cat "${srcdir}/Makefile.in" >> Makefile echo "OK. Now you can run make." echo "If make fails, check that the PNG library libpng is correctly installed" echo "(see INSTALL)." ocrad-0.29/doc/0000775000175000017500000000000014726507103013204 5ustar andriusandriusocrad-0.29/doc/ocrad.info0000644000175000017500000006735514552220115015157 0ustar andriusandriusThis is ocrad.info, produced by makeinfo version 4.13+ from ocrad.texi. INFO-DIR-SECTION GNU Packages START-INFO-DIR-ENTRY * Ocrad: (ocrad). The GNU OCR program END-INFO-DIR-ENTRY  File: ocrad.info, Node: Top, Next: Introduction, Up: (dir) GNU Ocrad Manual **************** This manual is for GNU Ocrad (version 0.29, 18 January 2024). * Menu: * Introduction:: Purpose and features of GNU Ocrad * Character sets:: Input charsets and output formats * Invoking ocrad:: Command-line interface * Filters:: Postprocessing the text produced * Library version:: Checking library version * Library functions:: Descriptions of the library functions * Library error codes:: Meaning of codes returned by functions * Image format conversion:: How to convert other formats to png or pnm * Algorithm:: How ocrad does its job * OCR results file:: Description of the ORF file format * Problems:: Reporting bugs * Concept index:: Index of concepts Copyright (C) 2003-2024 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute, and modify it.  File: ocrad.info, Node: Introduction, Next: Character sets, Prev: Top, Up: Top 1 Introduction ************** GNU Ocrad is an OCR (Optical Character Recognition) program and library based on a feature extraction method. It reads images in png or pnm formats and produces text in byte (8-bit) or UTF-8 formats. The formats pbm (bitmap), pgm (greyscale), and ppm (color) are collectively known as pnm. Ocrad includes a layout analyser able to separate the columns and blocks of text normally found on printed pages. For best results the characters should be at least 20 pixels high. If they are smaller, try the option '--scale'. Scanning the image at 300 dpi usually produces a character size good enough for ocrad.  File: ocrad.info, Node: Character sets, Next: Invoking ocrad, Prev: Introduction, Up: Top 2 Character sets **************** The character set internally used by ocrad is ISO 10646, also known as UCS (Universal Character Set), which can represent over two thousand million characters (2^31). As it is unpractical to try to recognize one among so many different characters, you can tell ocrad what character sets to recognize. You do this with the option '--charset'. If the input page contains characters from only one character set, say 'ISO-8859-15', you can use the default 'byte' output format. But in a page with 'ISO-8859-9' and 'ISO-8859-15' characters, you can't tell if a code of 0xFD represents a 'latin small letter i dotless' or a 'latin small letter y with acute'. You should use '--format=utf8' instead. Of course, you may request UTF-8 output in any case. NOTE: 10^9 is a thousand millions, a billion is a million millions (million^2), a trillion is a million million millions (million^3), and so on. Please, don't "embrace and extend" the meaning of prefixes, making communication among all people difficult. Thanks.  File: ocrad.info, Node: Invoking ocrad, Next: Filters, Prev: Character sets, Up: Top 3 Invoking ocrad **************** The format for running ocrad is: ocrad [OPTIONS] [FILES] A hyphen '-' used as a FILE argument means standard input. It can be mixed with other FILES and is read just once, the first time it appears in the command line. Ocrad can read concatenated files from standard input. Remember to prepend './' to any file name beginning with a hyphen, or use '--'. ocrad supports the following options: *Note Argument syntax: (arg_parser)Argument syntax. '-h' '--help' Print an informative help message describing the options and exit. 'ocrad --verbose --help' describes also hidden options. '-V' '--version' Print the version number of ocrad on the standard output and exit. This version number should be included in all bug reports. '-a' '--append' Append generated text to the output file instead of overwriting it. '-c NAME' '--charset=NAME' Enable recognition of the characters belonging to the character set given. You can repeat this option multiple times with different names for processing a page with characters from different character sets. If no charset is specified, 'iso-8859-15' (latin9) is assumed. Try '--charset=help' for a list of valid charset names. '-e NAME' '--filter=NAME' Pass the output text through the built-in postprocessing filter given (*note Filters::). Several filters can be applied in sequence using as many '--filter' and '--user-filter' options as needed. The filters are applied in the order they appear on the command line. Try '--filter=help' for a list of valid filter names. '-E FILE' '--user-filter=FILE' Pass the output text through the postprocessing filter defined in FILE. *Note Filters::, for a description of the format of FILE. Several filters can be applied in sequence using as many '--filter' and '--user-filter' options as needed. The filters are applied in the order they appear on the command line. '-f' '--force' Force overwrite of output files. '-F NAME' '--format=NAME' Select the output format. The valid names are 'byte' and 'utf8'. If no output format is specified, 'byte' (8 bit) is assumed. '-i' '--invert' Invert image levels (white on black). '-l' '--layout' Enable page layout analysis. Ocrad is able to separate blocks of text of arbitrary shape as long as they are clearly delimited by white space. '-o FILE' '--output=FILE' Place the output into FILE instead of into the standard output. Any missing parent directories are automatically created. '-q' '--quiet' Quiet operation. '-s VALUE' '--scale=VALUE' Scale up the input image by VALUE before layout analysis and recognition. If VALUE is negative, the input image is scaled down by -VALUE. '-t NAME' '--transform=NAME' Perform the transformation given (rotation or mirroring) on the input image before scaling, layout analysis, and recognition. Rotations are made counter-clockwise. Try '--transform=help' for a list of valid transformation names. '-T VALUE' '--threshold=VALUE' Set binarization threshold for png, pgm, and ppm files or for option '--scale' (only for scaled down images). VALUE should be a rational number between 0 and 1, and may be given as a percentage (50%), a fraction (1/2), or a decimal value (0.5). Pixel values greater than threshold are converted to white. The default value is 0.5. '-u LEFT,TOP,WIDTH,HEIGHT' '--cut=LEFT,TOP,WIDTH,HEIGHT' Cut the input image by the rectangle defined by LEFT, TOP, WIDTH, and HEIGHT. Values may be relative to the image size (-1.0 <= value <= +1.0), or absolute (abs( value ) > 1). Negative values of LEFT, TOP are relative to the right-bottom corner of the image. Values of WIDTH and HEIGHT must be positive. Absolute and relative values can be mixed. For example 'ocrad --cut 700,960,1,1' will extract from '700,960' to the right-bottom corner of the image. The cutting is performed before any other transformation (rotation or mirroring) on the input image, and before scaling, layout analysis, and recognition. '-v' '--verbose' Verbose mode. '-x FILE' '--export=FILE' Write (export) OCR results file to FILE (*note OCR results file::). '-x -' writes to stdout, overriding text output except if output has been also redirected with the option '-o'. Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid command-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused ocrad to panic.  File: ocrad.info, Node: Filters, Next: Library version, Prev: Invoking ocrad, Up: Top 4 Postprocessing the text produced ********************************** Filters replace some characters in the text output with different characters and remove some other characters from the output. For example, when recognizing a text that is known to contain just numbers, any character recognized as a 'Z' will probably be a '2'. Filters don't enable the recognition of characters, just filter them from the output. Use '--charset' to enable the recognition of a character set different from the default ISO-8859-15. Ocrad provides both built-in filters and user-defined filters. 4.1 User-defined filters ======================== The format of a user-defined filter file (*note --user-filter::) is very simple. Each line contains either a character conversion or a word that specifies the default behaviour for unlisted characters. A character conversion is a comma-separated list of quoted characters ('c'), character sets ([0-9A-Z]), character codes (U0063), or character ranges (U0000 - UFFFF), and an optional conversion (an equal sign (=) followed by a quoted character or a character code). The characters in the list are converted to the character in the conversion. If no conversion is specified, the character is left unmodified (converted to itself). The default behaviour is to discard unlisted characters, i.e. those characters not appearing in the file, either by themselves or included in a set or range. If a line containing just the word 'leave' is found in the file, unlisted characters are left unmodified. If the word is 'mark', unlisted characters are marked as unrecognized. The destination character of a conversion is considered as listed by default. Every character may be listed more than once, even as part of different conversions. The last conversion affecting a given character is the one that is performed. Character sets and quoted characters may contain escape sequences. The character '#' at begin of line or after whitespace starts a comment that extends to the end of the line. Ranges of characters may be specified in character sets by writing the starting and ending characters with a '-' between them. Thus, '[A-Z]' matches any ASCII uppercase letter. '-' may be specified by placing it first or last. ']' may be specified by placing it first. If the first character after the left bracket is '^', it indicates a "complemented set", which matches any character except the ones between the brackets. Literals (quoted characters and character sets) are decoded as ISO-8859-15. Character codes are decoded as UCS2. Thus, a 'latin capital letter y with diaeresis' is specified in a set as '[\xBE]', but its code is 'U0178'. Spaces and control characters are unaffected by filters, except that leadind, trailing, and duplicate spaces produced by the removal of other characters will be themselves removed. Here is an example user-defined filter file equivalent to the built-in filter 'numbers': leave # remove this line to get 'numbers_only' 'D', 'O', 'Q', 'o' = '0' 'I', 'L', 'l', '|' = '1' 'Z', 'z' = '2' '3' 'A', 'q' = '4' 'S', 's' = '5' 'G', 'b', U00F3 = '6' # U00F3 = latin small letter o with acute 'J', 'T' = '7' '&', 'B' = '8' 'g' = '9' 4.2 Built-in filters ==================== Ocrad provides the following built-in filters (*note --filter::): '--filter=letters' Forces every character that resembles a letter to be recognized as a letter. Other characters will be output without change. '--filter=letters_only' Same as '--filter=letters', but other characters will be discarded. '--filter=numbers' Forces every character that resembles a number to be recognized as a number. Other characters will be output without change. '--filter=numbers_only' Same as '--filter=numbers' but other characters will be discarded. '--filter=same_height' Discards any character (or noise) whose height differs in more than 10 percent from the median height of the characters in the line. '--filter=text_block' Discards any character (or noise) outside of a rectangular block of text lines. '--filter=upper_num' Forces every character that resembles a uppercase letter or a number to be recognized as such. Other characters will be output without change. '--filter=upper_num_mark' Same as '--filter=upper_num', but other characters will be marked as unrecognized. '--filter=upper_num_only' Same as '--filter=upper_num', but other characters will be discarded.  File: ocrad.info, Node: Library version, Next: Library functions, Prev: Filters, Up: Top 5 Library version ***************** -- Constant: OCRAD_API_VERSION This constant is defined in 'ocradlib.h' and works as a version test macro. The application should check at compile time that OCRAD_API_VERSION is equal to the version required by the application: #if !defined OCRAD_API_VERSION || OCRAD_API_VERSION != 28 #error "ocradlib 0.28 needed." #endif Before version 0.28, ocradlib didn't define OCRAD_API_VERSION. OCRAD_API_VERSION is defined as (major * 1000 + minor). NOTE: Version test macros are the library's way of announcing functionality to the application. They should not be confused with feature test macros, which allow the application to announce to the library its desire to have certain symbols and prototypes exposed. -- Function: int OCRAD_api_version ( void ) If OCRAD_API_VERSION >= 28, this function is declared in 'ocradlib.h' (else it doesn't exist). It returns the OCRAD_API_VERSION of the library object code being used. The application should check at run time that the value returned by 'OCRAD_api_version' is equal to the version required by the application. #if defined OCRAD_API_VERSION && OCRAD_API_VERSION >= 28 if( OCRAD_api_version() != 28 ) show_error( "ocradlib 0.28 needed." ); #endif -- Constant: const char * OCRAD_version_string This string constant is defined in the header file 'ocradlib.h' and represents the version of the library being used at compile time. -- Function: const char * OCRAD_version ( void ) This function returns a string representing the version of the library being used at run time.  File: ocrad.info, Node: Library functions, Next: Library error codes, Prev: Library version, Up: Top 6 Library functions ******************* These are the OCRAD library functions. In case of error, all of them return -1 or a null pointer, except 'OCRAD_open' whose return value must be checked by calling 'OCRAD_get_errno' before using it. -- Function: struct OCRAD_Descriptor * OCRAD_open ( void ) Initialize the internal library state and return a pointer that can only be used as the OCRDES argument for the other OCRAD functions, or a null pointer if the descriptor could not be allocated. The pointer returned must be checked by calling 'OCRAD_get_errno' before using it. If 'OCRAD_get_errno' does not return 'OCRAD_ok', the pointer returned must not be used and should be freed with 'OCRAD_close' to avoid memory leaks. -- Function: int OCRAD_close ( struct OCRAD_Descriptor * const OCRDES ) Free all dynamically allocated data structures for this descriptor. After a call to 'OCRAD_close', OCRDES can no more be used as an argument to any OCRAD function. -- Function: enum OCRAD_Errno OCRAD_get_errno ( struct OCRAD_Descriptor * const OCRDES ) Return the current error code for OCRDES. *Note Library error codes::. -- Function: int OCRAD_set_image ( struct OCRAD_Descriptor * const OCRDES, const struct OCRAD_Pixmap * const IMAGE, const bool INVERT ) Load IMAGE into the internal buffer. If INVERT is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. -- Function: int OCRAD_set_image_from_file ( struct OCRAD_Descriptor * const OCRDES, const char * const FILENAME, const bool INVERT ) Load a image from the file FILENAME into the internal buffer. If INVERT is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. -- Function: int OCRAD_set_utf8_format ( struct OCRAD_Descriptor * const OCRDES, const bool UTF8 ) Set the output format to 'byte' (if UTF8=false) or to 'utf8' (if UTF8=true). By default ocrad produces 'byte' (8 bit) output. -- Function: int OCRAD_set_threshold ( struct OCRAD_Descriptor * const OCRDES, const int THRESHOLD ) Set the binarization threshold for greymap and RGB images. THRESHOLD values between 0 and 255 set a fixed threshold. A value of -1 sets an automatic threshold. Pixel values greater than the resulting threshold are converted to white. The default threshold value if this function is not called is 127. -- Function: int OCRAD_scale ( struct OCRAD_Descriptor * const OCRDES, const int VALUE ) Scale up the image in the internal buffer by VALUE. If VALUE is negative, the image is scaled down by -VALUE. -- Function: int OCRAD_recognize ( struct OCRAD_Descriptor * const OCRDES, const bool LAYOUT ) Recognize the image loaded in the internal buffer and produce text results which can be later retrieved with the functions 'OCRAD_result_*'. The same image can be recognized as many times as desired, for example setting a new threshold each time for 3D greymap recognition. Every time this function is called, the text results produced replace any previous ones. If LAYOUT is true, page layout analysis is enabled, probably producing more than one text block. -- Function: int OCRAD_result_blocks ( struct OCRAD_Descriptor * const OCRDES ) Return the number of text blocks found in the image, or 0 if no text was found. The value returned is usually 1, but can be larger if layout analysis was requested. -- Function: int OCRAD_result_lines ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM ) Return the number of text lines contained in the text block given. -- Function: int OCRAD_result_chars_total ( struct OCRAD_Descriptor * const OCRDES ) Return the total number of text characters contained in the image recognized. -- Function: int OCRAD_result_chars_block ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM ) Return the number of text characters contained in the text block given. -- Function: int OCRAD_result_chars_line ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM, const int LINENUM ) Return the number of text characters contained in the text line given. -- Function: const char * OCRAD_result_line ( struct OCRAD_Descriptor * const OCRDES, const int BLOCKNUM, const int LINENUM ) Return the line of text specified by BLOCKNUM and LINENUM. -- Function: int OCRAD_result_first_character ( struct OCRAD_Descriptor * const OCRDES ) Return the byte result for the first character in the image. Return 0 if the image has no characters or if the first character could not be recognized. This function is a convenient short cut to the result for images containing a single character.  File: ocrad.info, Node: Library error codes, Next: Image format conversion, Prev: Library functions, Up: Top 7 Library error codes ********************* Most library functions return -1 or a null pointer to indicate that they have failed. But this return value only tells you that an error has occurred. To find out what kind of error it was, you need to check the error code by calling 'OCRAD_get_errno'. Library functions don't change the value returned by 'OCRAD_get_errno' when they succeed; thus, the value returned by 'OCRAD_get_errno' after a successful call is not necessarily OCRAD_ok, and you should not use 'OCRAD_get_errno' to determine whether a call failed. If the call failed, then you can examine 'OCRAD_get_errno'. The error codes are defined in the header file 'ocradlib.h'. -- Constant: enum OCRAD_Errno OCRAD_ok The value of this constant is 0 and is used to indicate that there is no error. -- Constant: enum OCRAD_Errno OCRAD_bad_argument At least one of the arguments passed to the library function was invalid. -- Constant: enum OCRAD_Errno OCRAD_mem_error No memory available. The system cannot allocate more virtual memory because its capacity is full. -- Constant: enum OCRAD_Errno OCRAD_sequence_error A library function was called in the wrong order. For example 'OCRAD_result_line' was called before 'OCRAD_recognize'. -- Constant: enum OCRAD_Errno OCRAD_library_error A bug was detected in the library. Please, report it. *Note Problems::.  File: ocrad.info, Node: Image format conversion, Next: Algorithm, Prev: Library error codes, Up: Top 8 Image format conversion ************************* There are a lot of image formats, but ocrad is able to decode only four of them; png, pbm, pgm, and ppm. In this chapter you will find command examples and advice about how to convert image files to a format that ocrad can manage. '.ps' '.pdf' Postscript or Portable Document Format file. Use the command 'gs -sPAPERSIZE=a4 -sDEVICE=pnmraw -r300 -dNOPAUSE -dBATCH -sOutputFile=- -q file.ps | ocrad'. You may also use the command 'pstopnm -stdout -dpi=300 -pgm file.ps | ocrad', but it seems not to work with pdf files. Also old versions of 'pstopnm' don't recognize the option '-dpi' and produce an image too small for OCR. '.tiff' TIFF file. Use the command 'tifftopnm file.tiff | ocrad'. '.jpg' JPEG file. Use the command 'djpeg -greyscale -pnm file.jpg | ocrad'. JPEG is a lossy format and is in general not recommended for text images. '.pnm.gz' Pnm file compressed with gzip. Use the command 'gzip -cd file.pnm.gz | ocrad'. '.pnm.lz' Pnm file compressed with lzip. Use the command 'lzip -cd file.pnm.lz | ocrad'.  File: ocrad.info, Node: Algorithm, Next: OCR results file, Prev: Image format conversion, Up: Top 9 Algorithm *********** Ocrad is mainly a research project. Many of the algorithms ocrad uses are ad hoc, and will change in successive releases as I myself gain understanding about OCR issues. The overall working of ocrad may be described as follows: 1) Read the image. 2) Optionally, perform some transformations (cut, rotate, scale, etc). 3) Optionally, perform layout detection. 4) Remove frames and pictures. 5) Detect characters and group them in lines. 6) Recognize characters (very ad hoc; one algorithm per character). 7) Correct some ambiguities (transform l.OOO into 1.000, etc). 8) Optionally, apply one or more filters to the text. 9) Output text result. Ocrad recognizes characters by its shape, and the reason it is so fast is that it does not compare the shape of every character against some sort of database of shapes and then chooses the best match. Instead of this, ocrad only compares the shape differences that are relevant to choose between two character categories, mostly like a binary search. As there is no such thing as a free lunch, this approach has some drawbacks. It makes ocrad very sensitive to character defects, and makes difficult to modify ocrad to recognize new characters.  File: ocrad.info, Node: OCR results file, Next: Problems, Prev: Algorithm, Up: Top 10 OCR results file ******************* Calling ocrad with option '-x' produces an OCR results file (ORF), that is, a parsable file containing the OCR results. The ORF format is as follows: - Any line beginning with '#' is a comment line. - The first non-comment line has the form 'source file FILENAME', where FILENAME is the name of the file being processed ('-' for standard input). This is the only line guaranteed to exist for every input file read without errors. If the file, or any block or line, has no text, the corresponding part in the ORF file will be missing. - The second non-comment line has the form 'total text blocks N', where N is the total number of text blocks in the source image. For each text block in the source image, the following data follows: - A line in the form 'text block I X Y W H'. Where I is the block number and X Y W H are the block position and size as described below for character boxes. - A line in the form 'lines N'. Where N is the number of lines in this block. For each line in every text block, the following data follows: - A line in the form 'line I chars N height H', where I is the line number, N is the number of characters in this line, and H is the mean height of the characters in this line (in pixels). - N lines (one for every character) in the form 'X Y W H; G[, 'C'V]...', where: X is the left border (x-coordinate) of the char bounding box in the source image (in pixels). Y is the top border (y-coordinate). W is the width of the bounding box. H is the height of the bounding box. G is the number of different recognition guesses for this character. The result characters follow after the number of guesses in the form of a comma-separated list of pairs. Every pair is formed by the actual recognised char C enclosed in single quotes, followed by the confidence value V, without space between them. The higher the value of confidence, the more confident is the result. Running './ocrad -x test.orf testsuite/test.pbm' in the source directory will give you an example ORF file.  File: ocrad.info, Node: Problems, Next: Concept index, Prev: OCR results file, Up: Top 11 Reporting bugs ***************** There are probably bugs in ocrad. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. If you find a bug in GNU Ocrad, please send electronic mail to . Include the version number, which you can find by running 'ocrad --version'.  File: ocrad.info, Node: Concept index, Prev: Problems, Up: Top Concept index ************* [index] * Menu: * algorithm: Algorithm. (line 6) * bugs: Problems. (line 6) * filters: Filters. (line 6) * getting help: Problems. (line 6) * image format conversion: Image format conversion. (line 6) * input charsets: Character sets. (line 6) * introduction: Introduction. (line 6) * invoking: Invoking ocrad. (line 6) * library error codes: Library error codes. (line 6) * library functions: Library functions. (line 6) * library version: Library version. (line 6) * OCR results file: OCR results file. (line 6) * options: Invoking ocrad. (line 6) * output format: Character sets. (line 6) * usage: Invoking ocrad. (line 6) * version: Invoking ocrad. (line 6)  Tag Table: Node: Top196 Node: Introduction1266 Node: Character sets1997 Node: Invoking ocrad3151 Ref: --filter4505 Ref: --user-filter4877 Node: Filters7971 Node: Library version12749 Node: Library functions14562 Node: Library error codes19649 Node: Image format conversion21192 Node: Algorithm22448 Node: OCR results file23781 Node: Problems26056 Node: Concept index26594  End Tag Table  Local Variables: coding: iso-8859-15 End: ocrad-0.29/doc/ocrad.10000644000175000017500000000643414552233122014355 0ustar andriusandrius.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2. .TH OCRAD "1" "January 2024" "GNU ocrad 0.29" "User Commands" .SH NAME ocrad \- optical text recognition tool .SH SYNOPSIS .B ocrad [\fI\,options\/\fR] [\fI\,files\/\fR] .SH DESCRIPTION GNU Ocrad is an OCR (Optical Character Recognition) program based on a feature extraction method. It reads images in png or pnm formats and produces text in byte (8\-bit) or UTF\-8 formats. The formats pbm (bitmap), pgm (greyscale), and ppm (color) are collectively known as pnm. .PP Ocrad includes a layout analyser able to separate the columns or blocks of text normally found on printed pages. .PP For best results the characters should be at least 20 pixels high. If they are smaller, try the option \fB\-\-scale\fR. Scanning the image at 300 dpi usually produces a character size good enough for ocrad. Merged, very bold, or very light (broken) characters are normally not recognized correctly. Try to avoid them. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR display this help and exit .TP \fB\-V\fR, \fB\-\-version\fR output version information and exit .TP \fB\-a\fR, \fB\-\-append\fR append text to output file .TP \fB\-c\fR, \fB\-\-charset=\fR try '\-\-charset=help' for a list of names .TP \fB\-e\fR, \fB\-\-filter=\fR try '\-\-filter=help' for a list of names .TP \fB\-E\fR, \fB\-\-user\-filter=\fR user\-defined filter, see manual for format .TP \fB\-f\fR, \fB\-\-force\fR force overwrite of output file .TP \fB\-F\fR, \fB\-\-format=\fR output format (byte, utf8) .TP \fB\-i\fR, \fB\-\-invert\fR invert image levels (white on black) .TP \fB\-l\fR, \fB\-\-layout\fR perform layout analysis .TP \fB\-o\fR, \fB\-\-output=\fR place the output into .TP \fB\-q\fR, \fB\-\-quiet\fR suppress all messages .TP \fB\-s\fR, \fB\-\-scale\fR=\fI\,[\-]\/\fR scale input image by [1/] .TP \fB\-t\fR, \fB\-\-transform=\fR try '\-\-transform=help' for a list of names .TP \fB\-T\fR, \fB\-\-threshold=\fR threshold for binarization (0.0\-1.0) .TP \fB\-u\fR, \fB\-\-cut=\fR cut the input image by the rectangle given .TP \fB\-v\fR, \fB\-\-verbose\fR be verbose .TP \fB\-x\fR, \fB\-\-export=\fR export results in ORF format to .PP If no files are specified, or if a file is '\-', ocrad reads the image from standard input. If the option \fB\-o\fR is not specified, ocrad sends text to standard output. .PP Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid command\-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused ocrad to panic. .SH "REPORTING BUGS" Report bugs to bug\-ocrad@gnu.org .br Ocrad home page: http://www.gnu.org/software/ocrad/ocrad.html .br General help using GNU software: http://www.gnu.org/gethelp .SH COPYRIGHT Copyright \(co 2024 Antonio Diaz Diaz. License GPLv2+: GNU GPL version 2 or later .br This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH "SEE ALSO" The full documentation for .B ocrad is maintained as a Texinfo manual. If the .B info and .B ocrad programs are properly installed at your site, the command .IP .B info ocrad .PP should give you access to the complete manual. ocrad-0.29/doc/ocrad.texi0000644000175000017500000006571514552220074015177 0ustar andriusandrius\input texinfo @c -*-texinfo-*- @c %**start of header @setfilename ocrad.info @documentencoding ISO-8859-15 @settitle GNU Ocrad Manual @finalout @c %**end of header @set UPDATED 18 January 2024 @set VERSION 0.29 @dircategory GNU Packages @direntry * Ocrad: (ocrad). The GNU OCR program @end direntry @ifnothtml @titlepage @title GNU Ocrad @subtitle The GNU OCR Program @subtitle for GNU Ocrad version @value{VERSION}, @value{UPDATED} @author by Antonio Diaz Diaz @page @vskip 0pt plus 1filll @end titlepage @contents @end ifnothtml @ifnottex @node Top @top This manual is for GNU Ocrad (version @value{VERSION}, @value{UPDATED}). @menu * Introduction:: Purpose and features of GNU Ocrad * Character sets:: Input charsets and output formats * Invoking ocrad:: Command-line interface * Filters:: Postprocessing the text produced * Library version:: Checking library version * Library functions:: Descriptions of the library functions * Library error codes:: Meaning of codes returned by functions * Image format conversion:: How to convert other formats to png or pnm * Algorithm:: How ocrad does its job * OCR results file:: Description of the ORF file format * Problems:: Reporting bugs * Concept index:: Index of concepts @end menu @sp 1 Copyright @copyright{} 2003-2024 Antonio Diaz Diaz. This manual is free documentation: you have unlimited permission to copy, distribute, and modify it. @end ifnottex @node Introduction @chapter Introduction @cindex introduction @uref{http://www.gnu.org/software/ocrad/ocrad.html,,GNU Ocrad} is an OCR (Optical Character Recognition) program and library based on a feature extraction method. It reads images in png or pnm formats and produces text in @w{byte (8-bit)} or UTF-8 formats. The formats pbm (bitmap), pgm (greyscale), and ppm (color) are collectively known as pnm. Ocrad includes a layout analyser able to separate the columns and blocks of text normally found on printed pages. For best results the characters should be at least 20 pixels high. If they are smaller, try the option @option{--scale}. Scanning the image at 300 dpi usually produces a character size good enough for ocrad. @node Character sets @chapter Character sets @cindex input charsets @cindex output format The character set internally used by ocrad is ISO 10646, also known as UCS (Universal Character Set), which can represent over two thousand million characters (2^31). As it is unpractical to try to recognize one among so many different characters, you can tell ocrad what character sets to recognize. You do this with the option @option{--charset}. If the input page contains characters from only one character set, say @samp{ISO-8859-15}, you can use the default @samp{byte} output format. But in a page with @samp{ISO-8859-9} and @samp{ISO-8859-15} characters, you can't tell if a code of 0xFD represents a @samp{latin small letter i dotless} or a @samp{latin small letter y with acute}. You should use @option{--format=utf8} instead. Of course, you may request UTF-8 output in any case. @sp 1 NOTE: 10^9 is a thousand millions, a billion is a million millions (million^2), a trillion is a million million millions (million^3), and so on. Please, don't "embrace and extend" the meaning of prefixes, making communication among all people difficult. Thanks. @node Invoking ocrad @chapter Invoking ocrad @cindex invoking @cindex options @cindex usage @cindex version The format for running ocrad is: @example ocrad [@var{options}] [@var{files}] @end example @noindent A hyphen @samp{-} used as a @var{file} argument means standard input. It can be mixed with other @var{files} and is read just once, the first time it appears in the command line. Ocrad can read concatenated files from standard input. Remember to prepend @file{./} to any file name beginning with a hyphen, or use @samp{--}. ocrad supports the following @uref{http://www.nongnu.org/arg-parser/manual/arg_parser_manual.html#Argument-syntax,,options}: @ifnothtml @xref{Argument syntax,,,arg_parser}. @end ifnothtml @table @code @item -h @itemx --help Print an informative help message describing the options and exit. @w{@samp{ocrad --verbose --help}} describes also hidden options. @item -V @itemx --version Print the version number of ocrad on the standard output and exit. This version number should be included in all bug reports. @item -a @itemx --append Append generated text to the output file instead of overwriting it. @item -c @var{name} @itemx --charset=@var{name} Enable recognition of the characters belonging to the character set given. You can repeat this option multiple times with different names for processing a page with characters from different character sets.@* If no charset is specified, @samp{iso-8859-15} (latin9) is assumed.@* Try @option{--charset=help} for a list of valid charset names. @anchor{--filter} @item -e @var{name} @itemx --filter=@var{name} Pass the output text through the built-in postprocessing filter given (@pxref{Filters}). Several filters can be applied in sequence using as many @option{--filter} and @option{--user-filter} options as needed. The filters are applied in the order they appear on the command line. Try @option{--filter=help} for a list of valid filter names. @anchor{--user-filter} @item -E @var{file} @itemx --user-filter=@var{file} Pass the output text through the postprocessing filter defined in @var{file}. @xref{Filters}, for a description of the format of @var{file}. Several filters can be applied in sequence using as many @option{--filter} and @option{--user-filter} options as needed. The filters are applied in the order they appear on the command line. @item -f @itemx --force Force overwrite of output files. @item -F @var{name} @itemx --format=@var{name} Select the output format. The valid names are @samp{byte} and @samp{utf8}.@* If no output format is specified, @samp{byte} (8 bit) is assumed. @item -i @itemx --invert Invert image levels (white on black). @item -l @itemx --layout Enable page layout analysis. Ocrad is able to separate blocks of text of arbitrary shape as long as they are clearly delimited by white space. @item -o @var{file} @itemx --output=@var{file} Place the output into @var{file} instead of into the standard output. Any missing parent directories are automatically created. @item -q @itemx --quiet Quiet operation. @item -s @var{value} @itemx --scale=@var{value} Scale up the input image by @var{value} before layout analysis and recognition. If @var{value} is negative, the input image is scaled down by @var{-value}. @item -t @var{name} @itemx --transform=@var{name} Perform the transformation given (rotation or mirroring) on the input image before scaling, layout analysis, and recognition. Rotations are made counter-clockwise.@* Try @option{--transform=help} for a list of valid transformation names. @item -T @var{value} @itemx --threshold=@var{value} Set binarization threshold for png, pgm, and ppm files or for option @option{--scale} (only for scaled down images). @var{value} should be a rational number between 0 and 1, and may be given as a percentage (50%), a fraction (1/2), or a decimal value (0.5). Pixel values greater than threshold are converted to white. The default value is 0.5. @item -u @var{left},@var{top},@var{width},@var{height} @itemx --cut=@var{left},@var{top},@var{width},@var{height} Cut the input image by the rectangle defined by @var{left}, @var{top}, @var{width}, and @var{height}. Values may be relative to the image size @w{(-1.0 <= value <= +1.0)}, or absolute @w{(abs( value ) > 1)}. Negative values of @var{left}, @var{top} are relative to the right-bottom corner of the image. Values of @var{width} and @var{height} must be positive. Absolute and relative values can be mixed. For example @w{@samp{ocrad --cut 700,960,1,1}} will extract from @samp{700,960} to the right-bottom corner of the image.@* The cutting is performed before any other transformation (rotation or mirroring) on the input image, and before scaling, layout analysis, and recognition. @item -v @itemx --verbose Verbose mode. @item -x @var{file} @itemx --export=@var{file} Write (export) OCR results file to @var{file} (@pxref{OCR results file}). @w{@option{-x -}} writes to stdout, overriding text output except if output has been also redirected with the option @option{-o}. @end table Exit status: 0 for a normal exit, 1 for environmental problems (file not found, invalid command-line options, I/O errors, etc), 2 to indicate a corrupt or invalid input file, 3 for an internal consistency error (e.g., bug) which caused ocrad to panic. @node Filters @chapter Postprocessing the text produced @cindex filters Filters replace some characters in the text output with different characters and remove some other characters from the output. For example, when recognizing a text that is known to contain just numbers, any character recognized as a @samp{Z} will probably be a @samp{2}. Filters don't enable the recognition of characters, just filter them from the output. Use @option{--charset} to enable the recognition of a character set different from the default ISO-8859-15. Ocrad provides both built-in filters and user-defined filters. @section User-defined filters The format of a user-defined filter file (@pxref{--user-filter}) is very simple. Each line contains either a character conversion or a word that specifies the default behaviour for unlisted characters. A character conversion is a comma-separated list of quoted characters ('c'), character sets ([0-9A-Z]), character codes (U0063), or character ranges (U0000 - UFFFF), and an optional conversion (an equal sign (=) followed by a quoted character or a character code). The characters in the list are converted to the character in the conversion. If no conversion is specified, the character is left unmodified (converted to itself). The default behaviour is to discard unlisted characters, i.e. those characters not appearing in the file, either by themselves or included in a set or range. If a line containing just the word @samp{leave} is found in the file, unlisted characters are left unmodified. If the word is @samp{mark}, unlisted characters are marked as unrecognized. The destination character of a conversion is considered as listed by default. Every character may be listed more than once, even as part of different conversions. The last conversion affecting a given character is the one that is performed. Character sets and quoted characters may contain escape sequences. The character @samp{#} at begin of line or after whitespace starts a comment that extends to the end of the line. Ranges of characters may be specified in character sets by writing the starting and ending characters with a @samp{-} between them. Thus, @samp{[A-Z]} matches any ASCII uppercase letter. @samp{-} may be specified by placing it first or last. @samp{]} may be specified by placing it first. If the first character after the left bracket is @samp{^}, it indicates a "complemented set", which matches any character except the ones between the brackets. Literals (quoted characters and character sets) are decoded as ISO-8859-15. Character codes are decoded as UCS2. Thus, a @samp{latin capital letter y with diaeresis} is specified in a set as @samp{[\xBE]}, but its code is @samp{U0178}. Spaces and control characters are unaffected by filters, except that leadind, trailing, and duplicate spaces produced by the removal of other characters will be themselves removed. @noindent Here is an example user-defined filter file equivalent to the built-in filter @samp{numbers}: @example leave # remove this line to get @samp{numbers_only} 'D', 'O', 'Q', 'o' = '0' 'I', 'L', 'l', '|' = '1' 'Z', 'z' = '2' '3' 'A', 'q' = '4' 'S', 's' = '5' 'G', 'b', U00F3 = '6' # U00F3 = latin small letter o with acute 'J', 'T' = '7' '&', 'B' = '8' 'g' = '9' @end example @section Built-in filters Ocrad provides the following built-in filters (@pxref{--filter}): @table @code @item --filter=letters Forces every character that resembles a letter to be recognized as a letter. Other characters will be output without change. @item --filter=letters_only Same as @option{--filter=letters}, but other characters will be discarded. @item --filter=numbers Forces every character that resembles a number to be recognized as a number. Other characters will be output without change. @item --filter=numbers_only Same as @option{--filter=numbers} but other characters will be discarded. @item --filter=same_height Discards any character (or noise) whose height differs in more than 10 percent from the median height of the characters in the line. @item --filter=text_block Discards any character (or noise) outside of a rectangular block of text lines. @item --filter=upper_num Forces every character that resembles a uppercase letter or a number to be recognized as such. Other characters will be output without change. @item --filter=upper_num_mark Same as @option{--filter=upper_num}, but other characters will be marked as unrecognized. @item --filter=upper_num_only Same as @option{--filter=upper_num}, but other characters will be discarded. @end table @node Library version @chapter Library version @cindex library version @defvr Constant OCRAD_API_VERSION This constant is defined in @samp{ocradlib.h} and works as a version test macro. The application should check at compile time that OCRAD_API_VERSION is equal to the version required by the application: @example #if !defined OCRAD_API_VERSION || OCRAD_API_VERSION != 28 #error "ocradlib 0.28 needed." #endif @end example Before version 0.28, ocradlib didn't define OCRAD_API_VERSION.@* OCRAD_API_VERSION is defined as (major * 1000 + minor). @end defvr NOTE: Version test macros are the library's way of announcing functionality to the application. They should not be confused with feature test macros, which allow the application to announce to the library its desire to have certain symbols and prototypes exposed. @deftypefun int OCRAD_api_version ( void ) If OCRAD_API_VERSION >= 28, this function is declared in @samp{ocradlib.h} (else it doesn't exist). It returns the OCRAD_API_VERSION of the library object code being used. The application should check at run time that the value returned by @code{OCRAD_api_version} is equal to the version required by the application. @example #if defined OCRAD_API_VERSION && OCRAD_API_VERSION >= 28 if( OCRAD_api_version() != 28 ) show_error( "ocradlib 0.28 needed." ); #endif @end example @end deftypefun @deftypevr Constant {const char *} OCRAD_version_string This string constant is defined in the header file @samp{ocradlib.h} and represents the version of the library being used at compile time. @end deftypevr @deftypefun {const char *} OCRAD_version ( void ) This function returns a string representing the version of the library being used at run time. @end deftypefun @node Library functions @chapter Library functions @cindex library functions These are the OCRAD library functions. In case of error, all of them return -1 or a null pointer, except @samp{OCRAD_open} whose return value must be checked by calling @samp{OCRAD_get_errno} before using it. @deftypefun {struct OCRAD_Descriptor *} OCRAD_open ( void ) Initialize the internal library state and return a pointer that can only be used as the @var{ocrdes} argument for the other OCRAD functions, or a null pointer if the descriptor could not be allocated. The pointer returned must be checked by calling @samp{OCRAD_get_errno} before using it. If @samp{OCRAD_get_errno} does not return @samp{OCRAD_ok}, the pointer returned must not be used and should be freed with @samp{OCRAD_close} to avoid memory leaks. @end deftypefun @deftypefun int OCRAD_close ( struct OCRAD_Descriptor * const @var{ocrdes} ) Free all dynamically allocated data structures for this descriptor. After a call to @samp{OCRAD_close}, @var{ocrdes} can no more be used as an argument to any OCRAD function. @end deftypefun @deftypefun {enum OCRAD_Errno} OCRAD_get_errno ( struct OCRAD_Descriptor * const @var{ocrdes} ) Return the current error code for @var{ocrdes}. @xref{Library error codes}. @end deftypefun @deftypefun int OCRAD_set_image ( struct OCRAD_Descriptor * const @var{ocrdes}, const struct OCRAD_Pixmap * const @var{image}, const bool @var{invert} ) Load @var{image} into the internal buffer. If @var{invert} is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. @end deftypefun @deftypefun int OCRAD_set_image_from_file ( struct OCRAD_Descriptor * const @var{ocrdes}, const char * const @var{filename}, const bool @var{invert} ) Load a image from the file @var{filename} into the internal buffer. If @var{invert} is true, image levels are inverted (white on black). Loading a new image deletes any previous text results. @end deftypefun @deftypefun int OCRAD_set_utf8_format ( struct OCRAD_Descriptor * const @var{ocrdes}, const bool @var{utf8} ) Set the output format to @samp{byte} (if @var{utf8}=false) or to @samp{utf8} (if @var{utf8}=true). By default ocrad produces @samp{byte} (8 bit) output. @end deftypefun @deftypefun int OCRAD_set_threshold ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{threshold} ) Set the binarization threshold for greymap and RGB images. @var{threshold} values between 0 and 255 set a fixed threshold. A value of -1 sets an automatic threshold. Pixel values greater than the resulting threshold are converted to white. The default threshold value if this function is not called is 127. @end deftypefun @deftypefun int OCRAD_scale ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{value} ) Scale up the image in the internal buffer by @var{value}. If @var{value} is negative, the image is scaled down by @var{-value}. @end deftypefun @deftypefun int OCRAD_recognize ( struct OCRAD_Descriptor * const @var{ocrdes}, const bool @var{layout} ) Recognize the image loaded in the internal buffer and produce text results which can be later retrieved with the functions @samp{OCRAD_result_*}. The same image can be recognized as many times as desired, for example setting a new threshold each time for 3D greymap recognition. Every time this function is called, the text results produced replace any previous ones. If @var{layout} is true, page layout analysis is enabled, probably producing more than one text block. @end deftypefun @deftypefun int OCRAD_result_blocks ( struct OCRAD_Descriptor * const @var{ocrdes} ) Return the number of text blocks found in the image, or 0 if no text was found. The value returned is usually 1, but can be larger if layout analysis was requested. @end deftypefun @deftypefun int OCRAD_result_lines ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum} ) Return the number of text lines contained in the text block given. @end deftypefun @deftypefun int OCRAD_result_chars_total ( struct OCRAD_Descriptor * const @var{ocrdes} ) Return the total number of text characters contained in the image recognized. @end deftypefun @deftypefun int OCRAD_result_chars_block ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum} ) Return the number of text characters contained in the text block given. @end deftypefun @deftypefun int OCRAD_result_chars_line ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum}, const int @var{linenum} ) Return the number of text characters contained in the text line given. @end deftypefun @deftypefun {const char *} OCRAD_result_line ( struct OCRAD_Descriptor * const @var{ocrdes}, const int @var{blocknum}, const int @var{linenum} ) Return the line of text specified by @var{blocknum} and @var{linenum}. @end deftypefun @deftypefun int OCRAD_result_first_character ( struct OCRAD_Descriptor * const @var{ocrdes} ) Return the byte result for the first character in the image. Return 0 if the image has no characters or if the first character could not be recognized. This function is a convenient short cut to the result for images containing a single character. @end deftypefun @node Library error codes @chapter Library error codes @cindex library error codes Most library functions return -1 or a null pointer to indicate that they have failed. But this return value only tells you that an error has occurred. To find out what kind of error it was, you need to check the error code by calling @samp{OCRAD_get_errno}. Library functions don't change the value returned by @samp{OCRAD_get_errno} when they succeed; thus, the value returned by @samp{OCRAD_get_errno} after a successful call is not necessarily OCRAD_ok, and you should not use @samp{OCRAD_get_errno} to determine whether a call failed. If the call failed, then you can examine @samp{OCRAD_get_errno}. The error codes are defined in the header file @samp{ocradlib.h}. @deftypevr Constant {enum OCRAD_Errno} OCRAD_ok The value of this constant is 0 and is used to indicate that there is no error. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_bad_argument At least one of the arguments passed to the library function was invalid. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_mem_error No memory available. The system cannot allocate more virtual memory because its capacity is full. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_sequence_error A library function was called in the wrong order. For example @samp{OCRAD_result_line} was called before @samp{OCRAD_recognize}. @end deftypevr @deftypevr Constant {enum OCRAD_Errno} OCRAD_library_error A bug was detected in the library. Please, report it. @xref{Problems}. @end deftypevr @node Image format conversion @chapter Image format conversion @cindex image format conversion There are a lot of image formats, but ocrad is able to decode only four of them; png, pbm, pgm, and ppm. In this chapter you will find command examples and advice about how to convert image files to a format that ocrad can manage. @table @samp @item .ps @itemx .pdf Postscript or Portable Document Format file. Use the command @w{@samp{gs -sPAPERSIZE=a4 -sDEVICE=pnmraw -r300 -dNOPAUSE -dBATCH -sOutputFile=- -q file.ps | ocrad}}.@* You may also use the command @w{@samp{pstopnm -stdout -dpi=300 -pgm file.ps | ocrad}}, but it seems not to work with pdf files. Also old versions of @samp{pstopnm} don't recognize the option @option{-dpi} and produce an image too small for OCR. @item .tiff TIFF file. Use the command @w{@samp{tifftopnm file.tiff | ocrad}}. @item .jpg JPEG file. Use the command @w{@samp{djpeg -greyscale -pnm file.jpg | ocrad}}.@* JPEG is a lossy format and is in general not recommended for text images. @item .pnm.gz Pnm file compressed with gzip. Use the command @w{@samp{gzip -cd file.pnm.gz | ocrad}}. @item .pnm.lz Pnm file compressed with @uref{http://www.nongnu.org/lzip/lzip.html,,lzip}. Use the command @w{@samp{lzip -cd file.pnm.lz | ocrad}}. @end table @node Algorithm @chapter Algorithm @cindex algorithm Ocrad is mainly a research project. Many of the algorithms ocrad uses are ad hoc, and will change in successive releases as I myself gain understanding about OCR issues. The overall working of ocrad may be described as follows:@* 1) Read the image.@* 2) Optionally, perform some transformations (cut, rotate, scale, etc).@* 3) Optionally, perform layout detection.@* 4) Remove frames and pictures.@* 5) Detect characters and group them in lines.@* 6) Recognize characters (very ad hoc; one algorithm per character).@* 7) Correct some ambiguities (transform l.OOO into 1.000, etc).@* 8) Optionally, apply one or more filters to the text.@* 9) Output text result. @sp 1 Ocrad recognizes characters by its shape, and the reason it is so fast is that it does not compare the shape of every character against some sort of database of shapes and then chooses the best match. Instead of this, ocrad only compares the shape differences that are relevant to choose between two character categories, mostly like a binary search. As there is no such thing as a free lunch, this approach has some drawbacks. It makes ocrad very sensitive to character defects, and makes difficult to modify ocrad to recognize new characters. @node OCR results file @chapter OCR results file @cindex OCR results file Calling ocrad with option @option{-x} produces an OCR results file (ORF), that is, a parsable file containing the OCR results. The ORF format is as follows: @itemize @minus @item Any line beginning with @samp{#} is a comment line. @item The first non-comment line has the form @w{@samp{source file @var{filename}}}, where @var{filename} is the name of the file being processed (@samp{-} for standard input). This is the only line guaranteed to exist for every input file read without errors. If the file, or any block or line, has no text, the corresponding part in the ORF file will be missing. @item The second non-comment line has the form @w{@samp{total text blocks @var{n}}}, where @var{n} is the total number of text blocks in the source image. @end itemize @noindent For each text block in the source image, the following data follows: @itemize @minus @item A line in the form @w{@samp{text block @var{i} @var{x y w h}}}. Where @var{i} is the block number and @var{x y w h} are the block position and size as described below for character boxes. @item A line in the form @samp{lines @var{n}}. Where @var{n} is the number of lines in this block. @end itemize @noindent For each line in every text block, the following data follows: @itemize @minus @item A line in the form @samp{line @var{i} chars @var{n} height @var{h}}, where @var{i} is the line number, @var{n} is the number of characters in this line, and @var{h} is the mean height of the characters in this line (in pixels). @item N lines (one for every character) in the form @w{@samp{@var{x} @var{y} @var{w} @var{h}; @var{g}[, '@var{c}'@var{v}]...}}, where:@* @var{x} is the left border (x-coordinate) of the char bounding box in the source image (in pixels).@* @var{y} is the top border (y-coordinate).@* @var{w} is the width of the bounding box.@* @var{h} is the height of the bounding box.@* @var{g} is the number of different recognition guesses for this character.@* The result characters follow after the number of guesses in the form of a comma-separated list of pairs. Every pair is formed by the actual recognised char @var{c} enclosed in single quotes, followed by the confidence value @var{v}, without space between them. The higher the value of confidence, the more confident is the result. @end itemize Running @samp{./ocrad -x test.orf testsuite/test.pbm} in the source directory will give you an example ORF file. @node Problems @chapter Reporting bugs @cindex bugs @cindex getting help There are probably bugs in ocrad. There are certainly errors and omissions in this manual. If you report them, they will get fixed. If you don't, no one will ever know about them and they will remain unfixed for all eternity, if not longer. If you find a bug in GNU Ocrad, please send electronic mail to @email{bug-ocrad@@gnu.org}. Include the version number, which you can find by running @w{@samp{ocrad --version}}. @node Concept index @unnumbered Concept index @printindex cp @bye