/********************************************************************** * File: edgblob.c (Formerly edgeloop.c) * Description: Functions to clean up an outline before approximation. * Author: Ray Smith * Created: Tue Mar 26 16:56:25 GMT 1991 * * (C) Copyright 1991, 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 "dirtab.h" #include "scanedg.h" #include "drawedg.h" #include "edgloop.h" #include "edgblob.h" #define EXTERN EXTERN INT_VAR (edges_children_per_grandchild, 10, "Importance ratio for chucking outlines"); EXTERN INT_VAR (edges_children_count_limit, 45, "Max holes allowed in blob"); EXTERN BOOL_VAR (edges_children_fix, FALSE, "Remove boxy parents of char-like children"); EXTERN INT_VAR (edges_min_nonhole, 12, "Min pixels for potential char in box"); EXTERN INT_VAR (edges_patharea_ratio, 40, "Max lensq/area for acceptable child outline"); EXTERN double_VAR (edges_childarea, 0.5, "Max area fraction of child outline"); EXTERN double_VAR (edges_boxarea, 0.875, "Min area fraction of grandchild for box"); /********************************************************************** * OL_BUCKETS::OL_BUCKETS * * Construct an array of buckets for associating outlines into blobs. **********************************************************************/ OL_BUCKETS::OL_BUCKETS ( ////constructor ICOORD bleft, //corners ICOORD tright): bl (bleft), tr (tright) { bxdim = (tright.x () - bleft.x ()) / BUCKETSIZE + 1; bydim = (tright.y () - bleft.y ()) / BUCKETSIZE + 1; //make array buckets = new C_OUTLINE_LIST[bxdim * bydim]; index = 0; } /********************************************************************** * OL_BUCKETS::operator( * * Return a pointer to a list of C_OUTLINEs corresponding to the * given pixel coordinates. **********************************************************************/ C_OUTLINE_LIST * OL_BUCKETS::operator () ( //array access inT16 x, //image coords inT16 y) { return &buckets[(y - bl.y ()) / BUCKETSIZE * bxdim + (x - bl.x ()) / BUCKETSIZE]; } /********************************************************************** * OL_BUCKETS::count_children * * Find number of descendants of this outline. **********************************************************************/ inT32 OL_BUCKETS::count_children( //recursive count C_OUTLINE *outline, //parent outline inT32 max_count //max output ) { BOOL8 parent_box; //could it be boxy inT16 xmin, xmax; //coord limits inT16 ymin, ymax; inT16 xindex, yindex; //current bucket C_OUTLINE *child; //current child inT32 child_count; //no of children inT32 grandchild_count; //no of grandchildren inT32 parent_area; //potential box FLOAT32 max_parent_area; //potential box inT32 child_area; //current child inT32 child_length; //current child TBOX olbox; C_OUTLINE_IT child_it; //search iterator olbox = outline->bounding_box (); xmin = (olbox.left () - bl.x ()) / BUCKETSIZE; xmax = (olbox.right () - bl.x ()) / BUCKETSIZE; ymin = (olbox.bottom () - bl.y ()) / BUCKETSIZE; ymax = (olbox.top () - bl.y ()) / BUCKETSIZE; child_count = 0; grandchild_count = 0; parent_area = 0; max_parent_area = 0; parent_box = TRUE; for (yindex = ymin; yindex <= ymax; yindex++) { for (xindex = xmin; xindex <= xmax; xindex++) { child_it.set_to_list (&buckets[yindex * bxdim + xindex]); if (child_it.empty ()) continue; for (child_it.mark_cycle_pt (); !child_it.cycled_list (); child_it.forward ()) { child = child_it.data (); if (child != outline && *child < *outline) { child_count++; if (child_count <= max_count) { int max_grand = (max_count - child_count) / edges_children_per_grandchild; if (max_grand > 0) grandchild_count += count_children (child, max_grand) * edges_children_per_grandchild; else grandchild_count += count_children(child, 1); } if (child_count + grandchild_count > max_count) { /* err.log(RESULT_OKAY,E_LOC,ERR_OCR, ERR_SCROLLING,ERR_CONTINUE,ERR_DEBUG, "Discarding parent with child count=%d, gc=%d", child_count,grandchild_count);*/ return child_count + grandchild_count; } if (parent_area == 0) { parent_area = outline->outer_area (); if (parent_area < 0) parent_area = -parent_area; max_parent_area = outline->bounding_box ().width () * outline->bounding_box ().height () * edges_boxarea; if (parent_area < max_parent_area) parent_box = FALSE; } if (parent_box && (!edges_children_fix || child->bounding_box ().height () > edges_min_nonhole) /**/) { child_area = child->outer_area (); if (child_area < 0) child_area = -child_area; if (edges_children_fix) { if (parent_area - child_area < max_parent_area) { parent_box = FALSE; continue; } if (grandchild_count > 0) { /* err.log(RESULT_OKAY,E_LOC,ERR_OCR, ERR_SCROLLING,ERR_CONTINUE,ERR_DEBUG, "Discarding parent of area %d, child area=%d, max%g with gc=%d", parent_area,child_area,max_parent_area,grandchild_count);*/ return max_count + 1; } child_length = child->pathlength (); if (child_length * child_length > child_area * edges_patharea_ratio) { /*/ err.log(RESULT_OKAY,E_LOC,ERR_OCR, ERR_SCROLLING,ERR_CONTINUE,ERR_DEBUG, "Discarding parent of area %d, child area=%d, max%g with child length=%d", parent_area,child_area,max_parent_area,child_length);*/ return max_count + 1; } } if (child_area < child->bounding_box ().width () * child->bounding_box ().height () * edges_childarea) { /* err.log(RESULT_OKAY,E_LOC,ERR_OCR, ERR_SCROLLING,ERR_CONTINUE,ERR_DEBUG, "Discarding parent of area %d, child area=%d, max%g with child rect=%d", parent_area,child_area,max_parent_area,child->bounding_box().width() *child->bounding_box().height()); */ return max_count + 1; } } } } } } return child_count + grandchild_count; } /********************************************************************** * OL_BUCKETS::extract_children * * Find number of descendants of this outline. **********************************************************************/ void OL_BUCKETS::extract_children( //recursive count C_OUTLINE *outline, //parent outline C_OUTLINE_IT *it //destination iterator ) { inT16 xmin, xmax; //coord limits inT16 ymin, ymax; inT16 xindex, yindex; //current bucket TBOX olbox; C_OUTLINE_IT child_it; //search iterator olbox = outline->bounding_box (); xmin = (olbox.left () - bl.x ()) / BUCKETSIZE; xmax = (olbox.right () - bl.x ()) / BUCKETSIZE; ymin = (olbox.bottom () - bl.y ()) / BUCKETSIZE; ymax = (olbox.top () - bl.y ()) / BUCKETSIZE; for (yindex = ymin; yindex <= ymax; yindex++) { for (xindex = xmin; xindex <= xmax; xindex++) { child_it.set_to_list (&buckets[yindex * bxdim + xindex]); for (child_it.mark_cycle_pt (); !child_it.cycled_list (); child_it.forward ()) { if (*child_it.data () < *outline) { it->add_after_then_move (child_it.extract ()); } } } } } /********************************************************************** * extract_edges * * Run the edge detector over the block and return a list of blobs. **********************************************************************/ void extract_edges( //find blobs #ifndef GRAPHICS_DISABLED ScrollView* window, //window for output #endif IMAGE *image, //image to scan IMAGE *t_image, //thresholded image ICOORD page_tr, //corner of page BLOCK *block //block to scan ) { ICOORD bleft; //block box ICOORD tright; C_OUTLINE_LIST outlines; //outlines in block //iterator C_OUTLINE_IT out_it = &outlines; #ifndef GRAPHICS_DISABLED get_outlines (window, image, t_image, page_tr, (PDBLK *) block, &out_it); #else get_outlines (image, t_image, page_tr, (PDBLK *) block, &out_it); #endif //block box block->bounding_box (bleft, tright); //make blobs outlines_to_blobs(block, bleft, tright, &outlines); } /********************************************************************** * outlines_to_blobs * * Gather together outlines into blobs using the usual bucket sort. **********************************************************************/ void outlines_to_blobs( //find blobs BLOCK *block, //block to scan ICOORD bleft, //block box //outlines in block ICOORD tright, C_OUTLINE_LIST *outlines) { //make buckets OL_BUCKETS buckets(bleft, tright); fill_buckets(outlines, &buckets); empty_buckets(block, &buckets); } /********************************************************************** * fill_buckets * * Run the edge detector over the block and return a list of blobs. **********************************************************************/ void fill_buckets( //find blobs C_OUTLINE_LIST *outlines, //outlines in block OL_BUCKETS *buckets //output buckets ) { TBOX ol_box; //outline box C_OUTLINE_IT out_it = outlines;//iterator C_OUTLINE_IT bucket_it; //iterator in bucket C_OUTLINE *outline; //current outline for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) { outline = out_it.extract (); //take off list //get box ol_box = outline->bounding_box (); bucket_it.set_to_list ((*buckets) (ol_box.left (), ol_box.bottom ())); bucket_it.add_to_end (outline); } } /********************************************************************** * empty_buckets * * Run the edge detector over the block and return a list of blobs. **********************************************************************/ void empty_buckets( //find blobs BLOCK *block, //block to scan OL_BUCKETS *buckets //output buckets ) { BOOL8 good_blob; //healthy blob C_OUTLINE_LIST outlines; //outlines in block //iterator C_OUTLINE_IT out_it = &outlines; C_OUTLINE_IT bucket_it = buckets->start_scan (); C_OUTLINE_IT parent_it; //parent outline C_BLOB *blob; //new blob C_BLOB_IT good_blobs = block->blob_list (); C_BLOB_IT junk_blobs = block->reject_blobs (); while (!bucket_it.empty ()) { out_it.set_to_list (&outlines); do { parent_it = bucket_it; //find outermost do bucket_it.forward (); while (!bucket_it.at_first () && !(*parent_it.data () < *bucket_it.data ())); } while (!bucket_it.at_first ()); //move to new list out_it.add_after_then_move (parent_it.extract ()); good_blob = capture_children (buckets, &junk_blobs, &out_it); blob = new C_BLOB (&outlines); if (good_blob) good_blobs.add_after_then_move (blob); else junk_blobs.add_after_then_move (blob); bucket_it.set_to_list (buckets->scan_next ()); } } /********************************************************************** * capture_children * * Find all neighbouring outlines that are children of this outline * and either move them to the output list or declare this outline * illegal and return FALSE. **********************************************************************/ BOOL8 capture_children( //find children OL_BUCKETS *buckets, //bucket sort clanss C_BLOB_IT *reject_it, //dead grandchildren C_OUTLINE_IT *blob_it //output outlines ) { C_OUTLINE *outline; //master outline inT32 child_count; //no of children outline = blob_it->data (); child_count = buckets->count_children (outline, edges_children_count_limit); if (child_count > edges_children_count_limit) return FALSE; if (child_count == 0) return TRUE; //get single level buckets->extract_children (outline, blob_it); #if 0 C_BLOB *blob; //reject C_OUTLINE *child; //child under test C_OUTLINE_LIST r_list; //rejects C_OUTLINE_IT r_it; //iterator C_OUTLINE_IT test_it; //for grandchildren BOOL8 anydone; //anything canned if (child_count == 1) return TRUE; do { anydone = FALSE; blob_it->move_to_first (); for (blob_it->mark_cycle_pt (); !blob_it->cycled_list (); blob_it->forward ()) { child = blob_it->data (); if (child != outline) { for (test_it = *blob_it, test_it.mark_cycle_pt (); !test_it.cycled_list (); test_it.forward ()) { if (test_it.data () != child && *test_it.data () < *child) { r_it.set_to_list (&r_list); r_it.add_after_then_move (test_it.extract ()); //turn to blob blob = new C_BLOB (&r_list); reject_it->add_after_then_move (blob); anydone = TRUE; } } if (anydone) break; //got to reatart } } } while (anydone); //got to restart #endif return TRUE; }