/* 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
#include "common.h"
#include "rectangle.h"
#include "bitmap.h"
// Creates 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 );
}
// Creates a Bitmap from part of another Bitmap
Bitmap::Bitmap( const Bitmap & source, const Rectangle & re )
: Rectangle( re ), data( re.height() )
{
if( !source.includes( re ) )
Ocrad::internal_error( "bad parameter building a Bitmap from part of another one." );
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() );
}
// Returns 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;
}
// Returns 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;
}
// Returns 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;
}
// Returns 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;
}
// Returns 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;
}
// Looks for an inverted-U-shaped curve near the top, then tests 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;
}
// Looks for an U-shaped curve near the bottom, then tests 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;
}