tesseract/ccmain/charcut.cpp

711 lines
23 KiB
C++
Raw Normal View History

/**********************************************************************
* File: charcut.cpp (Formerly charclip.c)
* Description: Code for character clipping
* Author: Phil Cheatle
* Created: Wed Nov 11 08:35:15 GMT 1992
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
#include "charcut.h"
#include "imgs.h"
#include "showim.h"
#include "evnts.h"
#include "notdll.h"
#define LARGEST(a,b) ( (a) > (b) ? (a) : (b) )
#define SMALLEST(a,b) ( (a) > (b) ? (b) : (a) )
#define BUG_OFFSET 1
#define EXTERN
EXTERN INT_VAR (pix_word_margin, 3, "How far outside word BB to grow");
extern IMAGE page_image;
ELISTIZE (PIXROW)
/*************************************************************************
* PIXROW::PIXROW()
*
* Constructor for a specified size PIXROW from a blob
*************************************************************************/
PIXROW::PIXROW(INT16 pos, INT16 count, PBLOB *blob) {
OUTLINE_LIST *outline_list;
OUTLINE_IT outline_it;
POLYPT_LIST *pts_list;
POLYPT_IT pts_it;
INT16 i;
FCOORD pt;
FCOORD vec;
float y_coord;
INT16 x_coord;
row_offset = pos;
row_count = count;
min = (INT16 *) alloc_mem (count * sizeof (INT16));
max = (INT16 *) alloc_mem (count * sizeof (INT16));
outline_list = blob->out_list ();
outline_it.set_to_list (outline_list);
for (i = 0; i < count; i++) {
min[i] = MAX_INT16 - 1;
max[i] = -MAX_INT16 + 1;
y_coord = row_offset + i + 0.5;
for (outline_it.mark_cycle_pt ();
!outline_it.cycled_list (); outline_it.forward ()) {
pts_list = outline_it.data ()->polypts ();
pts_it.set_to_list (pts_list);
for (pts_it.mark_cycle_pt ();
!pts_it.cycled_list (); pts_it.forward ()) {
pt = pts_it.data ()->pos;
vec = pts_it.data ()->vec;
if ((vec.y () != 0) &&
(((pt.y () <= y_coord) && (pt.y () + vec.y () >= y_coord))
|| ((pt.y () >= y_coord)
&& (pt.y () + vec.y () <= y_coord)))) {
/* The segment crosses y_coord so find x-point and check for min/max. */
x_coord = (INT16) floor ((y_coord -
pt.y ()) * vec.x () / vec.y () +
pt.x () + 0.5);
if (x_coord < min[i])
min[i] = x_coord;
x_coord--; //to get pix to left of line
if (x_coord > max[i])
max[i] = x_coord;
}
}
}
}
}
/*************************************************************************
* PIXROW::plot()
*
* Draw the PIXROW
*************************************************************************/
#ifndef GRAPHICS_DISABLED
void PIXROW::plot(WINDOW fd //where to paint
) const {
INT16 i;
INT16 y_coord;
for (i = 0; i < row_count; i++) {
y_coord = row_offset + i;
if (min[i] <= max[i]) {
rectangle (fd, min[i], y_coord, max[i] + 1, y_coord + 1);
}
}
}
#endif
/*************************************************************************
* PIXROW::bounding_box()
*
* Generate bounding box for blob image
*************************************************************************/
bool PIXROW::bad_box( //return true if box exceeds image
int xsize,
int ysize) const {
BOX bbox = bounding_box ();
if (bbox.left () < 0 || bbox.right () > xsize
|| bbox.top () > ysize || bbox.bottom () < 0) {
tprintf("Box (%d,%d)->(%d,%d) bad compared to %d,%d\n",
bbox.left(),bbox.bottom(), bbox.right(), bbox.top(),
xsize, ysize);
return true;
}
return false;
}
/*************************************************************************
* PIXROW::bounding_box()
*
* Generate bounding box for blob image
*************************************************************************/
BOX PIXROW::bounding_box() const {
INT16 i;
INT16 y_coord;
INT16 min_x = MAX_INT16 - 1;
INT16 min_y = MAX_INT16 - 1;
INT16 max_x = -MAX_INT16 + 1;
INT16 max_y = -MAX_INT16 + 1;
for (i = 0; i < row_count; i++) {
y_coord = row_offset + i;
if (min[i] <= max[i]) {
if (y_coord < min_y)
min_y = y_coord;
if (y_coord + 1 > max_y)
max_y = y_coord + 1;
if (min[i] < min_x)
min_x = min[i];
if (max[i] + 1 > max_x)
max_x = max[i] + 1;
}
}
if (min_x > max_x || min_y > max_y)
return BOX ();
else
return BOX (ICOORD (min_x, min_y), ICOORD (max_x, max_y));
}
/*************************************************************************
* PIXROW::contract()
*
* Reduce the mins and maxs so that they end on black pixels
*************************************************************************/
void PIXROW::contract( //image array
IMAGELINE *imlines,
INT16 x_offset, //of pixels[0]
INT16 foreground_colour //0 or 1
) {
INT16 i;
UINT8 *line_pixels;
for (i = 0; i < row_count; i++) {
if (min[i] > max[i])
continue;
line_pixels = imlines[i].pixels;
while (line_pixels[min[i] - x_offset] != foreground_colour) {
if (min[i] == max[i]) {
min[i] = MAX_INT16 - 1;
max[i] = -MAX_INT16 + 1;
goto nextline;
}
else
min[i]++;
}
while (line_pixels[max[i] - x_offset] != foreground_colour) {
if (min[i] == max[i]) {
min[i] = MAX_INT16 - 1;
max[i] = -MAX_INT16 + 1;
goto nextline;
}
else
max[i]--;
}
nextline:;
//goto label!
}
}
/*************************************************************************
* PIXROW::extend()
*
* 1 pixel extension in each direction to cover extra black area
*************************************************************************/
BOOL8 PIXROW::extend( //image array
IMAGELINE *imlines,
BOX &imbox,
PIXROW *prev, //for prev blob
PIXROW *next, //for next blob
INT16 foreground_colour) {
INT16 i;
INT16 x_offset = imbox.left ();
INT16 limit;
INT16 left_limit;
INT16 right_limit;
UINT8 *pixels = NULL;
UINT8 *pixels_below = NULL; //row below current
UINT8 *pixels_above = NULL; //row above current
BOOL8 changed = FALSE;
pixels_above = imlines[0].pixels;
for (i = 0; i < row_count; i++) {
pixels_below = pixels;
pixels = pixels_above;
if (i < (row_count - 1))
pixels_above = imlines[i + 1].pixels;
else
pixels_above = NULL;
/* Extend Left by one pixel*/
if (prev == NULL || prev->max[i] < prev->min[i])
limit = imbox.left ();
else
limit = prev->max[i] + 1;
if ((min[i] <= max[i]) &&
(min[i] > limit) &&
(pixels[min[i] - 1 - x_offset] == foreground_colour)) {
min[i]--;
changed = TRUE;
}
/* Extend Right by one pixel*/
if (next == NULL || next->min[i] > next->max[i])
limit = imbox.right () - 1;//-1 to index inside pix
else
limit = next->min[i] - 1;
if ((min[i] <= max[i]) &&
(max[i] < limit) &&
(pixels[max[i] + 1 - x_offset] == foreground_colour)) {
max[i]++;
changed = TRUE;
}
/* Extend down by one row */
if (pixels_below != NULL) {
if (min[i] < min[i - 1]) { //row goes left of row below
if (prev == NULL || prev->max[i - 1] < prev->min[i - 1])
left_limit = min[i];
else
left_limit = LARGEST (min[i], prev->max[i - 1] + 1);
}
else
left_limit = min[i - 1];
if (max[i] > max[i - 1]) { //row goes right of row below
if (next == NULL || next->min[i - 1] > next->max[i - 1])
right_limit = max[i];
else
right_limit = SMALLEST (max[i], next->min[i - 1] - 1);
}
else
right_limit = max[i - 1];
while ((left_limit <= right_limit) &&
(pixels_below[left_limit - x_offset] != foreground_colour))
left_limit++; //find black extremity
if ((left_limit <= right_limit) && (left_limit < min[i - 1])) {
min[i - 1] = left_limit; //widen left if poss
changed = TRUE;
}
while ((left_limit <= right_limit) &&
(pixels_below[right_limit - x_offset] != foreground_colour))
right_limit--; //find black extremity
if ((left_limit <= right_limit) && (right_limit > max[i - 1])) {
max[i - 1] = right_limit;//widen right if poss
changed = TRUE;
}
}
/* Extend up by one row */
if (pixels_above != NULL) {
if (min[i] < min[i + 1]) { //row goes left of row above
if (prev == NULL || prev->min[i + 1] > prev->max[i + 1])
left_limit = min[i];
else
left_limit = LARGEST (min[i], prev->max[i + 1] + 1);
}
else
left_limit = min[i + 1];
if (max[i] > max[i + 1]) { //row goes right of row above
if (next == NULL || next->min[i + 1] > next->max[i + 1])
right_limit = max[i];
else
right_limit = SMALLEST (max[i], next->min[i + 1] - 1);
}
else
right_limit = max[i + 1];
while ((left_limit <= right_limit) &&
(pixels_above[left_limit - x_offset] != foreground_colour))
left_limit++; //find black extremity
if ((left_limit <= right_limit) && (left_limit < min[i + 1])) {
min[i + 1] = left_limit; //widen left if poss
changed = TRUE;
}
while ((left_limit <= right_limit) &&
(pixels_above[right_limit - x_offset] != foreground_colour))
right_limit--; //find black extremity
if ((left_limit <= right_limit) && (right_limit > max[i + 1])) {
max[i + 1] = right_limit;//widen right if poss
changed = TRUE;
}
}
}
return changed;
}
/*************************************************************************
* PIXROW::char_clip_image()
* Cut out a sub image for a character
*************************************************************************/
void PIXROW::char_clip_image( //box of imlines extnt
IMAGELINE *imlines,
BOX &im_box,
ROW *row, //row containing word
IMAGE &clip_image, //unscaled sq subimage
float &baseline_pos //baseline ht in image
) {
INT16 clip_image_xsize; //sub image x size
INT16 clip_image_ysize; //sub image y size
INT16 x_shift; //from pixrow to subim
INT16 y_shift; //from pixrow to subim
BOX char_pix_box; //bbox of char pixels
INT16 y_dest;
INT16 x_min;
INT16 x_max;
INT16 x_min_dest;
INT16 x_max_dest;
INT16 x_width;
INT16 y;
clip_image_xsize = clip_image.get_xsize ();
clip_image_ysize = clip_image.get_ysize ();
char_pix_box = bounding_box ();
/*
The y shift is calculated by first finding the coord of the bottom of the
image relative to the image lines. Then reducing this so by the amount
relative to the clip image size, necessary to vertically position the
character.
*/
y_shift = char_pix_box.bottom () - row_offset -
(INT16) floor ((clip_image_ysize - char_pix_box.height () + 0.5) / 2);
/*
The x_shift is the shift to be applied to the page coord in the pixrow to
generate a centred char in the clip image. Thus the left hand edge of the
char is shifted to the margin width of the centred character.
*/
x_shift = char_pix_box.left () -
(INT16) floor ((clip_image_xsize - char_pix_box.width () + 0.5) / 2);
for (y = 0; y < row_count; y++) {
/*
Check that there is something in this row of the source that will fit in the
sub image. If there is, reduce x range if necessary, then copy it
*/
y_dest = y - y_shift;
if ((min[y] <= max[y]) && (y_dest >= 0) && (y_dest < clip_image_ysize)) {
x_min = min[y];
x_min_dest = x_min - x_shift;
if (x_min_dest < 0) {
x_min = x_min - x_min_dest;
x_min_dest = 0;
}
x_max = max[y];
x_max_dest = x_max - x_shift;
if (x_max_dest > clip_image_xsize - 1) {
x_max = x_max - (x_max_dest - (clip_image_xsize - 1));
x_max_dest = clip_image_xsize - 1;
}
x_width = x_max - x_min + 1;
if (x_width > 0) {
x_min -= im_box.left ();
//offset pixel ptr
imlines[y].pixels += x_min;
clip_image.put_line (x_min_dest, y_dest, x_width, imlines + y,
0);
imlines[y].init (); //reset pixel ptr
}
}
}
/*
Baseline position relative to clip image: First find the baseline relative
to the page origin at the x coord of the centre of the character. Then
make this relative to the character bottom. Finally shift by the margin
between the bottom of the character and the bottom of the clip image.
*/
if (row == NULL)
baseline_pos = 0; //Not needed
else
baseline_pos = row->base_line ((char_pix_box.left () +
char_pix_box.right ()) / 2.0)
- char_pix_box.bottom ()
+ ((clip_image_ysize - char_pix_box.height ()) / 2);
}
/*************************************************************************
* char_clip_word()
*
* Generate a PIXROW_LIST with one element for each blob in the word, together
* with the image lines for the whole word.
*************************************************************************/
void char_clip_word( //
WERD *word, //word to be processed
IMAGE &bin_image, //whole image
PIXROW_LIST *&pixrow_list, //pixrows built
IMAGELINE *&imlines, //lines cut from image
BOX &pix_box //box defining imlines
) {
BOX word_box = word->bounding_box ();
PBLOB_LIST *blob_list;
PBLOB_IT blob_it;
PIXROW_IT pixrow_it;
INT16 pix_offset; //Y pos of pixrow[0]
INT16 row_height; //No of pix rows
INT16 imlines_x_offset;
PIXROW *prev;
PIXROW *next;
PIXROW *current;
BOOL8 changed; //still improving
BOOL8 just_changed; //still improving
INT16 iteration_count = 0;
INT16 foreground_colour;
if (word->flag (W_INVERSE))
foreground_colour = 1;
else
foreground_colour = 0;
/* Define region for max pixrow expansion */
pix_box = word_box;
pix_box.move_bottom_edge (-pix_word_margin);
pix_box.move_top_edge (pix_word_margin);
pix_box.move_left_edge (-pix_word_margin);
pix_box.move_right_edge (pix_word_margin);
pix_box -= BOX (ICOORD (0, 0 + BUG_OFFSET),
ICOORD (bin_image.get_xsize (),
bin_image.get_ysize () - BUG_OFFSET));
/* Generate pixrows list */
pix_offset = pix_box.bottom ();
row_height = pix_box.height ();
blob_list = word->blob_list ();
blob_it.set_to_list (blob_list);
pixrow_list = new PIXROW_LIST;
pixrow_it.set_to_list (pixrow_list);
for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
PIXROW *row = new PIXROW (pix_offset, row_height, blob_it.data ());
ASSERT_HOST (!row->
bad_box (bin_image.get_xsize (), bin_image.get_ysize ()));
pixrow_it.add_after_then_move (row);
}
imlines = generate_imlines (bin_image, pix_box);
/* Contract pixrows - shrink min and max back to black pixels */
imlines_x_offset = pix_box.left ();
pixrow_it.move_to_first ();
for (pixrow_it.mark_cycle_pt ();
!pixrow_it.cycled_list (); pixrow_it.forward ()) {
ASSERT_HOST (!pixrow_it.data ()->
bad_box (bin_image.get_xsize (), bin_image.get_ysize ()));
pixrow_it.data ()->contract (imlines, imlines_x_offset,
foreground_colour);
ASSERT_HOST (!pixrow_it.data ()->
bad_box (bin_image.get_xsize (), bin_image.get_ysize ()));
}
/* Expand pixrows iteratively 1 pixel at a time */
do {
changed = FALSE;
pixrow_it.move_to_first ();
prev = NULL;
current = NULL;
next = pixrow_it.data ();
for (pixrow_it.mark_cycle_pt ();
!pixrow_it.cycled_list (); pixrow_it.forward ()) {
prev = current;
current = next;
if (pixrow_it.at_last ())
next = NULL;
else
next = pixrow_it.data_relative (1);
just_changed = current->extend (imlines, pix_box, prev, next,
foreground_colour);
ASSERT_HOST (!current->
bad_box (bin_image.get_xsize (),
bin_image.get_ysize ()));
changed = changed || just_changed;
}
iteration_count++;
}
while (changed);
}
/*************************************************************************
* generate_imlines()
* Get an array of IMAGELINES holding a portion of an image
*************************************************************************/
IMAGELINE *generate_imlines( //get some imagelines
IMAGE &bin_image, //from here
BOX &pix_box) {
IMAGELINE *imlines; //array of lines
int i;
imlines = new IMAGELINE[pix_box.height ()];
for (i = 0; i < pix_box.height (); i++) {
imlines[i].init (pix_box.width ());
//coord to start at
bin_image.fast_get_line (pix_box.left (),
pix_box.bottom () + i + BUG_OFFSET,
//line to get
pix_box.width (), //width to get
imlines + i); //dest imline
}
return imlines;
}
/*************************************************************************
* display_clip_image()
* All the boring user interface bits to let you see what's going on
*************************************************************************/
#ifndef GRAPHICS_DISABLED
WINDOW display_clip_image(WERD *word, //word to be processed
IMAGE &bin_image, //whole image
PIXROW_LIST *pixrow_list, //pixrows built
BOX &pix_box //box of subimage
) {
WINDOW clip_window; //window for debug
BOX word_box = word->bounding_box ();
int border = word_box.height () / 2;
BOX display_box = word_box;
display_box.move_bottom_edge (-border);
display_box.move_top_edge (border);
display_box.move_left_edge (-border);
display_box.move_right_edge (border);
display_box -= BOX (ICOORD (0, 0 - BUG_OFFSET),
ICOORD (bin_image.get_xsize (),
bin_image.get_ysize () - BUG_OFFSET));
pgeditor_msg ("Creating Clip window...");
clip_window =
create_window ("Clipped Blobs",
SCROLLINGWIN,
editor_word_xpos, editor_word_ypos,
3 * (word_box.width () + 2 * border),
3 * (word_box.height () + 2 * border),
//window width,height
// xmin, xmax
display_box.left (), display_box.right (),
display_box.bottom () - BUG_OFFSET,
display_box.top () - BUG_OFFSET,
// ymin, ymax
TRUE, FALSE, FALSE, TRUE); // down event & key only
pgeditor_msg ("Creating Clip window...Done");
clear_view_surface(clip_window);
show_sub_image (&bin_image,
display_box.left (),
display_box.bottom (),
display_box.width (),
display_box.height (),
clip_window,
display_box.left (), display_box.bottom () - BUG_OFFSET);
word->plot (clip_window, RED);
word_box.plot (clip_window, INT_HOLLOW, TRUE, BLUE, BLUE);
pix_box.plot (clip_window, INT_HOLLOW, TRUE, BLUE, BLUE);
plot_pixrows(pixrow_list, clip_window);
overlap_picture_ops(TRUE);
return clip_window;
}
/*************************************************************************
* display_images()
* Show a pair of clip and scaled character images and wait for key before
* continuing.
*************************************************************************/
void display_images(IMAGE &clip_image, IMAGE &scaled_image) {
WINDOW clip_im_window; //window for debug
WINDOW scale_im_window; //window for debug
INT16 i;
GRAPHICS_EVENT event; // c;
// xmin xmax ymin ymax
clip_im_window = create_window ("Clipped Blob", SCROLLINGWIN, editor_word_xpos - 20, editor_word_ypos - 100, 5 * clip_image.get_xsize (), 5 * clip_image.get_ysize (), 0, clip_image.get_xsize (), 0, clip_image.get_ysize (),
TRUE, FALSE, FALSE, TRUE); // down event & key only
clear_view_surface(clip_im_window);
show_sub_image (&clip_image,
0, 0,
clip_image.get_xsize (), clip_image.get_ysize (),
clip_im_window, 0, 0);
line_color_index(clip_im_window, RED);
for (i = 1; i < clip_image.get_xsize (); i++) {
move2d (clip_im_window, i, 0);
draw2d (clip_im_window, i, clip_image.get_xsize ());
}
for (i = 1; i < clip_image.get_ysize (); i++) {
move2d (clip_im_window, 0, i);
draw2d (clip_im_window, clip_image.get_xsize (), i);
}
// xmin xmax ymin ymax
scale_im_window = create_window ("Scaled Blob", SCROLLINGWIN, editor_word_xpos + 300, editor_word_ypos - 100, 5 * scaled_image.get_xsize (), 5 * scaled_image.get_ysize (), 0, scaled_image.get_xsize (), 0, scaled_image.get_ysize (),
TRUE, FALSE, FALSE, TRUE); // down event & key only
clear_view_surface(scale_im_window);
show_sub_image (&scaled_image,
0, 0,
scaled_image.get_xsize (), scaled_image.get_ysize (),
scale_im_window, 0, 0);
line_color_index(scale_im_window, RED);
for (i = 1; i < scaled_image.get_xsize (); i++) {
move2d (scale_im_window, i, 0);
draw2d (scale_im_window, i, scaled_image.get_xsize ());
}
for (i = 1; i < scaled_image.get_ysize (); i++) {
move2d (scale_im_window, 0, i);
draw2d (scale_im_window, scaled_image.get_xsize (), i);
}
overlap_picture_ops(TRUE);
await_event(scale_im_window, TRUE, ANY_EVENT, &event);
destroy_window(clip_im_window);
destroy_window(scale_im_window);
}
/*************************************************************************
* plot_pixrows()
* Display a list of pixrows
*************************************************************************/
void plot_pixrows( //plot for all blobs
PIXROW_LIST *pixrow_list,
WINDOW win) {
PIXROW_IT pixrow_it(pixrow_list);
INT16 colour = RED;
for (pixrow_it.mark_cycle_pt ();
!pixrow_it.cycled_list (); pixrow_it.forward ()) {
if (colour > RED + 7)
colour = RED;
perimeter_color_index (win, (COLOUR) colour);
interior_style(win, INT_HOLLOW, TRUE);
pixrow_it.data ()->plot (win);
colour++;
}
}
#endif