Tesseract  3.02
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
char_samp.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * File: char_samp.cpp
3  * Description: Implementation of a Character Bitmap Sample Class
4  * Author: Ahmad Abdulkader
5  * Created: 2007
6  *
7  * (C) Copyright 2008, Google Inc.
8  ** Licensed under the Apache License, Version 2.0 (the "License");
9  ** you may not use this file except in compliance with the License.
10  ** You may obtain a copy of the License at
11  ** http://www.apache.org/licenses/LICENSE-2.0
12  ** Unless required by applicable law or agreed to in writing, software
13  ** distributed under the License is distributed on an "AS IS" BASIS,
14  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  ** See the License for the specific language governing permissions and
16  ** limitations under the License.
17  *
18  **********************************************************************/
19 
20 #include <string.h>
21 #include <string>
22 #include "char_samp.h"
23 #include "cube_utils.h"
24 
25 namespace tesseract {
26 
27 #define MAX_LINE_LEN 1024
28 
30  : Bmp8(0, 0) {
31  left_ = 0;
32  top_ = 0;
33  label32_ = NULL;
34  page_ = -1;
35 }
36 
37 CharSamp::CharSamp(int wid, int hgt)
38  : Bmp8(wid, hgt) {
39  left_ = 0;
40  top_ = 0;
41  label32_ = NULL;
42  page_ = -1;
43 }
44 
45 CharSamp::CharSamp(int left, int top, int wid, int hgt)
46  : Bmp8(wid, hgt)
47  , left_(left)
48  , top_(top) {
49  label32_ = NULL;
50  page_ = -1;
51 }
52 
54  if (label32_ != NULL) {
55  delete []label32_;
56  label32_ = NULL;
57  }
58 }
59 
60 // returns a UTF-8 version of the string label
61 string CharSamp::stringLabel() const {
62  string str = "";
63  if (label32_ != NULL) {
64  string_32 str32(label32_);
65  CubeUtils::UTF32ToUTF8(str32.c_str(), &str);
66  }
67  return str;
68 }
69 
70 // set a the string label using a UTF encoded string
71 void CharSamp::SetLabel(string str) {
72  if (label32_ != NULL) {
73  delete []label32_;
74  label32_ = NULL;
75  }
76  string_32 str32;
77  CubeUtils::UTF8ToUTF32(str.c_str(), &str32);
78  SetLabel(reinterpret_cast<const char_32 *>(str32.c_str()));
79 }
80 
81 // creates a CharSamp object from file
83  unsigned short left;
84  unsigned short top;
85  unsigned short page;
86  unsigned short first_char;
87  unsigned short last_char;
88  unsigned short norm_top;
89  unsigned short norm_bottom;
90  unsigned short norm_aspect_ratio;
91  unsigned int val32;
92 
93  char_32 *label32;
94 
95  // read and check 32 bit marker
96  if (fp->Read(&val32, sizeof(val32)) != sizeof(val32)) {
97  return NULL;
98  }
99  if (val32 != 0xabd0fefe) {
100  return NULL;
101  }
102  // read label length,
103  if (fp->Read(&val32, sizeof(val32)) != sizeof(val32)) {
104  return NULL;
105  }
106  // the label is not null terminated in the file
107  if (val32 > 0) {
108  label32 = new char_32[val32 + 1];
109  if (label32 == NULL) {
110  return NULL;
111  }
112  // read label
113  if (fp->Read(label32, val32 * sizeof(*label32)) !=
114  (val32 * sizeof(*label32))) {
115  return NULL;
116  }
117  // null terminate
118  label32[val32] = 0;
119  } else {
120  label32 = NULL;
121  }
122  // read coordinates
123  if (fp->Read(&page, sizeof(page)) != sizeof(page)) {
124  return NULL;
125  }
126  if (fp->Read(&left, sizeof(left)) != sizeof(left)) {
127  return NULL;
128  }
129  if (fp->Read(&top, sizeof(top)) != sizeof(top)) {
130  return NULL;
131  }
132  if (fp->Read(&first_char, sizeof(first_char)) != sizeof(first_char)) {
133  return NULL;
134  }
135  if (fp->Read(&last_char, sizeof(last_char)) != sizeof(last_char)) {
136  return NULL;
137  }
138  if (fp->Read(&norm_top, sizeof(norm_top)) != sizeof(norm_top)) {
139  return NULL;
140  }
141  if (fp->Read(&norm_bottom, sizeof(norm_bottom)) != sizeof(norm_bottom)) {
142  return NULL;
143  }
144  if (fp->Read(&norm_aspect_ratio, sizeof(norm_aspect_ratio)) !=
145  sizeof(norm_aspect_ratio)) {
146  return NULL;
147  }
148  // create the object
149  CharSamp *char_samp = new CharSamp();
150  if (char_samp == NULL) {
151  return NULL;
152  }
153  // init
154  char_samp->label32_ = label32;
155  char_samp->page_ = page;
156  char_samp->left_ = left;
157  char_samp->top_ = top;
158  char_samp->first_char_ = first_char;
159  char_samp->last_char_ = last_char;
160  char_samp->norm_top_ = norm_top;
161  char_samp->norm_bottom_ = norm_bottom;
162  char_samp->norm_aspect_ratio_ = norm_aspect_ratio;
163  // load the Bmp8 part
164  if (char_samp->LoadFromCharDumpFile(fp) == false) {
165  delete char_samp;
166  return NULL;
167  }
168  return char_samp;
169 }
170 
171 // Load a Char Samp from a dump file
173  unsigned short left;
174  unsigned short top;
175  unsigned short page;
176  unsigned short first_char;
177  unsigned short last_char;
178  unsigned short norm_top;
179  unsigned short norm_bottom;
180  unsigned short norm_aspect_ratio;
181  unsigned int val32;
182  char_32 *label32;
183 
184  // read and check 32 bit marker
185  if (fread(&val32, 1, sizeof(val32), fp) != sizeof(val32)) {
186  return NULL;
187  }
188  if (val32 != 0xabd0fefe) {
189  return NULL;
190  }
191  // read label length,
192  if (fread(&val32, 1, sizeof(val32), fp) != sizeof(val32)) {
193  return NULL;
194  }
195  // the label is not null terminated in the file
196  if (val32 > 0) {
197  label32 = new char_32[val32 + 1];
198  if (label32 == NULL) {
199  return NULL;
200  }
201  // read label
202  if (fread(label32, 1, val32 * sizeof(*label32), fp) !=
203  (val32 * sizeof(*label32))) {
204  return NULL;
205  }
206  // null terminate
207  label32[val32] = 0;
208  } else {
209  label32 = NULL;
210  }
211  // read coordinates
212  if (fread(&page, 1, sizeof(page), fp) != sizeof(page)) {
213  return NULL;
214  }
215  if (fread(&left, 1, sizeof(left), fp) != sizeof(left)) {
216  return NULL;
217  }
218  if (fread(&top, 1, sizeof(top), fp) != sizeof(top)) {
219  return NULL;
220  }
221  if (fread(&first_char, 1, sizeof(first_char), fp) != sizeof(first_char)) {
222  return NULL;
223  }
224  if (fread(&last_char, 1, sizeof(last_char), fp) != sizeof(last_char)) {
225  return NULL;
226  }
227  if (fread(&norm_top, 1, sizeof(norm_top), fp) != sizeof(norm_top)) {
228  return NULL;
229  }
230  if (fread(&norm_bottom, 1, sizeof(norm_bottom), fp) != sizeof(norm_bottom)) {
231  return NULL;
232  }
233  if (fread(&norm_aspect_ratio, 1, sizeof(norm_aspect_ratio), fp) !=
234  sizeof(norm_aspect_ratio)) {
235  return NULL;
236  }
237  // create the object
238  CharSamp *char_samp = new CharSamp();
239  if (char_samp == NULL) {
240  return NULL;
241  }
242  // init
243  char_samp->label32_ = label32;
244  char_samp->page_ = page;
245  char_samp->left_ = left;
246  char_samp->top_ = top;
247  char_samp->first_char_ = first_char;
248  char_samp->last_char_ = last_char;
249  char_samp->norm_top_ = norm_top;
250  char_samp->norm_bottom_ = norm_bottom;
251  char_samp->norm_aspect_ratio_ = norm_aspect_ratio;
252  // load the Bmp8 part
253  if (char_samp->LoadFromCharDumpFile(fp) == false) {
254  return NULL;
255  }
256  return char_samp;
257 }
258 
259 // returns a copy of the charsamp that is scaled to the
260 // specified width and height
261 CharSamp *CharSamp::Scale(int wid, int hgt, bool isotropic) {
262  CharSamp *scaled_samp = new CharSamp(wid, hgt);
263  if (scaled_samp == NULL) {
264  return NULL;
265  }
266  if (scaled_samp->ScaleFrom(this, isotropic) == false) {
267  delete scaled_samp;
268  return NULL;
269  }
270  scaled_samp->left_ = left_;
271  scaled_samp->top_ = top_;
272  scaled_samp->page_ = page_;
273  scaled_samp->SetLabel(label32_);
274  scaled_samp->first_char_ = first_char_;
275  scaled_samp->last_char_ = last_char_;
276  scaled_samp->norm_top_ = norm_top_;
277  scaled_samp->norm_bottom_ = norm_bottom_;
278  scaled_samp->norm_aspect_ratio_ = norm_aspect_ratio_;
279  return scaled_samp;
280 }
281 
282 // Load a Char Samp from a dump file
283 CharSamp *CharSamp::FromRawData(int left, int top, int wid, int hgt,
284  unsigned char *data) {
285  // create the object
286  CharSamp *char_samp = new CharSamp(left, top, wid, hgt);
287  if (char_samp == NULL) {
288  return NULL;
289  }
290  if (char_samp->LoadFromRawData(data) == false) {
291  delete char_samp;
292  return NULL;
293  }
294  return char_samp;
295 }
296 
297 // Saves the charsamp to a dump file
298 bool CharSamp::Save2CharDumpFile(FILE *fp) const {
299  unsigned int val32;
300  // write and check 32 bit marker
301  val32 = 0xabd0fefe;
302  if (fwrite(&val32, 1, sizeof(val32), fp) != sizeof(val32)) {
303  return false;
304  }
305  // write label length
306  val32 = (label32_ == NULL) ? 0 : LabelLen(label32_);
307  if (fwrite(&val32, 1, sizeof(val32), fp) != sizeof(val32)) {
308  return false;
309  }
310  // write label
311  if (label32_ != NULL) {
312  if (fwrite(label32_, 1, val32 * sizeof(*label32_), fp) !=
313  (val32 * sizeof(*label32_))) {
314  return false;
315  }
316  }
317  // write coordinates
318  if (fwrite(&page_, 1, sizeof(page_), fp) != sizeof(page_)) {
319  return false;
320  }
321  if (fwrite(&left_, 1, sizeof(left_), fp) != sizeof(left_)) {
322  return false;
323  }
324  if (fwrite(&top_, 1, sizeof(top_), fp) != sizeof(top_)) {
325  return false;
326  }
327  if (fwrite(&first_char_, 1, sizeof(first_char_), fp) !=
328  sizeof(first_char_)) {
329  return false;
330  }
331  if (fwrite(&last_char_, 1, sizeof(last_char_), fp) != sizeof(last_char_)) {
332  return false;
333  }
334  if (fwrite(&norm_top_, 1, sizeof(norm_top_), fp) != sizeof(norm_top_)) {
335  return false;
336  }
337  if (fwrite(&norm_bottom_, 1, sizeof(norm_bottom_), fp) !=
338  sizeof(norm_bottom_)) {
339  return false;
340  }
341  if (fwrite(&norm_aspect_ratio_, 1, sizeof(norm_aspect_ratio_), fp) !=
342  sizeof(norm_aspect_ratio_)) {
343  return false;
344  }
345  if (SaveBmp2CharDumpFile(fp) == false) {
346  return false;
347  }
348  return true;
349 }
350 
351 // Crop the char samp such that there are no white spaces on any side.
352 // The norm_top_ and norm_bottom_ fields are the character top/bottom
353 // with respect to whatever context the character is being recognized
354 // in (e.g. word bounding box) normalized to a standard size of
355 // 255. Here they default to 0 and 255 (word box boundaries), but
356 // since they are context dependent, they may need to be reset by the
357 // calling function.
359  // get the dimesions of the cropped img
360  int cropped_left = 0;
361  int cropped_top = 0;
362  int cropped_wid = wid_;
363  int cropped_hgt = hgt_;
364  Bmp8::Crop(&cropped_left, &cropped_top,
365  &cropped_wid, &cropped_hgt);
366 
367  if (cropped_wid == 0 || cropped_hgt == 0) {
368  return NULL;
369  }
370  // create the cropped char samp
371  CharSamp *cropped_samp = new CharSamp(left_ + cropped_left,
372  top_ + cropped_top,
373  cropped_wid, cropped_hgt);
374  cropped_samp->SetLabel(label32_);
375  cropped_samp->SetFirstChar(first_char_);
376  cropped_samp->SetLastChar(last_char_);
377  // the following 3 fields may/should be reset by the calling function
378  // using context information, i.e., location of character box
379  // w.r.t. the word bounding box
380  cropped_samp->SetNormAspectRatio(255 *
381  cropped_wid / (cropped_wid + cropped_hgt));
382  cropped_samp->SetNormTop(0);
383  cropped_samp->SetNormBottom(255);
384 
385  // copy the bitmap to the cropped img
386  Copy(cropped_left, cropped_top, cropped_wid, cropped_hgt, cropped_samp);
387  return cropped_samp;
388 }
389 
390 // segment the char samp to connected components
391 // based on contiguity and vertical pixel density histogram
392 ConComp **CharSamp::Segment(int *segment_cnt, bool right_2_left,
393  int max_hist_wnd, int min_con_comp_size) const {
394  // init
395  (*segment_cnt) = 0;
396  int concomp_cnt = 0;
397  int seg_cnt = 0;
398  // find the concomps of the image
399  ConComp **concomp_array = FindConComps(&concomp_cnt, min_con_comp_size);
400  if (concomp_cnt <= 0 || !concomp_array) {
401  if (concomp_array)
402  delete []concomp_array;
403  return NULL;
404  }
405  ConComp **seg_array = NULL;
406  // segment each concomp further using vertical histogram
407  for (int concomp = 0; concomp < concomp_cnt; concomp++) {
408  int concomp_seg_cnt = 0;
409  // segment the concomp
410  ConComp **concomp_seg_array = NULL;
411  ConComp **concomp_alloc_seg =
412  concomp_array[concomp]->Segment(max_hist_wnd, &concomp_seg_cnt);
413  // no segments, add the whole concomp
414  if (concomp_alloc_seg == NULL) {
415  concomp_seg_cnt = 1;
416  concomp_seg_array = concomp_array + concomp;
417  } else {
418  // delete the original concomp, we no longer need it
419  concomp_seg_array = concomp_alloc_seg;
420  delete concomp_array[concomp];
421  }
422  // add the resulting segments
423  for (int seg_idx = 0; seg_idx < concomp_seg_cnt; seg_idx++) {
424  // too small of a segment: ignore
425  if (concomp_seg_array[seg_idx]->Width() < 2 &&
426  concomp_seg_array[seg_idx]->Height() < 2) {
427  delete concomp_seg_array[seg_idx];
428  } else {
429  // add the new segment
430  // extend the segment array
431  if ((seg_cnt % kConCompAllocChunk) == 0) {
432  ConComp **temp_segm_array =
433  new ConComp *[seg_cnt + kConCompAllocChunk];
434  if (temp_segm_array == NULL) {
435  fprintf(stderr, "Cube ERROR (CharSamp::Segment): could not "
436  "allocate additional connected components\n");
437  delete []concomp_seg_array;
438  delete []concomp_array;
439  delete []seg_array;
440  return NULL;
441  }
442  if (seg_cnt > 0) {
443  memcpy(temp_segm_array, seg_array, seg_cnt * sizeof(*seg_array));
444  delete []seg_array;
445  }
446  seg_array = temp_segm_array;
447  }
448  seg_array[seg_cnt++] = concomp_seg_array[seg_idx];
449  }
450  } // segment
451  if (concomp_alloc_seg != NULL) {
452  delete []concomp_alloc_seg;
453  }
454  } // concomp
455  delete []concomp_array;
456 
457  // sort the concomps from Left2Right or Right2Left, based on the reading order
458  if (seg_cnt > 0 && seg_array != NULL) {
459  qsort(seg_array, seg_cnt, sizeof(*seg_array), right_2_left ?
461  }
462  (*segment_cnt) = seg_cnt;
463  return seg_array;
464 }
465 
466 // builds a char samp from a set of connected components
467 CharSamp *CharSamp::FromConComps(ConComp **concomp_array, int strt_concomp,
468  int seg_flags_size, int *seg_flags,
469  bool *left_most, bool *right_most,
470  int word_hgt) {
471  int concomp;
472  int end_concomp;
473  int concomp_cnt = 0;
474  end_concomp = strt_concomp + seg_flags_size;
475  // determine ID range
476  bool once = false;
477  int min_id = -1;
478  int max_id = -1;
479  for (concomp = strt_concomp; concomp < end_concomp; concomp++) {
480  if (!seg_flags || seg_flags[concomp - strt_concomp] != 0) {
481  if (!once) {
482  min_id = concomp_array[concomp]->ID();
483  max_id = concomp_array[concomp]->ID();
484  once = true;
485  } else {
486  UpdateRange(concomp_array[concomp]->ID(), &min_id, &max_id);
487  }
488  concomp_cnt++;
489  }
490  }
491  if (concomp_cnt < 1 || !once || min_id == -1 || max_id == -1) {
492  return NULL;
493  }
494  // alloc memo for computing leftmost and right most attributes
495  int id_cnt = max_id - min_id + 1;
496  bool *id_exist = new bool[id_cnt];
497  bool *left_most_exist = new bool[id_cnt];
498  bool *right_most_exist = new bool[id_cnt];
499  if (!id_exist || !left_most_exist || !right_most_exist)
500  return NULL;
501  memset(id_exist, 0, id_cnt * sizeof(*id_exist));
502  memset(left_most_exist, 0, id_cnt * sizeof(*left_most_exist));
503  memset(right_most_exist, 0, id_cnt * sizeof(*right_most_exist));
504  // find the dimensions of the charsamp
505  once = false;
506  int left = -1;
507  int right = -1;
508  int top = -1;
509  int bottom = -1;
510  int unq_ids = 0;
511  int unq_left_most = 0;
512  int unq_right_most = 0;
513  for (concomp = strt_concomp; concomp < end_concomp; concomp++) {
514  if (!seg_flags || seg_flags[concomp - strt_concomp] != 0) {
515  if (!once) {
516  left = concomp_array[concomp]->Left();
517  right = concomp_array[concomp]->Right();
518  top = concomp_array[concomp]->Top();
519  bottom = concomp_array[concomp]->Bottom();
520  once = true;
521  } else {
522  UpdateRange(concomp_array[concomp]->Left(),
523  concomp_array[concomp]->Right(), &left, &right);
524  UpdateRange(concomp_array[concomp]->Top(),
525  concomp_array[concomp]->Bottom(), &top, &bottom);
526  }
527  // count unq ids, unq left most and right mosts ids
528  int concomp_id = concomp_array[concomp]->ID() - min_id;
529  if (!id_exist[concomp_id]) {
530  id_exist[concomp_id] = true;
531  unq_ids++;
532  }
533  if (concomp_array[concomp]->LeftMost()) {
534  if (left_most_exist[concomp_id] == false) {
535  left_most_exist[concomp_id] = true;
536  unq_left_most++;
537  }
538  }
539  if (concomp_array[concomp]->RightMost()) {
540  if (right_most_exist[concomp_id] == false) {
541  right_most_exist[concomp_id] = true;
542  unq_right_most++;
543  }
544  }
545  }
546  }
547  delete []id_exist;
548  delete []left_most_exist;
549  delete []right_most_exist;
550  if (!once || left == -1 || top == -1 || right == -1 || bottom == -1) {
551  return NULL;
552  }
553  (*left_most) = (unq_left_most >= unq_ids);
554  (*right_most) = (unq_right_most >= unq_ids);
555  // create the char sample object
556  CharSamp *samp = new CharSamp(left, top, right - left + 1, bottom - top + 1);
557  if (!samp) {
558  return NULL;
559  }
560 
561  // set the foreground pixels
562  for (concomp = strt_concomp; concomp < end_concomp; concomp++) {
563  if (!seg_flags || seg_flags[concomp - strt_concomp] != 0) {
564  ConCompPt *pt_ptr = concomp_array[concomp]->Head();
565  while (pt_ptr) {
566  samp->line_buff_[pt_ptr->y() - top][pt_ptr->x() - left] = 0;
567  pt_ptr = pt_ptr->Next();
568  }
569  }
570  }
571  return samp;
572 }
573 
574 // clones the object
576  // create the cropped char samp
577  CharSamp *samp = new CharSamp(left_, top_, wid_, hgt_);
578  samp->SetLabel(label32_);
579  samp->SetFirstChar(first_char_);
580  samp->SetLastChar(last_char_);
581  samp->SetNormTop(norm_top_);
582  samp->SetNormBottom(norm_bottom_);
583  samp->SetNormAspectRatio(norm_aspect_ratio_);
584  // copy the bitmap to the cropped img
585  Copy(0, 0, wid_, hgt_, samp);
586  return samp;
587 }
588 
589 // Load a Char Samp from a dump file
590 CharSamp *CharSamp::FromCharDumpFile(unsigned char **raw_data_ptr) {
591  unsigned int val32;
592  char_32 *label32;
593  unsigned char *raw_data = *raw_data_ptr;
594 
595  // read and check 32 bit marker
596  memcpy(&val32, raw_data, sizeof(val32));
597  raw_data += sizeof(val32);
598  if (val32 != 0xabd0fefe) {
599  return NULL;
600  }
601  // read label length,
602  memcpy(&val32, raw_data, sizeof(val32));
603  raw_data += sizeof(val32);
604  // the label is not null terminated in the file
605  if (val32 > 0) {
606  label32 = new char_32[val32 + 1];
607  if (label32 == NULL) {
608  return NULL;
609  }
610  // read label
611  memcpy(label32, raw_data, val32 * sizeof(*label32));
612  raw_data += (val32 * sizeof(*label32));
613  // null terminate
614  label32[val32] = 0;
615  } else {
616  label32 = NULL;
617  }
618 
619  // create the object
620  CharSamp *char_samp = new CharSamp();
621  if (char_samp == NULL) {
622  return NULL;
623  }
624 
625  // read coordinates
626  char_samp->label32_ = label32;
627  memcpy(&char_samp->page_, raw_data, sizeof(char_samp->page_));
628  raw_data += sizeof(char_samp->page_);
629  memcpy(&char_samp->left_, raw_data, sizeof(char_samp->left_));
630  raw_data += sizeof(char_samp->left_);
631  memcpy(&char_samp->top_, raw_data, sizeof(char_samp->top_));
632  raw_data += sizeof(char_samp->top_);
633  memcpy(&char_samp->first_char_, raw_data, sizeof(char_samp->first_char_));
634  raw_data += sizeof(char_samp->first_char_);
635  memcpy(&char_samp->last_char_, raw_data, sizeof(char_samp->last_char_));
636  raw_data += sizeof(char_samp->last_char_);
637  memcpy(&char_samp->norm_top_, raw_data, sizeof(char_samp->norm_top_));
638  raw_data += sizeof(char_samp->norm_top_);
639  memcpy(&char_samp->norm_bottom_, raw_data, sizeof(char_samp->norm_bottom_));
640  raw_data += sizeof(char_samp->norm_bottom_);
641  memcpy(&char_samp->norm_aspect_ratio_, raw_data,
642  sizeof(char_samp->norm_aspect_ratio_));
643  raw_data += sizeof(char_samp->norm_aspect_ratio_);
644 
645  // load the Bmp8 part
646  if (char_samp->LoadFromCharDumpFile(&raw_data) == false) {
647  delete char_samp;
648  return NULL;
649  }
650 
651  (*raw_data_ptr) = raw_data;
652  return char_samp;
653 }
654 
655 // computes the features corresponding to the char sample
656 bool CharSamp::ComputeFeatures(int conv_grid_size, float *features) {
657  // Create a scaled BMP
658  CharSamp *scaled_bmp = Scale(conv_grid_size, conv_grid_size);
659  if (!scaled_bmp) {
660  return false;
661  }
662  // prepare input
663  unsigned char *buff = scaled_bmp->RawData();
664  // bitmap features
665  int input;
666  int bmp_size = conv_grid_size * conv_grid_size;
667  for (input = 0; input < bmp_size; input++) {
668  features[input] = 255.0f - (1.0f * buff[input]);
669  }
670  // word context features
671  features[input++] = FirstChar();
672  features[input++] = LastChar();
673  features[input++] = NormTop();
674  features[input++] = NormBottom();
675  features[input++] = NormAspectRatio();
676  delete scaled_bmp;
677  return true;
678 }
679 } // namespace tesseract