1 /******************************************************************************
2  ** Filename:    intproto.c
3  ** Purpose:     Definition of data structures for integer protos.
4  ** Author:      Dan Johnson
5  **
6  ** (c) Copyright Hewlett-Packard Company, 1988.
7  ** Licensed under the Apache License, Version 2.0 (the "License");
8  ** you may not use this file except in compliance with the License.
9  ** You may obtain a copy of the License at
10  ** http://www.apache.org/licenses/LICENSE-2.0
11  ** Unless required by applicable law or agreed to in writing, software
12  ** distributed under the License is distributed on an "AS IS" BASIS,
13  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  ** See the License for the specific language governing permissions and
15  ** limitations under the License.
16  ******************************************************************************/
17 /*-----------------------------------------------------------------------------
18           Include Files and Type Defines
19 -----------------------------------------------------------------------------*/
20 
21 #define _USE_MATH_DEFINES // for M_PI
22 
23 // Include automatically generated configuration file if running autoconf.
24 #ifdef HAVE_CONFIG_H
25 #  include "config_auto.h"
26 #endif
27 
28 #include "intproto.h"
29 
30 #include "classify.h"
31 #include "fontinfo.h"
32 #include "mfoutline.h"
33 #include "picofeat.h"
34 #include "points.h"
35 #include "shapetable.h"
36 #ifndef GRAPHICS_DISABLED
37 #include "svmnode.h"
38 #endif
39 
40 #include "helpers.h"
41 
42 #include <algorithm>
43 #include <cassert>
44 #include <cmath> // for M_PI, std::floor
45 #include <cstdio>
46 
47 namespace tesseract {
48 
49 /* match debug display constants*/
50 #define PROTO_PRUNER_SCALE (4.0)
51 
52 #define INT_DESCENDER (0.0 * INT_CHAR_NORM_RANGE)
53 #define INT_BASELINE (0.25 * INT_CHAR_NORM_RANGE)
54 #define INT_XHEIGHT (0.75 * INT_CHAR_NORM_RANGE)
55 #define INT_CAPHEIGHT (1.0 * INT_CHAR_NORM_RANGE)
56 
57 #define INT_XCENTER (0.5 * INT_CHAR_NORM_RANGE)
58 #define INT_YCENTER (0.5 * INT_CHAR_NORM_RANGE)
59 #define INT_XRADIUS (0.2 * INT_CHAR_NORM_RANGE)
60 #define INT_YRADIUS (0.2 * INT_CHAR_NORM_RANGE)
61 #define INT_MIN_X 0
62 #define INT_MIN_Y 0
63 #define INT_MAX_X INT_CHAR_NORM_RANGE
64 #define INT_MAX_Y INT_CHAR_NORM_RANGE
65 
66 /** define pad used to snap near horiz/vertical protos to horiz/vertical */
67 #define HV_TOLERANCE (0.0025) /* approx 0.9 degrees */
68 
69 typedef enum { StartSwitch, EndSwitch, LastSwitch } SWITCH_TYPE;
70 #define MAX_NUM_SWITCHES 3
71 
72 struct FILL_SWITCH {
73   SWITCH_TYPE Type;
74   int8_t X, Y;
75   int16_t YInit;
76   int16_t Delta;
77 };
78 
79 struct TABLE_FILLER {
80   uint8_t NextSwitch;
81   uint8_t AngleStart, AngleEnd;
82   int8_t X;
83   int16_t YStart, YEnd;
84   int16_t StartDelta, EndDelta;
85   FILL_SWITCH Switch[MAX_NUM_SWITCHES];
86 };
87 
88 struct FILL_SPEC {
89   int8_t X;
90   int8_t YStart, YEnd;
91   uint8_t AngleStart, AngleEnd;
92 };
93 
94 /* constants for conversion from old inttemp format */
95 #define OLD_MAX_NUM_CONFIGS 32
96 #define OLD_WERDS_PER_CONFIG_VEC ((OLD_MAX_NUM_CONFIGS + BITS_PER_WERD - 1) / BITS_PER_WERD)
97 
98 /*-----------------------------------------------------------------------------
99             Macros
100 -----------------------------------------------------------------------------*/
101 /** macro for performing circular increments of bucket indices */
102 #define CircularIncrement(i, r) (((i) < (r)-1) ? ((i)++) : ((i) = 0))
103 
104 /** macro for mapping floats to ints without bounds checking */
105 #define MapParam(P, O, N) (std::floor(((P) + (O)) * (N)))
106 
107 /*---------------------------------------------------------------------------
108             Private Function Prototypes
109 ----------------------------------------------------------------------------*/
110 float BucketStart(int Bucket, float Offset, int NumBuckets);
111 
112 float BucketEnd(int Bucket, float Offset, int NumBuckets);
113 
114 void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask,
115             uint32_t ClassCount, uint32_t WordIndex);
116 
117 bool FillerDone(TABLE_FILLER *Filler);
118 
119 void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
120                         float Center, float Spread, bool debug);
121 
122 void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
123                       float Center, float Spread, bool debug);
124 
125 void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad);
126 
127 ScrollView::Color GetMatchColorFor(float Evidence);
128 
129 void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill);
130 
131 void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto,
132                      TABLE_FILLER *Filler);
133 
134 #ifndef GRAPHICS_DISABLED
135 void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT *Feature,
136                       ScrollView::Color color);
137 
138 void RenderIntProto(ScrollView *window, INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, ScrollView::Color color);
139 #endif // !GRAPHICS_DISABLED
140 
141 /*-----------------------------------------------------------------------------
142         Global Data Definitions and Declarations
143 -----------------------------------------------------------------------------*/
144 
145 #ifndef GRAPHICS_DISABLED
146 /* global display lists used to display proto and feature match information*/
147 static ScrollView *IntMatchWindow = nullptr;
148 static ScrollView *FeatureDisplayWindow = nullptr;
149 static ScrollView *ProtoDisplayWindow = nullptr;
150 #endif
151 
152 /*-----------------------------------------------------------------------------
153         Variables
154 -----------------------------------------------------------------------------*/
155 
156 /* control knobs */
157 static INT_VAR(classify_num_cp_levels, 3, "Number of Class Pruner Levels");
158 static double_VAR(classify_cp_angle_pad_loose, 45.0, "Class Pruner Angle Pad Loose");
159 static double_VAR(classify_cp_angle_pad_medium, 20.0, "Class Pruner Angle Pad Medium");
160 static double_VAR(classify_cp_angle_pad_tight, 10.0, "CLass Pruner Angle Pad Tight");
161 static double_VAR(classify_cp_end_pad_loose, 0.5, "Class Pruner End Pad Loose");
162 static double_VAR(classify_cp_end_pad_medium, 0.5, "Class Pruner End Pad Medium");
163 static double_VAR(classify_cp_end_pad_tight, 0.5, "Class Pruner End Pad Tight");
164 static double_VAR(classify_cp_side_pad_loose, 2.5, "Class Pruner Side Pad Loose");
165 static double_VAR(classify_cp_side_pad_medium, 1.2, "Class Pruner Side Pad Medium");
166 static double_VAR(classify_cp_side_pad_tight, 0.6, "Class Pruner Side Pad Tight");
167 static double_VAR(classify_pp_angle_pad, 45.0, "Proto Pruner Angle Pad");
168 static double_VAR(classify_pp_end_pad, 0.5, "Proto Prune End Pad");
169 static double_VAR(classify_pp_side_pad, 2.5, "Proto Pruner Side Pad");
170 
171 /**
172  * This routine truncates Param to lie within the range
173  * of Min-Max inclusive.
174  *
175  * @param Param   parameter value to be truncated
176  * @param Min, Max  parameter limits (inclusive)
177  *
178  * @return Truncated parameter.
179  */
TruncateParam(float Param,int Min,int Max)180 static int TruncateParam(float Param, int Min, int Max) {
181   int result;
182   if (Param < Min) {
183     result = Min;
184   } else if (Param > Max) {
185     result = Max;
186   } else {
187     result = static_cast<int>(std::floor(Param));
188   }
189   return result;
190 }
191 
192 /*-----------------------------------------------------------------------------
193               Public Code
194 -----------------------------------------------------------------------------*/
195 /// Builds a feature from an FCOORD for position with all the necessary
196 /// clipping and rounding.
INT_FEATURE_STRUCT(const FCOORD & pos,uint8_t theta)197 INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(const FCOORD &pos, uint8_t theta)
198     : X(ClipToRange<int16_t>(static_cast<int16_t>(pos.x() + 0.5), 0, 255))
199     , Y(ClipToRange<int16_t>(static_cast<int16_t>(pos.y() + 0.5), 0, 255))
200     , Theta(theta)
201     , CP_misses(0) {}
202 /** Builds a feature from ints with all the necessary clipping and casting. */
INT_FEATURE_STRUCT(int x,int y,int theta)203 INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(int x, int y, int theta)
204     : X(static_cast<uint8_t>(ClipToRange<int>(x, 0, UINT8_MAX)))
205     , Y(static_cast<uint8_t>(ClipToRange<int>(y, 0, UINT8_MAX)))
206     , Theta(static_cast<uint8_t>(ClipToRange<int>(theta, 0, UINT8_MAX)))
207     , CP_misses(0) {}
208 
209 /**
210  * This routine adds a new class structure to a set of
211  * templates. Classes have to be added to Templates in
212  * the order of increasing ClassIds.
213  *
214  * @param Templates templates to add new class to
215  * @param ClassId   class id to associate new class with
216  * @param Class   class data structure to add to templates
217  *
218  * Globals: none
219  */
AddIntClass(INT_TEMPLATES_STRUCT * Templates,CLASS_ID ClassId,INT_CLASS_STRUCT * Class)220 void AddIntClass(INT_TEMPLATES_STRUCT *Templates, CLASS_ID ClassId, INT_CLASS_STRUCT *Class) {
221   int Pruner;
222 
223   assert(LegalClassId(ClassId));
224   if (static_cast<unsigned>(ClassId) != Templates->NumClasses) {
225     fprintf(stderr,
226             "Please make sure that classes are added to templates"
227             " in increasing order of ClassIds\n");
228     exit(1);
229   }
230   ClassForClassId(Templates, ClassId) = Class;
231   Templates->NumClasses++;
232 
233   if (Templates->NumClasses > MaxNumClassesIn(Templates)) {
234     Pruner = Templates->NumClassPruners++;
235     Templates->ClassPruners[Pruner] = new CLASS_PRUNER_STRUCT;
236     memset(Templates->ClassPruners[Pruner], 0, sizeof(CLASS_PRUNER_STRUCT));
237   }
238 } /* AddIntClass */
239 
240 /**
241  * This routine returns the index of the next free config
242  * in Class.
243  *
244  * @param Class class to add new configuration to
245  *
246  * Globals: none
247  *
248  * @return Index of next free config.
249  */
AddIntConfig(INT_CLASS_STRUCT * Class)250 int AddIntConfig(INT_CLASS_STRUCT *Class) {
251   int Index;
252 
253   assert(Class->NumConfigs < MAX_NUM_CONFIGS);
254 
255   Index = Class->NumConfigs++;
256   Class->ConfigLengths[Index] = 0;
257   return Index;
258 } /* AddIntConfig */
259 
260 /**
261  * This routine allocates the next free proto in Class and
262  * returns its index.
263  *
264  * @param Class class to add new proto to
265  *
266  * Globals: none
267  *
268  * @return Proto index of new proto.
269  */
AddIntProto(INT_CLASS_STRUCT * Class)270 int AddIntProto(INT_CLASS_STRUCT *Class) {
271   if (Class->NumProtos >= MAX_NUM_PROTOS) {
272     return (NO_PROTO);
273   }
274 
275   int Index = Class->NumProtos++;
276 
277   if (Class->NumProtos > MaxNumIntProtosIn(Class)) {
278     int ProtoSetId = Class->NumProtoSets++;
279     auto ProtoSet = new PROTO_SET_STRUCT;
280     Class->ProtoSets[ProtoSetId] = ProtoSet;
281     memset(ProtoSet, 0, sizeof(*ProtoSet));
282 
283     /* reallocate space for the proto lengths and install in class */
284     Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
285   }
286 
287   /* initialize proto so its length is zero and it isn't in any configs */
288   Class->ProtoLengths[Index] = 0;
289   auto Proto = ProtoForProtoId(Class, Index);
290   for (uint32_t *Word = Proto->Configs; Word < Proto->Configs + WERDS_PER_CONFIG_VEC; *Word++ = 0) {
291   }
292 
293   return (Index);
294 }
295 
296 /**
297  * This routine adds Proto to the class pruning tables
298  * for the specified class in Templates.
299  *
300  * Globals:
301  *  - classify_num_cp_levels number of levels used in the class pruner
302  * @param Proto   floating-pt proto to add to class pruner
303  * @param ClassId   class id corresponding to Proto
304  * @param Templates set of templates containing class pruner
305  */
AddProtoToClassPruner(PROTO_STRUCT * Proto,CLASS_ID ClassId,INT_TEMPLATES_STRUCT * Templates)306 void AddProtoToClassPruner(PROTO_STRUCT *Proto, CLASS_ID ClassId, INT_TEMPLATES_STRUCT *Templates)
307 #define MAX_LEVEL 2
308 {
309   CLASS_PRUNER_STRUCT *Pruner;
310   uint32_t ClassMask;
311   uint32_t ClassCount;
312   uint32_t WordIndex;
313   int Level;
314   float EndPad, SidePad, AnglePad;
315   TABLE_FILLER TableFiller;
316   FILL_SPEC FillSpec;
317 
318   Pruner = CPrunerFor(Templates, ClassId);
319   WordIndex = CPrunerWordIndexFor(ClassId);
320   ClassMask = CPrunerMaskFor(MAX_LEVEL, ClassId);
321 
322   for (Level = classify_num_cp_levels - 1; Level >= 0; Level--) {
323     GetCPPadsForLevel(Level, &EndPad, &SidePad, &AnglePad);
324     ClassCount = CPrunerMaskFor(Level, ClassId);
325     InitTableFiller(EndPad, SidePad, AnglePad, Proto, &TableFiller);
326 
327     while (!FillerDone(&TableFiller)) {
328       GetNextFill(&TableFiller, &FillSpec);
329       DoFill(&FillSpec, Pruner, ClassMask, ClassCount, WordIndex);
330     }
331   }
332 } /* AddProtoToClassPruner */
333 
334 /**
335  * This routine updates the proto pruner lookup tables
336  * for Class to include a new proto identified by ProtoId
337  * and described by Proto.
338  * @param Proto floating-pt proto to be added to proto pruner
339  * @param ProtoId id of proto
340  * @param Class integer class that contains desired proto pruner
341  * @param debug debug flag
342  * @note Globals: none
343  */
AddProtoToProtoPruner(PROTO_STRUCT * Proto,int ProtoId,INT_CLASS_STRUCT * Class,bool debug)344 void AddProtoToProtoPruner(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class, bool debug) {
345   float X, Y, Length;
346   float Pad;
347 
348   if (ProtoId >= Class->NumProtos) {
349     tprintf("AddProtoToProtoPruner:assert failed: %d < %d", ProtoId, Class->NumProtos);
350   }
351   assert(ProtoId < Class->NumProtos);
352 
353   int Index = IndexForProto(ProtoId);
354   auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
355 
356   float Angle = Proto->Angle;
357 #ifndef _WIN32
358   assert(!std::isnan(Angle));
359 #endif
360 
361   FillPPCircularBits(ProtoSet->ProtoPruner[PRUNER_ANGLE], Index, Angle + ANGLE_SHIFT,
362                      classify_pp_angle_pad / 360.0, debug);
363 
364   Angle *= 2.0 * M_PI;
365   Length = Proto->Length;
366 
367   X = Proto->X + X_SHIFT;
368   Pad = std::max(fabs(std::cos(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
369                  fabs(std::sin(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
370 
371   FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_X], Index, X, Pad, debug);
372 
373   Y = Proto->Y + Y_SHIFT;
374   Pad = std::max(fabs(std::sin(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
375                  fabs(std::cos(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
376 
377   FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_Y], Index, Y, Pad, debug);
378 } /* AddProtoToProtoPruner */
379 
380 /**
381  * Returns a quantized bucket for the given param shifted by offset,
382  * notionally (param + offset) * num_buckets, but clipped and casted to the
383  * appropriate type.
384  */
Bucket8For(float param,float offset,int num_buckets)385 uint8_t Bucket8For(float param, float offset, int num_buckets) {
386   int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
387   return static_cast<uint8_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
388 }
Bucket16For(float param,float offset,int num_buckets)389 uint16_t Bucket16For(float param, float offset, int num_buckets) {
390   int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
391   return static_cast<uint16_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
392 }
393 
394 /**
395  * Returns a quantized bucket for the given circular param shifted by offset,
396  * notionally (param + offset) * num_buckets, but modded and casted to the
397  * appropriate type.
398  */
CircBucketFor(float param,float offset,int num_buckets)399 uint8_t CircBucketFor(float param, float offset, int num_buckets) {
400   int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
401   return static_cast<uint8_t>(Modulo(bucket, num_buckets));
402 } /* CircBucketFor */
403 
404 #ifndef GRAPHICS_DISABLED
405 /**
406  * This routine clears the global feature and proto
407  * display lists.
408  *
409  * Globals:
410  * - FeatureShapes display list for features
411  * - ProtoShapes display list for protos
412  */
UpdateMatchDisplay()413 void UpdateMatchDisplay() {
414   if (IntMatchWindow != nullptr) {
415     IntMatchWindow->Update();
416   }
417 } /* ClearMatchDisplay */
418 #endif
419 
420 /**
421  * This operation updates the config vectors of all protos
422  * in Class to indicate that the protos with 1's in Config
423  * belong to a new configuration identified by ConfigId.
424  * It is assumed that the length of the Config bit vector is
425  * equal to the number of protos in Class.
426  * @param Config    config to be added to class
427  * @param ConfigId  id to be used for new config
428  * @param Class   class to add new config to
429  */
ConvertConfig(BIT_VECTOR Config,int ConfigId,INT_CLASS_STRUCT * Class)430 void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS_STRUCT *Class) {
431   int ProtoId;
432   INT_PROTO_STRUCT *Proto;
433   int TotalLength;
434 
435   for (ProtoId = 0, TotalLength = 0; ProtoId < Class->NumProtos; ProtoId++) {
436     if (test_bit(Config, ProtoId)) {
437       Proto = ProtoForProtoId(Class, ProtoId);
438       SET_BIT(Proto->Configs, ConfigId);
439       TotalLength += Class->ProtoLengths[ProtoId];
440     }
441   }
442   Class->ConfigLengths[ConfigId] = TotalLength;
443 } /* ConvertConfig */
444 
445 /**
446  * This routine converts Proto to integer format and
447  * installs it as ProtoId in Class.
448  * @param Proto floating-pt proto to be converted to integer format
449  * @param ProtoId id of proto
450  * @param Class integer class to add converted proto to
451  */
ConvertProto(PROTO_STRUCT * Proto,int ProtoId,INT_CLASS_STRUCT * Class)452 void Classify::ConvertProto(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class) {
453   assert(ProtoId < Class->NumProtos);
454 
455   INT_PROTO_STRUCT *P = ProtoForProtoId(Class, ProtoId);
456 
457   float Param = Proto->A * 128;
458   P->A = TruncateParam(Param, -128, 127);
459 
460   Param = -Proto->B * 256;
461   P->B = TruncateParam(Param, 0, 255);
462 
463   Param = Proto->C * 128;
464   P->C = TruncateParam(Param, -128, 127);
465 
466   Param = Proto->Angle * 256;
467   if (Param < 0 || Param >= 256) {
468     P->Angle = 0;
469   } else {
470     P->Angle = static_cast<uint8_t>(Param);
471   }
472 
473   /* round proto length to nearest integer number of pico-features */
474   Param = (Proto->Length / GetPicoFeatureLength()) + 0.5;
475   Class->ProtoLengths[ProtoId] = TruncateParam(Param, 1, 255);
476   if (classify_learning_debug_level >= 2) {
477     tprintf("Converted ffeat to (A=%d,B=%d,C=%d,L=%d)", P->A, P->B, P->C,
478             Class->ProtoLengths[ProtoId]);
479   }
480 } /* ConvertProto */
481 
482 /**
483  * This routine converts from the old floating point format
484  * to the new integer format.
485  * @param FloatProtos prototypes in old floating pt format
486  * @param target_unicharset the UNICHARSET to use
487  * @return New set of training templates in integer format.
488  * @note Globals: none
489  */
CreateIntTemplates(CLASSES FloatProtos,const UNICHARSET & target_unicharset)490 INT_TEMPLATES_STRUCT *Classify::CreateIntTemplates(CLASSES FloatProtos,
491                                            const UNICHARSET &target_unicharset) {
492   CLASS_TYPE FClass;
493   INT_CLASS_STRUCT *IClass;
494   int ProtoId;
495   int ConfigId;
496 
497   auto IntTemplates = new INT_TEMPLATES_STRUCT;
498 
499   for (unsigned ClassId = 0; ClassId < target_unicharset.size(); ClassId++) {
500     FClass = &(FloatProtos[ClassId]);
501     if (FClass->NumProtos == 0 && FClass->NumConfigs == 0 &&
502         strcmp(target_unicharset.id_to_unichar(ClassId), " ") != 0) {
503       tprintf("Warning: no protos/configs for %s in CreateIntTemplates()\n",
504               target_unicharset.id_to_unichar(ClassId));
505     }
506     assert(UnusedClassIdIn(IntTemplates, ClassId));
507     IClass = new INT_CLASS_STRUCT(FClass->NumProtos, FClass->NumConfigs);
508     FontSet fs{FClass->font_set.size()};
509     for (unsigned i = 0; i < fs.size(); ++i) {
510       fs[i] = FClass->font_set.at(i);
511     }
512     IClass->font_set_id = this->fontset_table_.push_back(fs);
513     AddIntClass(IntTemplates, ClassId, IClass);
514 
515     for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) {
516       AddIntProto(IClass);
517       ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass);
518       AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass,
519                             classify_learning_debug_level >= 2);
520       AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates);
521     }
522 
523     for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) {
524       AddIntConfig(IClass);
525       ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass);
526     }
527   }
528   return (IntTemplates);
529 } /* CreateIntTemplates */
530 
531 #ifndef GRAPHICS_DISABLED
532 /**
533  * This routine renders the specified feature into a
534  * global display list.
535  *
536  * Globals:
537  * - FeatureShapes global display list for features
538  * @param Feature   pico-feature to be displayed
539  * @param Evidence  best evidence for this feature (0-1)
540  */
DisplayIntFeature(const INT_FEATURE_STRUCT * Feature,float Evidence)541 void DisplayIntFeature(const INT_FEATURE_STRUCT *Feature, float Evidence) {
542   ScrollView::Color color = GetMatchColorFor(Evidence);
543   RenderIntFeature(IntMatchWindow, Feature, color);
544   if (FeatureDisplayWindow) {
545     RenderIntFeature(FeatureDisplayWindow, Feature, color);
546   }
547 } /* DisplayIntFeature */
548 
549 /**
550  * This routine renders the specified proto into a
551  * global display list.
552  *
553  * Globals:
554  * - ProtoShapes global display list for protos
555  * @param Class   class to take proto from
556  * @param ProtoId   id of proto in Class to be displayed
557  * @param Evidence  total evidence for proto (0-1)
558  */
DisplayIntProto(INT_CLASS_STRUCT * Class,PROTO_ID ProtoId,float Evidence)559 void DisplayIntProto(INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, float Evidence) {
560   ScrollView::Color color = GetMatchColorFor(Evidence);
561   RenderIntProto(IntMatchWindow, Class, ProtoId, color);
562   if (ProtoDisplayWindow) {
563     RenderIntProto(ProtoDisplayWindow, Class, ProtoId, color);
564   }
565 } /* DisplayIntProto */
566 #endif
567 
568 /// This constructor creates a new integer class data structure
569 /// and returns it.  Sufficient space is allocated
570 /// to handle the specified number of protos and configs.
571 /// @param MaxNumProtos  number of protos to allocate space for
572 /// @param MaxNumConfigs number of configs to allocate space for
INT_CLASS_STRUCT(int MaxNumProtos,int MaxNumConfigs)573 INT_CLASS_STRUCT::INT_CLASS_STRUCT(int MaxNumProtos, int MaxNumConfigs) :
574   NumProtos(0),
575   NumProtoSets((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) / PROTOS_PER_PROTO_SET),
576   NumConfigs(0),
577   ProtoLengths(MaxNumIntProtosIn(this))
578 {
579   assert(MaxNumConfigs <= MAX_NUM_CONFIGS);
580   assert(NumProtoSets <= MAX_NUM_PROTO_SETS);
581 
582   for (int i = 0; i < NumProtoSets; i++) {
583     /* allocate space for a proto set, install in class, and initialize */
584     auto ProtoSet = new PROTO_SET_STRUCT;
585     memset(ProtoSet, 0, sizeof(*ProtoSet));
586     ProtoSets[i] = ProtoSet;
587 
588     /* allocate space for the proto lengths and install in class */
589   }
590   memset(ConfigLengths, 0, sizeof(ConfigLengths));
591 }
592 
~INT_CLASS_STRUCT()593 INT_CLASS_STRUCT::~INT_CLASS_STRUCT() {
594   for (int i = 0; i < NumProtoSets; i++) {
595     delete ProtoSets[i];
596   }
597 }
598 
599 /// This constructor allocates a new set of integer templates
600 /// initialized to hold 0 classes.
INT_TEMPLATES_STRUCT()601 INT_TEMPLATES_STRUCT::INT_TEMPLATES_STRUCT() {
602   NumClasses = 0;
603   NumClassPruners = 0;
604 
605   for (int i = 0; i < MAX_NUM_CLASSES; i++) {
606     ClassForClassId(this, i) = nullptr;
607   }
608 }
609 
~INT_TEMPLATES_STRUCT()610 INT_TEMPLATES_STRUCT::~INT_TEMPLATES_STRUCT() {
611   for (unsigned i = 0; i < NumClasses; i++) {
612     delete Class[i];
613   }
614   for (unsigned i = 0; i < NumClassPruners; i++) {
615     delete ClassPruners[i];
616   }
617 }
618 
619 /**
620  * This routine reads a set of integer templates from
621  * File.  File must already be open and must be in the
622  * correct binary format.
623  * @param  fp open file to read templates from
624  * @return Pointer to integer templates read from File.
625  * @note Globals: none
626  */
ReadIntTemplates(TFile * fp)627 INT_TEMPLATES_STRUCT *Classify::ReadIntTemplates(TFile *fp) {
628   int j, w, x, y, z;
629   INT_TEMPLATES_STRUCT *Templates;
630   CLASS_PRUNER_STRUCT *Pruner;
631   INT_CLASS_STRUCT *Class;
632 
633   /* variables for conversion from older inttemp formats */
634   int b, bit_number, last_cp_bit_number, new_b, new_i, new_w;
635   CLASS_ID class_id, max_class_id;
636   std::vector<CLASS_ID> ClassIdFor(MAX_NUM_CLASSES);
637   std::vector<CLASS_PRUNER_STRUCT *> TempClassPruner(MAX_NUM_CLASS_PRUNERS);
638   uint32_t SetBitsForMask =          // word with NUM_BITS_PER_CLASS
639       (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0
640   uint32_t Mask, NewMask, ClassBits;
641   unsigned MaxNumConfigs = MAX_NUM_CONFIGS;
642   unsigned WerdsPerConfigVec = WERDS_PER_CONFIG_VEC;
643 
644   /* first read the high level template struct */
645   Templates = new INT_TEMPLATES_STRUCT;
646   // Read Templates in parts for 64 bit compatibility.
647   uint32_t unicharset_size;
648   if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1) {
649     tprintf("Bad read of inttemp!\n");
650   }
651   int32_t version_id = 0;
652   if (fp->FReadEndian(&version_id, sizeof(version_id), 1) != 1 ||
653       fp->FReadEndian(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1) != 1) {
654     tprintf("Bad read of inttemp!\n");
655   }
656   if (version_id < 0) {
657     // This file has a version id!
658     version_id = -version_id;
659     if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses), 1) != 1) {
660       tprintf("Bad read of inttemp!\n");
661     }
662   } else {
663     Templates->NumClasses = version_id;
664   }
665 
666   if (version_id < 3) {
667     MaxNumConfigs = OLD_MAX_NUM_CONFIGS;
668     WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC;
669   }
670 
671   if (version_id < 2) {
672     std::vector<int16_t> IndexFor(MAX_NUM_CLASSES);
673     if (fp->FReadEndian(&IndexFor[0], sizeof(IndexFor[0]), unicharset_size) != unicharset_size) {
674       tprintf("Bad read of inttemp!\n");
675     }
676     if (fp->FReadEndian(&ClassIdFor[0], sizeof(ClassIdFor[0]), Templates->NumClasses) !=
677         Templates->NumClasses) {
678       tprintf("Bad read of inttemp!\n");
679     }
680   }
681 
682   /* then read in the class pruners */
683   const unsigned kNumBuckets = NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR;
684   for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
685     Pruner = new CLASS_PRUNER_STRUCT;
686     if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) != kNumBuckets) {
687       tprintf("Bad read of inttemp!\n");
688     }
689     if (version_id < 2) {
690       TempClassPruner[i] = Pruner;
691     } else {
692       Templates->ClassPruners[i] = Pruner;
693     }
694   }
695 
696   /* fix class pruners if they came from an old version of inttemp */
697   if (version_id < 2) {
698     // Allocate enough class pruners to cover all the class ids.
699     max_class_id = 0;
700     for (unsigned i = 0; i < Templates->NumClasses; i++) {
701       if (ClassIdFor[i] > max_class_id) {
702         max_class_id = ClassIdFor[i];
703       }
704     }
705     for (int i = 0; i <= CPrunerIdFor(max_class_id); i++) {
706       Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT;
707       memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT));
708     }
709     // Convert class pruners from the old format (indexed by class index)
710     // to the new format (indexed by class id).
711     last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1;
712     for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
713       for (x = 0; x < NUM_CP_BUCKETS; x++) {
714         for (y = 0; y < NUM_CP_BUCKETS; y++) {
715           for (z = 0; z < NUM_CP_BUCKETS; z++) {
716             for (w = 0; w < WERDS_PER_CP_VECTOR; w++) {
717               if (TempClassPruner[i]->p[x][y][z][w] == 0) {
718                 continue;
719               }
720               for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) {
721                 bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b;
722                 if (bit_number > last_cp_bit_number) {
723                   break; // the rest of the bits in this word are not used
724                 }
725                 class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS];
726                 // Single out NUM_BITS_PER_CLASS bits relating to class_id.
727                 Mask = SetBitsForMask << b;
728                 ClassBits = TempClassPruner[i]->p[x][y][z][w] & Mask;
729                 // Move these bits to the new position in which they should
730                 // appear (indexed corresponding to the class_id).
731                 new_i = CPrunerIdFor(class_id);
732                 new_w = CPrunerWordIndexFor(class_id);
733                 new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS;
734                 if (new_b > b) {
735                   ClassBits <<= (new_b - b);
736                 } else {
737                   ClassBits >>= (b - new_b);
738                 }
739                 // Copy bits relating to class_id to the correct position
740                 // in Templates->ClassPruner.
741                 NewMask = SetBitsForMask << new_b;
742                 Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask;
743                 Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits;
744               }
745             }
746           }
747         }
748       }
749     }
750     for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
751       delete TempClassPruner[i];
752     }
753   }
754 
755   /* then read in each class */
756   for (unsigned i = 0; i < Templates->NumClasses; i++) {
757     /* first read in the high level struct for the class */
758     Class = new INT_CLASS_STRUCT;
759     if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 ||
760         fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 ||
761         fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1) {
762       tprintf("Bad read of inttemp!\n");
763     }
764     if (version_id == 0) {
765       // Only version 0 writes 5 pointless pointers to the file.
766       for (j = 0; j < 5; ++j) {
767         int32_t junk;
768         if (fp->FRead(&junk, sizeof(junk), 1) != 1) {
769           tprintf("Bad read of inttemp!\n");
770         }
771       }
772     }
773     unsigned num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs;
774     ASSERT_HOST(num_configs <= MaxNumConfigs);
775     if (fp->FReadEndian(Class->ConfigLengths, sizeof(uint16_t), num_configs) != num_configs) {
776       tprintf("Bad read of inttemp!\n");
777     }
778     if (version_id < 2) {
779       ClassForClassId(Templates, ClassIdFor[i]) = Class;
780     } else {
781       ClassForClassId(Templates, i) = Class;
782     }
783 
784     /* then read in the proto lengths */
785     Class->ProtoLengths.clear();
786     if (MaxNumIntProtosIn(Class) > 0) {
787       Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
788       if (fp->FRead(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class)) !=
789           MaxNumIntProtosIn(Class)) {
790         tprintf("Bad read of inttemp!\n");
791       }
792     }
793 
794     /* then read in the proto sets */
795     for (j = 0; j < Class->NumProtoSets; j++) {
796       auto ProtoSet = new PROTO_SET_STRUCT;
797       unsigned num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR;
798       if (fp->FReadEndian(&ProtoSet->ProtoPruner, sizeof(ProtoSet->ProtoPruner[0][0][0]),
799                           num_buckets) != num_buckets) {
800         tprintf("Bad read of inttemp!\n");
801       }
802       for (x = 0; x < PROTOS_PER_PROTO_SET; x++) {
803         if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A), 1) != 1 ||
804             fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B), 1) != 1 ||
805             fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C), 1) != 1 ||
806             fp->FRead(&ProtoSet->Protos[x].Angle, sizeof(ProtoSet->Protos[x].Angle), 1) != 1) {
807           tprintf("Bad read of inttemp!\n");
808         }
809         if (fp->FReadEndian(&ProtoSet->Protos[x].Configs, sizeof(ProtoSet->Protos[x].Configs[0]),
810                             WerdsPerConfigVec) != WerdsPerConfigVec) {
811           tprintf("Bad read of inttemp!\n");
812         }
813       }
814       Class->ProtoSets[j] = ProtoSet;
815     }
816     if (version_id < 4) {
817       Class->font_set_id = -1;
818     } else {
819       fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1);
820     }
821   }
822 
823   if (version_id < 2) {
824     /* add an empty nullptr class with class id 0 */
825     assert(UnusedClassIdIn(Templates, 0));
826     ClassForClassId(Templates, 0) = new INT_CLASS_STRUCT(1, 1);
827     ClassForClassId(Templates, 0)->font_set_id = -1;
828     Templates->NumClasses++;
829     /* make sure the classes are contiguous */
830     for (unsigned i = 0; i < MAX_NUM_CLASSES; i++) {
831       if (i < Templates->NumClasses) {
832         if (ClassForClassId(Templates, i) == nullptr) {
833           fprintf(stderr, "Non-contiguous class ids in inttemp\n");
834           exit(1);
835         }
836       } else {
837         if (ClassForClassId(Templates, i) != nullptr) {
838           fprintf(stderr, "Class id %u exceeds NumClassesIn (Templates) %u\n", i,
839                   Templates->NumClasses);
840           exit(1);
841         }
842       }
843     }
844   }
845   if (version_id >= 4) {
846     using namespace std::placeholders; // for _1, _2
847     this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2));
848     if (version_id >= 5) {
849       this->fontinfo_table_.read(fp, std::bind(read_spacing_info, _1, _2));
850     }
851     this->fontset_table_.read(fp, [](auto *f, auto *fs) { return f->DeSerialize(*fs); } );
852   }
853 
854   return (Templates);
855 } /* ReadIntTemplates */
856 
857 #ifndef GRAPHICS_DISABLED
858 /**
859  * This routine sends the shapes in the global display
860  * lists to the match debugger window.
861  *
862  * Globals:
863  * - FeatureShapes display list containing feature matches
864  * - ProtoShapes display list containing proto matches
865  */
ShowMatchDisplay()866 void Classify::ShowMatchDisplay() {
867   InitIntMatchWindowIfReqd();
868   if (ProtoDisplayWindow) {
869     ProtoDisplayWindow->Clear();
870   }
871   if (FeatureDisplayWindow) {
872     FeatureDisplayWindow->Clear();
873   }
874   ClearFeatureSpaceWindow(static_cast<NORM_METHOD>(static_cast<int>(classify_norm_method)),
875                           IntMatchWindow);
876   IntMatchWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
877   if (ProtoDisplayWindow) {
878     ProtoDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
879   }
880   if (FeatureDisplayWindow) {
881     FeatureDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
882   }
883 } /* ShowMatchDisplay */
884 
885 /// Clears the given window and draws the featurespace guides for the
886 /// appropriate normalization method.
ClearFeatureSpaceWindow(NORM_METHOD norm_method,ScrollView * window)887 void ClearFeatureSpaceWindow(NORM_METHOD norm_method, ScrollView *window) {
888   window->Clear();
889 
890   window->Pen(ScrollView::GREY);
891   // Draw the feature space limit rectangle.
892   window->Rectangle(0, 0, INT_MAX_X, INT_MAX_Y);
893   if (norm_method == baseline) {
894     window->SetCursor(0, INT_DESCENDER);
895     window->DrawTo(INT_MAX_X, INT_DESCENDER);
896     window->SetCursor(0, INT_BASELINE);
897     window->DrawTo(INT_MAX_X, INT_BASELINE);
898     window->SetCursor(0, INT_XHEIGHT);
899     window->DrawTo(INT_MAX_X, INT_XHEIGHT);
900     window->SetCursor(0, INT_CAPHEIGHT);
901     window->DrawTo(INT_MAX_X, INT_CAPHEIGHT);
902   } else {
903     window->Rectangle(INT_XCENTER - INT_XRADIUS, INT_YCENTER - INT_YRADIUS,
904                       INT_XCENTER + INT_XRADIUS, INT_YCENTER + INT_YRADIUS);
905   }
906 }
907 #endif
908 
909 /**
910  * This routine writes Templates to File.  The format
911  * is an efficient binary format.  File must already be open
912  * for writing.
913  * @param File open file to write templates to
914  * @param Templates templates to save into File
915  * @param target_unicharset the UNICHARSET to use
916  */
WriteIntTemplates(FILE * File,INT_TEMPLATES_STRUCT * Templates,const UNICHARSET & target_unicharset)917 void Classify::WriteIntTemplates(FILE *File, INT_TEMPLATES_STRUCT *Templates,
918                                  const UNICHARSET &target_unicharset) {
919   INT_CLASS_STRUCT *Class;
920   auto unicharset_size = target_unicharset.size();
921   int version_id = -5; // When negated by the reader -1 becomes +1 etc.
922 
923   if (Templates->NumClasses != unicharset_size) {
924     tprintf(
925         "Warning: executing WriteIntTemplates() with %d classes in"
926         " Templates, while target_unicharset size is %zu\n",
927         Templates->NumClasses, unicharset_size);
928   }
929 
930   /* first write the high level template struct */
931   fwrite(&unicharset_size, sizeof(unicharset_size), 1, File);
932   fwrite(&version_id, sizeof(version_id), 1, File);
933   fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1, File);
934   fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File);
935 
936   /* then write out the class pruners */
937   for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
938     fwrite(Templates->ClassPruners[i], sizeof(CLASS_PRUNER_STRUCT), 1, File);
939   }
940 
941   /* then write out each class */
942   for (unsigned i = 0; i < Templates->NumClasses; i++) {
943     Class = Templates->Class[i];
944 
945     /* first write out the high level struct for the class */
946     fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File);
947     fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File);
948     ASSERT_HOST(Class->NumConfigs == this->fontset_table_.at(Class->font_set_id).size());
949     fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File);
950     for (int j = 0; j < Class->NumConfigs; ++j) {
951       fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File);
952     }
953 
954     /* then write out the proto lengths */
955     if (MaxNumIntProtosIn(Class) > 0) {
956       fwrite(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class), File);
957     }
958 
959     /* then write out the proto sets */
960     for (int j = 0; j < Class->NumProtoSets; j++) {
961       fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File);
962     }
963 
964     /* then write the fonts info */
965     fwrite(&Class->font_set_id, sizeof(int), 1, File);
966   }
967 
968   /* Write the fonts info tables */
969   using namespace std::placeholders; // for _1, _2
970   this->fontinfo_table_.write(File, std::bind(write_info, _1, _2));
971   this->fontinfo_table_.write(File, std::bind(write_spacing_info, _1, _2));
972   this->fontset_table_.write(File, std::bind(write_set, _1, _2));
973 } /* WriteIntTemplates */
974 
975 /*-----------------------------------------------------------------------------
976               Private Code
977 -----------------------------------------------------------------------------*/
978 /**
979  * This routine returns the parameter value which
980  * corresponds to the beginning of the specified bucket.
981  * The bucket number should have been generated using the
982  * BucketFor() function with parameters Offset and NumBuckets.
983  * @param Bucket    bucket whose start is to be computed
984  * @param Offset    offset used to map params to buckets
985  * @param NumBuckets  total number of buckets
986  * @return Param value corresponding to start position of Bucket.
987  * @note Globals: none
988  */
BucketStart(int Bucket,float Offset,int NumBuckets)989 float BucketStart(int Bucket, float Offset, int NumBuckets) {
990   return static_cast<float>(Bucket) / NumBuckets - Offset;
991 
992 } /* BucketStart */
993 
994 /**
995  * This routine returns the parameter value which
996  * corresponds to the end of the specified bucket.
997  * The bucket number should have been generated using the
998  * BucketFor() function with parameters Offset and NumBuckets.
999  * @param Bucket    bucket whose end is to be computed
1000  * @param Offset    offset used to map params to buckets
1001  * @param NumBuckets  total number of buckets
1002  * @return Param value corresponding to end position of Bucket.
1003  * @note Globals: none
1004  */
BucketEnd(int Bucket,float Offset,int NumBuckets)1005 float BucketEnd(int Bucket, float Offset, int NumBuckets) {
1006   return static_cast<float>(Bucket + 1) / NumBuckets - Offset;
1007 } /* BucketEnd */
1008 
1009 /**
1010  * This routine fills in the section of a class pruner
1011  * corresponding to a single x value for a single proto of
1012  * a class.
1013  * @param FillSpec  specifies which bits to fill in pruner
1014  * @param Pruner    class pruner to be filled
1015  * @param ClassMask indicates which bits to change in each word
1016  * @param ClassCount  indicates what to change bits to
1017  * @param WordIndex indicates which word to change
1018  */
DoFill(FILL_SPEC * FillSpec,CLASS_PRUNER_STRUCT * Pruner,uint32_t ClassMask,uint32_t ClassCount,uint32_t WordIndex)1019 void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask,
1020             uint32_t ClassCount, uint32_t WordIndex) {
1021   int X, Y, Angle;
1022   uint32_t OldWord;
1023 
1024   X = FillSpec->X;
1025   if (X < 0) {
1026     X = 0;
1027   }
1028   if (X >= NUM_CP_BUCKETS) {
1029     X = NUM_CP_BUCKETS - 1;
1030   }
1031 
1032   if (FillSpec->YStart < 0) {
1033     FillSpec->YStart = 0;
1034   }
1035   if (FillSpec->YEnd >= NUM_CP_BUCKETS) {
1036     FillSpec->YEnd = NUM_CP_BUCKETS - 1;
1037   }
1038 
1039   for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++) {
1040     for (Angle = FillSpec->AngleStart;; CircularIncrement(Angle, NUM_CP_BUCKETS)) {
1041       OldWord = Pruner->p[X][Y][Angle][WordIndex];
1042       if (ClassCount > (OldWord & ClassMask)) {
1043         OldWord &= ~ClassMask;
1044         OldWord |= ClassCount;
1045         Pruner->p[X][Y][Angle][WordIndex] = OldWord;
1046       }
1047       if (Angle == FillSpec->AngleEnd) {
1048         break;
1049       }
1050     }
1051   }
1052 } /* DoFill */
1053 
1054 /**
1055  * Return true if the specified table filler is done, i.e.
1056  * if it has no more lines to fill.
1057  * @param Filler    table filler to check if done
1058  * @return true if no more lines to fill, false otherwise.
1059  * @note Globals: none
1060  */
FillerDone(TABLE_FILLER * Filler)1061 bool FillerDone(TABLE_FILLER *Filler) {
1062   FILL_SWITCH *Next;
1063 
1064   Next = &(Filler->Switch[Filler->NextSwitch]);
1065 
1066   return Filler->X > Next->X && Next->Type == LastSwitch;
1067 
1068 } /* FillerDone */
1069 
1070 /**
1071  * This routine sets Bit in each bit vector whose
1072  * bucket lies within the range Center +- Spread.  The fill
1073  * is done for a circular dimension, i.e. bucket 0 is adjacent
1074  * to the last bucket.  It is assumed that Center and Spread
1075  * are expressed in a circular coordinate system whose range
1076  * is 0 to 1.
1077  * @param ParamTable  table of bit vectors, one per param bucket
1078  * @param Bit bit position in vectors to be filled
1079  * @param Center center of filled area
1080  * @param Spread spread of filled area
1081  * @param debug debug flag
1082  */
FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],int Bit,float Center,float Spread,bool debug)1083 void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1084                         float Center, float Spread, bool debug) {
1085   int i, FirstBucket, LastBucket;
1086 
1087   if (Spread > 0.5) {
1088     Spread = 0.5;
1089   }
1090 
1091   FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1092   if (FirstBucket < 0) {
1093     FirstBucket += NUM_PP_BUCKETS;
1094   }
1095 
1096   LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1097   if (LastBucket >= NUM_PP_BUCKETS) {
1098     LastBucket -= NUM_PP_BUCKETS;
1099   }
1100   if (debug) {
1101     tprintf("Circular fill from %d to %d", FirstBucket, LastBucket);
1102   }
1103   for (i = FirstBucket; true; CircularIncrement(i, NUM_PP_BUCKETS)) {
1104     SET_BIT(ParamTable[i], Bit);
1105 
1106     /* exit loop after we have set the bit for the last bucket */
1107     if (i == LastBucket) {
1108       break;
1109     }
1110   }
1111 
1112 } /* FillPPCircularBits */
1113 
1114 /**
1115  * This routine sets Bit in each bit vector whose
1116  * bucket lies within the range Center +- Spread.  The fill
1117  * is done for a linear dimension, i.e. there is no wrap-around
1118  * for this dimension.  It is assumed that Center and Spread
1119  * are expressed in a linear coordinate system whose range
1120  * is approximately 0 to 1.  Values outside this range will
1121  * be clipped.
1122  * @param ParamTable table of bit vectors, one per param bucket
1123  * @param Bit bit number being filled
1124  * @param Center center of filled area
1125  * @param Spread spread of filled area
1126  * @param debug debug flag
1127  */
FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR],int Bit,float Center,float Spread,bool debug)1128 void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1129                       float Center, float Spread, bool debug) {
1130   int i, FirstBucket, LastBucket;
1131 
1132   FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1133   if (FirstBucket < 0) {
1134     FirstBucket = 0;
1135   }
1136 
1137   LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1138   if (LastBucket >= NUM_PP_BUCKETS) {
1139     LastBucket = NUM_PP_BUCKETS - 1;
1140   }
1141 
1142   if (debug) {
1143     tprintf("Linear fill from %d to %d", FirstBucket, LastBucket);
1144   }
1145   for (i = FirstBucket; i <= LastBucket; i++) {
1146     SET_BIT(ParamTable[i], Bit);
1147   }
1148 
1149 } /* FillPPLinearBits */
1150 
1151 /*---------------------------------------------------------------------------*/
1152 #ifndef GRAPHICS_DISABLED
1153 /**
1154  * This routine prompts the user with Prompt and waits
1155  * for the user to enter something in the debug window.
1156  * @param Prompt prompt to print while waiting for input from window
1157  * @param adaptive_on
1158  * @param pretrained_on
1159  * @param shape_id
1160  * @return Character entered in the debug window.
1161  * @note Globals: none
1162  */
GetClassToDebug(const char * Prompt,bool * adaptive_on,bool * pretrained_on,int * shape_id)1163 CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *pretrained_on,
1164                                    int *shape_id) {
1165   tprintf("%s\n", Prompt);
1166   SVEvent *ev;
1167   SVEventType ev_type;
1168   int unichar_id = INVALID_UNICHAR_ID;
1169   // Wait until a click or popup event.
1170   do {
1171     ev = IntMatchWindow->AwaitEvent(SVET_ANY);
1172     ev_type = ev->type;
1173     if (ev_type == SVET_POPUP) {
1174       if (ev->command_id == IDA_SHAPE_INDEX) {
1175         if (shape_table_ != nullptr) {
1176           *shape_id = atoi(ev->parameter);
1177           *adaptive_on = false;
1178           *pretrained_on = true;
1179           if (*shape_id >= 0 && static_cast<unsigned>(*shape_id) < shape_table_->NumShapes()) {
1180             int font_id;
1181             shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id, &font_id);
1182             tprintf("Shape %d, first unichar=%d, font=%d\n", *shape_id, unichar_id, font_id);
1183             return unichar_id;
1184           }
1185           tprintf("Shape index '%s' not found in shape table\n", ev->parameter);
1186         } else {
1187           tprintf("No shape table loaded!\n");
1188         }
1189       } else {
1190         if (unicharset.contains_unichar(ev->parameter)) {
1191           unichar_id = unicharset.unichar_to_id(ev->parameter);
1192           if (ev->command_id == IDA_ADAPTIVE) {
1193             *adaptive_on = true;
1194             *pretrained_on = false;
1195             *shape_id = -1;
1196           } else if (ev->command_id == IDA_STATIC) {
1197             *adaptive_on = false;
1198             *pretrained_on = true;
1199           } else {
1200             *adaptive_on = true;
1201             *pretrained_on = true;
1202           }
1203           if (ev->command_id == IDA_ADAPTIVE || shape_table_ == nullptr) {
1204             *shape_id = -1;
1205             return unichar_id;
1206           }
1207           for (unsigned s = 0; s < shape_table_->NumShapes(); ++s) {
1208             if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) {
1209               tprintf("%s\n", shape_table_->DebugStr(s).c_str());
1210             }
1211           }
1212         } else {
1213           tprintf("Char class '%s' not found in unicharset", ev->parameter);
1214         }
1215       }
1216     }
1217     delete ev;
1218   } while (ev_type != SVET_CLICK);
1219   return 0;
1220 } /* GetClassToDebug */
1221 
1222 #endif
1223 
1224 /**
1225  * This routine copies the appropriate global pad variables
1226  * into EndPad, SidePad, and AnglePad.  This is a kludge used
1227  * to get around the fact that global control variables cannot
1228  * be arrays.  If the specified level is illegal, the tightest
1229  * possible pads are returned.
1230  * @param Level   "tightness" level to return pads for
1231  * @param EndPad    place to put end pad for Level
1232  * @param SidePad   place to put side pad for Level
1233  * @param AnglePad  place to put angle pad for Level
1234  */
GetCPPadsForLevel(int Level,float * EndPad,float * SidePad,float * AnglePad)1235 void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad) {
1236   switch (Level) {
1237     case 0:
1238       *EndPad = classify_cp_end_pad_loose * GetPicoFeatureLength();
1239       *SidePad = classify_cp_side_pad_loose * GetPicoFeatureLength();
1240       *AnglePad = classify_cp_angle_pad_loose / 360.0;
1241       break;
1242 
1243     case 1:
1244       *EndPad = classify_cp_end_pad_medium * GetPicoFeatureLength();
1245       *SidePad = classify_cp_side_pad_medium * GetPicoFeatureLength();
1246       *AnglePad = classify_cp_angle_pad_medium / 360.0;
1247       break;
1248 
1249     case 2:
1250       *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1251       *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1252       *AnglePad = classify_cp_angle_pad_tight / 360.0;
1253       break;
1254 
1255     default:
1256       *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1257       *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1258       *AnglePad = classify_cp_angle_pad_tight / 360.0;
1259       break;
1260   }
1261   if (*AnglePad > 0.5) {
1262     *AnglePad = 0.5;
1263   }
1264 
1265 } /* GetCPPadsForLevel */
1266 
1267 /**
1268  * @param Evidence  evidence value to return color for
1269  * @return Color which corresponds to specified Evidence value.
1270  * @note Globals: none
1271  */
GetMatchColorFor(float Evidence)1272 ScrollView::Color GetMatchColorFor(float Evidence) {
1273   assert(Evidence >= 0.0);
1274   assert(Evidence <= 1.0);
1275 
1276   if (Evidence >= 0.90) {
1277     return ScrollView::WHITE;
1278   } else if (Evidence >= 0.75) {
1279     return ScrollView::GREEN;
1280   } else if (Evidence >= 0.50) {
1281     return ScrollView::RED;
1282   } else {
1283     return ScrollView::BLUE;
1284   }
1285 } /* GetMatchColorFor */
1286 
1287 /**
1288  * This routine returns (in Fill) the specification of
1289  * the next line to be filled from Filler.  FillerDone() should
1290  * always be called before GetNextFill() to ensure that we
1291  * do not run past the end of the fill table.
1292  * @param Filler    filler to get next fill spec from
1293  * @param Fill    place to put spec for next fill
1294  */
GetNextFill(TABLE_FILLER * Filler,FILL_SPEC * Fill)1295 void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill) {
1296   FILL_SWITCH *Next;
1297 
1298   /* compute the fill assuming no switches will be encountered */
1299   Fill->AngleStart = Filler->AngleStart;
1300   Fill->AngleEnd = Filler->AngleEnd;
1301   Fill->X = Filler->X;
1302   Fill->YStart = Filler->YStart >> 8;
1303   Fill->YEnd = Filler->YEnd >> 8;
1304 
1305   /* update the fill info and the filler for ALL switches at this X value */
1306   Next = &(Filler->Switch[Filler->NextSwitch]);
1307   while (Filler->X >= Next->X) {
1308     Fill->X = Filler->X = Next->X;
1309     if (Next->Type == StartSwitch) {
1310       Fill->YStart = Next->Y;
1311       Filler->StartDelta = Next->Delta;
1312       Filler->YStart = Next->YInit;
1313     } else if (Next->Type == EndSwitch) {
1314       Fill->YEnd = Next->Y;
1315       Filler->EndDelta = Next->Delta;
1316       Filler->YEnd = Next->YInit;
1317     } else { /* Type must be LastSwitch */
1318       break;
1319     }
1320     Filler->NextSwitch++;
1321     Next = &(Filler->Switch[Filler->NextSwitch]);
1322   }
1323 
1324   /* prepare the filler for the next call to this routine */
1325   Filler->X++;
1326   Filler->YStart += Filler->StartDelta;
1327   Filler->YEnd += Filler->EndDelta;
1328 
1329 } /* GetNextFill */
1330 
1331 /**
1332  * This routine computes a data structure (Filler)
1333  * which can be used to fill in a rectangle surrounding
1334  * the specified Proto. Results are returned in Filler.
1335  *
1336  * @param EndPad, SidePad, AnglePad padding to add to proto
1337  * @param Proto       proto to create a filler for
1338  * @param Filler        place to put table filler
1339  */
InitTableFiller(float EndPad,float SidePad,float AnglePad,PROTO_STRUCT * Proto,TABLE_FILLER * Filler)1340 void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto, TABLE_FILLER *Filler)
1341 #define XS X_SHIFT
1342 #define YS Y_SHIFT
1343 #define AS ANGLE_SHIFT
1344 #define NB NUM_CP_BUCKETS
1345 {
1346   float Angle;
1347   float X, Y, HalfLength;
1348   float Cos, Sin;
1349   float XAdjust, YAdjust;
1350   FPOINT Start, Switch1, Switch2, End;
1351   int S1 = 0;
1352   int S2 = 1;
1353 
1354   Angle = Proto->Angle;
1355   X = Proto->X;
1356   Y = Proto->Y;
1357   HalfLength = Proto->Length / 2.0;
1358 
1359   Filler->AngleStart = CircBucketFor(Angle - AnglePad, AS, NB);
1360   Filler->AngleEnd = CircBucketFor(Angle + AnglePad, AS, NB);
1361   Filler->NextSwitch = 0;
1362 
1363   if (fabs(Angle - 0.0) < HV_TOLERANCE || fabs(Angle - 0.5) < HV_TOLERANCE) {
1364     /* horizontal proto - handle as special case */
1365     Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB);
1366     Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256);
1367     Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256);
1368     Filler->StartDelta = 0;
1369     Filler->EndDelta = 0;
1370     Filler->Switch[0].Type = LastSwitch;
1371     Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB);
1372   } else if (fabs(Angle - 0.25) < HV_TOLERANCE || fabs(Angle - 0.75) < HV_TOLERANCE) {
1373     /* vertical proto - handle as special case */
1374     Filler->X = Bucket8For(X - SidePad, XS, NB);
1375     Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256);
1376     Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256);
1377     Filler->StartDelta = 0;
1378     Filler->EndDelta = 0;
1379     Filler->Switch[0].Type = LastSwitch;
1380     Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB);
1381   } else {
1382     /* diagonal proto */
1383 
1384     if ((Angle > 0.0 && Angle < 0.25) || (Angle > 0.5 && Angle < 0.75)) {
1385       /* rising diagonal proto */
1386       Angle *= 2.0 * M_PI;
1387       Cos = fabs(std::cos(Angle));
1388       Sin = fabs(std::sin(Angle));
1389 
1390       /* compute the positions of the corners of the acceptance region */
1391       Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1392       Start.y = Y - (HalfLength + EndPad) * Sin + SidePad * Cos;
1393       End.x = 2.0 * X - Start.x;
1394       End.y = 2.0 * Y - Start.y;
1395       Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1396       Switch1.y = Y - (HalfLength + EndPad) * Sin - SidePad * Cos;
1397       Switch2.x = 2.0 * X - Switch1.x;
1398       Switch2.y = 2.0 * Y - Switch1.y;
1399 
1400       if (Switch1.x > Switch2.x) {
1401         S1 = 1;
1402         S2 = 0;
1403       }
1404 
1405       /* translate into bucket positions and deltas */
1406       Filler->X = Bucket8For(Start.x, XS, NB);
1407       Filler->StartDelta = -static_cast<int16_t>((Cos / Sin) * 256);
1408       Filler->EndDelta = static_cast<int16_t>((Sin / Cos) * 256);
1409 
1410       XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1411       YAdjust = XAdjust * Cos / Sin;
1412       Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1413       YAdjust = XAdjust * Sin / Cos;
1414       Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1415 
1416       Filler->Switch[S1].Type = StartSwitch;
1417       Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1418       Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1419       XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1420       YAdjust = XAdjust * Sin / Cos;
1421       Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256);
1422       Filler->Switch[S1].Delta = Filler->EndDelta;
1423 
1424       Filler->Switch[S2].Type = EndSwitch;
1425       Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1426       Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1427       XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1428       YAdjust = XAdjust * Cos / Sin;
1429       Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256);
1430       Filler->Switch[S2].Delta = Filler->StartDelta;
1431 
1432       Filler->Switch[2].Type = LastSwitch;
1433       Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1434     } else {
1435       /* falling diagonal proto */
1436       Angle *= 2.0 * M_PI;
1437       Cos = fabs(std::cos(Angle));
1438       Sin = fabs(std::sin(Angle));
1439 
1440       /* compute the positions of the corners of the acceptance region */
1441       Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1442       Start.y = Y + (HalfLength + EndPad) * Sin - SidePad * Cos;
1443       End.x = 2.0 * X - Start.x;
1444       End.y = 2.0 * Y - Start.y;
1445       Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1446       Switch1.y = Y + (HalfLength + EndPad) * Sin + SidePad * Cos;
1447       Switch2.x = 2.0 * X - Switch1.x;
1448       Switch2.y = 2.0 * Y - Switch1.y;
1449 
1450       if (Switch1.x > Switch2.x) {
1451         S1 = 1;
1452         S2 = 0;
1453       }
1454 
1455       /* translate into bucket positions and deltas */
1456       Filler->X = Bucket8For(Start.x, XS, NB);
1457       Filler->StartDelta = static_cast<int16_t>(
1458           ClipToRange<int>(-IntCastRounded((Sin / Cos) * 256), INT16_MIN, INT16_MAX));
1459       Filler->EndDelta = static_cast<int16_t>(
1460           ClipToRange<int>(IntCastRounded((Cos / Sin) * 256), INT16_MIN, INT16_MAX));
1461 
1462       XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1463       YAdjust = XAdjust * Sin / Cos;
1464       Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1465       YAdjust = XAdjust * Cos / Sin;
1466       Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1467 
1468       Filler->Switch[S1].Type = EndSwitch;
1469       Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1470       Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1471       XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1472       YAdjust = XAdjust * Sin / Cos;
1473       Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256);
1474       Filler->Switch[S1].Delta = Filler->StartDelta;
1475 
1476       Filler->Switch[S2].Type = StartSwitch;
1477       Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1478       Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1479       XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1480       YAdjust = XAdjust * Cos / Sin;
1481       Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256);
1482       Filler->Switch[S2].Delta = Filler->EndDelta;
1483 
1484       Filler->Switch[2].Type = LastSwitch;
1485       Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1486     }
1487   }
1488 } /* InitTableFiller */
1489 
1490 /*---------------------------------------------------------------------------*/
1491 #ifndef GRAPHICS_DISABLED
1492 /**
1493  * This routine renders the specified feature into ShapeList.
1494  * @param window to add feature rendering to
1495  * @param Feature feature to be rendered
1496  * @param color color to use for feature rendering
1497  * @return New shape list with rendering of Feature added.
1498  * @note Globals: none
1499  */
RenderIntFeature(ScrollView * window,const INT_FEATURE_STRUCT * Feature,ScrollView::Color color)1500 void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT *Feature,
1501                       ScrollView::Color color) {
1502   float X, Y, Dx, Dy, Length;
1503 
1504   window->Pen(color);
1505   assert(Feature != nullptr);
1506   assert(color != 0);
1507 
1508   X = Feature->X;
1509   Y = Feature->Y;
1510   Length = GetPicoFeatureLength() * 0.7 * INT_CHAR_NORM_RANGE;
1511   // The -PI has no significant effect here, but the value of Theta is computed
1512   // using BinaryAnglePlusPi in intfx.cpp.
1513   Dx = (Length / 2.0) * cos((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI);
1514   Dy = (Length / 2.0) * sin((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI);
1515 
1516   window->SetCursor(X, Y);
1517   window->DrawTo(X + Dx, Y + Dy);
1518 } /* RenderIntFeature */
1519 
1520 /**
1521  * This routine extracts the parameters of the specified
1522  * proto from the class description and adds a rendering of
1523  * the proto onto the ShapeList.
1524  *
1525  * @param window ScrollView instance
1526  * @param Class class that proto is contained in
1527  * @param ProtoId id of proto to be rendered
1528  * @param color color to render proto in
1529  *
1530  * Globals: none
1531  *
1532  * @return New shape list with a rendering of one proto added.
1533  */
RenderIntProto(ScrollView * window,INT_CLASS_STRUCT * Class,PROTO_ID ProtoId,ScrollView::Color color)1534 void RenderIntProto(ScrollView *window, INT_CLASS_STRUCT *Class, PROTO_ID ProtoId,
1535                     ScrollView::Color color) {
1536   INT_PROTO_STRUCT *Proto;
1537   int ProtoSetIndex;
1538   int ProtoWordIndex;
1539   float Length;
1540   int Xmin, Xmax, Ymin, Ymax;
1541   float X, Y, Dx, Dy;
1542   uint32_t ProtoMask;
1543   int Bucket;
1544 
1545   assert(ProtoId >= 0);
1546   assert(Class != nullptr);
1547   assert(ProtoId < Class->NumProtos);
1548   assert(color != 0);
1549   window->Pen(color);
1550 
1551   auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
1552   ProtoSetIndex = IndexForProto(ProtoId);
1553   Proto = &(ProtoSet->Protos[ProtoSetIndex]);
1554   Length = (Class->ProtoLengths[ProtoId] * GetPicoFeatureLength() * INT_CHAR_NORM_RANGE);
1555   ProtoMask = PPrunerMaskFor(ProtoId);
1556   ProtoWordIndex = PPrunerWordIndexFor(ProtoId);
1557 
1558   // find the x and y extent of the proto from the proto pruning table
1559   Xmin = Ymin = NUM_PP_BUCKETS;
1560   Xmax = Ymax = 0;
1561   for (Bucket = 0; Bucket < NUM_PP_BUCKETS; Bucket++) {
1562     if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_X][Bucket][ProtoWordIndex]) {
1563       UpdateRange(Bucket, &Xmin, &Xmax);
1564     }
1565 
1566     if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_Y][Bucket][ProtoWordIndex]) {
1567       UpdateRange(Bucket, &Ymin, &Ymax);
1568     }
1569   }
1570   X = (Xmin + Xmax + 1) / 2.0 * PROTO_PRUNER_SCALE;
1571   Y = (Ymin + Ymax + 1) / 2.0 * PROTO_PRUNER_SCALE;
1572   // The -PI has no significant effect here, but the value of Theta is computed
1573   // using BinaryAnglePlusPi in intfx.cpp.
1574   Dx = (Length / 2.0) * cos((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI);
1575   Dy = (Length / 2.0) * sin((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI);
1576 
1577   window->SetCursor(X - Dx, Y - Dy);
1578   window->DrawTo(X + Dx, Y + Dy);
1579 } /* RenderIntProto */
1580 #endif
1581 
1582 #ifndef GRAPHICS_DISABLED
1583 /**
1584  * Initializes the int matcher window if it is not already
1585  * initialized.
1586  */
InitIntMatchWindowIfReqd()1587 void InitIntMatchWindowIfReqd() {
1588   if (IntMatchWindow == nullptr) {
1589     IntMatchWindow = CreateFeatureSpaceWindow("IntMatchWindow", 50, 200);
1590     auto *popup_menu = new SVMenuNode();
1591 
1592     popup_menu->AddChild("Debug Adapted classes", IDA_ADAPTIVE, "x", "Class to debug");
1593     popup_menu->AddChild("Debug Static classes", IDA_STATIC, "x", "Class to debug");
1594     popup_menu->AddChild("Debug Both", IDA_BOTH, "x", "Class to debug");
1595     popup_menu->AddChild("Debug Shape Index", IDA_SHAPE_INDEX, "0", "Index to debug");
1596     popup_menu->BuildMenu(IntMatchWindow, false);
1597   }
1598 }
1599 
1600 /**
1601  * Initializes the proto display window if it is not already
1602  * initialized.
1603  */
InitProtoDisplayWindowIfReqd()1604 void InitProtoDisplayWindowIfReqd() {
1605   if (ProtoDisplayWindow == nullptr) {
1606     ProtoDisplayWindow = CreateFeatureSpaceWindow("ProtoDisplayWindow", 550, 200);
1607   }
1608 }
1609 
1610 /**
1611  * Initializes the feature display window if it is not already
1612  * initialized.
1613  */
InitFeatureDisplayWindowIfReqd()1614 void InitFeatureDisplayWindowIfReqd() {
1615   if (FeatureDisplayWindow == nullptr) {
1616     FeatureDisplayWindow = CreateFeatureSpaceWindow("FeatureDisplayWindow", 50, 700);
1617   }
1618 }
1619 
1620 /// Creates a window of the appropriate size for displaying elements
1621 /// in feature space.
CreateFeatureSpaceWindow(const char * name,int xpos,int ypos)1622 ScrollView *CreateFeatureSpaceWindow(const char *name, int xpos, int ypos) {
1623   return new ScrollView(name, xpos, ypos, 520, 520, 260, 260, true);
1624 }
1625 #endif // !GRAPHICS_DISABLED
1626 
1627 } // namespace tesseract
1628