/* GNU Ocrad - Optical Character Recognition program Copyright (C) 2003, 2004, 2005, 2006, 2007 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 3 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 "rational.h" #include "rectangle.h" #include "track.h" #include "page_image.h" void Page_image::find_columns( const Rectangle & rin, bool recursive ) throw() { if( !this->includes( rin ) ) return; const int colmin = ( width() > 100 ) ? width() / 10 : 10; const int gapmin = ( width() > 300 ) ? width() / 100 : 3; const int min_width = ( 2 * colmin ) + gapmin; if( rin.width() < min_width ) { rv.push_back( rin ); return; } const unsigned int rv_size_orig = rv.size(); const int ldiff = rin.left() - left(); const int tdiff = rin.top() - top(); std::vector< int > h_outline( rin.width(), 0 ); for( int row = 0; row < rin.height(); ++row ) { const std::vector< unsigned char > & datarow = data[row+tdiff]; for( int col = 0; col < rin.width(); ++col ) if( datarow[col+ldiff] == 0 ) ++h_outline[col]; } int total_dots = 0; for( int col = 0; col < rin.width(); ++col ) total_dots += h_outline[col]; if( 10 * total_dots > 8 * rin.size() ) return; // eliminates images const int threshold_col = std::max( total_dots / ( rin.width() * 4 ), 10 ); const int threshold_gap = std::max( total_dots / ( rin.width() * 20 ), 1 ); int ileft = 0, iright = rin.width() - 1; while( ileft < iright && h_outline[ileft] <= 0 ) ++ileft; // cut left border if( rin.left() == left() && 10 * ileft < iright ) // cut left border noise { int l = ileft + gapmin; while( l < iright && h_outline[l] <= 0 ) ++l; if( l > ileft + ( 2 * gapmin ) ) ileft = l; } ileft = std::max( 0, ileft - gapmin ); while( iright > ileft && h_outline[iright] <= 0 ) --iright; // cut right border iright = std::min( iright + gapmin, rin.width() - 1 ); while( iright - ileft >= 2 * colmin ) { int l, r; for( l = r = ileft; r < iright; ++r ) // look for text if( h_outline[r] < threshold_col ) { if( r - l >= colmin ) break; else l = r; } if( r - l < colmin ) break; for( l = r; r < iright; ++r ) // look for gap if( h_outline[r] > threshold_gap ) { if( r - l >= gapmin ) break; else l = r; } if( r - l < gapmin ) break; if( r < iright ) // cut by a minimum near the center { int mid = ( r + l ) / 2, half = ( r - l ) / 2; r = mid; for( int i = 1; i <= half && h_outline[r] > 0; ++i ) { if( h_outline[mid+i] < h_outline[r] ) r = mid + i; if( h_outline[mid-i] < h_outline[r] ) r = mid - i; } } Rectangle re( rin.left() + ileft, rin.top(), rin.left() + r, rin.bottom() ); if( recursive ) find_rows( re, re.width() >= min_width ); else rv.push_back( re ); ileft = r; } if( iright - ileft > gapmin ) { Rectangle re( rin.left() + ileft, rin.top(), rin.left() + iright, rin.bottom() ); if( recursive && ( rv.size() > rv_size_orig || rv.size() == 0 ) ) find_rows( re, re.width() >= min_width ); else rv.push_back( re ); } } void Page_image::find_rows( const Rectangle & rin, bool recursive ) throw() { if( !this->includes( rin ) ) return; const int rowmin = ( height() > 100 ) ? height() / 10 : 10; const int gapmin = ( height() > 300 ) ? height() / 100 : 3; const int min_height = ( 2 * rowmin ) + gapmin; if( rin.height() < min_height ) { rv.push_back( rin ); return; } const unsigned int rv_size_orig = rv.size(); const int ldiff = rin.left() - left(); const int tdiff = rin.top() - top(); std::vector< int > v_outline( rin.height(), 0 ); int total_dots = 0; for( int row = 0; row < rin.height(); ++row ) { const std::vector< unsigned char > & datarow = data[row+tdiff]; for( int col = 0; col < rin.width(); ++col ) if( datarow[col+ldiff] == 0 ) ++v_outline[row]; total_dots += v_outline[row]; } if( 10 * total_dots > 8 * rin.size() ) return; // eliminates images const int threshold_gap = ( total_dots / ( rin.height() * 20 ) ) + 1; int itop = 0, ibottom = rin.height() - 1; while( itop < ibottom && v_outline[itop] <= 0 ) ++itop; // cut top border itop = std::max( 0, itop - gapmin ); while( ibottom > itop && v_outline[ibottom] <= 0 ) --ibottom; // cut bottom border ibottom = std::min( ibottom + gapmin, rin.height() - 1 ); while( ibottom - itop >= min_height ) { int t, b; // top and bottom of gap for( t = b = itop + gapmin; t < ibottom - gapmin; ++t ) if( v_outline[t] < threshold_gap ) { for( b = t + 1; b < ibottom && v_outline[b] < threshold_gap; ++b ); if( b - t >= gapmin ) break; else t = b; } if( b - t < gapmin ) break; if( b < ibottom ) // cut by a minimum near the center { const int mid = ( b + t ) / 2, half = ( b - t ) / 2; b = mid; for( int i = 1; i <= half && v_outline[b] > 0; ++i ) { if( v_outline[mid+i] < v_outline[b] ) b = mid + i; if( v_outline[mid-i] < v_outline[b] ) b = mid - i; } } Rectangle re( rin.left(), rin.top() + itop, rin.right(), rin.top() + b ); if( recursive ) find_columns( re, re.height() >= min_height && ibottom - b > gapmin ); else rv.push_back( re ); itop = b; } if( ibottom - itop > gapmin ) { Rectangle re( rin.left(), rin.top() + itop, rin.right(), rin.top() + ibottom ); if( recursive && rv.size() > rv_size_orig ) find_columns( re, re.height() >= min_height ); else rv.push_back( re ); } } // Creates a reduced Page_image // Page_image::Page_image( const Page_image & source, const int scale ) throw() : Rectangle( source ), _maxval( source._maxval ), _threshold( source._threshold ) { if( scale < 2 || scale > source.width() || scale > source.height() ) Ocrad::internal_error( "bad parameter building a reduced Page_image" ); const int scale2 = scale * scale; Rectangle::height( source.height() / scale ); Rectangle::width( source.width() / scale ); data.resize( height() ); for( int row = 0; row < height(); ++row ) { const int srow = ( row * scale ) + scale; data[row].reserve( width() ); std::vector< unsigned char > & datarow = data[row]; for( int col = 0; col < width(); ++col ) { const int scol = ( col * scale ) + scale; int sum = 0; for( int i = srow - scale; i < srow; ++i ) { const std::vector< unsigned char > & sdatarow = source.data[i]; for( int j = scol - scale; j < scol; ++j ) sum += sdatarow[j]; } datarow.push_back( sum / scale2 ); } } } // Creates a reduced, b/w Page_image // Page_image::Page_image( const Page_image & source, const int scale, const Rational & th ) throw() : Rectangle( source ), _maxval( 1 ), _threshold( 0 ) { if( scale < 2 || scale > source.width() || scale > source.height() ) Ocrad::internal_error( "bad parameter building a reduced Page_image" ); const int ith = ( th >= 0 && th <= 1 ) ? ( th * ( scale * scale ) ).trunc() : ( ( scale * scale ) - 1 ) / 2; Rectangle::height( source.height() / scale ); Rectangle::width( source.width() / scale ); data.resize( height() ); for( int row = 0; row < height(); ++row ) { const int srow = ( row * scale ) + scale; data[row].reserve( width() ); std::vector< unsigned char > & datarow = data[row]; for( int col = 0; col < width(); ++col ) { const int scol = ( col * scale ) + scale; int counter = 0; for( int i = srow - scale; i < srow; ++i ) { const std::vector< unsigned char > & sdatarow = source.data[i]; for( int j = scol - scale; j < scol; ++j ) if( sdatarow[j] <= source._threshold && ++counter > ith ) goto L1; } L1: datarow.push_back( ( counter > ith ) ? 0 : 1 ); } } } int Page_image::analyse_layout( const int layout_level ) throw() { rv.clear(); if( layout_level >= 1 && layout_level <= 2 && left() == 0 && top() == 0 && width() > 200 && height() > 200 ) { Page_image reduced( *this, 8, Rational( 7, 64 ) ); reduced.find_columns( reduced, layout_level >= 2 ); rv = reduced.rv; for( unsigned int i = 0; i < rv.size(); ++i ) rv[i].enlarge( 8 ); } if( rv.size() == 0 ) rv.push_back( *this ); tv.clear(); tv.insert( tv.end(), rv.size(), _threshold ); return rv.size(); } void Page_image::draw_rectangle( const Rectangle & re ) throw() { int l = std::max( left(), re.left() ); int t = std::max( top(), re.top() ); int r = std::min( right(), re.right() ); int b = std::min( bottom(), 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 ) throw() { int l = std::max( left(), tr.left() ); int r = std::min( right(), tr.right() ); if( l == tr.left() ) for( int row = tr.top( l ); row <= tr.bottom( l ); ++row ) if( row >= top() && row <= bottom() ) set_bit( row, l, true ); if( r == tr.right() ) for( int row = tr.top( r ); row <= tr.bottom( r ); ++row ) if( row >= top() && row <= bottom() ) set_bit( row, r, true ); for( int col = l; col <= r; ++col ) { int row = tr.top( col ); if( row >= top() && row <= bottom() ) set_bit( row, col, true ); row = tr.bottom( col ); if( row >= top() && row <= bottom() ) set_bit( row, col, true ); } }