inT16 OUTLINE::winding_number( //winding number const FCOORD &point //point to wind around ) { inT16 count; //winding count POLYPT *polypt; //current point FCOORD vec; //to current point float cross; //cross product POLYPT_IT it = &outline; //iterator count = 0; do { polypt = it.data (); vec = polypt->pos - point; //crossing the line if (vec.y () <= 0 && vec.y () + polypt->vec.y () > 0) { cross = vec * polypt->vec; //cross product if (cross > 0) count++; //crossing right half else if (cross == 0) return INTERSECTING; //going through point } else if (vec.y () > 0 && vec.y () + polypt->vec.y () <= 0) { cross = vec * polypt->vec; if (cross < 0) count--; //crossing back else if (cross == 0) return INTERSECTING; //illegal } it.forward (); } while (!it.at_first ()); return count; //winding number }
void OUTLINE::scale( // scale OUTLINE const FCOORD vector //by fcoord ) { //child outline itertr OUTLINE_IT child_it(&children); POLYPT_IT poly_it(&outline); //outline point itertr POLYPT *pt; box.scale (vector); start.set_x ((inT16) floor (start.x () * vector.x () + 0.5)); // ?? Why ICOORD? start.set_y ((inT16) floor (start.y () * vector.y () + 0.5)); // ?? Why ICOORD? for (poly_it.mark_cycle_pt (); !poly_it.cycled_list (); poly_it.forward ()) { pt = poly_it.data (); pt->pos = FCOORD (pt->pos.x () * vector.x (), pt->pos.y () * vector.y ()); pt->vec = FCOORD (pt->vec.x () * vector.x (), pt->vec.y () * vector.y ()); } for (child_it.mark_cycle_pt (); !child_it.cycled_list (); child_it.forward ()) //scale child outlines child_it.data ()->scale (vector); }
// Rotates by the given rotation in place. void TESSLINE::Rotate(const FCOORD rot) { EDGEPT* pt = loop; do { int tmp = static_cast<int>(floor(pt->pos.x * rot.x() - pt->pos.y * rot.y() + 0.5)); pt->pos.y = static_cast<int>(floor(pt->pos.y * rot.x() + pt->pos.x * rot.y() + 0.5)); pt->pos.x = tmp; pt = pt->next; } while (pt != loop); SetupFromPos(); }
// Draws the features in the given window. void WordFeature::Draw(const GenericVector<WordFeature>& features, ScrollView* window) { for (int f = 0; f < features.size(); ++f) { FCOORD pos(features[f].x_, features[f].y_); FCOORD dir; dir.from_direction(features[f].dir_); dir *= 8.0f; window->SetCursor(IntCastRounded(pos.x() - dir.x()), IntCastRounded(pos.y() - dir.y())); window->DrawTo(IntCastRounded(pos.x() + dir.x()), IntCastRounded(pos.y() + dir.y())); } }
// Rotate the grid by rotation, keeping cell contents. // rotation must be a multiple of 90 degrees. // NOTE: due to partial cells, cell coverage in the rotated grid will be // inexact. This is why there is no Rotate for the generic BBGrid. // TODO(rays) investigate fixing this inaccuracy by moving the origin after // rotation. void IntGrid::Rotate(const FCOORD& rotation) { ASSERT_HOST(rotation.x() == 0.0f || rotation.y() == 0.0f); ICOORD old_bleft(bleft()); ICOORD old_tright(tright()); int old_width = gridwidth(); int old_height = gridheight(); TBOX box(bleft(), tright()); box.rotate(rotation); int* old_grid = grid_; grid_ = NULL; Init(gridsize(), box.botleft(), box.topright()); // Iterate over the old grid, copying data to the rotated position in the new. int oldi = 0; FCOORD x_step(rotation); x_step *= gridsize(); for (int oldy = 0; oldy < old_height; ++oldy) { FCOORD line_pos(old_bleft.x(), old_bleft.y() + gridsize() * oldy); line_pos.rotate(rotation); for (int oldx = 0; oldx < old_width; ++oldx, line_pos += x_step, ++oldi) { int grid_x, grid_y; GridCoords(static_cast<int>(line_pos.x() + 0.5), static_cast<int>(line_pos.y() + 0.5), &grid_x, &grid_y); grid_[grid_y * gridwidth() + grid_x] = old_grid[oldi]; } } delete [] old_grid; }
// Copies the given feature_space and uses it as the index feature map // from INT_FEATURE_STRUCT. void IntFeatureMap::Init(const IntFeatureSpace& feature_space) { feature_space_ = feature_space; mapping_changed_ = false; int sparse_size = feature_space_.Size(); feature_map_.Init(sparse_size, true); feature_map_.Setup(); compact_size_ = feature_map_.CompactSize(); // Initialize look-up tables if needed. FCOORD dir = FeatureDirection(0); if (dir.x() == 0.0f && dir.y() == 0.0f) InitIntegerFX(); // Compute look-up tables to generate offset features. for (int dir = 0; dir < kNumOffsetMaps; ++dir) { delete [] offset_plus_[dir]; delete [] offset_minus_[dir]; offset_plus_[dir] = new int[sparse_size]; offset_minus_[dir] = new int[sparse_size]; } for (int dir = 1; dir <= kNumOffsetMaps; ++dir) { for (int i = 0; i < sparse_size; ++i) { int offset_index = ComputeOffsetFeature(i, dir); offset_plus_[dir - 1][i] = offset_index; offset_index = ComputeOffsetFeature(i, -dir); offset_minus_[dir - 1][i] = offset_index; } } }
// Returns the direction of the fitted line as a unit vector, using the // least mean squared perpendicular distance. The line runs through the // mean_point, i.e. a point p on the line is given by: // p = mean_point() + lambda * vector_fit() for some real number lambda. // Note that the result (0<=x<=1, -1<=y<=-1) is directionally ambiguous // and may be negated without changing its meaning. FCOORD LLSQ::vector_fit() const { double x_var = x_variance(); double y_var = y_variance(); double covar = covariance(); FCOORD result; if (x_var >= y_var) { if (x_var == 0.0) return FCOORD(0.0f, 0.0f); result.set_x(x_var / sqrt(x_var * x_var + covar * covar)); result.set_y(sqrt(1.0 - result.x() * result.x())); } else { result.set_y(y_var / sqrt(y_var * y_var + covar * covar)); result.set_x(sqrt(1.0 - result.y() * result.y())); } if (covar < 0.0) result.set_y(-result.y()); return result; }
void DENORM::LocalNormTransform(const FCOORD& pt, FCOORD* transformed) const { FCOORD translated(pt.x() - x_origin_, pt.y() - YOriginAtOrigX(pt.x())); translated.set_x(translated.x() * x_scale_); translated.set_y(translated.y() * YScaleAtOrigX(pt.x())); if (rotation_ != NULL) translated.rotate(*rotation_); transformed->set_x(translated.x() + final_xshift_); transformed->set_y(translated.y() + final_yshift_); }
void find_blob_limits( //get y limits PBLOB *blob, //blob to search float leftx, //x limits float rightx, FCOORD rotation, //for landscape float &ymin, //output y limits float &ymax) { float testy; //y intercept FCOORD pos; //rotated FCOORD vec; POLYPT *polypt; //current point //outlines OUTLINE_IT out_it = blob->out_list (); POLYPT_IT poly_it; //outline pts ymin = (float) MAX_INT32; ymax = (float) -MAX_INT32; for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) { //get points poly_it.set_to_list (out_it.data ()->polypts ()); for (poly_it.mark_cycle_pt (); !poly_it.cycled_list (); poly_it.forward ()) { polypt = poly_it.data (); pos = polypt->pos; pos.rotate (rotation); vec = polypt->vec; vec.rotate (rotation); if (pos.x () < leftx && pos.x () + vec.x () > leftx || pos.x () > leftx && pos.x () + vec.x () < leftx) { testy = pos.y () + vec.y () * (leftx - pos.x ()) / vec.x (); //intercept of boundary if (testy < ymin) ymin = testy; if (testy > ymax) ymax = testy; } if (pos.x () >= leftx && pos.x () <= rightx) { if (pos.y () > ymax) ymax = pos.y (); if (pos.y () < ymin) ymin = pos.y (); } if (pos.x () > rightx && pos.x () + vec.x () < rightx || pos.x () < rightx && pos.x () + vec.x () > rightx) { testy = pos.y () + vec.y () * (rightx - pos.x ()) / vec.x (); //intercept of boundary if (testy < ymin) ymin = testy; if (testy > ymax) ymax = testy; } } } }
/************************************************************************* * 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; } } } } }
void DENORM::LocalDenormTransform(const FCOORD& pt, FCOORD* original) const { FCOORD rotated(pt.x() - final_xshift_, pt.y() - final_yshift_); if (rotation_ != NULL) { FCOORD inverse_rotation(rotation_->x(), -rotation_->y()); rotated.rotate(inverse_rotation); } original->set_x(rotated.x() / x_scale_ + x_origin_); float y_scale = y_scale_; if (num_segs_ > 0) y_scale = YScaleAtOrigX(original->x()); original->set_y(rotated.y() / y_scale + YOriginAtOrigX(original->x())); }
void DENORM::LocalNormTransform(const FCOORD& pt, FCOORD* transformed) const { FCOORD translated(pt.x() - x_origin_, pt.y() - y_origin_); if (x_map_ != NULL && y_map_ != NULL) { int x = ClipToRange(IntCastRounded(translated.x()), 0, x_map_->size()-1); translated.set_x((*x_map_)[x]); int y = ClipToRange(IntCastRounded(translated.y()), 0, y_map_->size()-1); translated.set_y((*y_map_)[y]); } else { translated.set_x(translated.x() * x_scale_); translated.set_y(translated.y() * y_scale_); if (rotation_ != NULL) translated.rotate(*rotation_); } transformed->set_x(translated.x() + final_xshift_); transformed->set_y(translated.y() + final_yshift_); }
void POLY_BLOCK::rotate(FCOORD rotation) { FCOORD pos; //current pos; ICOORDELT *pt; //current point ICOORDELT_IT pts = &vertices; //iterator do { pt = pts.data (); pos.set_x (pt->x ()); pos.set_y (pt->y ()); pos.rotate (rotation); pt->set_x ((inT16) (floor (pos.x () + 0.5))); pt->set_y ((inT16) (floor (pos.y () + 0.5))); pts.forward (); } while (!pts.at_first ()); compute_bb(); }
void DENORM::LocalDenormTransform(const FCOORD& pt, FCOORD* original) const { FCOORD rotated(pt.x() - final_xshift_, pt.y() - final_yshift_); if (x_map_ != NULL && y_map_ != NULL) { int x = x_map_->binary_search(rotated.x()); original->set_x(x + x_origin_); int y = y_map_->binary_search(rotated.y()); original->set_y(y + y_origin_); } else { if (rotation_ != NULL) { FCOORD inverse_rotation(rotation_->x(), -rotation_->y()); rotated.rotate(inverse_rotation); } original->set_x(rotated.x() / x_scale_ + x_origin_); float y_scale = y_scale_; original->set_y(rotated.y() / y_scale + y_origin_); } }
void OUTLINE::move( // reposition OUTLINE const FCOORD vec // by vector ) { //child outline itertr OUTLINE_IT child_it(&children); POLYPT_IT poly_it(&outline); //outline point itertr box.move (vec); start.set_x ((inT16) floor (start.x () + vec.x () + 0.5)); // ?? Why ICOORD? start.set_y ((inT16) floor (start.y () + vec.y () + 0.5)); // ?? Why ICOORD? for (poly_it.mark_cycle_pt (); !poly_it.cycled_list (); poly_it.forward ()) poly_it.data ()->pos += vec; for (child_it.mark_cycle_pt (); !child_it.cycled_list (); child_it.forward ()) child_it.data ()->move (vec); // move child outlines }
DIR128::DIR128( //from fcoord const FCOORD fc //vector to quantize ) { int high, low, current; //binary search low = 0; if (fc.y () == 0) { if (fc.x () >= 0) dir = 0; else dir = MODULUS / 2; return; } high = MODULUS; do { current = (high + low) / 2; if (dirtab[current] * fc >= 0) low = current; else high = current; } while (high - low > 1); dir = low; }
// Adds any edges from a single segment of outline between pt1 and pt2 to // the x_coords, y_coords vectors. pt1 and pt2 should be relative to the // bottom-left of the bounding box, hence indices to x_coords, y_coords // are clipped to ([0,x_limit], [0,y_limit]). // See GetEdgeCoords above for a description of x_coords, y_coords. static void SegmentCoords(const FCOORD& pt1, const FCOORD& pt2, int x_limit, int y_limit, GenericVector<GenericVector<int> >* x_coords, GenericVector<GenericVector<int> >* y_coords) { FCOORD step(pt2); step -= pt1; int start = ClipToRange(IntCastRounded(MIN(pt1.x(), pt2.x())), 0, x_limit); int end = ClipToRange(IntCastRounded(MAX(pt1.x(), pt2.x())), 0, x_limit); for (int x = start; x < end; ++x) { int y = IntCastRounded(pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x()); (*y_coords)[x].push_back(y); } start = ClipToRange(IntCastRounded(MIN(pt1.y(), pt2.y())), 0, y_limit); end = ClipToRange(IntCastRounded(MAX(pt1.y(), pt2.y())), 0, y_limit); for (int y = start; y < end; ++y) { int x = IntCastRounded(pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y()); (*x_coords)[y].push_back(x); } }
// Accumulates the segment between pt1 and pt2 in the LLSQ, quantizing over // the integer coordinate grid to properly weight long vectors. static void SegmentLLSQ(const FCOORD& pt1, const FCOORD& pt2, LLSQ* accumulator) { FCOORD step(pt2); step -= pt1; int xstart = IntCastRounded(MIN(pt1.x(), pt2.x())); int xend = IntCastRounded(MAX(pt1.x(), pt2.x())); int ystart = IntCastRounded(MIN(pt1.y(), pt2.y())); int yend = IntCastRounded(MAX(pt1.y(), pt2.y())); if (xstart == xend && ystart == yend) return; // Nothing to do. double weight = step.length() / (xend - xstart + yend - ystart); // Compute and save the y-position at the middle of each x-step. for (int x = xstart; x < xend; ++x) { double y = pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x(); accumulator->add(x + 0.5, y, weight); } // Compute and save the x-position at the middle of each y-step. for (int y = ystart; y < yend; ++y) { double x = pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y(); accumulator->add(x, y + 0.5, weight); } }
// Adds any edges from a single segment of outline between pt1 and pt2 to // the bbox such that it guarantees to contain anything produced by // SegmentCoords. static void SegmentBBox(const FCOORD& pt1, const FCOORD& pt2, TBOX* bbox) { FCOORD step(pt2); step -= pt1; int x1 = IntCastRounded(MIN(pt1.x(), pt2.x())); int x2 = IntCastRounded(MAX(pt1.x(), pt2.x())); if (x2 > x1) { int y1 = IntCastRounded(pt1.y() + step.y() * (x1 + 0.5 - pt1.x()) / step.x()); int y2 = IntCastRounded(pt1.y() + step.y() * (x2 - 0.5 - pt1.x()) / step.x()); TBOX point(x1, MIN(y1, y2), x2, MAX(y1, y2)); *bbox += point; } int y1 = IntCastRounded(MIN(pt1.y(), pt2.y())); int y2 = IntCastRounded(MAX(pt1.y(), pt2.y())); if (y2 > y1) { int x1 = IntCastRounded(pt1.x() + step.x() * (y1 + 0.5 - pt1.y()) / step.y()); int x2 = IntCastRounded(pt1.x() + step.x() * (y2 - 0.5 - pt1.y()) / step.y()); TBOX point(MIN(x1, x2), y1, MAX(x1, x2), y2); *bbox += point; } }
WordFeature::WordFeature(const FCOORD& fcoord, uinT8 dir) : x_(IntCastRounded(fcoord.x())), y_(ClipToRange(IntCastRounded(fcoord.y()), 0, MAX_UINT8)), dir_(dir) { }
void OUTLINE::compute_bb() { //constructor ICOORD ibl, itr; //integer bb FCOORD botleft; //bounding box FCOORD topright; FCOORD pos; //current pos; POLYPT_IT polypts = &outline; //iterator botleft = polypts.data ()->pos; topright = botleft; start = ICOORD ((inT16) botleft.x (), (inT16) botleft.y ()); do { pos = polypts.data ()->pos; if (pos.x () < botleft.x ()) //get bounding box botleft = FCOORD (pos.x (), botleft.y ()); if (pos.y () < botleft.y ()) botleft = FCOORD (botleft.x (), pos.y ()); if (pos.x () > topright.x ()) topright = FCOORD (pos.x (), topright.y ()); if (pos.y () > topright.y ()) topright = FCOORD (topright.x (), pos.y ()); polypts.forward (); } while (!polypts.at_first ()); ibl = ICOORD ((inT16) botleft.x (), (inT16) botleft.y ()); itr = ICOORD ((inT16) topright.x () + 1, (inT16) topright.y () + 1); box = TBOX (ibl, itr); }
// Groups blocks by rotation, then, for each group, makes a WordGrid and calls // TransferDiacriticsToWords to copy the diacritic blobs to the most // appropriate words in the group of blocks. Source blobs are not touched. void Textord::TransferDiacriticsToBlockGroups(BLOBNBOX_LIST* diacritic_blobs, BLOCK_LIST* blocks) { // Angle difference larger than this is too much to consider equal. // They should only be in multiples of M_PI/2 anyway. const double kMaxAngleDiff = 0.01; // About 0.6 degrees. PointerVector<BlockGroup> groups; BLOCK_IT bk_it(blocks); for (bk_it.mark_cycle_pt(); !bk_it.cycled_list(); bk_it.forward()) { BLOCK* block = bk_it.data(); if (block->pdblk.poly_block() != nullptr && !block->pdblk.poly_block()->IsText()) { continue; } // Linear search of the groups to find a matching rotation. float block_angle = block->re_rotation().angle(); int best_g = 0; float best_angle_diff = MAX_FLOAT32; for (int g = 0; g < groups.size(); ++g) { double angle_diff = fabs(block_angle - groups[g]->angle); if (angle_diff > M_PI) angle_diff = fabs(angle_diff - 2.0 * M_PI); if (angle_diff < best_angle_diff) { best_angle_diff = angle_diff; best_g = g; } } if (best_angle_diff > kMaxAngleDiff) { groups.push_back(new BlockGroup(block)); } else { groups[best_g]->blocks.push_back(block); groups[best_g]->bounding_box += block->pdblk.bounding_box(); float x_height = block->x_height(); if (x_height < groups[best_g]->min_xheight) groups[best_g]->min_xheight = x_height; } } // Now process each group of blocks. PointerVector<WordWithBox> word_ptrs; for (int g = 0; g < groups.size(); ++g) { const BlockGroup* group = groups[g]; if (group->bounding_box.null_box()) continue; WordGrid word_grid(group->min_xheight, group->bounding_box.botleft(), group->bounding_box.topright()); for (int b = 0; b < group->blocks.size(); ++b) { ROW_IT row_it(group->blocks[b]->row_list()); for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) { ROW* row = row_it.data(); // Put the words of the row into the grid. WERD_IT w_it(row->word_list()); for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) { WERD* word = w_it.data(); WordWithBox* box_word = new WordWithBox(word); word_grid.InsertBBox(true, true, box_word); // Save the pointer where it will be auto-deleted. word_ptrs.push_back(box_word); } } } FCOORD rotation = group->rotation; // Make it a forward rotation that will transform blob coords to block. rotation.set_y(-rotation.y()); TransferDiacriticsToWords(diacritic_blobs, rotation, &word_grid); } }