tesseract
3.03
|
00001 /********************************************************************** 00002 * File: text2image.cpp 00003 * Description: Program to generate OCR training pages. Given a text file it 00004 * outputs an image with a given font and degradation. 00005 * 00006 * Note that since the results depend on the fonts available on 00007 * your system, running the code on a different machine, or 00008 * different OS, or even at a different time on the same machine, 00009 * may produce different fonts even if --font is given explicitly. 00010 * To see names of available fonts, use --list_available_fonts with 00011 * the appropriate --fonts_dir path. 00012 * Specifying --use_only_legacy_fonts will restrict the available 00013 * fonts to those listed in legacy_fonts.h 00014 * 00015 * Authors: Ranjith Unnikrishnan, Ray Smith 00016 * Created: Tue Nov 19 2013 00017 * 00018 * (C) Copyright 2013, Google Inc. 00019 * Licensed under the Apache License, Version 2.0 (the "License"); 00020 * you may not use this file except in compliance with the License. 00021 * You may obtain a copy of the License at 00022 * http://www.apache.org/licenses/LICENSE-2.0 00023 * Unless required by applicable law or agreed to in writing, software 00024 * distributed under the License is distributed on an "AS IS" BASIS, 00025 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00026 * See the License for the specific language governing permissions and 00027 * limitations under the License. 00028 * 00029 **********************************************************************/ 00030 00031 #include <stdlib.h> 00032 #include <string.h> 00033 #include <algorithm> 00034 #include <iostream> 00035 #include <map> 00036 #include <string> 00037 #include <utility> 00038 #include <vector> 00039 00040 #include "allheaders.h" // from leptonica 00041 #include "boxchar.h" 00042 #include "commandlineflags.h" 00043 #include "degradeimage.h" 00044 #include "errcode.h" 00045 #include "fileio.h" 00046 #include "normstrngs.h" 00047 #include "stringrenderer.h" 00048 #include "tlog.h" 00049 #include "unicharset.h" 00050 #include "util.h" 00051 00052 #ifdef USE_STD_NAMESPACE 00053 using std::make_pair; 00054 using std::map; 00055 using std::pair; 00056 #endif 00057 00058 // The text input file. 00059 STRING_PARAM_FLAG(text, "", "File name of text input to process"); 00060 00061 // The text output file. 00062 STRING_PARAM_FLAG(outputbase, "", "Basename for output image/box file"); 00063 00064 // Degrade the rendered image to mimic scanner quality. 00065 BOOL_PARAM_FLAG(degrade_image, true, 00066 "Degrade rendered image with speckle noise, dilation/erosion " 00067 "and rotation"); 00068 00069 // Degradation to apply to the image. 00070 INT_PARAM_FLAG(exposure, 0, "Exposure level in photocopier"); 00071 00072 // Output image resolution. 00073 INT_PARAM_FLAG(resolution, 300, "Pixels per inch"); 00074 00075 // Width of output image (in pixels). 00076 INT_PARAM_FLAG(xsize, 3600, "Width of output image"); 00077 00078 // Max height of output image (in pixels). 00079 INT_PARAM_FLAG(ysize, 4800, "Height of output image"); 00080 00081 // Margin around text (in pixels). 00082 INT_PARAM_FLAG(margin, 100, "Margin round edges of image"); 00083 00084 // Size of text (in points). 00085 INT_PARAM_FLAG(ptsize, 12, "Size of printed text"); 00086 00087 // Inter-character space (in ems). 00088 DOUBLE_PARAM_FLAG(char_spacing, 0, "Inter-character space in ems"); 00089 00090 // Inter-line space (in pixels). 00091 INT_PARAM_FLAG(leading, 12, "Inter-line space (in pixels)"); 00092 00093 // Layout and glyph orientation on rendering. 00094 STRING_PARAM_FLAG(writing_mode, "horizontal", 00095 "Specify one of the following writing" 00096 " modes.\n" 00097 "'horizontal' : Render regular horizontal text. (default)\n" 00098 "'vertical' : Render vertical text. Glyph orientation is" 00099 " selected by Pango.\n" 00100 "'vertical-upright' : Render vertical text. Glyph " 00101 " orientation is set to be upright."); 00102 00103 INT_PARAM_FLAG(box_padding, 0, "Padding around produced bounding boxes"); 00104 00105 BOOL_PARAM_FLAG(strip_unrenderable_words, false, 00106 "Remove unrenderable words from source text"); 00107 00108 // Font name. 00109 STRING_PARAM_FLAG(font, "Arial", "Font description name to use"); 00110 00111 BOOL_PARAM_FLAG(ligatures, false, 00112 "Rebuild and render ligatures"); 00113 00114 BOOL_PARAM_FLAG(find_fonts, false, 00115 "Search for all fonts that can render the text"); 00116 BOOL_PARAM_FLAG(render_per_font, true, 00117 "If find_fonts==true, render each font to its own image. " 00118 "Image filenames are of the form output_name.font_name.tif"); 00119 00120 BOOL_PARAM_FLAG(list_available_fonts, false, "List available fonts and quit."); 00121 00122 BOOL_PARAM_FLAG(render_ngrams, false, "Put each space-separated entity from the" 00123 " input file into one bounding box. The ngrams in the input" 00124 " file will be randomly permuted before rendering (so that" 00125 " there is sufficient variety of characters on each line)."); 00126 00127 BOOL_PARAM_FLAG(output_word_boxes, false, 00128 "Output word bounding boxes instead of character boxes. " 00129 "This is used for Cube training, and implied by " 00130 "--render_ngrams."); 00131 00132 STRING_PARAM_FLAG(unicharset_file, "", 00133 "File with characters in the unicharset. If --render_ngrams" 00134 " is true and --unicharset_file is specified, ngrams with" 00135 " characters that are not in unicharset will be omitted"); 00136 00137 BOOL_PARAM_FLAG(bidirectional_rotation, false, 00138 "Rotate the generated characters both ways."); 00139 00140 BOOL_PARAM_FLAG(only_extract_font_properties, false, 00141 "Assumes that the input file contains a list of ngrams. Renders" 00142 " each ngram, extracts spacing properties and records them in" 00143 " output_base/[font_name].fontinfo file."); 00144 00145 // Use these flags to output zero-padded, square individual character images 00146 BOOL_PARAM_FLAG(output_individual_glyph_images, false, 00147 "If true also outputs individual character images"); 00148 INT_PARAM_FLAG(glyph_resized_size, 0, 00149 "Each glyph is square with this side length in pixels"); 00150 INT_PARAM_FLAG(glyph_num_border_pixels_to_pad, 0, 00151 "Final_size=glyph_resized_size+2*glyph_num_border_pixels_to_pad"); 00152 00153 namespace tesseract { 00154 00155 struct SpacingProperties { 00156 SpacingProperties() : x_gap_before(0), x_gap_after(0) {} 00157 SpacingProperties(int b, int a) : x_gap_before(b), x_gap_after(a) {} 00158 // These values are obtained from FT_Glyph_Metrics struct 00159 // used by the FreeType font engine. 00160 int x_gap_before; // horizontal x bearing 00161 int x_gap_after; // horizontal advance - x_gap_before - width 00162 map<string, int> kerned_x_gaps; 00163 }; 00164 00165 static bool IsWhitespaceBox(const BoxChar* boxchar) { 00166 return (boxchar->box() == NULL || 00167 SpanUTF8Whitespace(boxchar->ch().c_str())); 00168 } 00169 00170 static string StringReplace(const string& in, 00171 const string& oldsub, const string& newsub) { 00172 string out; 00173 int start_pos = 0; 00174 do { 00175 int pos = in.find(oldsub, start_pos); 00176 if (pos == string::npos) break; 00177 out.append(in.data() + start_pos, pos - start_pos); 00178 out.append(newsub.data(), newsub.length()); 00179 start_pos = pos + oldsub.length(); 00180 } while (true); 00181 out.append(in.data() + start_pos, in.length() - start_pos); 00182 return out; 00183 } 00184 00185 // Assumes that each word (whitespace-separated entity) in text is a bigram. 00186 // Renders the bigrams and calls FontInfo::GetSpacingProperties() to 00187 // obtain spacing information. Produces the output .fontinfo file with a line 00188 // per unichar of the form: 00189 // unichar space_before space_after kerned1 kerned_space1 kerned2 ... 00190 // Fox example, if unichar "A" has spacing of 0 pixels before and -1 pixels 00191 // after, is kerned with "V" resulting in spacing of "AV" to be -7 and kerned 00192 // with "T", such that "AT" has spacing of -5, the entry/line for unichar "A" 00193 // in .fontinfo file will be: 00194 // A 0 -1 T -5 V -7 00195 void ExtractFontProperties(const string &utf8_text, 00196 StringRenderer *render, 00197 const string &output_base) { 00198 map<string, SpacingProperties> spacing_map; 00199 map<string, SpacingProperties>::iterator spacing_map_it0; 00200 map<string, SpacingProperties>::iterator spacing_map_it1; 00201 int x_bearing, x_advance; 00202 int len = utf8_text.length(); 00203 int offset = 0; 00204 const char* text = utf8_text.c_str(); 00205 while (offset < len) { 00206 offset += render->RenderToImage(text + offset, strlen(text + offset), NULL); 00207 const vector<BoxChar*> &boxes = render->GetBoxes(); 00208 00209 // If the page break split a bigram, correct the offset so we try the bigram 00210 // on the next iteration. 00211 if (boxes.size() > 2 && !IsWhitespaceBox(boxes[boxes.size() - 1]) && 00212 IsWhitespaceBox(boxes[boxes.size() - 2])) { 00213 if (boxes.size() > 3) { 00214 tprintf("WARNING: Adjusting to bad page break after '%s%s'\n", 00215 boxes[boxes.size() - 4]->ch().c_str(), 00216 boxes[boxes.size() - 3]->ch().c_str()); 00217 } 00218 offset -= boxes[boxes.size() - 1]->ch().size(); 00219 } 00220 00221 for (int b = 0; b < boxes.size(); b += 2) { 00222 while (b < boxes.size() && IsWhitespaceBox(boxes[b])) ++b; 00223 if (b + 1 >= boxes.size()) break; 00224 const string &ch0 = boxes[b]->ch(); 00225 // We encountered a ligature. This happens in at least two scenarios: 00226 // One is when the rendered bigram forms a grapheme cluster (eg. the 00227 // second character in the bigram is a combining vowel), in which case we 00228 // correctly output only one bounding box. 00229 // A second far less frequent case is when caused some fonts like 'DejaVu 00230 // Sans Ultra-Light' force Pango to render a ligatured character even if 00231 // the input consists of the separated characters. NOTE(ranjith): As per 00232 // behdad@ this is not currently controllable at the level of the Pango 00233 // API. 00234 // Safeguard against these cases here by just skipping the bigram. 00235 if (IsWhitespaceBox(boxes[b+1])) { 00236 tprintf("WARNING: Found unexpected ligature: %s\n", ch0.c_str()); 00237 continue; 00238 } 00239 int xgap = (boxes[b+1]->box()->x - 00240 (boxes[b]->box()->x + boxes[b]->box()->w)); 00241 spacing_map_it0 = spacing_map.find(ch0); 00242 int ok_count = 0; 00243 if (spacing_map_it0 == spacing_map.end() && 00244 render->font().GetSpacingProperties(ch0, &x_bearing, &x_advance)) { 00245 spacing_map[ch0] = SpacingProperties( 00246 x_bearing, x_advance - x_bearing - boxes[b]->box()->w); 00247 spacing_map_it0 = spacing_map.find(ch0); 00248 ++ok_count; 00249 } 00250 const string &ch1 = boxes[b+1]->ch(); 00251 tlog(3, "%s%s\n", ch0.c_str(), ch1.c_str()); 00252 spacing_map_it1 = spacing_map.find(ch1); 00253 if (spacing_map_it1 == spacing_map.end() && 00254 render->font().GetSpacingProperties(ch1, &x_bearing, &x_advance)) { 00255 spacing_map[ch1] = SpacingProperties( 00256 x_bearing, x_advance - x_bearing - boxes[b+1]->box()->w); 00257 spacing_map_it1 = spacing_map.find(ch1); 00258 ++ok_count; 00259 } 00260 if (ok_count == 2 && xgap != (spacing_map_it0->second.x_gap_after + 00261 spacing_map_it1->second.x_gap_before)) { 00262 spacing_map_it0->second.kerned_x_gaps[ch1] = xgap; 00263 } 00264 } 00265 render->ClearBoxes(); 00266 } 00267 string output_string; 00268 const int kBufSize = 1024; 00269 char buf[kBufSize]; 00270 snprintf(buf, kBufSize, "%d\n", static_cast<int>(spacing_map.size())); 00271 output_string.append(buf); 00272 map<string, SpacingProperties>::const_iterator spacing_map_it; 00273 for (spacing_map_it = spacing_map.begin(); 00274 spacing_map_it != spacing_map.end(); ++spacing_map_it) { 00275 snprintf(buf, kBufSize, 00276 "%s %d %d %d", spacing_map_it->first.c_str(), 00277 spacing_map_it->second.x_gap_before, 00278 spacing_map_it->second.x_gap_after, 00279 static_cast<int>(spacing_map_it->second.kerned_x_gaps.size())); 00280 output_string.append(buf); 00281 map<string, int>::const_iterator kern_it; 00282 for (kern_it = spacing_map_it->second.kerned_x_gaps.begin(); 00283 kern_it != spacing_map_it->second.kerned_x_gaps.end(); ++kern_it) { 00284 snprintf(buf, kBufSize, 00285 " %s %d", kern_it->first.c_str(), kern_it->second); 00286 output_string.append(buf); 00287 } 00288 output_string.append("\n"); 00289 } 00290 File::WriteStringToFileOrDie(output_string, output_base + ".fontinfo"); 00291 } 00292 00293 bool MakeIndividualGlyphs(Pix* pix, 00294 const vector<BoxChar*>& vbox, 00295 const int input_tiff_page) { 00296 // If checks fail, return false without exiting text2image 00297 if (!pix) { 00298 tprintf("ERROR: MakeIndividualGlyphs(): Input Pix* is NULL\n"); 00299 return false; 00300 } else if (FLAGS_glyph_resized_size <= 0) { 00301 tprintf("ERROR: --glyph_resized_size must be positive\n"); 00302 return false; 00303 } else if (FLAGS_glyph_num_border_pixels_to_pad < 0) { 00304 tprintf("ERROR: --glyph_num_border_pixels_to_pad must be 0 or positive\n"); 00305 return false; 00306 } 00307 00308 const int n_boxes = vbox.size(); 00309 int n_boxes_saved = 0; 00310 int current_tiff_page = 0; 00311 int y_previous = 0; 00312 static int glyph_count = 0; 00313 for (int i = 0; i < n_boxes; i++) { 00314 // Get one bounding box 00315 Box* b = vbox[i]->mutable_box(); 00316 if (!b) continue; 00317 const int x = b->x; 00318 const int y = b->y; 00319 const int w = b->w; 00320 const int h = b->h; 00321 // Check present tiff page (for multipage tiff) 00322 if (y < y_previous-pixGetHeight(pix)/10) { 00323 tprintf("ERROR: Wrap-around encountered, at i=%d\n", i); 00324 current_tiff_page++; 00325 } 00326 if (current_tiff_page < input_tiff_page) continue; 00327 else if (current_tiff_page > input_tiff_page) break; 00328 // Check box validity 00329 if (x < 0 || y < 0 || 00330 (x+w-1) >= pixGetWidth(pix) || 00331 (y+h-1) >= pixGetHeight(pix)) { 00332 tprintf("ERROR: MakeIndividualGlyphs(): Index out of range, at i=%d" 00333 " (x=%d, y=%d, w=%d, h=%d\n)", i, x, y, w, h); 00334 continue; 00335 } else if (w < FLAGS_glyph_num_border_pixels_to_pad && 00336 h < FLAGS_glyph_num_border_pixels_to_pad) { 00337 tprintf("ERROR: Input image too small to be a character, at i=%d\n", i); 00338 continue; 00339 } 00340 // Crop the boxed character 00341 Pix* pix_glyph = pixClipRectangle(pix, b, NULL); 00342 if (!pix_glyph) { 00343 tprintf("ERROR: MakeIndividualGlyphs(): Failed to clip, at i=%d\n", i); 00344 continue; 00345 } 00346 // Resize to square 00347 Pix* pix_glyph_sq = pixScaleToSize(pix_glyph, 00348 FLAGS_glyph_resized_size, 00349 FLAGS_glyph_resized_size); 00350 if (!pix_glyph_sq) { 00351 tprintf("ERROR: MakeIndividualGlyphs(): Failed to resize, at i=%d\n", i); 00352 continue; 00353 } 00354 // Zero-pad 00355 Pix* pix_glyph_sq_pad = pixAddBorder(pix_glyph_sq, 00356 FLAGS_glyph_num_border_pixels_to_pad, 00357 0); 00358 if (!pix_glyph_sq_pad) { 00359 tprintf("ERROR: MakeIndividualGlyphs(): Failed to zero-pad, at i=%d\n", i); 00360 continue; 00361 } 00362 // Write out 00363 Pix* pix_glyph_sq_pad_8 = pixConvertTo8(pix_glyph_sq_pad, false); 00364 char filename[1024]; 00365 snprintf(filename, 1024, "%s_%d.jpg", FLAGS_outputbase.c_str(), 00366 glyph_count++); 00367 if (pixWriteJpeg(filename, pix_glyph_sq_pad_8, 100, 0)) { 00368 tprintf("ERROR: MakeIndividualGlyphs(): Failed to write JPEG to %s," 00369 " at i=%d\n", filename, i); 00370 continue; 00371 } 00372 00373 pixDestroy(&pix_glyph); 00374 pixDestroy(&pix_glyph_sq); 00375 pixDestroy(&pix_glyph_sq_pad); 00376 pixDestroy(&pix_glyph_sq_pad_8); 00377 n_boxes_saved++; 00378 y_previous = y; 00379 } 00380 if (n_boxes_saved == 0) { 00381 return false; 00382 } else { 00383 tprintf("Total number of characters saved = %d\n", n_boxes_saved); 00384 return true; 00385 } 00386 } 00387 } // namespace tesseract 00388 00389 using tesseract::DegradeImage; 00390 using tesseract::ExtractFontProperties; 00391 using tesseract::File; 00392 using tesseract::FontUtils; 00393 using tesseract::SpanUTF8NotWhitespace; 00394 using tesseract::SpanUTF8Whitespace; 00395 using tesseract::StringRenderer; 00396 00397 int main(int argc, char** argv) { 00398 tesseract::ParseCommandLineFlags(argv[0], &argc, &argv, true); 00399 00400 if (FLAGS_list_available_fonts) { 00401 const vector<string>& all_fonts = FontUtils::ListAvailableFonts(); 00402 for (int i = 0; i < all_fonts.size(); ++i) { 00403 tprintf("%3d: %s\n", i, all_fonts[i].c_str()); 00404 ASSERT_HOST_MSG(FontUtils::IsAvailableFont(all_fonts[i].c_str()), 00405 "Font %s is unrecognized.\n", all_fonts[i].c_str()); 00406 } 00407 return EXIT_SUCCESS; 00408 } 00409 // Check validity of input flags. 00410 ASSERT_HOST_MSG(!FLAGS_text.empty(), "Text file missing!\n"); 00411 ASSERT_HOST_MSG(!FLAGS_outputbase.empty(), "Output file missing!\n"); 00412 ASSERT_HOST_MSG(FLAGS_render_ngrams || FLAGS_unicharset_file.empty(), 00413 "Use --unicharset_file only if --render_ngrams is set.\n"); 00414 00415 ASSERT_HOST_MSG(FLAGS_find_fonts || 00416 FontUtils::IsAvailableFont(FLAGS_font.c_str()), 00417 "Could not find font named %s\n", FLAGS_font.c_str()); 00418 00419 if (FLAGS_render_ngrams) 00420 FLAGS_output_word_boxes = true; 00421 00422 char font_desc_name[1024]; 00423 snprintf(font_desc_name, 1024, "%s %d", FLAGS_font.c_str(), 00424 static_cast<int>(FLAGS_ptsize)); 00425 StringRenderer render(font_desc_name, FLAGS_xsize, FLAGS_ysize); 00426 render.set_add_ligatures(FLAGS_ligatures); 00427 render.set_leading(FLAGS_leading); 00428 render.set_resolution(FLAGS_resolution); 00429 render.set_char_spacing(FLAGS_char_spacing * FLAGS_ptsize); 00430 render.set_h_margin(FLAGS_margin); 00431 render.set_v_margin(FLAGS_margin); 00432 render.set_output_word_boxes(FLAGS_output_word_boxes); 00433 render.set_box_padding(FLAGS_box_padding); 00434 render.set_strip_unrenderable_words(FLAGS_strip_unrenderable_words); 00435 00436 // Set text rendering orientation and their forms. 00437 if (FLAGS_writing_mode == "horizontal") { 00438 // Render regular horizontal text (default). 00439 render.set_vertical_text(false); 00440 render.set_gravity_hint_strong(false); 00441 render.set_render_fullwidth_latin(false); 00442 } else if (FLAGS_writing_mode == "vertical") { 00443 // Render vertical text. Glyph orientation is selected by Pango. 00444 render.set_vertical_text(true); 00445 render.set_gravity_hint_strong(false); 00446 render.set_render_fullwidth_latin(false); 00447 } else if (FLAGS_writing_mode == "vertical-upright") { 00448 // Render vertical text. Glyph orientation is set to be upright. 00449 // Also Basic Latin characters are converted to their fullwidth forms 00450 // on rendering, since fullwidth Latin characters are well designed to fit 00451 // vertical text lines, while .box files store halfwidth Basic Latin 00452 // unichars. 00453 render.set_vertical_text(true); 00454 render.set_gravity_hint_strong(true); 00455 render.set_render_fullwidth_latin(true); 00456 } else { 00457 TLOG_FATAL("Invalid writing mode : %s\n", FLAGS_writing_mode.c_str()); 00458 } 00459 00460 string src_utf8; 00461 // This c_str is NOT redundant! 00462 File::ReadFileToStringOrDie(FLAGS_text.c_str(), &src_utf8); 00463 00464 // Remove the unicode mark if present. 00465 if (strncmp(src_utf8.c_str(), "\xef\xbb\xbf", 3) == 0) { 00466 src_utf8.erase(0, 3); 00467 } 00468 tlog(1, "Render string of size %d\n", src_utf8.length()); 00469 00470 if (FLAGS_render_ngrams || FLAGS_only_extract_font_properties) { 00471 // Try to preserve behavior of old text2image by expanding inter-word 00472 // spaces by a factor of 4. 00473 const string kSeparator = FLAGS_render_ngrams ? " " : " "; 00474 // Also restrict the number of charactes per line to try and avoid 00475 // line-breaking in the middle of words like "-A", "R$" etc. which are 00476 // otherwise allowed by the standard unicode line-breaking rules. 00477 const int kCharsPerLine = (FLAGS_ptsize > 20) ? 50 : 100; 00478 string rand_utf8; 00479 UNICHARSET unicharset; 00480 if (FLAGS_render_ngrams && !FLAGS_unicharset_file.empty() && 00481 !unicharset.load_from_file(FLAGS_unicharset_file.c_str())) { 00482 TLOG_FATAL("Failed to load unicharset from file %s\n", 00483 FLAGS_unicharset_file.c_str()); 00484 } 00485 00486 // If we are rendering ngrams that will be OCRed later, shuffle them so that 00487 // tesseract does not have difficulties finding correct baseline, word 00488 // spaces, etc. 00489 const char *str8 = src_utf8.c_str(); 00490 int len = src_utf8.length(); 00491 int step; 00492 vector<pair<int, int> > offsets; 00493 int offset = SpanUTF8Whitespace(str8); 00494 while (offset < len) { 00495 step = SpanUTF8NotWhitespace(str8 + offset); 00496 offsets.push_back(make_pair(offset, step)); 00497 offset += step; 00498 offset += SpanUTF8Whitespace(str8 + offset); 00499 } 00500 if (FLAGS_render_ngrams) 00501 std::random_shuffle(offsets.begin(), offsets.end()); 00502 00503 for (int i = 0, line = 1; i < offsets.size(); ++i) { 00504 const char *curr_pos = str8 + offsets[i].first; 00505 int ngram_len = offsets[i].second; 00506 // Skip words that contain characters not in found in unicharset. 00507 if (!FLAGS_unicharset_file.empty() && 00508 !unicharset.encodable_string(curr_pos, NULL)) { 00509 continue; 00510 } 00511 rand_utf8.append(curr_pos, ngram_len); 00512 if (rand_utf8.length() > line * kCharsPerLine) { 00513 rand_utf8.append(" \n"); 00514 ++line; 00515 if (line & 0x1) rand_utf8.append(kSeparator); 00516 } else { 00517 rand_utf8.append(kSeparator); 00518 } 00519 } 00520 tlog(1, "Rendered ngram string of size %d\n", rand_utf8.length()); 00521 src_utf8.swap(rand_utf8); 00522 } 00523 if (FLAGS_only_extract_font_properties) { 00524 tprintf("Extracting font properties only\n"); 00525 ExtractFontProperties(src_utf8, &render, FLAGS_outputbase.c_str()); 00526 tprintf("Done!\n"); 00527 return 0; 00528 } 00529 00530 int im = 0; 00531 vector<float> page_rotation; 00532 const char* to_render_utf8 = src_utf8.c_str(); 00533 00534 // We use a two pass mechanism to rotate images in both direction. 00535 // The first pass(0) will rotate the images in random directions and 00536 // the second pass(1) will mirror those rotations. 00537 int num_pass = FLAGS_bidirectional_rotation ? 2 : 1; 00538 for (int pass = 0; pass < num_pass; ++pass) { 00539 int page_num = 0; 00540 string font_used; 00541 for (int offset = 0; offset < strlen(to_render_utf8); ++im, ++page_num) { 00542 tlog(1, "Starting page %d\n", im); 00543 Pix* pix = NULL; 00544 if (FLAGS_find_fonts) { 00545 offset += render.RenderAllFontsToImage(to_render_utf8 + offset, 00546 strlen(to_render_utf8 + offset), 00547 &font_used, &pix); 00548 } else { 00549 offset += render.RenderToImage(to_render_utf8 + offset, 00550 strlen(to_render_utf8 + offset), &pix); 00551 } 00552 if (pix != NULL) { 00553 float rotation = 0; 00554 if (pass == 1) { 00555 // Pass 2, do mirror rotation. 00556 rotation = -1 * page_rotation[page_num]; 00557 } 00558 if (FLAGS_degrade_image) { 00559 pix = DegradeImage(pix, FLAGS_exposure, &rotation); 00560 } 00561 render.RotatePageBoxes(rotation); 00562 00563 if (pass == 0) { 00564 // Pass 1, rotate randomly and store the rotation.. 00565 page_rotation.push_back(rotation); 00566 } 00567 00568 Pix* gray_pix = pixConvertTo8(pix, false); 00569 pixDestroy(&pix); 00570 Pix* binary = pixThresholdToBinary(gray_pix, 128); 00571 pixDestroy(&gray_pix); 00572 char tiff_name[1024]; 00573 if (FLAGS_find_fonts && FLAGS_render_per_font) { 00574 string fontname_for_file = tesseract::StringReplace( 00575 font_used, " ", "_"); 00576 snprintf(tiff_name, 1024, "%s.%s.tif", FLAGS_outputbase.c_str(), 00577 fontname_for_file.c_str()); 00578 pixWriteTiff(tiff_name, binary, IFF_TIFF_G4, "w"); 00579 } else { 00580 snprintf(tiff_name, 1024, "%s.tif", FLAGS_outputbase.c_str()); 00581 pixWriteTiff(tiff_name, binary, IFF_TIFF_G4, im == 0 ? "w" : "a"); 00582 } 00583 tprintf("Rendered page %d to file %s\n", im, tiff_name); 00584 // Make individual glyphs 00585 if (FLAGS_output_individual_glyph_images) { 00586 if (!MakeIndividualGlyphs(binary, render.GetBoxes(), im)) { 00587 tprintf("ERROR: Individual glyphs not saved\n"); 00588 } 00589 } 00590 pixDestroy(&binary); 00591 } 00592 } 00593 } 00594 if (!FLAGS_find_fonts) { 00595 string box_name = FLAGS_outputbase.c_str(); 00596 box_name += ".box"; 00597 render.WriteAllBoxes(box_name); 00598 } 00599 00600 return 0; 00601 }