Example #1
0
BOX* M_Utils::getColPartImCoords(ColPartition* cp, PIX* im) {
  BLOBNBOX_CLIST* blobnboxes = cp->boxes();
  CLIST_ITERATOR bbox_it(blobnboxes);
  l_int32 height = (l_int32)im->h;
  l_int32 left = INT_MAX;
  l_int32 right = INT_MIN;
  l_int32 top = INT_MAX;
  l_int32 bottom = INT_MIN;
  l_int32 l, r, t, b;
  for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list();
      bbox_it.forward()) {
    BLOBNBOX* blobnbox = (BLOBNBOX*)bbox_it.data();
    l = (l_int32)blobnbox->cblob()->bounding_box().left();
    r = (l_int32)blobnbox->cblob()->bounding_box().right();
    t = height - (l_int32)blobnbox->cblob()->bounding_box().top();
    b = height - (l_int32)blobnbox->cblob()->bounding_box().bottom();
    if(l < left)
      left = l;
    if(r > right)
      right = r;
    if(t < top)
      top = t;
    if(b > bottom)
      bottom = b;
  }
  BOX* boxret = boxCreate(left, top, right-left, bottom-top);
  return boxret;
}
Example #2
0
// Helper to compute edge offsets for  all the blobs on the list.
// See coutln.h for an explanation of edge offsets.
void BLOBNBOX::ComputeEdgeOffsets(Pix* thresholds, Pix* grey,
                                  BLOBNBOX_LIST* blobs) {
  int grey_height = 0;
  int thr_height = 0;
  int scale_factor = 1;
  if (thresholds != NULL && grey != NULL) {
    grey_height = pixGetHeight(grey);
    thr_height = pixGetHeight(thresholds);
    scale_factor =
        IntCastRounded(static_cast<double>(grey_height) / thr_height);
  }
  BLOBNBOX_IT blob_it(blobs);
  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
    BLOBNBOX* blob = blob_it.data();
    if (blob->cblob() != NULL) {
      // Get the threshold that applies to this blob.
      l_uint32 threshold = 128;
      if (thresholds != NULL && grey != NULL) {
        const TBOX& box = blob->cblob()->bounding_box();
        // Transform the coordinates if required.
        TPOINT pt((box.left() + box.right()) / 2,
                  (box.top() + box.bottom()) / 2);
        pixGetPixel(thresholds, pt.x / scale_factor,
                    thr_height - 1 - pt.y / scale_factor, &threshold);
      }
      blob->cblob()->ComputeEdgeOffsets(threshold, grey);
    }
  }
}
Example #3
0
//yangjing01 modified : 
bool TAL_make_single_word(bool one_blob, TO_ROW_LIST* rows, ROW_LIST* real_rows)
{
  TO_ROW_IT to_row_it(rows);
  ROW_IT row_it(real_rows);
  //to_real_row is the real row information of single row or single char mode
  TO_ROW* real_to_row = NULL;
  float row_max_height = 0.0;
  for (to_row_it.mark_cycle_pt();
    !to_row_it.cycled_list(); to_row_it.forward()){
    TO_ROW* row = to_row_it.data();
    float row_min_y = row->min_y();
    float row_max_y = row->max_y();
    float row_height = abs(row_max_y - row_min_y);
    if (real_to_row == NULL
      || row_height > row_max_height
      || fabs(row_height - row_max_height) < 1.0f){
      row_max_height = row_height;
      real_to_row = row;
    }
  }

  if (real_to_row == NULL){
    return false;
  }

  C_BLOB_LIST cblobs;
  C_BLOB_IT cblob_it(&cblobs);
  BLOBNBOX_IT box_it(real_to_row->blob_list());
  for (; !box_it.empty(); box_it.forward()){
    BLOBNBOX* bblob = box_it.extract();
    if (bblob->joined_to_prev() || (one_blob && !cblob_it.empty())) {
      if (bblob->cblob() != NULL){
        C_OUTLINE_IT cout_it(cblob_it.data()->out_list());
        cout_it.move_to_last();
        cout_it.add_list_after(bblob->cblob()->out_list());
        delete bblob->cblob();
      }
    }
    else {
      if (bblob->cblob() != NULL)
        cblob_it.add_after_then_move(bblob->cblob());
    }
    delete bblob;
  }
  // Convert the TO_ROW to a ROW.
  ROW* real_row = new ROW(real_to_row, static_cast<inT16>(real_to_row->kern_size),
    static_cast<inT16>(real_to_row->space_size));
  WERD_IT word_it(real_row->word_list());
  WERD* word = new WERD(&cblobs, 0, NULL);
  word->set_flag(W_BOL, TRUE);
  word->set_flag(W_EOL, TRUE);
  word->set_flag(W_DONT_CHOP, one_blob);
  word_it.add_after_then_move(word);
  row_it.add_after_then_move(real_row);

  return true;
}
Example #4
0
WERD *make_real_word(BLOBNBOX_IT *box_it,  //iterator
                     inT32 blobcount,      //no of blobs to use
                     BOOL8 bol,            //start of line
                     uinT8 blanks          //no of blanks
                    ) {
  OUTLINE_IT out_it;             // outlines
  C_OUTLINE_IT cout_it;
  PBLOB_LIST blobs;              // blobs in word
  C_BLOB_LIST cblobs;
  PBLOB_IT blob_it = &blobs;     // iterator
  C_BLOB_IT cblob_it = &cblobs;
  WERD *word;                    // new word
  BLOBNBOX *bblob;               // current blob
  inT32 blobindex;               // in row

  for (blobindex = 0; blobindex < blobcount; blobindex++) {
    bblob = box_it->extract();
    if (bblob->joined_to_prev()) {
      if (bblob->blob() != NULL) {
        out_it.set_to_list(blob_it.data()->out_list());
        out_it.move_to_last();
        out_it.add_list_after(bblob->blob()->out_list());
        delete bblob->blob();
      }
      else if (bblob->cblob() != NULL) {
        cout_it.set_to_list(cblob_it.data()->out_list());
        cout_it.move_to_last();
        cout_it.add_list_after(bblob->cblob()->out_list());
        delete bblob->cblob();
      }
    }
    else {
      if (bblob->blob() != NULL)
        blob_it.add_after_then_move(bblob->blob());
      else if (bblob->cblob() != NULL)
        cblob_it.add_after_then_move(bblob->cblob());
    }
    delete bblob;
    box_it->forward();          // next one
  }

  if (blanks < 1)
    blanks = 1;

  if (blob_it.empty())
    word = new WERD(&cblobs, blanks, NULL);
  else
    word = new WERD(&blobs, blanks, NULL);

  if (bol)
    word->set_flag(W_BOL, TRUE);
  if (box_it->at_first())
    word->set_flag(W_EOL, TRUE);  // at end of line

  return word;
}
Example #5
0
static void clear_blobnboxes(BLOBNBOX_LIST* boxes) {
  BLOBNBOX_IT it = boxes;
  // A BLOBNBOX generally doesn't own its blobs, so if they do, you
  // have to delete them explicitly.
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    BLOBNBOX* box = it.data();
    if (box->cblob() != NULL)
      delete box->cblob();
  }
}
Example #6
0
// Tests each blob in the list to see if it is certain non-text using 2
// conditions:
// 1. blob overlaps a cell with high value in noise_density_ (previously set
// by ComputeNoiseDensity).
// OR 2. The blob overlaps more than max_blob_overlaps in *this grid. This
// condition is disabled with max_blob_overlaps == -1.
// If it does, the blob is declared non-text, and is used to mark up the
// nontext_mask. Such blobs are fully deleted, and non-noise blobs have their
// neighbours reset, as they may now point to deleted data.
// WARNING: The blobs list blobs may be in the *this grid, but they are
// not removed. If any deleted blobs might be in *this, then this must be
// Clear()ed immediately after MarkAndDeleteNonTextBlobs is called.
// If the win is not NULL, deleted blobs are drawn on it in red, and kept
// blobs are drawn on it in ok_color.
void CCNonTextDetect::MarkAndDeleteNonTextBlobs(BLOBNBOX_LIST* blobs,
        int max_blob_overlaps,
        ScrollView* win,
        ScrollView::Color ok_color,
        Pix* nontext_mask) {
    int imageheight = tright().y() - bleft().x();
    BLOBNBOX_IT blob_it(blobs);
    BLOBNBOX_LIST dead_blobs;
    BLOBNBOX_IT dead_it(&dead_blobs);
    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
        BLOBNBOX* blob = blob_it.data();
        TBOX box = blob->bounding_box();
        if (!noise_density_->RectMostlyOverThreshold(box, max_noise_count_) &&
                (max_blob_overlaps < 0 ||
                 !BlobOverlapsTooMuch(blob, max_blob_overlaps))) {
            blob->ClearNeighbours();
#ifndef GRAPHICS_DISABLED
            if (win != NULL)
                blob->plot(win, ok_color, ok_color);
#endif  // GRAPHICS_DISABLED
        } else {
            if (noise_density_->AnyZeroInRect(box)) {
                // There is a danger that the bounding box may overlap real text, so
                // we need to render the outline.
                Pix* blob_pix = blob->cblob()->render_outline();
                pixRasterop(nontext_mask, box.left(), imageheight - box.top(),
                            box.width(), box.height(), PIX_SRC | PIX_DST,
                            blob_pix, 0, 0);
                pixDestroy(&blob_pix);
            } else {
                if (box.area() < gridsize() * gridsize()) {
                    // It is a really bad idea to make lots of small components in the
                    // photo mask, so try to join it to a bigger area by expanding the
                    // box in a way that does not touch any zero noise density cell.
                    box = AttemptBoxExpansion(box, *noise_density_, gridsize());
                }
                // All overlapped cells are non-zero, so just mark the rectangle.
                pixRasterop(nontext_mask, box.left(), imageheight - box.top(),
                            box.width(), box.height(), PIX_SET, NULL, 0, 0);
            }
#ifndef GRAPHICS_DISABLED
            if (win != NULL)
                blob->plot(win, ScrollView::RED, ScrollView::RED);
#endif  // GRAPHICS_DISABLED
            // It is safe to delete the cblob now, as it isn't used by the grid
            // or BlobOverlapsTooMuch, and the BLOBNBOXes will go away with the
            // dead_blobs list.
            // TODO(rays) delete the delete when the BLOBNBOX destructor deletes
            // the cblob.
            delete blob->cblob();
            dead_it.add_to_end(blob_it.extract());
        }
    }
}
Example #7
0
/** Handles a click event in a display window. */
void StrokeWidth::HandleClick(int x, int y) {
  BBGrid<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT>::HandleClick(x, y);
  // Run a radial search for blobs that overlap.
  BlobGridSearch radsearch(this);
  radsearch.StartRadSearch(x, y, 1);
  BLOBNBOX* neighbour;
  FCOORD click(static_cast<float>(x), static_cast<float>(y));
  while ((neighbour = radsearch.NextRadSearch()) != NULL) {
    TBOX nbox = neighbour->bounding_box();
    if (nbox.contains(click) && neighbour->cblob() != NULL) {
      PrintBoxWidths(neighbour);
      if (neighbour->neighbour(BND_LEFT) != NULL)
        PrintBoxWidths(neighbour->neighbour(BND_LEFT));
      if (neighbour->neighbour(BND_RIGHT) != NULL)
        PrintBoxWidths(neighbour->neighbour(BND_RIGHT));
      if (neighbour->neighbour(BND_ABOVE) != NULL)
        PrintBoxWidths(neighbour->neighbour(BND_ABOVE));
      if (neighbour->neighbour(BND_BELOW) != NULL)
        PrintBoxWidths(neighbour->neighbour(BND_BELOW));
      int gaps[BND_COUNT];
      neighbour->NeighbourGaps(gaps);
      tprintf("Left gap=%d, right=%d, above=%d, below=%d, horz=%d, vert=%d\n"
              "Good=    %d        %d        %d        %d\n",
              gaps[BND_LEFT], gaps[BND_RIGHT],
              gaps[BND_ABOVE], gaps[BND_BELOW],
              neighbour->horz_possible(),
              neighbour->vert_possible(),
              neighbour->good_stroke_neighbour(BND_LEFT),
              neighbour->good_stroke_neighbour(BND_RIGHT),
              neighbour->good_stroke_neighbour(BND_ABOVE),
              neighbour->good_stroke_neighbour(BND_BELOW));
      break;
    }
  }
}
Example #8
0
TBOX box_next(                 //get bounding box
             BLOBNBOX_IT *it  //iterator to blobds
            ) {
  BLOBNBOX *blob;                //current blob
  TBOX result;                    //total box

  blob = it->data ();
  result = blob->bounding_box ();
  do {
    it->forward ();
    blob = it->data ();
    if (blob->cblob() == NULL)
                                 //was pre-chopped
      result += blob->bounding_box ();
  }
                                 //until next real blob
  while ((blob->cblob() == NULL) || blob->joined_to_prev());
  return result;
}
Example #9
0
// Helper to delete all the deletable blobs on the list.
void BLOBNBOX::DeleteNoiseBlobs(BLOBNBOX_LIST* blobs) {
  BLOBNBOX_IT blob_it(blobs);
  for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
    BLOBNBOX* blob = blob_it.data();
    if (blob->DeletableNoise()) {
      delete blob->cblob();
      delete blob_it.extract();
    }
  }
}
Example #10
0
WERD *make_real_word(BLOBNBOX_IT *box_it,  //iterator
                     int32_t blobcount,      //no of blobs to use
                     bool bol,            //start of line
                     uint8_t blanks          //no of blanks
                    ) {
  C_OUTLINE_IT cout_it;
  C_BLOB_LIST cblobs;
  C_BLOB_IT cblob_it = &cblobs;
  WERD *word;                    // new word
  BLOBNBOX *bblob;               // current blob
  int32_t blobindex;               // in row

  for (blobindex = 0; blobindex < blobcount; blobindex++) {
    bblob = box_it->extract();
    if (bblob->joined_to_prev()) {
      if (bblob->cblob() != nullptr) {
        cout_it.set_to_list(cblob_it.data()->out_list());
        cout_it.move_to_last();
        cout_it.add_list_after(bblob->cblob()->out_list());
        delete bblob->cblob();
      }
    }
    else {
      if (bblob->cblob() != nullptr)
        cblob_it.add_after_then_move(bblob->cblob());
    }
    delete bblob;
    box_it->forward();          // next one
  }

  if (blanks < 1)
    blanks = 1;

  word = new WERD(&cblobs, blanks, nullptr);

  if (bol)
    word->set_flag(W_BOL, true);
  if (box_it->at_first())
    word->set_flag(W_EOL, true);  // at end of line

  return word;
}
Example #11
0
void TO_ROW::compute_vertical_projection() {  //project whole row
  TBOX row_box;                   //bound of row
  BLOBNBOX *blob;                //current blob
  TBOX blob_box;                  //bounding box
  BLOBNBOX_IT blob_it = blob_list ();

  if (blob_it.empty ())
    return;
  row_box = blob_it.data ()->bounding_box ();
  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ())
    row_box += blob_it.data ()->bounding_box ();

  projection.set_range (row_box.left () - PROJECTION_MARGIN,
    row_box.right () + PROJECTION_MARGIN);
  projection_left = row_box.left () - PROJECTION_MARGIN;
  projection_right = row_box.right () + PROJECTION_MARGIN;
  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
    blob = blob_it.data();
    if (blob->cblob() != NULL)
      vertical_cblob_projection(blob->cblob(), &projection);
  }
}
Example #12
0
// Places a copy of blobs that are near a word (after applying rotation to the
// blob) in the most appropriate word, unless there is doubt, in which case a
// blob can end up in two words. Source blobs are not touched.
void Textord::TransferDiacriticsToWords(BLOBNBOX_LIST* diacritic_blobs,
                                        const FCOORD& rotation,
                                        WordGrid* word_grid) {
  WordSearch ws(word_grid);
  BLOBNBOX_IT b_it(diacritic_blobs);
  // Apply rotation to each blob before finding the nearest words. The rotation
  // allows us to only consider above/below placement and not left/right on
  // vertical text, because all text is horizontal here.
  for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
    BLOBNBOX* blobnbox = b_it.data();
    TBOX blob_box = blobnbox->bounding_box();
    blob_box.rotate(rotation);
    ws.StartRectSearch(blob_box);
    // Above/below refer to word position relative to diacritic. Since some
    // scripts eg Kannada/Telugu habitually put diacritics below words, and
    // others eg Thai/Vietnamese/Latin put most diacritics above words, try
    // for both if there isn't much in it.
    WordWithBox* best_above_word = nullptr;
    WordWithBox* best_below_word = nullptr;
    int best_above_distance = 0;
    int best_below_distance = 0;
    for (WordWithBox* word = ws.NextRectSearch(); word != nullptr;
         word = ws.NextRectSearch()) {
      if (word->word()->flag(W_REP_CHAR)) continue;
      TBOX word_box = word->true_bounding_box();
      int x_distance = blob_box.x_gap(word_box);
      int y_distance = blob_box.y_gap(word_box);
      if (x_distance > 0) {
        // Arbitrarily divide x-distance by 2 if there is a major y overlap,
        // and the word is to the left of the diacritic. If the
        // diacritic is a dropped broken character between two words, this will
        // help send all the pieces to a single word, instead of splitting them
        // over the 2 words.
        if (word_box.major_y_overlap(blob_box) &&
            blob_box.left() > word_box.right()) {
          x_distance /= 2;
        }
        y_distance += x_distance;
      }
      if (word_box.y_middle() > blob_box.y_middle() &&
          (best_above_word == nullptr || y_distance < best_above_distance)) {
        best_above_word = word;
        best_above_distance = y_distance;
      }
      if (word_box.y_middle() <= blob_box.y_middle() &&
          (best_below_word == nullptr || y_distance < best_below_distance)) {
        best_below_word = word;
        best_below_distance = y_distance;
      }
    }
    bool above_good =
        best_above_word != nullptr &&
        (best_below_word == nullptr ||
         best_above_distance < best_below_distance + blob_box.height());
    bool below_good =
        best_below_word != nullptr && best_below_word != best_above_word &&
        (best_above_word == nullptr ||
         best_below_distance < best_above_distance + blob_box.height());
    if (below_good) {
      C_BLOB* copied_blob = C_BLOB::deep_copy(blobnbox->cblob());
      copied_blob->rotate(rotation);
      // Put the blob into the word's reject blobs list.
      C_BLOB_IT blob_it(best_below_word->RejBlobs());
      blob_it.add_to_end(copied_blob);
    }
    if (above_good) {
      C_BLOB* copied_blob = C_BLOB::deep_copy(blobnbox->cblob());
      copied_blob->rotate(rotation);
      // Put the blob into the word's reject blobs list.
      C_BLOB_IT blob_it(best_above_word->RejBlobs());
      blob_it.add_to_end(copied_blob);
    }
  }
}
Example #13
0
// Creates and returns a Pix with the same resolution as the original
// in which 1 (black) pixels represent likely non text (photo, line drawing)
// areas of the page, deleting from the blob_block the blobs that were
// determined to be non-text.
// The photo_map is used to bias the decision towards non-text, rather than
// supplying definite decision.
// The blob_block is the usual result of connected component analysis,
// holding the detected blobs.
// The returned Pix should be PixDestroyed after use.
Pix* CCNonTextDetect::ComputeNonTextMask(bool debug, Pix* photo_map,
        TO_BLOCK* blob_block) {
    // Insert the smallest blobs into the grid.
    InsertBlobList(&blob_block->small_blobs);
    InsertBlobList(&blob_block->noise_blobs);
    // Add the medium blobs that don't have a good strokewidth neighbour.
    // Those that do go into good_grid as an antidote to spreading beyond the
    // real reaches of a noise region.
    BlobGrid good_grid(gridsize(), bleft(), tright());
    BLOBNBOX_IT blob_it(&blob_block->blobs);
    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
        BLOBNBOX* blob = blob_it.data();
        double perimeter_area_ratio = blob->cblob()->perimeter() / 4.0;
        perimeter_area_ratio *= perimeter_area_ratio / blob->enclosed_area();
        if (blob->GoodTextBlob() == 0 || perimeter_area_ratio < kMinGoodTextPARatio)
            InsertBBox(true, true, blob);
        else
            good_grid.InsertBBox(true, true, blob);
    }
    noise_density_ = ComputeNoiseDensity(debug, photo_map, &good_grid);
    good_grid.Clear();  // Not needed any more.
    Pix* pix = noise_density_->ThresholdToPix(max_noise_count_);
    if (debug) {
        pixWrite("junknoisemask.png", pix, IFF_PNG);
    }
    ScrollView* win = NULL;
#ifndef GRAPHICS_DISABLED
    if (debug) {
        win = MakeWindow(0, 400, "Photo Mask Blobs");
    }
#endif  // GRAPHICS_DISABLED
    // Large and medium blobs are not text if they overlap with "a lot" of small
    // blobs.
    MarkAndDeleteNonTextBlobs(&blob_block->large_blobs,
                              kMaxLargeOverlapsWithSmall,
                              win, ScrollView::DARK_GREEN, pix);
    MarkAndDeleteNonTextBlobs(&blob_block->blobs, kMaxMediumOverlapsWithSmall,
                              win, ScrollView::WHITE, pix);
    // Clear the grid of small blobs and insert the medium blobs.
    Clear();
    InsertBlobList(&blob_block->blobs);
    MarkAndDeleteNonTextBlobs(&blob_block->large_blobs,
                              kMaxLargeOverlapsWithMedium,
                              win, ScrollView::DARK_GREEN, pix);
    // Clear again before we start deleting the blobs in the grid.
    Clear();
    MarkAndDeleteNonTextBlobs(&blob_block->noise_blobs, -1,
                              win, ScrollView::CORAL, pix);
    MarkAndDeleteNonTextBlobs(&blob_block->small_blobs, -1,
                              win, ScrollView::GOLDENROD, pix);
    MarkAndDeleteNonTextBlobs(&blob_block->blobs, -1,
                              win, ScrollView::WHITE, pix);
    if (debug) {
#ifndef GRAPHICS_DISABLED
        win->Update();
#endif  // GRAPHICS_DISABLED
        pixWrite("junkccphotomask.png", pix, IFF_PNG);
#ifndef GRAPHICS_DISABLED
        delete win->AwaitEvent(SVET_DESTROY);
        delete win;
#endif  // GRAPHICS_DISABLED
    }
    return pix;
}
Example #14
0
void plot_word_decisions(              //draw words
        ScrollView *win,   //window tro draw in
        inT16 pitch,  //of block
        TO_ROW *row   //row to draw
) {
    ScrollView::Color colour = ScrollView::MAGENTA;       //current colour
    ScrollView::Color rect_colour;            //fuzzy colour
    inT32 prev_x;                  //end of prev blob
    inT16 blob_count;              //blobs in word
    BLOBNBOX *blob;                //current blob
    TBOX blob_box;                  //bounding box
    //iterator
    BLOBNBOX_IT blob_it = row->blob_list();
    BLOBNBOX_IT start_it = blob_it;//word start

    rect_colour = ScrollView::BLACK;
    prev_x = -MAX_INT16;
    blob_count = 0;
    for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
        blob = blob_it.data();
        blob_box = blob->bounding_box();
        if (!blob->joined_to_prev()
            && blob_box.left() - prev_x > row->max_nonspace) {
            if ((blob_box.left() - prev_x >= row->min_space
                 || blob_box.left() - prev_x > row->space_threshold)
                && blob_count > 0) {
                if (pitch > 0 && textord_show_fixed_cuts)
                    plot_fp_cells(win, colour, &start_it, pitch, blob_count,
                                  &row->projection, row->projection_left,
                                  row->projection_right,
                                  row->xheight * textord_projection_scale);
                blob_count = 0;
                start_it = blob_it;
            }
            if (colour == ScrollView::MAGENTA)
                colour = ScrollView::RED;
            else
                colour = (ScrollView::Color)(colour + 1);
            if (blob_box.left() - prev_x < row->min_space) {
                if (blob_box.left() - prev_x > row->space_threshold)
                    rect_colour = ScrollView::GOLDENROD;
                else
                    rect_colour = ScrollView::CORAL;
                //fill_color_index(win, rect_colour);
                win->Brush(rect_colour);
                win->Rectangle(prev_x, blob_box.bottom(),
                               blob_box.left(), blob_box.top());
            }
        }
        if (!blob->joined_to_prev())
            prev_x = blob_box.right();
        if (blob->cblob() != NULL)
            blob->cblob()->plot(win, colour, colour);
        if (!blob->joined_to_prev() && blob->cblob() != NULL)
            blob_count++;
    }
    if (pitch > 0 && textord_show_fixed_cuts && blob_count > 0)
        plot_fp_cells(win, colour, &start_it, pitch, blob_count,
                      &row->projection, row->projection_left,
                      row->projection_right,
                      row->xheight * textord_projection_scale);
}