/* GNU Ocrad - Optical Character Recognition program
Copyright (C) 2003-2019 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 ) Ocrad::internal_error( "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 ) Ocrad::internal_error( "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() )
Ocrad::internal_error( "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() )
Ocrad::internal_error( "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 ); }
}
*/
}